《UNIX网络编程卷1:套接字联网API》第4章 基本TCP套接字编程

《UNIX网络编程卷1:套接字联网API》第4章 基本TCP套接字编程


4.1 TCP套接字编程核心流程

TCP套接字编程遵循客户-服务器模型,其核心流程可分解为以下步骤(图4-1):

服务器端

  1. socket():创建监听套接字
  2. bind():绑定本地IP与端口
  3. listen():开启监听模式
  4. accept():接受客户端连接请求
  5. read()/write():与客户端通信
  6. close():关闭连接

客户端

  1. socket():创建通信套接字
  2. connect():发起连接请求
  3. read()/write():与服务器通信
  4. close():关闭连接
c 复制代码
服务端流程:
开始
  ↓
socket() 创建监听套接字
  ↓
bind() 绑定本地IP与端口
  ↓
listen() 开启监听模式
  ↓
accept() 接受客户端连接请求
  ↓
read()/write() 与客户端通信
  ↓
close() 关闭连接
结束

客户端流程:
开始
  ↓
socket() 创建通信套接字
  ↓
connect() 发起连接请求
  ↓
read()/write() 与服务器通信
  ↓
close() 关闭连接
结束

4.2 套接字创建与协议选择
4.2.1 socket()函数详解
c 复制代码
#include <sys/socket.h>
int socket(int family, int type, int protocol);
  • 参数解析

    • family:协议族(如AF_INETAF_INET6AF_UNIX);
    • type:套接字类型(SOCK_STREAMSOCK_DGRAM);
    • protocol:通常为0(自动选择默认协议)。
  • 返回值

    • 成功:返回非负套接字描述符;
    • 失败:返回-1,设置errno(如EAFNOSUPPORT协议不支持)。

示例:创建IPv4 TCP套接字

c 复制代码
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0)
    err_sys("socket error");
4.2.2 协议选择的工程实践
  • IPv4与IPv6兼容性设计 :优先使用AF_UNSPEC配合getaddrinfo(详见第11章);
  • TCP与SCTP对比:TCP适合文件传输,SCTP适合多流场景(如5G信令)。

4.3 绑定地址与端口:bind()函数
4.3.1 bind()函数原型
c 复制代码
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 关键参数

    • addr:指向特定协议地址结构(如sockaddr_in);
    • addrlen:地址结构长度。
  • 常见错误

    • EADDRINUSE:端口被占用;
    • EACCES:绑定特权端口(<1024)权限不足。

示例:绑定IPv4地址到套接字

c 复制代码
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 通配地址
servaddr.sin_port = htons(9999); // 端口号
if (bind(listenfd, (SA *)&servaddr, sizeof(servaddr)) < 0)
    err_sys("bind error");
4.3.2 通配地址与特定地址
  • INADDR_ANY:允许套接字监听所有本地接口;
  • 指定IP :仅监听特定接口(如inet_pton(AF_INET, "192.168.1.100", &servaddr.sin_addr))。

4.4 监听与连接队列:listen()函数
4.4.1 listen()函数原型
c 复制代码
int listen(int sockfd, int backlog);
  • backlog参数
    定义内核维护的**未完成连接队列(SYN_RCVD状态) 已完成连接队列(ESTABLISHED状态)**的总长度上限。
    • 传统BSD实现:backlog为两个队列总和;
    • Linux 4.3+:通过/proc/sys/net/core/somaxconn动态调整。

最佳实践

c 复制代码
const int BACKLOG = 1024;
if (listen(listenfd, BACKLOG) < 0)
    err_sys("listen error");
4.4.2 连接队列溢出处理
  • 客户端收到ECONNREFUSED错误;
  • 服务器可通过netstat -s | grep overflow监控溢出次数。

4.5 接受连接:accept()函数
4.5.1 accept()函数原型
c 复制代码
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
  • 参数特性
    • cliaddr:返回客户端地址信息;
    • addrlen:值-结果参数(需初始化为缓冲区大小)。

示例:获取客户端IP与端口

c 复制代码
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
int connfd = accept(listenfd, (SA *)&cliaddr, &clilen);
if (connfd < 0)
    err_sys("accept error");

char client_ip[INET_ADDRSTRLEN];
printf("Client connected from %s:%d\n",
       inet_ntop(AF_INET, &cliaddr.sin_addr, client_ip, sizeof(client_ip)),
       ntohs(cliaddr.sin_port));
4.5.2 非阻塞accept与EAGAIN
  • 若监听套接字设为非阻塞且无连接到达,返回EAGAIN错误;
  • 需结合I/O复用(如selectepoll)处理。

4.6 发起连接:connect()函数
4.6.1 connect()函数原型
c 复制代码
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
  • 连接过程
    • 客户端发送SYN;
    • 等待服务器SYN+ACK;
    • 发送ACK完成三次握手(图4-2)。

示例:客户端连接服务器

c 复制代码
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9999);
inet_pton(AF_INET, "192.168.1.100", &servaddr.sin_addr);
if (connect(sockfd, (SA *)&servaddr, sizeof(servaddr)) < 0)
    err_sys("connect error");
4.6.2 超时与错误处理
  • ETIMEDOUT:未收到SYN+ACK(可能服务器宕机);
  • ECONNREFUSED:目标端口无监听服务;
  • ENETUNREACH:路由不可达。

4.7 数据传输:read()与write()的局限性

TCP为字节流协议,需处理粘包部分读写问题:

  • 粘包 :多次write可能被合并为一个TCP段;
  • 部分读写read可能返回小于请求长度的数据。

解决方案

  • 定义应用层协议(如固定长度头+变长体);
  • 使用readn/writen包裹函数(见第3章)。

