十七、TCP编程

发布于:2025-04-13 ⋅ 阅读:(26) ⋅ 点赞:(0)

  TCP 编程是网络通信的核心,其 API 围绕面向连接的特性设计,涵盖服务端和客户端的交互流程。以下是基于 ​C 语言的 TCP 编程核心 API 及使用流程的详细解析:


  核心 API 概览

函数 角色 描述
socket() 通用 创建套接字,指定协议族(IPv4/IPv6)和类型(SOCK_STREAM)。
bind() 服务端 将套接字绑定到特定 IP 地址和端口。
listen() 服务端 将套接字设为监听模式,等待客户端连接请求。
accept() 服务端 接受客户端连接,返回用于通信的新套接字。
connect() 客户端 客户端主动连接服务端。
send()/write() 通用 发送数据(TCP 保证数据顺序,可能拆包/粘包)。
recv()/read() 通用 接收数据(需处理部分读取和缓冲区管理)。
close() 通用 关闭套接字,终止连接。
shutdown() 通用 优雅关闭连接(可选关闭读/写方向)。

1、socket函数

函数原型与头文件
#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

  参数详解

  domain(协议族/地址族)​

  定义通信使用的协议族,常见值包括:

​值 ​描述
AF_INET IPv4 协议(最常用)
AF_INET6 IPv6 协议
AF_UNIX/AF_LOCAL 本地进程间通信(UNIX 域套接字)
AF_PACKET 底层数据包接口(如原始以太网帧捕获)

  如果是IPV6编程,要使用struct sockddr_in6结构体(man 7 IPV6,通常使用struct sockaddr_storage来编程。

  type(套接字类型)​

指定数据传输的语义,常用类型:

​值 ​描述
SOCK_STREAM 面向连接的流式套接字(TCP,可靠传输)
SOCK_DGRAM 无连接的数据报套接字(UDP,尽最大努力交付)
SOCK_RAW 原始套接字(直接访问 IP/ICMP 等协议)
protocol(具体协议​)

通常设为 0,表示根据 domaintype ​自动选择默认协议。例如:

  • SOCK_STREAM 默认使用 IPPROTO_TCP
  • SOCK_DGRAM 默认使用 IPPROTO_UDP

若需显式指定协议,可用:

​值 ​描述
IPPROTO_TCP 强制使用 TCP 协议
IPPROTO_UDP 强制使用 UDP 协议
IPPROTO_ICMP 用于原始套接字的 ICMP 协议

返回值

  • ​成功:返回一个非负整数​(套接字文件描述符),后续操作(如 bind, connect)均基于此描述符。
  • ​失败:返回 -1,并设置 errno 表示错误原因(如 EACCES, EAFNOSUPPORT)。

典型使用场景

创建 TCP 套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
    perror("socket() failed");
    exit(EXIT_FAILURE);
}
  • 用于 HTTP、FTP 等需要可靠传输的应用。
​创建 UDP 套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
    perror("socket() failed");
    exit(EXIT_FAILURE);
}
  • 用于 DNS 查询、实时音视频传输等场景。
创建原始套接字(需 root 权限)​
int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sockfd == -1) {
    perror("socket() failed");
    exit(EXIT_FAILURE);
}
  • 可手动构造 IP 或 ICMP 头部,用于网络探测工具(如 ping)。

2、bind函数

  bind() 函数是网络编程中用于将套接字(socket)​与特定的IP地址和端口绑定的关键步骤,常用于服务端设置监听地址。以下是 bind() 的详细解析,包含函数原型、参数解释、使用示例及常见问题:

函数原型
#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数

​参数 ​类型 ​描述
sockfd int 已创建的套接字描述符(由socket()返回)
addr struct sockaddr* 指向保存绑定地址信息的结构体指针(需根据协议族填充对应的结构类型)
addrlen socklen_t 地址结构体的长度(字节数)

地址结构体

​1. IPv4 地址结构 (struct sockaddr_in)
struct sockaddr_in {
    sa_family_t    sin_family;   // 地址族(如 AF_INET)
    in_port_t      sin_port;     // 端口号(需用 `htons()` 转换字节序)
    struct in_addr sin_addr;     // IPv4 地址(需用 `inet_pton()` 或 `htonl(INADDR_ANY)`)
    unsigned char  sin_zero[8];  // 填充字段(一般置零)
};

struct in_addr {
    uint32_t s_addr;            // 32位 IPv4 地址(网络字节序)
};
​2. IPv6 地址结构 (struct sockaddr_in6)
struct sockaddr_in6 {
    sa_family_t     sin6_family;   // 地址族(AF_INET6)
    in_port_t       sin6_port;     // 端口号(网络字节序)
    uint32_t        sin6_flowinfo; // IPv6 流信息(通常为0)
    struct in6_addr sin6_addr;     // IPv6 地址
    uint32_t        sin6_scope_id; // 接口范围标识符(用于本地链路地址)
};

struct in6_addr {
    unsigned char s6_addr[16];     // 128位 IPv6 地址
};
​3. 通用地址结构 (struct sockaddr)
struct sockaddr {
    sa_family_t sa_family;  // 地址族(AF_xxx)
    char        sa_data[14];// 具体地址数据(由子结构展开填充)
};

  使用场景与示例

​1. 服务端绑定 IP 和端口
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) { perror("socket"); exit(1); }

// 允许地址重用(避免服务端重启时 bind 失败)
int optval = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;                       // IPv4
addr.sin_addr.s_addr = htonl(INADDR_ANY);        // 绑定所有本地 IP
addr.sin_port = htons(8080);                     // 绑定端口 8080

if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    perror("bind");
    close(server_fd);
    exit(1);
}

// 后续可调用 listen() 启动监听
​2. 客户端绑定特定源地址
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in client_addr = {0};
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(12345);             // 指定客户端端口
inet_pton(AF_INET, "192.168.1.100", &client_addr.sin_addr); // 指定源 IP

bind(client_fd, (struct sockaddr*)&client_addr, sizeof(client_addr));
connect(client_fd, server_addr, sizeof(server_addr));

3、listen函数

函数原型
#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);

  参数详解

参数 类型 描述
sockfd int 已通过 bind() 绑定地址的套接字描述符(必须是 ​SOCK_STREAM 类型)。
backlog int 已建立连接(完成三次握手)的队列最大长度,决定同时等待 accept() 处理的连接数。

  核心作用

  1. ​转换套接字状态:

    • 将套接字从主动模式​(默认)转为被动模式,使其能够接收客户端的连接请求。
    • 未调用 listen() 的套接字无法调用 accept()
  2. ​管理连接队列:

    • 内核为监听套接字维护两个队列​(具体实现可能因操作系统而异):
      • ​未完成队列(SYN_RCVD 状态)​:客户端已发送 SYN,但未完成三次握手。
      • ​已完成队列(ESTABLISHED 状态)​:已完成三次握手,等待 accept() 取出。
    • backlog 参数通常指已完成队列的最大长度​(Linux 中默认上限由 /proc/sys/net/core/somaxconn 定义)。

  使用场景与示例

​1. 服务端启动监听
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
// ...绑定地址(bind())...

// 设置监听队列长度为 128
if (listen(server_fd, 128) == -1) {
    perror("listen() failed");
    close(server_fd);
    exit(EXIT_FAILURE);
}

// 循环接受客户端连接
while (1) {
    struct sockaddr_in client_addr;
    socklen_t addrlen = sizeof(client_addr);
    int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addrlen);
    // ...处理 client_fd...
}
​2. backlog 的合理取值
​经验值:通常设为 SOMAXCONN(系统定义的最大值,如 Linux 默认 4096)。
​调整方法​(Linux):

# 临时修改 somaxconn
echo 4096 > /proc/sys/net/core/somaxconn

# 永久修改(需编辑 /etc/sysctl.conf)
net.core.somaxconn = 4096
  • 注意:实际允许的连接数受系统资源和并发模型(如多线程、epoll)影响。

4、accept函数

函数原型
#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  参数详解

​参数

​类型

​描述

sockfd

int

处于监听状态的套接字描述符(由 listen() 设置)。

addr

struct sockaddr*

输出参数,用于存储客户端地址信息(如 IP 和端口)。若不需要可设为 NULL

addrlen

socklen_t*

输入输出参数:传入 addr 缓冲区的长度,返回实际写入的地址长度。若 addrNULL,可设为 NULL


  返回值

  • ​成功:返回一个新的已连接套接字描述符​(非负整数),专门用于与客户端通信。

  • ​失败:返回 -1,并设置 errno(如 EINTRECONNABORTED)。


  核心作用

  1. ​提取连接:从监听套接字的已完成连接队列​(已完成三次握手)中取出一个客户端连接。

  2. ​生成新套接字:返回的已连接套接字与客户端一一对应,原监听套接字继续接受其他连接。

  3. ​获取客户端地址:通过 addr 参数获取客户端的 IP 地址和端口(可选)。

5、connect函数

函数原型
#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

  参数详解

参数 类型 描述
sockfd int 客户端套接字描述符(由 socket() 创建)。
addr struct sockaddr* 指向服务端地址结构体的指针(如 sockaddr_in)。
addrlen socklen_t 地址结构体的长度(单位:字节)。

  返回值

  • 成功:返回 0,套接字进入已连接状态(TCP)或设置默认地址(UDP)。
  • 失败:返回 -1,并设置 errno 表示错误原因。