如何在Linux中选择TCP协议进行网络通信?

在 Linux 中,使用 ss 命令可以查看和管理 TCP 连接。ss -t 列出所有 TCP 连接。

在Linux操作系统中,TCP(传输控制协议)是网络通信的核心组件之一,它提供了一种可靠的、面向连接的数据传输方式,确保数据包按顺序和无错误地到达目标地址,本文将深入探讨如何在Linux环境下使用select系统调用来监控多个TCP套接字(sockets),以及这种技术如何帮助开发者实现高效的网络编程。

理解TCP与Socket

select linux tcp

TCP是一种面向连接的协议,意味着在数据传输之前,通信双方需要先建立一个连接,这个连接通过套接字(socket)来实现,套接字是一个通信端点,可以看作是网络通信中的一个“电话”,在Linux中,套接字可以通过文件描述符来引用,就像普通文件一样进行读写操作。

`select`系统调用

select是Linux提供的一个系统调用,用于监视文件描述符集合的变化情况,特别是检查一个或多个套接字是否有数据可读、可写或有异常条件发生,这对于编写能够同时处理多个网络连接的服务器程序尤为重要,因为它允许程序在不阻塞的情况下等待多个事件的发生。

select函数原型

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

nfds: 要检查的文件描述符数量。

readfds: 指向要监视可读性的文件描述符集合。

writefds: 指向要监视可写性的文件描述符集合。

exceptfds: 指向要监视异常情况的文件描述符集合。

select linux tcp

timeout: 指定等待的最长时间,如果为NULL则无限期等待。

返回值:

成功时返回准备好的文件描述符数量。

失败时返回-1,并设置errno

如果超时则返回0。

使用`select`监控TCP套接字

假设我们有一个多客户端的TCP服务器,想要同时处理来自不同客户端的连接请求和数据发送,以下是一个简单的示例代码片段,展示了如何使用select来实现这一目标。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define MAXLINE 1024
int main() {
    int listener, new_socket, max_sd, activity, valread, sd;
    int client_socket[30], max_clients = 30; // 最大客户端数
    struct sockaddr_in address;
    char buffer[MAXLINE];
    fd_set readfds;
    int opt = 1;
    int addrlen = sizeof(address);
    // 创建监听套接字
    if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    // 绑定地址和端口
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    if (bind(listener, (struct sockaddr *)&address, sizeof(address))<0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    // 开始监听
    if (listen(listener, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    printf("Listening on port %d
", PORT);
    while (1) {
        FD_ZERO(&readfds);
        FD_SET(listener, &readfds);
        max_sd = listener;
        // 添加现有套接字到集合中
        for (int i = 0; i < max_clients; i++) {
            sd = client_socket[i];
            if (sd > 0) FD_SET(sd, &readfds);
            if (sd > max_sd) max_sd = sd;
        }
        // 等待事件发生
        activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);
        if (activity < 0 && errno != EINTR) {
            printf("select error");
        }
        // 如果监听套接字上有事件发生,接受新的连接
        if (FD_ISSET(listener, &readfds)) {
            if ((new_socket = accept(listener, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {
                perror("accept");
                exit(EXIT_FAILURE);
            }
            printf("New connection, socket fd is %d
", new_socket);
            // 将新套接字添加到数组中
            for (int i = 0; i < max_clients; i++) {
                if (client_socket[i] == 0) {
                    client_socket[i] = new_socket;
                    printf("Adding to list of sockets as %d
", i);
                    break;
                }
            }
        }
        // 处理已连接的套接字上的事件
        for (int i = 0; i < max_clients; i++) {
            sd = client_socket[i];
            if (FD_ISSET(sd, &readfds)) {
                // 读取数据并响应
                valread = read(sd, buffer, 1024);
                buffer[valread] = '