1. Socket基础回顾:TCP/IP协议栈、Socket是什么、Socket类型(TCP/UDP)、Socket工作流程、Socket API概览
好,咱们正式开始。第一章,我打算把Socket最基础的东西再过一遍。你可能会想:“这些东西我都知道,还用讲?” 嗯,我当年也这么想。但后来在项目里踩了不少坑,才发现很多问题就出在最基础的理解上。所以,这一章咱们不赶进度,把地基夯实了。
1.1 TCP/IP协议栈:数据是怎么“包”起来的?
Socket编程,说白了就是操作TCP/IP协议栈。你得先明白数据从你的应用程序到网线,中间经历了什么。
我个人习惯把TCP/IP协议栈想象成一个“俄罗斯套娃”的过程。数据从应用层下来,每一层都会给它套上一个“头”(头部信息)。
你看这张图,数据从上往下走,每经过一层就加一个头。接收端反过来,从下往上走,每层剥掉一个头。这就是封装和解封装。
1.2 Socket是什么?别把它想得太玄乎
Socket,中文叫“套接字”。很多教材把它讲得很抽象。其实,你可以把它理解成“网络世界的文件描述符”。
在Linux里,一切皆文件。你打开一个本地文件,得到一个文件描述符(fd),然后读写它。Socket也一样:你创建一个Socket,得到一个fd,然后通过它跟另一台机器上的程序通信。
我记得刚入行时,有个老大哥跟我说:“Socket就是两个程序之间的电话线。” 一端拨号(connect),另一端接听(accept),然后两边对着话筒说话(send/recv)。这个比喻我一直用到现在。
1.3 Socket类型:TCP vs UDP,你该怎么选?
Socket主要分两种:流式Socket(SOCK_STREAM) 和 数据报Socket(SOCK_DGRAM)。它们背后对应的就是TCP和UDP协议。
很多新手会纠结:“我该用TCP还是UDP?” 我建议你先看业务场景。下面这个表,是我自己总结的,你直接拿去用。
| 特性 | TCP (SOCK_STREAM) | UDP (SOCK_DGRAM) |
|---|---|---|
| 连接 | 面向连接(先建立连接再通信) | 无连接(直接发数据) |
| 可靠性 | 可靠传输(有确认、重传机制) | 不可靠(发出去就不管了) |
| 数据边界 | 无边界(流式,可能粘包) | 有边界(每个包独立) |
| 速度 | 相对较慢(有握手和确认开销) | 快(无握手,无确认) |
| 典型应用 | HTTP、FTP、邮件、数据库连接 | DNS、视频直播、在线游戏、VoIP |
1.4 Socket工作流程:一次完整的“握手”
咱们以最常用的TCP Socket为例,走一遍完整的工作流程。这就像两个人打电话,有固定的步骤。
流程很清晰,对吧?服务器是“被动方”,先启动,等着别人来连。客户端是“主动方”,去连服务器。这里面有几个关键点,我单独拎出来说说。
1.5 Socket API概览:常用的几个函数
API不用死记硬背,用多了自然就熟了。我把最核心的几个列出来,你有个印象就行。
| 函数 | 作用 | 关键参数 |
|---|---|---|
socket() |
创建一个Socket | domain (AF_INET), type (SOCK_STREAM), protocol (0) |
bind() |
将Socket绑定到本地IP和端口 | sockfd, sockaddr结构体, addrlen |
listen() |
将Socket设为监听模式 | sockfd, backlog (最大等待连接数) |
accept() |
接受一个客户端连接 | sockfd, 客户端sockaddr, addrlen |
connect() |
客户端向服务器发起连接 | sockfd, 服务器sockaddr, addrlen |
send() |
发送数据 | sockfd, buf, len, flags |
recv() |
接收数据 | sockfd, buf, len, flags |
close() |
关闭Socket | sockfd |
咱们来看一个最简单的TCP回射服务器(Echo Server)的代码片段,感受一下这些API是怎么串起来的。
// 服务器端核心代码 (C语言风格)
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
// 1. 创建Socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
// 2. 绑定IP和端口
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
server_addr.sin_port = htons(8080); // 端口8080
bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
// 3. 开始监听
listen(server_fd, 5); // 最大等待队列长度为5
// 4. 接受客户端连接 (阻塞)
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
// 5. 收发数据
char buffer[1024] = {0};
recv(client_fd, buffer, sizeof(buffer), 0);
send(client_fd, buffer, strlen(buffer), 0); // 原样返回
// 6. 关闭连接
close(client_fd);
close(server_fd);
注意: 上面的代码只是为了演示流程,完全没有做错误处理。在实际项目中,每个API调用后都必须检查返回值。比如 socket() 返回 -1 表示创建失败,accept() 返回 -1 表示接受失败。忽略这些检查,是线上事故最常见的根源之一。
嗯,基础就这些。你可能会觉得:“就这?太简单了。” 别急,这只是开胃菜。后面的章节,咱们会把这些基础放到真实的企业级场景里,看看它们是怎么被玩出花样的。比如,高并发下怎么处理成千上万个连接?粘包问题怎么解决?这些才是真正的挑战。