7.1 概述
TCP和UDP网络编程存在一些本质的差异,主要是由于传输层的差别:UDP是无连接的不可靠的数据报协议,而TCP是面向连接的字节流协议。
下图是典型的UDP客户端和服务器之间的通信流程。客户不与服务器建立连接,而是只管使用sendto函数。服务器不接受来自客户的连接,而是只管调用recvfrom函数,等待某个客户的数据到达。
7.2 recvfrom和sendto函数
这两个函数类似标准的read、write函数,不过需要额外的三个参数
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
recvfrom的参数说明如下。
- socket:创建的套接字描述符
- buffer:指向输入缓冲区的指针
- length:缓冲区大小
- flags:在本文中,可以将 flags 置为0即可
- src_addr:指向客户套接字地址的指针
- src_len:地址长度
- recvfrom的返回值为读入数据的长度。
ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr *to, socklen_t addrlen);
/*以上函数返回:成功返回读或写到的字节数,出错返回-1*/
sendto函数的参数说明如下:
- socket:创建的套接字描述符
- buffer:输出缓冲区的指针
- length:缓冲区大小
- flags:正常应用中,flags一般设置为0
- dest_addr:指向服务器套接字地址的指针
- dest_len:地址长度
前三个参数sockfd、buff和nbytes分别代表:描述符、指向输入或写出缓冲区的指针和读写的字节数。
flags参数在讨论recv、send、recvmsg和sendmsg函数时再介绍,这里默认设置为0。
sendto的to参数指向一个含有数据报接受者的协议地址(IP地址及端口号)的套接字地址结构,其大小由addrlen指定。recvfrom的from参数指向一个含有数据报发送者的协议地址(IP地址及端口号)的套接字地址结构,其大小由addrlen指定。注意sendto的最后一个参数是一个整数值,而recvfrom的最后一个参数是一个指向整形的指针(即值-结果函数)。
写一个长度位(0)的数据报是可行的(TCP不允许),这会发送一个只有IP首部和UDP首部,没有数据的数据报。类似的,recvfrom返回0值也是可以的(TCP则代表对端已关闭连接)。如果recvfrom的from参数是一个空指针,相应的addrlen为0,说明我们不关心发送者的协议地址。recvfrom和sendto函数都可以用于TCP,尽管通常不这么做。
7.3 UDP套接字编程流程
- 创建套接字
- 命名套接字
- 在服务器端,等待客户的消息
- 在客户端,发送客户消息
- 关闭套接字
7.3.1 创建套接字
可以使用系统调用socket来创建一个套接字并返回该套接字的文件描述符。
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
7.3.2 命名套接字
要想让创建的套接字可以被其他进程使用,那必须给该套接字命名。对套接字命名的意思是指将该套接字关联一个IP地址和端口号,可以使用系统调用bind来实现。
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address, size_t address_len);
bind系统调用把参数address中的地址分配给与文件描述符socket关联的套接字,地址结构的长度由参数address_len传递。
7.3.3 接收/发送消息
不同于TCP提供的面向连接的可靠字节流协议,UDP 是无连接不可靠的数据报协议。服务器不接收来自客户的连接,而只管调用recvfrom系统调用,等待客户的数据到达。recvfrom的声明如下:
#include <sys/socket.h>
int recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *src_addr, socklen_t *src_len);
UDP是无连接的,故客户可以直接向服务器发送消息而不需要建立连接。客户使用sendto系统调用向服务器发送消息:
#include <sys/socket.h>
int sendto(int socket, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
7.3.4 关闭套接字
操作系统为每个套接字分配了一个文件描述符,为了让操作系统回收该文件描述符,可以使用 close 系统调用:
close(fd);