4.8 连接终止:close()与shutdown()
4.8.1 close()函数
  • 减少套接字引用计数,计数为0时发送FIN;
  • 可能因SO_LINGER选项改变行为(详见第7章)。
4.8.2 shutdown()函数
c 复制代码
int shutdown(int sockfd, int howto);
  • howto参数
    • SHUT_RD:关闭读端;
    • SHUT_WR:关闭写端(发送FIN);
    • SHUT_RDWR:全双工关闭。

优势:允许半关闭连接(如HTTP/1.1持久连接)。


4.9 并发服务器模型
4.9.1 多进程模型
c 复制代码
pid_t pid;
int connfd;
for (;;) {
    connfd = accept(listenfd, NULL, NULL);
    if ((pid = fork()) == 0) {  // 子进程
        close(listenfd);       // 关闭监听套接字
        // 处理客户端请求
        exit(0);
    }
    close(connfd);             // 父进程关闭连接套接字
}
4.9.2 多线程模型
c 复制代码
#include <pthread.h>
void *handle_client(void *arg) {
    int connfd = *((int *)arg);
    // 处理客户端请求
    close(connfd);
    return NULL;
}

int main() {
    pthread_t tid;
    int connfd;
    for (;;) {
        connfd = accept(listenfd, NULL, NULL);
        pthread_create(&tid, NULL, handle_client, (void *)&connfd);
    }
}
4.9.3 I/O复用模型
c 复制代码
// 使用select实现(详见第6章)
fd_set readset;
FD_ZERO(&readset);
FD_SET(listenfd, &readset);
select(listenfd + 1, &readset, NULL, NULL, NULL);
if (FD_ISSET(listenfd, &readset)) {
    connfd = accept(listenfd, NULL, NULL);
    // 处理连接
}

4.10 实战:完整Echo服务器与客户端
4.10.1 Echo服务器代码
c 复制代码
#include "unp.h"

void str_echo(int sockfd) {
    ssize_t n;
    char buf[MAXLINE];
again:
    while ((n = read(sockfd, buf, MAXLINE)) > 0)
        writen(sockfd, buf, n);
    if (n < 0 && errno == EINTR)
        goto again;
    else if (n < 0)
        err_sys("str_echo: read error");
}

int main() {
    int listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    // 绑定与监听(代码同前)
    for (;;) {
        int connfd = accept(listenfd, NULL, NULL);
        if (fork() == 0) {
            close(listenfd);
            str_echo(connfd);
            exit(0);
        }
        close(connfd);
    }
}
4.10.2 Echo客户端代码
c 复制代码
#include "unp.h"

void str_cli(FILE *fp, int sockfd) {
    char sendline[MAXLINE], recvline[MAXLINE];
    while (fgets(sendline, MAXLINE, fp) != NULL) {
        writen(sockfd, sendline, strlen(sendline));
        if (readline(sockfd, recvline, MAXLINE) == 0)
            err_quit("str_cli: server terminated prematurely");
        fputs(recvline, stdout);
    }
}

int main() {
    int sockfd = Socket(AF_INET, SOCK_STREAM, 0);
    // 连接服务器(代码同前)
    str_cli(stdin, sockfd);
    exit(0);
}

4.11 本章小结与习题

小结:本章系统讲解了TCP套接字编程的核心API与并发模型,通过Echo案例展示了完整开发流程。

习题

  1. 实现支持多客户端的UDP Echo服务器,对比TCP/UDP编程差异;
  2. 修改Echo服务器为线程池模型,测试并发性能;
  3. 使用tcpdump抓取三次握手与四次挥手数据包,分析字段含义。

付费用户专属资源

  • 完整Echo工程(含Makefile与压力测试脚本);
  • TCP状态转换图(矢量图);
  • 扩展阅读:《TCP协议栈内核实现剖析》。

4.11 本章小结与习题

小结:本章系统讲解了TCP套接字编程的核心API与并发模型,通过Echo案例展示了完整开发流程。

习题

  1. 实现支持多客户端的UDP Echo服务器,对比TCP/UDP编程差异;
  2. 修改Echo服务器为线程池模型,测试并发性能;
  3. 使用tcpdump抓取三次握手与四次挥手数据包,分析字段含义。

付费用户专属资源

  • 完整Echo工程(含Makefile与压力测试脚本);
  • TCP状态转换图(矢量图);
  • 扩展阅读:《TCP协议栈内核实现剖析》。

通过本章学习,读者将掌握TCP套接字编程的核心技术,并具备开发高并发网络服务的能力。

相关推荐
敖行客 Allthinker几秒前
GitHub 封禁中国 IP:影响、原因及应对
网络协议·tcp/ip·github
D龙源4 分钟前
VSCode-IoC和DI
后端·架构
小锋学长生活大爆炸10 分钟前
【教程】检查RDMA网卡状态和测试带宽 | 附测试脚本
运维·服务器·网络·ubuntu·网卡·rdma
EasyDSS21 分钟前
安防监控视频管理平台EasyCVR助力建筑工地施工4G/5G远程视频监管方案
大数据·网络·网络协议·音视频
学废了wuwu25 分钟前
【计算机网络】什么是路由?核心概念与实战详解
网络·计算机网络·智能路由器
AronTing38 分钟前
10-Spring Cloud Alibaba 之 Dubbo 深度剖析与实战
后端·面试·架构
virelin_Y.lin1 小时前
系统与网络安全------网络通信原理(5)
网络·安全·web安全·udp·tcp·传输层
格里姆肖1 小时前
LVGL源码(7):渲染
c语言·stm32·单片机
code菜只因1 小时前
初始C语言(2)
c语言
双叶8362 小时前
(51单片机)LCD显示日期时间时钟(DS1302时钟模块教学)(LCD1602教程)
c语言·开发语言·数据库·单片机·嵌入式硬件·mongodb·51单片机