Linux30 网络编程TCP流程
5.Socket网络编程
5.1Socket 核心概念
1. 什么是 Socket?
Socket 是 “IP 地址 + 端口号 + 传输协议” 的组合,唯一标识网络中的一个通信进程(比如 “192.168.1.100:8080/TCP” 对应电脑上的某个服务程序)。
类比:Socket 就像 “电话”,IP 地址是 “电话号码”,端口号是 “分机号”,传输协议(TCP/UDP)是 “通话规则”。
5.2Socket编程流程
5.2.1 TCP 编程流程(面向连接,可靠传输)
TCP 需先建立连接(三次握手),再传输数据,最后断开连接(四次挥手),流程类似 “打电话”:
服务端(被动监听,等待客户端连接)
- 创建 Socket:创建 TCP 类型的 Socket 端点。
- 绑定地址(bind):将 Socket 与 “IP 地址 + 端口号” 绑定(确定服务端的通信地址)。
- 监听连接(listen):将 Socket 设为 “监听模式”,等待客户端连接。
- 接受连接(accept):阻塞等待客户端连接,连接成功后返回新的 Socket(用于与该客户端通信)。
- 收发数据(read/send):通过新 Socket 与客户端双向传输数据。
- 关闭连接(close):通信结束后,关闭 Socket 释放资源。
客户端(主动发起连接)
- 创建 Socket:创建 TCP 类型的 Socket 端点。
- 连接服务端(connect):主动向服务端的 “IP + 端口” 发起连接。
- 收发数据(read/send):连接成功后,与服务端双向传输数据。
- 关闭连接(close):通信结束后,关闭 Socket。
服务端:socket() → bind() → listen() → accept() → read()/write() → close()↑客户端:socket() → connect() → read()/write() → close()
5.3TCP连接函数详解
5.3.1创建套接字socket函数

函数功能:创建一个用于网络通信的套接字(Socket),返回套接字描述符(类似文件描述符,用于后续网络操作)。
| 参数 | 取值与含义 |
|---|---|
domain | 协议族,如AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNIX(本地进程间通信) |
type | 套接字类型,如SOCK_STREAM(流式,TCP)、SOCK_DGRAM(数据报,UDP) |
protocol | 协议,通常为 0(表示使用type对应的默认协议,如SOCK_STREAM默认 TCP) |
-
成功:返回非负套接字描述符(用于
bind、connect等后续操作)。 -
失败:返回 - 1,可通过
errno查看错误原因。
| Socket 类型 | 对应协议 | 特点 | 适用场景 |
|---|---|---|---|
SOCK_STREAM | TCP | 面向连接、可靠传输、字节流、无边界 | 网页、文件传输、登录验证(需确保数据完整) |
SOCK_DGRAM | UDP | 无连接、不可靠、数据报(有边界) | 视频、语音、实时游戏(允许少量丢包,追求速度) |
5.3.2绑定套接字与地址bind函数

函数功能:将套接字描述符sockfd与指定的网络地址addr绑定,使套接字能通过该地址接收网络请求(服务器端必须调用,客户端通常可选)。
-
成功:返回 0。
-
失败:返回 - 1,可通过
errno查看错误原因(如地址已被占用、权限不足等)
| 参数 | 含义与要求 |
|---|---|
sockfd | 套接字描述符(由socket函数创建返回) |
addr | 指向sockaddr结构体的指针,需根据协议族(如 IPv4/IPv6)填充具体地址信息(如 IP、端口) |
addrlen | addr结构体的长度(字节数),用于告知内核地址结构的大小 |
| 维度 | IPv4 | IPv6 |
|---|---|---|
| 协议族 | AF_INET | AF_INET6 |
| 地址结构体 | struct sockaddr_in | struct sockaddr_in6 |
| 地址表示 | 32 位(如192.168.1.1) | 128 位(如::1表示本地回环) |
| 作用域 ID | 无 | 本地链路地址需设置sin6_scope_id |
bind是服务器端启动流程的核心步骤之一:只有绑定地址后,套接字才能 “监听” 该地址上的连接请求(通过listen函数)。它确保了客户端能通过明确的 IP 和端口找到服务器,是 TCP/UDP 通信中 “地址寻址” 的基础。
5.3.3将套接字转为被动监听状态listen函数

函数功能:将通过socket创建、bind绑定的套接字,转为被动监听状态,开始接收客户端的连接请求。
| 参数 | 含义与要求 |
|---|---|
sockfd | 套接字描述符(需先通过socket创建、bind绑定) |
backlog | 监听队列的最大长度(客户端连接请求的等待队列长度,实际有效长度由系统内核限制,通常设为5、10等合理值) |
-
成功:返回
0。 -
失败:返回
-1,可通过errno查看错误原因(如套接字未绑定、状态非法等)。
listen是服务器端 “接收连接” 流程的核心:调用后,套接字会维护一个半连接队列(客户端发起连接但未完成三次握手的请求)和全连接队列(已完成三次握手、等待服务器accept的请求),backlog参数控制全连接队列的最大长度。只有通过listen,服务器才能用accept函数从队列中取出并处理客户端的连接请求。
5.3.4接收客户端连接的accept4函数

