概述
在使用TCP编写的应用程序和使用UDP编写的应用程序之间存在一些本质差异,其原因在于这两个传输层之间的差别:UDP是无连接不可靠的数据报协议,不同于TCP提供的面向连接的可靠字节流。从资源的角度来看,相对来说UDP套接字开销较小,因为不需要维持网络连接,而且因为无需花费时间来连接连接,所以UDP套接字的速度也较快。
因为UDP提供的是不可靠服务,所以数据可能会丢失。如果数据对于我们来说非常重要,就需要小心编写UDP客户程序,以检查错误并在必要时重传。实际上,UDP套接字在局域网中是非常可靠的。
图:UDP 客户 / 服务器程序使用的套接字函数
上图展示了客户与服务器使用UDP套接字进行通信的过程。在UDP套接字程序中,客户不需要与服务器建立连接,而只管直接使用sendto
函数给服务器发送数据报。同样的,服务器不需要接受来自客户的连接,而只管调用recvfrom
函数,等待来自某个客户的数据到达。
如何编写UDP套接字程序
编写UDP套接字应用程序,涉及一定的步骤:
- 创建套接字
- 命名套接字
- 在服务器端,等待客户的消息
- 在客户端,发送客户消息
- 关闭套接字
步骤1:创建套接字
可以使用系统调用socket
来创建一个套接字并返回该套接字的文件描述符。
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
创建的套接字是一条通信线路的一个端点。
domain
domain
参数指定哪种协议族,常见的协议族包括 AF_UNIX 和 AF_INET。AF_UNIX 用于通过文件系统实现的本地套接字,AF_INET 用于网络套接字。
type
type
参数指定这个套接字的通信类型,取值包括 SOCK_STREAM 和 SOCK_DGRAM。
SOCK_STREAM 即流套接字,基于 TCP,提供可靠,有序的服务。
SOCK_DGRAM 即数据报套接字,基于 UDP,提供不可靠,无序的服务。SOCK_DGRAM 类型的套接字为本文讲述的重点。
protocol
protocol
允许为套接字指定一种协议。对于 AF_UNIX 和 AF_INET,我们使用默认值即可。
以下代码创建一个 UDP socket,domain
使用 AF_INET,type
使用 SOCK_DGRAM,protocol
协议使用默认的 0 值。
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("cannot create socket");
return 0;
}
printf("created socket, fd: %d\n", fd);
exit(0);
}
步骤2:命名套接字
要想让创建的套接字可以被其他进程使用,那必须给该套接字命名。对套接字命名的意思是指将该套接字关联一个IP地址和端口号,可以使用系统调用bind
来实现。
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address, size_t address_len);
bind
系统调用把参数address
中的地址分配给与文件描述符socket
关联的套接字,地址结构的长度由参数address_len
传递。
每种套接字域都有其自己的格式,对于 AF_INET 域来说,套接字地址由结构 socket_in
来指定,它至少包含以下几个成员:
struct sockaddr_in {
short int sin_family; // AF_INET
unsigned short int sin_port; // 端口号
struct in_addr sin_addr; // IP地址
};
成员sin_port
表示套接字的端口号。对于客户套接字,我们一般不需要指定套接字的端口号,而对于服务器套接字,我们需要指定套接字的端口号以便让客户正确向服务器发送数据。如果不需要指定端口号,可以将sin_port
的值赋为0。
成员sin_addr
表示套接字的地址,即机器的IP地址。如果我们没特别为套接字绑定IP地址,可以让操作系统选择一个,即sin_addr
使用地址0.0.0.0,使用INADDR_ANY来表示这个地址常量。
对一个套接字进行命名的代码如下:
#include <sys/socket.h>
#include <stdlib.h>