从零编写基于 UDP 的通信程序

本贴最后更新于 1492 天前,其中的信息可能已经时过境迁

为什么使用 UDP

首先,我们知道 TCP 在弱网环境下有性能问题参考

为什么 TCP 协议有性能问题

那么使用 UDP 有有哪些优势呢:

  • UDP 是无连接的,即通信时不需要创建连接(发送数据结束时也没有连接可以释放)所以减小了开销和发送数据前的时延;
  • UDP 采用最大努力交付,不保证可靠交付,因此主机不需要维护复杂的连接状态;
  • UDP 是面向报文的,只在应用层交下来的报文前增加了首部后就向下交付 IP 层;
  • UDP 是无阻塞控制的,即使网络中存在阻塞,也不会影响发送端的发送频率;
  • UDP 支持一对一、一对多、多对一、多对多的交互通信;
  • DUP 的首部开销小,只有 8 个字节,它比 TCP 的 20 个字节的首部要短。

怎样实现 UDP 通信

server.png
udp 通信流程图

首先实现 server 端:

int main(int argc, char **argv) {
    if(argc < 2) {
        printf("Usage: %s port\n", argv[0]);
        return -1;
    }
    int buf_size = BUF_SIZE;
    int len = sizeof(int);
    int s;
    struct sockaddr_in addr;
  
    unsigned short port = atoi(argv[1]);

    s = socket(AF_INET, SOCK_DGRAM, 0);  // 创建socket
    if(s < 0) {
        return -1;
    }

    // 服务端应该处理多个客户端请求,适当增大缓冲区,防止因为服务器处理慢导致丢包
	setsockopt(s, SOL_SOCKET, SO_SNDBUF, &buf_size, len);  // 设置发送buf
	setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buf_size, len);  // 设置接收buf

    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 绑定服务端端口号,客户端向此端口发送数据就可以
    if(bind(s, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0 ){
        return -1;
    }
    // int flags;
	// if ((flags = fcntl(s, F_GETFL, 0)) < 0 || fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0) {
	// 	return -1;
	// }
    struct sockaddr_in recv_addr;
    socklen_t addr_len = sizeof(struct sockaddr_in);
    char buf[1024];
    while(1){
        memset(buf, 0, sizeof(buf));
        // 接收其他客户端消息
        int ret = recvfrom(s, buf, 1024, 0, (struct sockaddr*)&recv_addr, &addr_len);
        if(ret <= 0) {
            printf("server recv err: %d\n", ret);
            return -1;
        }
        char addr_s[INET_ADDRSTRLEN];
        inet_ntop(addr.sin_family, &recv_addr.sin_addr, addr_s, sizeof(addr_s));
        printf("recv from[%s:%d]: %s\n", addr_s, ntohs(recv_addr.sin_port), buf);
        // 回复客户端
        memset(buf, 0, sizeof(buf));
        sprintf(buf, "ok");
        sendto(s, buf, strlen(buf), 0, (struct sockaddr*)&recv_addr, addr_len);
    }
  
    return 0;
}

实现相应客户端

int main(int argc, char **argv) {
    if(argc < 3) {
        printf("Usage: %s addr port\n", argv[0]);
        return -1;
    }
    int s;
    struct sockaddr_in addr;
  
    unsigned short port = atoi(argv[2]);

    s = socket(AF_INET, SOCK_DGRAM, 0);  // 创建socket
    if(s < 0) {
        printf("create socket err %d\n", s);
        return -1;
    }
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    char buf[1024];

    struct sockaddr_in recv_addr;
    socklen_t addr_len = sizeof(struct sockaddr_in);
    while (1)
    {
        memset(buf, 0, 1024);
        scanf("%s", buf);
        if(strncmp(buf, "quit", 4) == 0) {
            return 0;
        }
        // 发送数据
        sendto(s, buf, strlen(buf), 0, (struct sockaddr*)&addr, sizeof(struct sockaddr));
        memset(buf, 0, sizeof(buf));
        // 响应服务端返回
        int ret = recvfrom(s, buf, 1024, 0, (struct sockaddr*)&recv_addr, &addr_len);
        if(ret <= 0) {
            printf("server recv err: %d\n", ret);
            return -1;
        }
        char addr_s[INET_ADDRSTRLEN];
        inet_ntop(addr.sin_family, &recv_addr.sin_addr, addr_s, sizeof(addr_s));
        printf("recv from[%s:%d]: %s\n", addr_s, ntohs(recv_addr.sin_port), buf);
    }
    return 0;
}

实现结果查看

result.jpg
程序效果

实现了客户端发送,服务器响应打印,并回复客户端,可以看到发送消息后,客户端正常收到服务器回复的 ok

总结

本例通过最简单的 socket 编程,实现了简单的 udp 通信,当然实际在服务器处理中还有很多复杂的逻辑,比如服务器目前是阻塞在 udp 的 recvfrom,实际中服务器不可能只做这一件事,需要使用非阻塞方式,或者使用多线程模式。另外,我们知道 udp 是不可靠传输,需要制定协议,做消息重排序,丢包重传,或者使用 fec 技术恢复,这些留在下篇介绍。

  • RTC
    3 引用
  • 实时通信
    3 引用
  • UDP
    5 引用 • 4 回帖
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    85 引用 • 165 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...