TCP 编程探秘:建立连接、数据传输与多路复用的精髓

4 篇文章 0 订阅
2 篇文章 0 订阅

0. TCP 编程函数概览

函数名用法参数返回值说明
socketint socket(int domain, int type, int protocol);domain: 协议族
type: 套接字类型
protocol: 协议类型
成功时返回套接字描述符,失败时返回 -1创建一个套接字
bindint bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);sockfd: 套接字描述符
addr: 指向地址结构的指针
addrlen: 地址结构的长度
成功时返回 0,失败时返回 -1将套接字与特定地址和端口绑定
listenint listen(int sockfd, int backlog);sockfd: 套接字描述符
backlog: 等待连接队列的最大长度
成功时返回 0,失败时返回 -1将套接字设置为监听状态
acceptint accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);sockfd: 套接字描述符
addr: 指向用于存放客户端地址的缓冲区
addrlen: 地址结构的长度
成功时返回新的套接字描述符,失败时返回 -1接受客户端的连接请求
connectint connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);sockfd: 套接字描述符
addr: 指向服务器地址的指针
addrlen: 地址结构的长度
成功时返回 0,失败时返回 -1发起与服务器的连接请求
sendssize_t send(int sockfd, const void *buf, size_t len, int flags);sockfd: 套接字描述符
buf: 指向要发送数据的缓冲区
len: 要发送的数据长度
flags: 发送标志
成功时返回发送的字节数,失败时返回 -1发送数据
recvssize_t recv(int sockfd, void *buf, size_t len, int flags);sockfd: 套接字描述符
buf: 用于存放接收数据的缓冲区
len: 缓冲区的长度
flags: 接收标志
成功时返回接收的字节数,失败时返回 -1接收数据
closeint close(int sockfd);sockfd: 套接字描述符成功时返回 0,失败时返回 -1关闭套接字

0.1. 错误处理相关函数

函数名用法参数返回值说明
perrorvoid perror(const char *s);s: 用于输出错误信息的字符串无返回值输出描述性错误消息
errnoint errno;无参数整数值全局变量,存储发生错误的代码
strerrorchar *strerror(int errnum);errnum: 错误码返回错误描述字符串根据错误码返回描述性字符串

I. 建立TCP连接

A. TCP简介

  • TCP传输控制协议概述

    • TCP是一种面向连接的、可靠的传输层协议,用于在网络中提供可靠的数据传输服务。
    • 它通过各种机制(如序列号、确认应答、重传)确保数据的完整性和可靠性。
  • 可靠、面向连接通信的重要性

    • 可靠性:TCP通过序列号、确认和重传等机制,保证数据在传输过程中不丢失、不重复,并按顺序交付。
    • 面向连接:建立连接的过程中,通信双方会进行协商,确保彼此能够正常通信,并在通信结束后释放连接,释放资源。

B. 三次握手

  • 三次握手的解释:SYN、SYN-ACK、ACK

    握手阶段发送方接收方描述
    1. 发送SYNClientServer发起连接请求,同步序列号(SYN)
    2. 接收SYNServerClient收到请求,回复同步确认和自己的SYN(SYN-ACK)
    3. 发送ACKClientServer确认接收方的SYN,完成握手(ACK)
  • 序列号在建立可靠连接中的重要性

    • 每个TCP报文段都包含一个序列号,用于标识发送的数据在整个数据流中的位置。序列号的使用使得接收方能够正确地组装数据流,并实现可靠的数据传输。
  • 通过示例代码演示三次握手过程

    • 下面是一个简单的C语言示例代码,演示了TCP三次握手的过程:
    // Client
    send_syn();        // 发送SYN
    recv_syn_ack();    // 接收SYN-ACK
    send_ack();        // 发送ACK
    
    // Server
    recv_syn();        // 接收SYN
    send_syn_ack();    // 发送SYN-ACK
    recv_ack();        // 接收ACK
    

II. 数据传输

A. TCP数据传输方式

  • 数据流的概念

    • TCP提供的是一个面向字节流的数据传输方式,数据被看作是无边界的字节流,而不是分段的消息。
    • 这意味着发送方的数据可以被接收方按照任意大小的块进行接收。
  • 使用send()recv()等相关函数进行数据传输

    • send(socket, buffer, size, flags):将数据从缓冲区发送到连接的套接字。
    • recv(socket, buffer, size, flags):从连接的套接字接收数据并存储到缓冲区。
  • 处理粘包和拆包问题

    • 粘包(Packet Pawning):多个发送的小数据包被接收方当作一个大数据包处理。
    • 拆包(Packet Splitting):一个发送的大数据包被接收方分割成多个小数据包处理。
    • 解决方案包括消息边界标记、固定长度消息和长度字段等方法。

III. 多路复用

A. 多路复用机制介绍

  • select()poll()epoll()等多路复用机制概述

    • 多路复用允许单个进程处理多个连接,提高系统的并发性能。
    • select():基于轮询的机制,效率较低。
    • poll():改进的select(),支持更多的文件描述符,但仍有限。
    • epoll():Linux特有的高性能多路复用机制,支持水平触发和边缘触发。
  • 多路复用的优势和适用场景

    • 通过单一的系统调用来监视多个文件描述符,提高效率。
    • 适用于大量的并发连接,如服务器需要同时处理多个客户端请求。
  • 通过代码示例演示多路复用的基本用法

    • 示例代码(使用epoll()):
    // 创建epoll实例
    int epoll_fd = epoll_create1(0);
    
    // 将套接字添加到epoll监视列表
    struct epoll_event event; 
    event.events = EPOLLIN;
    event.data.fd = server_socket;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event);
    
    while (1) {
        struct epoll_event events[MAX_EVENTS];
        int ready_fds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
    
        for (int i = 0; i < ready_fds; ++i) {
            if (events[i].data.fd == server_socket) {
                // 处理新的客户端连接
                // ...
            } else {
                // 处理已连接客户端的数据
                // ...
            }
        }
    }
    