函数功能:从监听套接字的全连接队列中取出一个客户端连接,返回新的套接字描述符(用于与该客户端通信),同时可获取客户端的地址信息。
| 参数 | 含义与要求 |
|---|---|
sockfd | 监听套接字描述符(需先通过socket、bind、listen初始化) |
addr | 指向sockaddr结构体的指针,用于存储客户端的地址信息(若为NULL,则不获取地址) |
addrlen | 指向socklen_t变量的指针,输入时为addr结构体的长度,输出时为实际填充的长度 |
flags | 标志位,可选项如SOCK_NONBLOCK(将新套接字设为非阻塞)、SOCK_CLOEXEC(执行 exec 时关闭套接字) |
-
成功:返回新的套接字描述符(用于与客户端通信)。
-
失败:返回
-1,可通过errno查看错误原因(如无连接可接受、参数非法等)。
accept4是accept的扩展,通过flags参数支持非阻塞模式和文件描述符的自动关闭(CLOEXEC),更适配现代网络编程的高性能、高安全性需求。它是服务器端 “建立连接” 流程的最后一步,调用后服务器即可通过新套接字与客户端进行数据收发。
5.3.5客户端连接服务器connect函数

函数功能:客户端通过connect函数向服务器发起 TCP 连接请求,触发 TCP 三次握手流程,建立端到端的通信链路。
| 参数 | 含义与要求 |
|---|---|
sockfd | 客户端套接字描述符(由socket函数创建,未连接状态) |
addr | 指向sockaddr结构体的指针,需填充服务器的 IP 地址和端口(如struct sockaddr_in用于 IPv4) |
addrlen | addr结构体的长度(字节数),通常用sizeof(struct sockaddr_in)获取 |
-
成功:返回
0,TCP 三次握手完成,连接建立。 -
失败:返回
-1,可通过errno查看错误原因(如ECONNREFUSED表示服务器拒绝连接、ETIMEDOUT表示连接超时)。
5.3.6从已连接套接字接收数据的recv函数

函数功能:从已建立连接的套接字(如 TCP 连接)中接收数据,将数据存入指定缓冲区。
| 参数 | 含义与要求 |
|---|---|
sockfd | 已连接的套接字描述符(如accept返回的服务端客户端套接字、connect后的客户端套接字) |
buf | 存储接收数据的缓冲区指针 |
len | 缓冲区长度(字节数) |
flags | 标志位,常见取值0(阻塞接收),也可设MSG_DONTWAIT(非阻塞)、MSG_PEEK(预览数据不删除)等 |
5.3.7发送网络数据的send函数

函数功能:向套接字发送数据,支持 TCP 流式发送和 UDP 数据报发送(需结合套接字类型)。
| 参数 | 含义与要求 |
|---|---|
sockfd | 套接字描述符(已连接的 TCP 套接字或未连接的 UDP 套接字均可) |
buf | 待发送数据的缓冲区指针(const 修饰表示数据只读) |
len | 待发送数据的长度(字节数) |
flags | 标志位,常见取值0(默认发送),也可设MSG_DONTWAIT(非阻塞发送)、MSG_OOB(发送带外数据)等 |
-
成功:返回实际发送的字节数(≥0)。
-
失败:返回
-1,可通过errno查看错误原因(如EAGAIN表示非阻塞时发送队列满)。
5.4TCP网络编程连接示例
服务器端代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main(){int sersockfd=socket(AF_INET,SOCK_STREAM,0);//地址族 选择socket类型TCP/UDP 协议if(sersockfd==-1){exit(1);}struct sockaddr_in saddr,caddr;//saddr存储服务器地址 caddr存储客户端地址memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");int n=bind(sersockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(n==-1){close(sersockfd);exit(1);}n=listen(sersockfd,5);if(n==-1){close(sersockfd);exit(1);}int len=sizeof(caddr);int cilsockfd=accept(sersockfd,(struct sockaddr*)&caddr,&len);if(cilsockfd<0){close(cilsockfd);exit(1);}printf("accept sockfd =%d port=%d ip=%s\n",cilsockfd,ntohs(caddr.sin_port),inet_ntoa(caddr.sin_addr));if(cilsockfd==0){printf("cilent close\n");close(cilsockfd);exit(1);}char buff[128]={0};printf("input:");fgets(buff,128,stdin);int byte=send(cilsockfd,buff,sizeof(buff),0);memset(buff,0,sizeof(buff));byte=recv(cilsockfd,buff,127,0);printf("recv=%s\n",buff);close(sersockfd);close(cilsockfd);exit(0);
}
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 地址族 选择socket类型TCP/UDP 协议if (sockfd == -1){exit(1);}struct sockaddr_in saddr; // saddr存储服务器地址memset(&saddr, 0, sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int c = connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));if (c == -1){exit(1);}char buff[128] = {0};int byte = recv(sockfd, buff, 127, 0);printf("buff=%s\n", buff);byte = send(sockfd, "ok", 2, 0);close(sockfd);exit(0);
}
结果示例:

