3. Socket编程基础:Socket是什么、Socket类型与基本流程
各位同学,今天我们来聊聊网络编程里最基础、也最核心的一个概念——Socket。
说实话,我刚开始学网络编程那会儿,看到「Socket」这个词,第一反应是「这玩意儿到底是个啥?」。后来踩了不少坑,才真正搞明白。今天我就用最直白的方式,把Socket给你讲透。
3.1 Socket是什么?
Socket,中文叫「套接字」。名字挺拗口,但说白了,它就是网络通信的「门」。
你想想看,两台电脑要通信,总得有个入口吧?就像你家的大门,快递员要送东西,得先找到门。Socket就是程序在网络上的那扇门。
从技术角度讲,Socket是操作系统提供的一个抽象接口。它把复杂的网络协议栈(TCP/IP)给封装起来了。你不需要管底层怎么发包、怎么路由,只需要操作Socket这个文件描述符就行。
核心理解:Socket = IP地址 + 端口号 + 协议类型。这三者唯一确定了一个网络通信的端点。
我个人习惯把Socket比作「电话机」。你想想看:
- IP地址 = 对方的电话号码
- 端口号 = 分机号(找具体的人)
- 协议类型 = 你用座机还是手机(TCP还是UDP)
嗯,这样是不是好理解多了?
3.2 Socket的类型
Socket主要分两种:流式Socket和数据报Socket。这两种我都在项目里用过,差别还挺大的。
3.2.1 流式Socket(SOCK_STREAM)
流式Socket基于TCP协议。它的特点是:可靠、有序、面向连接。
什么意思呢?就像你打电话——先拨号(建立连接),然后通话(传输数据),最后挂断(关闭连接)。数据保证按顺序到达,不会丢包。
我在项目中遇到过一个问题:用流式Socket传文件,结果对方收到的文件总是少几个字节。排查了半天,发现是缓冲区没刷干净。后来加了个shutdown()调用才解决。
我的经验:流式Socket适合对数据完整性要求高的场景,比如文件传输、网页浏览、邮件收发。但要注意,它是有状态的,服务端需要维护每个连接。
3.2.2 数据报Socket(SOCK_DGRAM)
数据报Socket基于UDP协议。它的特点是:不可靠、无序、无连接。
这就像寄明信片——你写好地址扔进邮筒,对方能不能收到、什么时候收到、顺序对不对,你都不确定。但好处是快啊!
我曾经做过一个视频直播项目,一开始用的TCP,结果延迟高得离谱。后来换成UDP,延迟降了10倍。虽然偶尔丢几帧画面,但用户根本感觉不到。
注意:数据报Socket不保证数据到达。如果你要用它传重要数据,必须在应用层自己做可靠性处理(比如加序列号、超时重传)。
两种Socket的对比,我整理了一张表:
| 特性 | 流式Socket (TCP) | 数据报Socket (UDP) |
|---|---|---|
| 连接方式 | 面向连接 | 无连接 |
| 可靠性 | 可靠,保证到达 | 不可靠,可能丢包 |
| 数据顺序 | 保证顺序 | 不保证顺序 |
| 传输速度 | 较慢(有握手和确认) | 较快(无握手) |
| 典型应用 | HTTP、FTP、SMTP | DNS、视频直播、游戏 |
3.3 Socket编程的基本流程
好了,理论说完了,咱们来点实际的。Socket编程到底怎么搞?
其实流程很固定,我总结为「三步走」:创建、通信、关闭。但服务端和客户端略有不同。
3.3.1 服务端流程
服务端是「被动方」,它等着客户端来找它。流程如下:
- 创建Socket:调用
socket()函数,指定协议族、类型、协议。 - 绑定地址:调用
bind(),把Socket和IP地址、端口号绑在一起。 - 监听连接:调用
listen(),告诉系统「我准备好了,可以接受连接了」。 - 接受连接:调用
accept(),阻塞等待客户端连接。连接成功后返回一个新的Socket。 - 收发数据:用
read()/write()或recv()/send()通信。 - 关闭连接:调用
close()。
代码示例(伪代码,但逻辑完全正确):
// 服务端
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
bind(server_fd, &addr, sizeof(addr));
listen(server_fd, 5);
while (1) {
int client_fd = accept(server_fd, NULL, NULL);
// 处理客户端请求
char buffer[1024];
read(client_fd, buffer, sizeof(buffer));
send(client_fd, "Hello", 5, 0);
close(client_fd);
}
close(server_fd);
3.3.2 客户端流程
客户端是「主动方」,它去连接服务端。流程更简单:
- 创建Socket:同样调用
socket()。 - 连接服务端:调用
connect(),指定服务端的IP和端口。 - 收发数据:和服务端一样,用
read()/write()。 - 关闭连接:调用
close()。
// 客户端
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
connect(sock_fd, &server_addr, sizeof(server_addr));
send(sock_fd, "Hello", 5, 0);
char buffer[1024];
read(sock_fd, buffer, sizeof(buffer));
close(sock_fd);
关键点:服务端有两个Socket——一个监听Socket(负责接客),一个通信Socket(负责聊天)。客户端只有一个Socket。
3.4 核心流程图
下面我用一张SVG图,把整个流程串起来。你一看就明白了:
3.5 避坑指南
最后,分享几个我踩过的坑,你遇到了能少走弯路:
我曾经犯过的错:
- 忘记bind():服务端不bind,系统会随机分配端口。客户端怎么连你?
- 端口被占用:程序崩溃后端口没释放,重启时报错。加个
SO_REUSEADDR选项能解决。 - 缓冲区太小:一次recv()可能收不完数据。记得循环接收,直到收完为止。
- 阻塞与非阻塞:默认是阻塞模式。如果不想卡住,可以用
fcntl()设置非阻塞。
嗯,Socket基础就讲这么多。说白了,它就是网络通信的「门」,你只要记住创建、绑定、监听、连接、收发、关闭这六个动作,基本就掌握了。
下次我们聊更深入的东西——多线程并发处理多个客户端连接。到时候你就知道,Socket的世界远比想象中精彩。