【Linux网络编程】UDP服务器
1. 相关概念
传输层主要应用的协议模型有两种,一种是TCP
协议,另外一种则是UDP
协议。TCP
协议在网络通信中占主导地位,绝大多数的网络通信借助TCP
协议完成数据传输。但UDP
也是网络通信中不可或缺的重要通信手段。
相较于TCP
而言,UDP
通信的形式更像是发短信。不需要在数据传输之前建立、维护连接。只专心获取数据就好。省去了三次握手的过程,通信速度可以大大提高,但与之伴随的通信的稳定性和正确率便得不到保证。因此,我们称UDP
为“无连接的不可靠报文传递”。
那么与我们熟知的TCP
相比,UDP
有哪些优点和不足呢?由于无需创建连接,所以UDP
开销较小,数据传输速度快,实时性较强。多用于对实时性要求较高的通信场合,如视频会议、电话会议等。但随之也伴随着数据传输不可靠,传输数据的正确率、传输顺序和流量都得不到控制和保证。所以,通常情况下,使用UDP
协议进行数据传输,为保证数据的正确性,我们需要在应用层添加辅助校验协议来弥补UDP
的不足,以达到数据可靠传输的目的。
与TCP
类似的,UDP
也有可能出现缓冲区被填满后,再接收数据时丢包的现象。由于它没有TCP
滑动窗口的机制,通常采用如下两种方法解决:
服务器应用层设计流量控制,控制发送数据速度。
借助
setsockopt
函数改变接收缓冲区大小。如:1
2
3
4
5
6
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
//eg:
int n = 220x1024
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
2. C/S模型-UDP
由于UDP
不需要维护连接,程序逻辑简单了很多,但是UDP
协议是不可靠的,保证通讯可靠性的机制需要在应用层实现。
3. UDP通信server和client流程
server端
创建通信的套接字
1
2// 第二个参数是 SOCK_DGRAM, 第三个参数0表示使用报式协议中的udp
int fd = socket(AF_INET, SOCK_DGRAM, 0);使用通信的套接字和本地的 IP 和端口绑定,IP 和端口需要转换为大端
1
bind();
设置监听(可选)
1
listen();
与客户端进行通信
1
2
3
4// 接收数据
recvfrom();
// 发送数据
sendto();关闭套接字
1
close(fd);
client端
创建通信的套接字
1
2// 第二个参数是 SOCK_DGRAM, 第三个参数0表示使用报式协议中的udp
int fd = socket(AF_INET, SOCK_DGRAM, 0);与客户端进行通信
1
2
3
4// 发送数据
sendto();
// 接收数据
recvfrom();关闭套接字
1
close(fd);
4.相关通信函数
4.1 socket()函数
使用套接字通信函数需要包含头文件 <arpa/inet.h>,包含了这个头文件 <sys/socket.h> 就不用在包含了。
基于 UDP 进行套接字通信,创建套接字的函数还是socket()
但是第二个参数的值需要指定为 SOCK_DGRAM
,通过该参数指定要创建一个基于报式传输协议的套接字,最后一个参数指定为 0 表示使用报式协议中的 UDP 协议。
1 | // 创建一个套接字 |
- 参数:
- domain: 使用的地址族协议
- AF_INET: 使用 IPv4 格式的 ip 地址
- AF_INET6: 使用 IPv4 格式的 ip 地址
- type:
- SOCK_STREAM: 使用流式的传输协议(通常对于TCP协议)
- SOCK_DGRAM: 使用报式 (报文) 的传输协议(通常对于UDP协议)
- protocol: 一般写 0 即可,使用默认的协议
- SOCK_STREAM: 流式传输默认使用的是 tcp
- SOCK_DGRAM: 报式传输默认使用的 udp
- domain: 使用的地址族协议
- 返回值:
- 成功:可用于套接字通信的文件描述符
- 失败: -1,设置errno
4.2 recvfrom()函数
1 | // 接收数据, 如果没有数据,该函数阻塞 |
- 参数
sockfd
: 基于udp
的通信的文件描述符buf
: 指针指向的地址用来存储接收的数据len
:buf
指针指向的内存的容量,最多能存储多少字节flags
: 设置套接字属性,一般使用默认属性,指定为 0 即可src_addr
: 发送数据的一端的地址信息,IP 和端口都存储在这里边,是大端存储的- 如果这个参数中的信息对当前业务处理没有用处,可以指定为 NULL, 不保存这些信息
addrlen
: 类似于accept ()
函数的最后一个参数,是一个传入传出参数- 传入的是
src_addr
参数指向的内存的大小,传出的也是这块内存的大小 - 如果
src_addr
参数指定为NULL
, 这个参数也指定为NULL
即可
- 传入的是
- 返回值:成功返回接收的字节数
- 若失败返回 - 1,设置errorno
- 若返回0,表示对端关闭
4.3 sendto()函数
1 | // 发送数据函数 |
- 参数:
- sockfd: 基于 udp 的通信的文件描述符
- buf: 这个指针指向的内存中存储了要发送的数据
- len: 要发送的数据的实际长度
- flags: 设置套接字属性,一般使用默认属性,指定为 0 即可
- dest_addr: 接收数据的一端对应的地址信息,大端的 IP 和端口
- addrlen: 参数 dest_addr 指向的内存大小
- 返回值:函数调用成功返回实际发送的字节数,调用失败返回 - 1
5.代码实现
server:
1 | /************************************************************************* |
client
1 | /************************************************************************* |
编译运行,结果如下:
服务器端:
客户端1:
客户端2:
==可以看出,使用UDP实现的的服务器,在没有使用IO多路复用等技术时,依然具备并发能力==
6.TCP通信和UDP通信的优缺点比较
TCP: 面向连接的,可靠数据包传输。对于不稳定的网络层,采取完全弥补的通信方式。 丢包重传。
优点:
稳定
数据流量稳定、速度稳定、顺序稳定
缺点:
- 传输速度慢。传输效率低。开销大。
使用场景:数据的完整型要求较高,不追求效率。
UDP: 无连接的,不可靠的数据报传递。对于不稳定的网络层,采取完全不弥补的通信方式。 默认还原网络状况
优点:
- 传输速度块。传输效率高。开销小。
缺点:
- 不稳定。
- 数据流量不稳定。速度不稳当。顺序不稳定,可能会出现后发送的包比先发送的包先到达的情况。
使用场景:对时效性要求较高场合。稳定性其次。