您现在的位置是:首页 >技术交流 >UDP - C/S模型网站首页技术交流
UDP - C/S模型

由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,保证通讯可靠性的机制需要在应用层实现。


通信函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen); 
参数:
sockfd: 套接字
buf:缓冲区地址
len:缓冲区大小
flags: 0
src_addr:(struct sockaddr *)&addr 传出。 对端地址结构
addrlen:传入传出。
返回值:
成功:接收数据字节数。
失败:-1 errn。
0: 对端关闭。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen); 
参数:
sockfd: 套接字
buf:存储数据的缓冲区
len:数据长度
flags: 0
src_addr:(struct sockaddr *)&addr 传入。 目标地址结构
addrlen:地址结构长度。
返回值:
成功:写出数据字节数。
失败:-1, errno
UDP通信实现流程
/* 编程模型 */
Server							Client
创建套接字(socket)				创建套接字(socket)
准备地址(本机地址sockaddr_in)	准备地址(目标机地址sockaddr_in)
绑定(bind(sockfd+addr))			.....
接受请求(recvfrom)				发送请求(sendto)
响应请求(sendto)					接受请求(recvfrom)
关闭套接字(close)				关闭套接字(close) 

服务器端代码实现
- UDP服务器端算法的步骤描述
 ① 调用socket()函数创建服务器端无连接套接字。
② 调用bind()函数将套接字绑定到本机的一个可用的端点地址。
③ 调用recvfrom()函数从套接字接收来自远程客户端的数据并存入到缓冲区中,同时获得远程客户的套接字端点地址并保存。
④ 基于保存的远程客户的套接字端点地址,调用sendto()函数将缓冲区中的数据从套接字发送给该远程客户。
⑤ 与客户交互完毕,调用close()函数将套接字关闭,释放所占用的系统资源。
注意:
- 没有 accept,不需要建立建立连接;
 - 使用 recvfrom 代替 read,失败返回 -1,成功返回 - 从内核缓冲区读到的字节数
 - 使用 sendto 代替 write,失败返回 -1,成功返回 - 写到内核缓冲区的字节数
 
/*server.c*/
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h>
#define SERV_PORT 8000
int main(void)
{
    struct sockaddr_in serv_addr, clie_addr;
    socklen_t clie_addr_len;
    int sockfd;
    char buf[BUFSIZ];
    char str[INET_ADDRSTRLEN];
    int i, n;
    /* 打开一个网络通讯端口,分配一个文件描述符sockfd */
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    bzero(&serv_addr, sizeof(serv_addr)); //初始化为空
    serv_addr.sin_family = AF_INET; //地址采用IPv4地址
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //地址从主机字节顺序转换成网络字节顺序
    serv_addr.sin_port = htons(SERV_PORT); //端口号从主机字节顺序转换成网络字节顺序
    /* 将文件描述符sockfd和服务器地址绑定 */
    bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    printf("Accepting connections ...
");
    while (1) {
         /* 接收client端传过来的的字符串,写入buf */
        clie_addr_len = sizeof(clie_addr);
        n = recvfrom(sockfd, buf, BUFSIZ,0, (struct sockaddr *)&clie_addr, &clie_addr_len);
        if (n == -1)
            perror("recvfrom error");
        
        /* 打印客户端IP及端口 */
        printf("received from %s at PORT %d
",
                inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
                ntohs(clie_addr.sin_port));
        /* 小写转为大写 */        
        for (i = 0; i < n; i++)
            buf[i] = toupper(buf[i]);
        /* 把数据发送给客户端 */
        n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&clie_addr, sizeof(clie_addr));
        if (n == -1)
            perror("sendto error");
    }
    close(sockfd);
    return 0;
} 
客户端代码实现
- UDP客户端算法的步骤描述
 ① 调用socket()函数创建客户端无连接套接字。
② 找到期望与之通信的远程服务器ip地址和协议端口号;然后再调用sendto()函数将缓冲区中的数据从套接字发送给远程服务器。
③ 调用recvfrom()函数从套接字接收来自远程服务器端的数据并存入缓冲区中。
④ 与服务器交互完毕,调用close()函数将套接字关闭,释放所占用的系统资源。
注意:
- 没有 connect,不需要建立建立连接;
 - 使用 sendto 代替 write,失败返回 -1,成功返回 - 写到内核缓冲区的字节数
 - 使用 recvfrom 代替 read,失败返回 -1,成功返回 - 从内核缓冲区读到的字节数
 
/*client.c*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h>
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    int sockfd, n;
    char buf[BUFSIZ];
    /* 打开一个网络通讯端口,分配一个文件描述符sockfd */
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    bzero(&servaddr, sizeof(servaddr)); //初始化为空
    servaddr.sin_family = AF_INET; //地址采用IPv4地址
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); //将“点分十进制” -> “二进制整数”
    servaddr.sin_port = htons(SERV_PORT); //端口号从主机字节顺序转换成网络字节顺序
    //bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    while (fgets(buf, BUFSIZ, stdin) != NULL) {
        /* 发送给服务端 */
        n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
        if (n == -1)
            perror("sendto error");
        
        /* 从服务端接收数据 */
        n = recvfrom(sockfd, buf, BUFSIZ, 0, NULL, 0);         //NULL:不关心对端信息
        if (n == -1)
            perror("recvfrom error");
        
        /* 输出服务器处理后的数据 */
        write(STDOUT_FILENO, buf, n);
    }
    close(sockfd);
    return 0;
} 


            




U8W/U8W-Mini使用与常见问题解决
QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。...
stm32使用HAL库配置串口中断收发数据(保姆级教程)
分享几个国内免费的ChatGPT镜像网址(亲测有效)
Allegro16.6差分等长设置及走线总结