目录
4.2.1 UDP 服务器代码(udp_server.c):
4.2.2 UDP 客户端代码(udp_client.c):
一、套接字:网络通信的基石
在日常生活中,打电话是我们再熟悉不过的交流方式。当你拿起电话,拨通对方号码的那一刻,其实就已经在进行一次通信连接。你的手机是一个通信端点,对方的手机则是另一个端点,而电话号码就像是端点的标识,通过电话网络,双方可以实现语音的传输与接收 。在网络世界里,也有类似的概念,那就是套接字(Socket)。
套接字可以简单理解为网络通信的端点,是不同主机上的应用程序进程之间进行双向通信的 “大门”。它就如同电话系统中的电话机,一端连接着本地的应用程序,另一端则伸向网络,与远程的应用程序进行交互。通过套接字,应用程序能够发送和接收数据,实现如文件传输、网页浏览、即时通讯等丰富多彩的网络应用功能。每个套接字都有一个唯一的标识,如同电话号码一样,它由 IP 地址和端口号组成。IP 地址用于标识网络中的主机,就像城市中的街道地址,指明了通信的目的地;端口号则进一步区分主机上的不同应用程序,类似于街道地址中的门牌号,确保数据能够准确无误地送达目标应用程序。
二、Linux 套接字的关键要素
2.1 套接字的属性
在 Linux 系统中,套接字有三个关键属性:域(domain)、类型(type)和协议(protocol)。这些属性就像是房子的地址、结构类型和建筑材料,共同决定了套接字的工作方式和用途。
套接字的域(domain),也称为协议族(protocol family),它定义了套接字通信所使用的网络介质和地址类型。常见的域有 AF_INET(IPv4)和 AF_INET6(IPv6),分别用于基于 IPv4 和 IPv6 协议的网络通信。AF_INET 就像是只允许使用传统 4 段数字 IP 地址(如 192.168.1.1)的通信区域,而 AF_INET6 则支持更复杂、更庞大地址空间的 IPv6 地址(如 2001:0db8:85a3:0000:0000:8a2e:0370:7334 ) 。还有 AF_UNIX(也称为 AF_LOCAL),用于同一台主机上进程间的通信,它使用文件系统路径作为地址,就像是在同一栋楼内不同房间(进程)之间传递消息,不需要通过外部网络。
类型(type)决定了套接字的数据传输方式和特性。常见的类型包括 SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据报套接字)和 SOCK_RAW(原始套接字) 。SOCK_STREAM 提供可靠的、面向连接的字节流服务,如同在两个城市之间建立了一条稳定的高速公路,数据就像车辆一样按顺序、无差错地在这条路上行驶;SOCK_DGRAM 则是无连接的,以独立的数据包形式传输数据,类似于快递包裹,每个包裹独立发送,不保证顺序和可靠性;SOCK_RAW 允许直接访问底层网络协议,常用于开发网络测试工具、自定义协议实现等高级场景,它赋予开发者更多的控制权,就像直接操控网络通信的底层引擎。
协议(protocol)进一步细化了通信所使用的具体协议。在大多数情况下,当指定了域和类型后,协议可以设置为 0,系统会自动选择合适的默认协议。比如在 AF_INET 域中使用 SOCK_STREAM 类型时,默认协议通常是 IPPROTO_TCP(TCP 协议);而使用 SOCK_DGRAM 类型时,默认协议是 IPPROTO_UDP(UDP 协议) 。但在某些特殊情况下,可能需要显式指定协议,以满足特定的通信需求。
2.2 套接字的类型
流式套接字(SOCK_STREAM):提供可靠的、面向连接的通信服务,就像打电话时建立的稳定语音连接,数据在连接建立后按顺序传输,不会丢失或乱序。它基于 TCP 协议实现,在传输数据前,需要先通过三次握手建立连接,连接建立后,数据以字节流的形式在连接上传输。当数据发送方调用 send 函数发送数据时,数据会被缓存在发送缓冲区,然后由 TCP 协议负责将数据可靠地传输到接收方的接收缓冲区。接收方通过 recv 函数从接收缓冲区读取数据,确保数据的顺序和完整性。在文件传输场景中,如使用 FTP 协议进行文件下载,流式套接字能保证文件的每个字节都准确无误地传输到客户端,不会出现数据丢失或错位的情况,从而确保文件的完整性。
数据报套接字(SOCK_DGRAM):是无连接的,数据以独立的数据包(也称为数据报)形式传输,每个数据包都有自己的目标地址和源地址,就像寄快递时,每个包裹都是独立的,不需要提前建立连接。它基于 UDP 协议实现,UDP 协议不保证数据的可靠传输,数据可能会在传输过程中丢失、重复或乱序到达。但 UDP 协议的优势在于传输效率高,因为它不需要建立连接和进行复杂的确认机制。在实时视频直播场景中,由于需要快速传输大量的视频数据,对实时性要求较高,而少量的数据丢失对视频观看体验影响较小,所以常使用数据报套接字,即使偶尔有一些数据包丢失,视频仍然可以继续播放,不会出现明显的卡顿。
原始套接字(SOCK_RAW):允许直接访问底层网络协议,如 IP、ICMP 等,开发者可以自定义数据包的内容,实现一些高级的网络功能,如网络协议测试、数据包捕获和分析等。与流式套接字和数据报套接字不同,原始套接字绕过了传输层的一些默认处理,直接与网络层交互。例如,在开发网络安全工具时,可以使用原始套接字捕获网络中的所有数据包,分析其中的内容,检测是否存在恶意攻击行为。但使用原始套接字需要较高的权限,因为它可以对网络进行更底层的操作,不当使用可能会影响网络的正常运行。
2.3 socket 函数:创建套接字的关键
在 Linux 中,使用 socket 函数来创建套接字,它是开启网络通信大门的钥匙。socket 函数的原型如下:
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
其中,domain 参数指定套接字的域,如 AF_INET、AF_INET6 或 AF_UNIX 等;type 参数指定套接字的类型,如 SOCK_STREAM、SOCK_DGRAM 或 SOCK_RAW 等;protocol 参数指定使用的协议,通常设置为 0,让系统自动选择默认协议。
当 socket 函数成功执行时,会返回一个非负整数,这个整数就像套接字的身份证,被称为套接字描述符(socket descriptor),后续对套接字的操作,如绑定地址、发送和接收数据等,都要通过这个描述符来进行。如果创建失败,socket 函数会返回 - 1,并设置相应的错误码,通过查看错误码可以了解创建失败的原因,如权限不足、参数错误等。
比如,要创建一个基于 IPv4 的 TCP 套接字,可以这样调用 socket 函数:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket creation failed");
return 1;
}
上述代码中,首先调用 socket 函数,传入 AF_INET 表示使用 IPv4 协议族,SOCK_STREAM 表示创建流式套接字,最后一个参数 0 表示让系统自动选择默认的 TCP 协议。如果 socket 函数返回 - 1,说明创建套接字失败,通过 perror 函数打印错误信息。
三、Linux 套接字的工作流程
了解了 Linux 套接字的关键要素后,我们来深入探讨一下套接字的工作流程。套接字的工作流程就像是一场精心编排的交响乐,各个环节紧密配合,共同完成网络通信的任务。在这个流程中,创建套接字是开场的序曲,它为后续的通信搭建了舞台;绑定地址则如同为演出确定场地,明确了通信的端点;监听连接和接受连接是服务器端的重要环节,它们确保服务器能够及时响应客户端的请求;发送和接收数据是交响乐的高潮部分,实现了数据的交互;而关闭套接字则是落幕的尾声,释放资源,结束通信。下面我们将详细介绍每个步骤的具体实现和作用。
3.1 创建套接字
创建套接字是网络通信的第一步,就像搭建舞台一样,为后续的通信过程奠定基础。在 Linux 中,使用 socket 函数来创建套接字,它的作用是向系统申请一个套接字资源,并返回一个用于标识这个套接字的描述符,这个描述符就像是进入通信世界的通行证。
socket 函数的原型为:
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
其中,domain 参数指定套接字所属的协议族,常见的有 AF_INET(IPv4 协议族)、AF_INET6(IPv6 协议族)和 AF_UNIX(本地通信协议族)等。type 参数指定套接字的类型,如 SOCK_STREAM(流式套接字,基于 TCP 协议,提供可靠的面向连接的通信服务)、SOCK_DGRAM(数据报套接字,基于 UDP 协议,提供无连接的不可靠通信服务)和 SOCK_RAW(原始套接字,允许直接访问底层网络协议)等。protocol 参数通常设置为 0,表示使用默认协议,系统会根据 domain 和 type 参数自动选择合适的协议。
例如,创建一个基于 IPv4 的 TCP 套接字,可以这样调用 socket 函数:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket creation failed");
return 1;
}
在这段代码中,首先调用 socket 函数,传入 AF_INET 表示使用 IPv4 协议族,SOCK_STREAM 表示创建流式套接字,最后一个参数