TCP套接字编程 完整流程解析(服务端 + 客户端)
前言
Socket(套接字)是 TCP/IP 网络体系中,位于应用层与传输层之间的标准网络编程接口。套接字本质是一种特殊的文件描述符,操作系统将网络通信行为抽象为文件读写,开发者可通过 read、write 等文件操作函数,实现跨主机、跨进程的网络数据收发,完成双向网络通信。
TCP 协议对应流式套接字(SOCK_STREAM),具备三大核心特性:面向连接、可靠传输、有序无重复。通信前通过三次握手建立专属连接通道,保障数据可靠交付;通信结束后通过四次挥手正常释放连接资源。
TCP 网络通信默认采用客户端-服务器(C/S)架构:服务器负责监听、接收连接、处理业务数据;客户端主动发起连接请求,与服务端建立通信会话。
本文优化说明:市面多数基础 Demo 仅实现「单向发送、无应答」的简易通信,存在逻辑残缺。本文基于标准 TCP 双向通信流程,修复单向通信缺陷,配套完整一问一答双向交互可运行源码,适配零基础学习与工程入门参考。
一、TCP 整体通信时序与核心流程
TCP 通信存在严格的先后执行顺序:必须先启动服务器完成初始化,再启动客户端发起连接,否则客户端连接会直接失败。

