1、网络基础与Socket概念:OSI七层模型、TCP/IP协议栈、Socket是什么、Socket通信流程

各位同学,咱们今天正式开课。做网络编程,尤其是Socket通信,很多人一上来就撸代码,结果遇到莫名其妙的问题——连不上、发乱码、收不全。说白了,底层概念没吃透。

我个人习惯,学任何网络技术,先看它站在哪一层。今天咱们就把地基打牢。

1.1 OSI七层模型——网络世界的分层哲学

OSI七层模型,全称“开放系统互连参考模型”。它是个理论框架,定义了网络通信应该怎么分层。你想想看,两个人打电话,从拨号到挂断,中间经历了多少环节?OSI就是把这些环节拆成了七层。

层级 名称 核心作用 常见协议/设备
7 应用层 为用户提供网络服务接口 HTTP、FTP、SMTP
6 表示层 数据格式转换、加密、压缩 SSL/TLS、JPEG
5 会话层 建立、管理、终止会话 NetBIOS、RPC
4 传输层 端到端可靠传输 TCP、UDP
3 网络层 路由寻址、分组转发 IP、ICMP、路由器
2 数据链路层 帧封装、MAC寻址、差错检测 以太网、交换机
1 物理层 比特流传输、电气特性 网线、集线器、光纤
我的经验: 面试时经常有人把七层背得滚瓜烂熟,但一问“HTTP在哪一层”就卡壳。记住一个口诀:“应表会传网数物”,从上往下记。实际工作中,我们最常打交道的是传输层(4层)和应用层(7层)。

1.2 TCP/IP协议栈——工业界的“事实标准”

OSI七层模型太完美了,完美到没人能完全实现它。工业界用的是TCP/IP协议栈,它把七层压缩成了四层。嗯,这里要注意,TCP/IP不是TCP和IP的简单组合,而是一整套协议簇。

TCP/IP四层模型 对应OSI层级 核心协议
应用层 5~7层(会话、表示、应用) HTTP、DNS、SSH
传输层 4层(传输层) TCP、UDP
网络层 3层(网络层) IP、ICMP、ARP
网络接口层 1~2层(物理、数据链路) 以太网、Wi-Fi

为什么会这样?因为OSI的会话层和表示层在实际应用中,功能被合并到了应用层协议里。比如HTTPS,加密(表示层功能)直接在应用层用SSL/TLS搞定,不需要单独一层。

核心认知: 我们写Socket程序,本质上就是在传输层(TCP/UDP)和应用层之间搭桥。Socket是操作系统提供给应用层的编程接口,让我们能直接操作传输层的数据收发。

1.3 Socket到底是什么?

很多教材把Socket翻译成“套接字”,听着挺玄乎。说白了,Socket就是一个文件描述符。

在Linux系统里,一切皆文件。网络连接也不例外。当你创建一个Socket时,操作系统返回给你一个整数(比如3、4、5),你往这个整数里写数据,数据就发出去了;你从这个整数里读数据,数据就接收到了。

避坑指南: 我曾经遇到过一个线上问题,服务器突然无法建立新连接。排查了半天,发现是文件描述符用完了。Linux默认单个进程最多打开1024个文件描述符,Socket也占这个配额。所以生产环境一定要调大 ulimit -n。

Socket的完整定义:它是IP地址和端口号的组合,唯一标识了网络上的一个通信端点。

比如 192.168.1.100:8080 就是一个Socket。IP地址找到主机,端口号找到主机上的具体进程。

1.4 Socket通信流程——从建立到关闭

咱们以最经典的TCP Socket为例,走一遍完整流程。TCP是面向连接的、可靠的传输协议,它的通信流程可以用“三次握手”和“四次挥手”来概括。

我建议: 初学者先别急着写代码,把下面这张流程图看懂。代码只是流程的翻译,流程才是灵魂。
TCP Socket 通信流程 客户端 (Client) 服务端 (Server) socket() 创建套接字 connect() 发起连接 send() / write() 发送数据 recv() / read() 接收数据 close() 关闭连接 socket() 创建监听套接字 bind() 绑定IP和端口 listen() 进入监听状态 accept() 接受客户端连接 recv() / send() 收发数据 close() 关闭连接 SYN SYN+ACK ACK 数据流 (Data Flow) FIN ACK 说明:虚线为控制报文(SYN/ACK/FIN),实线为数据流。服务端 accept() 后产生新的连接套接字。

1.5 核心步骤详解

咱们把流程拆开,一个一个说。

  1. socket() 创建套接字:告诉操作系统,我要用IPv4还是IPv6,用TCP还是UDP。返回一个文件描述符。
  2. bind() 绑定地址(服务端):把IP和端口号绑定到套接字上。不绑定的话,操作系统随机分配端口。
  3. listen() 监听(服务端):把主动套接字变成被动套接字,开始等待客户端连接。参数是最大等待队列长度。
  4. connect() 发起连接(客户端):客户端主动连接服务端。这里触发三次握手。
  5. accept() 接受连接(服务端):从等待队列里取出一个已完成三次握手的连接,返回一个新的套接字用于通信。
  6. send() / recv() 收发数据:通过套接字读写数据。TCP保证数据按序、不丢失到达。
  7. close() 关闭连接:释放资源。TCP四次挥手在这里发生。
关键点: 服务端有两个套接字——监听套接字(专门负责接客)和连接套接字(专门负责聊天)。监听套接字只做 accept(),不做数据收发。这是初学者最容易搞混的地方。

1.6 一个最简单的TCP Socket代码示例

光说不练假把式。咱们看一段最精简的代码,感受一下流程。

// 服务端示例(伪代码风格,聚焦流程)
int server_fd = socket(AF_INET, SOCK_STREAM, 0);   // 1. 创建
bind(server_fd, &addr, sizeof(addr));               // 2. 绑定
listen(server_fd, 5);                               // 3. 监听

while (1) {
    int client_fd = accept(server_fd, NULL, NULL);  // 4. 接受连接
    char buffer[1024] = {0};
    recv(client_fd, buffer, 1024, 0);               // 5. 接收数据
    printf("收到: %s\n", buffer);
    send(client_fd, "OK", 2, 0);                    // 6. 发送响应
    close(client_fd);                               // 7. 关闭连接
}
// 客户端示例
int sock = socket(AF_INET, SOCK_STREAM, 0);         // 1. 创建
connect(sock, &addr, sizeof(addr));                 // 2. 连接
send(sock, "Hello", 5, 0);                          // 3. 发送
recv(sock, buffer, 1024, 0);                        // 4. 接收
close(sock);                                        // 5. 关闭
我曾经踩过的坑: 服务端代码里,accept() 返回的 client_fd 用完一定要 close()。否则文件描述符泄漏,跑个几千个连接后,服务端就再也无法 accept 新连接了。嗯,线上故障就是这么来的。

1.7 本章小结

咱们这一章把网络基础捋了一遍。OSI七层模型是理论框架,TCP/IP四层是实战标准。Socket就是操作传输层的文件描述符。通信流程七个步骤:创建、绑定、监听、连接、接受、收发、关闭。

我个人觉得,理解Socket的关键不在于记住函数名,而在于脑子里有那张流程图。数据怎么从应用层一层层封装,穿过网线,再一层层解封装到对端应用层——这个画面感建立起来,后面学HTTP、WebSocket、epoll都会轻松很多。

课后建议: 打开你的Linux虚拟机,用 man socket、man bind 看看手册。然后照着上面的代码敲一遍,用 telnet 或者 nc 连上去测试。亲手跑通一次,比看十遍书都管用。