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 服务端流程

服务端是「被动方」,它等着客户端来找它。流程如下:

  1. 创建Socket:调用socket()函数,指定协议族、类型、协议。
  2. 绑定地址:调用bind(),把Socket和IP地址、端口号绑在一起。
  3. 监听连接:调用listen(),告诉系统「我准备好了,可以接受连接了」。
  4. 接受连接:调用accept(),阻塞等待客户端连接。连接成功后返回一个新的Socket。
  5. 收发数据:用read()/write()或recv()/send()通信。
  6. 关闭连接:调用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 客户端流程

客户端是「主动方」,它去连接服务端。流程更简单:

  1. 创建Socket:同样调用socket()。
  2. 连接服务端:调用connect(),指定服务端的IP和端口。
  3. 收发数据:和服务端一样,用read()/write()。
  4. 关闭连接:调用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图,把整个流程串起来。你一看就明白了:

Socket编程基本流程 服务端 客户端 socket() 创建 bind() 绑定 listen() 监听 accept() 等待连接 read()/write() 通信 close() 关闭 socket() 创建 connect() 连接 read()/write() 通信 close() 关闭 三次握手建立连接 数据交换 四次挥手关闭连接

3.5 避坑指南

最后,分享几个我踩过的坑,你遇到了能少走弯路:

我曾经犯过的错:

  • 忘记bind():服务端不bind,系统会随机分配端口。客户端怎么连你?
  • 端口被占用:程序崩溃后端口没释放,重启时报错。加个SO_REUSEADDR选项能解决。
  • 缓冲区太小:一次recv()可能收不完数据。记得循环接收,直到收完为止。
  • 阻塞与非阻塞:默认是阻塞模式。如果不想卡住,可以用fcntl()设置非阻塞。

嗯,Socket基础就讲这么多。说白了,它就是网络通信的「门」,你只要记住创建、绑定、监听、连接、收发、关闭这六个动作,基本就掌握了。

下次我们聊更深入的东西——多线程并发处理多个客户端连接。到时候你就知道,Socket的世界远比想象中精彩。