1.1 TCP 服务器完整执行流程
服务端核心职责:创建服务端点、绑定地址端口、开启监听、等待客户端接入、处理双向数据交互、释放资源。完整调用链路如下:
socket() → bind() → listen() → accept() → 双向数据读写 → close()
各步骤详细解析:
1. 创建套接字 socket():生成专属监听套接字文件描述符 server_fd,专门用于监听和接收新客户端连接,不负责业务数据收发。
2. 绑定地址端口 bind():为套接字绑定本机 IP 地址与固定端口号,确定服务唯一访问入口,客户端通过「IP+端口」精准定位服务器程序。
3. 开启监听 listen():将普通套接字转换为监听套接字,设置连接等待队列上限,系统开始持续监听客户端的连接请求。
4. 阻塞接收连接 accept():核心阻塞函数,无客户端连接时程序休眠等待;客户端三次握手完成后,函数返回全新的通信套接字,专门用于数据交互。
核心区分:监听套接字(server_fd)只负责接收新连接;所有数据收发、业务交互,必须使用 accept 返回的通信套接字。
5. 双向数据交互:通过通信套接字读取客户端数据,完成业务逻辑处理后,向客户端返回应答数据,实现完整双向通信。
6. 释放资源 close():客户端断开后关闭对应通信套接字;服务程序退出时,关闭监听套接字,释放端口、文件描述符等系统资源。
1.2 TCP 客户端完整执行流程
客户端核心职责:创建套接字、主动发起连接、与服务端双向收发数据、断开连接释放资源。完整调用链路如下:
socket() → connect() → 数据发送 → 应答接收 → close()
各步骤详细解析:
1. 创建套接字 socket():创建客户端流式套接字,生成客户端专属文件描述符。
2. 发起连接 connect():填入服务器 IP 与端口,主动向服务端发起 TCP 连接请求,底层自动完成三次握手,建立通信通道。
3. 双向数据交互:连接建立成功后,向服务端发送业务数据,同时阻塞接收服务端返回的应答报文。
缺陷修复:传统入门代码仅实现客户端单向发送,缺失应答接收逻辑,本文代码已完整补齐双向收发能力。
4. 断开连接释放资源:收到退出指令后,调用 close 关闭套接字,触发 TCP 四次挥手,正常断开连接、释放系统资源。
二、TCP 核心通信 API 详解
TCP 套接字编程所有功能,均依托固定系统 API 实现,各函数功能、使用主体明确,是网络编程核心基础。
|
核心函数 |
核心功能说明 |
调用主体 |
|---|---|---|
|
socket() |
创建流式套接字,返回套接字文件描述符,完成网络通信初始化 |
服务端、客户端 |
|
bind() |
绑定本机 IP 与端口号,固定服务访问地址,对外暴露服务入口 |
主要服务端调用,客户端可选 |
|
listen() |
将普通套接字转为监听套接字,设置连接等待队列,开启监听状态 |
仅服务端调用 |
|
accept() |
阻塞等待客户端连接,三次握手完成后返回数据通信套接字 |
仅服务端调用 |
|
connect() |
主动向远端服务器发起 TCP 连接请求,完成三次握手建连 |
仅客户端调用 |
|
read()/recv() |
从套接字缓冲区读取对端发送的网络数据 |
服务端、客户端 |
|
write()/send() |
向套接字缓冲区写入数据,发送给通信对端 |
服务端、客户端 |
|
close()/shutdown() |
关闭套接字,释放文件描述符、端口、TCP 连接等系统资源 |
服务端、客户端 |
三、TCP 一对一通信模型解析
一对一串行通信是 TCP 最基础的通信模型,也是入门学习的核心模型,所有基础 Demo 均基于该模型实现。
3.1 模型运行逻辑
1. 服务端完成 socket、bind、listen 初始化后,阻塞在 accept 函数,等待客户端接入;
2. 成功连接单个客户端后,进入数据交互循环,持续处理当前客户端的收发请求;
3. 串行阻塞特性:当前客户端未断开连接时,服务端无法执行 accept,不能接收新的客户端连接;
4. 客户端主动退出、关闭通信套接字后,服务端结束当前会话,重新阻塞在 accept,等待下一个客户端接入。
3.2 模型优缺点
优点:逻辑简单、流程清晰、代码极简,适合初学者理解 TCP 三次握手、数据交互、四次挥手完整流程。
缺点:无并发能力,同一时刻仅能服务一个客户端,无法满足多用户同时访问的业务场景,仅适用于学习测试。
四、TCP 一对一双向通信完整可运行代码
以下为 Linux C 语言完整源码,已修复单向通信缺陷,实现客户端发消息、服务端自动应答的标准双向交互,可直接编译运行测试。
4.1 服务端代码(server.c)
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#define PORT 8888
#define BUF_SIZE 1024
int main(void)
{
// 1. 创建监听套接字
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if(server_fd < 0)
{
perror("socket create fail");
return -1;
}
// 配置服务器地址信息
struct sockaddr_in s_addr;
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(PORT);
s_addr.sin_addr.s_addr = INADDR_ANY;
memset(s_addr.sin_zero, 0, sizeof(s_addr.sin_zero));
// 2. 绑定IP和端口
if(bind(server_fd, (struct sockaddr *)&s_addr, sizeof(s_addr)) < 0)
{
perror("bind fail");
close(server_fd);
return -1;
}
// 3. 开启监听
if(listen(server_fd, 5) < 0)
{
perror("listen fail");
close(server_fd);
return -1;
}
printf("服务端启动成功,监听端口:%d\n", PORT);
char buf[BUF_SIZE] = {0};
// 循环等待客户端连接
while(1)
{
struct sockaddr_in c_addr;
socklen_t c_len = sizeof(c_addr);
// 4. 阻塞等待客户端连接
int conn_fd = accept(server_fd, (struct sockaddr *)&c_addr, &c_len);
if(conn_fd < 0)
{
perror("accept fail");
continue;
}
printf("客户端连接成功!\n");
// 双向数据交互循环
while(1)
{
memset(buf, 0, BUF_SIZE);
// 读取客户端数据
int ret = read(conn_fd, buf, BUF_SIZE);
if(ret <= 0)
{
printf("客户端断开连接\n");
break;
}
printf("接收客户端消息:%s\n", buf);
// 服务端返回应答消息
char rsp[BUF_SIZE] = {0};
snprintf(rsp, BUF_SIZE, "服务端已收到:%s", buf);
write(conn_fd, rsp, strlen(rsp));
}
close(conn_fd);
}
close(server_fd);
return 0;
}
4.2 客户端代码(client.c)
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define PORT 8888
#define BUF_SIZE 1024
int main(void)
{
// 1. 创建客户端套接字
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
if(client_fd < 0)
{
perror("socket create fail");
return -1;
}
// 配置服务器地址信息
struct sockaddr_in s_addr;
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &s_addr.sin_addr);
memset(s_addr.sin_zero, 0, sizeof(s_addr.sin_zero));
// 2. 发起连接请求
if(connect(client_fd, (struct sockaddr *)&s_addr, sizeof(s_addr)) < 0)
{
perror("connect fail");
close(client_fd);
return -1;
}
printf("成功连接服务端!\n");
char send_buf[BUF_SIZE] = {0};
char recv_buf[BUF_SIZE] = {0};
// 循环双向通信
while(1)
{
printf("请输入发送消息(输入exit退出):");
fgets(send_buf, BUF_SIZE, stdin);
// 退出逻辑
if(strstr(send_buf, "exit"))
break;
// 发送数据到服务端
write(client_fd, send_buf, strlen(send_buf));
memset(recv_buf, 0, BUF_SIZE);
// 接收服务端应答
read(client_fd, recv_buf, BUF_SIZE);
printf("接收服务端应答:%s\n\n", recv_buf);
}
close(client_fd);
return 0;
}
五、编译与运行教程
5.1 编译命令(Linux 环境)
gcc server.c -o server
gcc client.c -o client
5.2 运行顺序
1. 终端1启动服务端:./server
2. 终端2启动客户端:./client
3. 客户端输入任意消息,服务端自动回复应答,输入 exit 可退出客户端、断开 TCP 连接。
六、总结
1. TCP 套接字编程核心分为服务端初始化流程与客户端建连流程,启动时序不可颠倒;
2. 严格区分监听套接字与通信套接字,职责分离是 TCP 编程的核心关键点;
3. 本文代码补齐了传统 Demo 的单向通信缺陷,实现标准 C/S 架构双向问答通信;
4. 一对一模型适合入门学习,若需支持多客户端并发,可在此基础上拓展多进程、多线程或 IO 多路复用模型。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)