B. select()函数详解

  • select()函数的使用方法和参数

    • select(max_fd + 1, &read_fds, &write_fds, &except_fds, &timeout):监视文件描述符的可读、可写和异常情况。
  • 阻塞和非阻塞模式的区别

    • 阻塞模式:调用select()后,会一直等待直到有文件描述符就绪。
    • 非阻塞模式:通过设置非阻塞标志,select()调用会立即返回,无论文件描述符是否就绪。
  • 错误处理和超时设置

    • 返回值小于0表示发生错误,可以通过errno获取错误信息。
    • 超时设置通过timeout参数实现,允许程序定时检查文件描述符的状态。

C. poll()函数详解

  • poll()函数的使用方法和参数

    • poll(struct pollfd fds[], nfds, timeout):监视一组文件描述符的可读、可写和异常情况。
  • select()的比较优势

    • poll()没有文件描述符数的限制,更适用于大规模的并发连接。
  • 处理大量文件描述符的效率

    • 采用数组结构,通过revents字段判断文件描述符的状态,效率相对较高。

D. epoll()函数详解

  • epoll()函数的使用方法和参数

    • epoll_create1(flags):创建一个epoll实例,epoll_ctl()用于添加、修改和删除文件描述符,epoll_wait()等待文件描述符就绪。
  • 边缘触发和水平触发模式的区别

    • 边缘触发:只在状态变化时通知一次,需要使用非阻塞模式处理文件描述符。
    • 水平触发:在状态保持的情况下会持续通知。
  • 高效处理大量并发连接的方法

    • 通过事件驱动的方式,只关注活跃的文件描述符,避免遍历整个文件描述符集合。
    • 使用EPOLLET标志开启边缘触发模式,提高效率。

IV. 实际应用案例

A. 开发一个简单的TCP服务器和客户端

1. TCP服务器
  • 服务器端伪代码

    // 创建服务器套接字
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);          
    
    // 绑定服务器地址
    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = INADDR_ANY;
    server_address.sin_port = htons(PORT);
    bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address));
    
    // 监听连接请求
    listen(server_socket, BACKLOG);
    
    // 接受客户端连接
    int client_socket = accept(server_socket, NULL, NULL);
    
    // 接收和处理客户端数据
    char buffer[BUFFER_SIZE];
    recv(client_socket, buffer, BUFFER_SIZE, 0);
    
    // 发送数据给客户端
    send(client_socket, "Hello, client!", strlen("Hello, client!"), 0);
    
    // 关闭连接
    close(client_socket);
    close(server_socket);
    
  • 服务器端流程图

绑定服务器地址
接受客户端连接
发送数据给客户端
关闭服务器套接字
创建服务器套接字
监听连接请求
接收和处理客户端数据
关闭连接
2. TCP客户端
  • 客户端伪代码

    // 创建客户端套接字
    int client_socket = socket(AF_INET, SOCK_STREAM, 0);             
    
    // 设置服务器地址
    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr(SERVER_IP);
    server_address.sin_port = htons(PORT);
    
    // 连接到服务器
    connect(client_socket, (struct sockaddr*)&server_address, sizeof(server_address));
    
    // 发送数据给服务器
    send(client_socket, "Hello, server!", strlen("Hello, server!"), 0);
    
    // 接收服务器响应
    char buffer[BUFFER_SIZE];
    recv(client_socket, buffer, BUFFER_SIZE, 0);
    
    // 处理服务器响应
    printf("Server response: %s\n", buffer);
    
    // 关闭连接
    close(client_socket);
    
  • 客户端流程图

设置服务器地址
发送数据给服务器
处理服务器响应
关闭客户端套接字
创建客户端套接字
连接到服务器
接收服务器响应
关闭连接
3. 处理连接、数据传输和关闭连接的过程
  • 连接过程

    • 服务器通过socket()bind()listen()创建并准备监听套接字。
    • 客户端通过socket()connect()创建并连接到服务器。
  • 建立连接流程图

Client Server 创建套接字 socket() socket(AF_INET, SOCK_STREAM, 0) 设置服务器地址和端口 memset(&serverAddr, 0, sizeof(serverAddr)) serverAddr.sin_family = AF_INET serverAddr.sin_port = htons(SERVER_PORT) inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) 发起连接 connect() connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) 创建套接字 socket() socket(AF_INET, SOCK_STREAM, 0) 绑定地址和端口 bind() memset(&serverAddr, 0, sizeof(serverAddr)) serverAddr.sin_family = AF_INET serverAddr.sin_port = htons(SERVER_PORT) serverAddr.sin_addr.s_addr = htonl(INADDR_ANY) bind(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) 监听连接 listen() listen(sockfd, BACKLOG) 接受连接 accept() clientSock = accept(sockfd, (struct sockaddr*)&clientAddr, &clientAddrLen) 连接建立成功 // 处理连接成功的逻辑 Client Server
  • 数据传输过程

    • 服务器通过accept()接受客户端连接,并使用send()recv()进行数据传输。
    • 客户端通过send()recv()与服务器进行数据交互。
  • 关闭连接过程

    • 服务器和客户端均通过close()关闭套接字,释放相关资源。

这个案例演示了一个简单的TCP服务器和客户端的实现过程,使用了伪代码、流程图和文字描述,以便更好地理解连接、数据传输和关闭连接的整个过程。

  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值