ss
命令可以查看和管理 TCP 连接。ss -t
列出所有 TCP 连接。在Linux操作系统中,TCP(传输控制协议)是网络通信的核心组件之一,它提供了一种可靠的、面向连接的数据传输方式,确保数据包按顺序和无错误地到达目标地址,本文将深入探讨如何在Linux环境下使用select
系统调用来监控多个TCP套接字(sockets),以及这种技术如何帮助开发者实现高效的网络编程。
理解TCP与Socket
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: 指向要监视异常情况的文件描述符集合。
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] = '\0'; printf("Received: %s ", buffer); send(sd, buffer, strlen(buffer), 0); } } } return 0; }
表格:select
与其他I/O多路复用技术的对比
特性 | select | poll | epoll | kqueue | /dev/poll | io_uring |
系统兼容性 | 广泛支持 | 广泛支持 | Linux特有 | BSD特有 | Solaris特有 | Linux 5.1+ |
性能 | 较低(线性扫描) | 中等 | 高(基于事件驱动) | 高 | 中等 | 极高 |
使用复杂度 | 简单 | 简单 | 中等 | 中等 | 中等 | 复杂 |
适用场景 | 少量文件描述符 | 少量至中等量级文件描述符 | 大量文件描述符 | 大量文件描述符 | 大量文件描述符 | 高性能需求场景 |
资源占用 | 较高(每次调用都需要复制fd_set) | 中等 | 低(动态管理) | 低 | 低 | 极低 |
FAQs
Q1:select
系统调用为什么被称为“线性扫描”?
A1:select
系统调用之所以被称为“线性扫描”,是因为它会遍历所有传入的文件描述符集(readfds
,writefds
,exceptfds
),即使这些集合中的大部分文件描述符都没有就绪事件,这种遍历方式导致其时间复杂度为O(n),其中n是监视的文件描述符数量,当文件描述符数量很大时,这会成为性能瓶颈。
Q2: 何时使用select
而非其他更高效的I/O多路复用技术?
A2:select
虽然在某些方面不如poll
,epoll
,kqueue
等现代I/O多路复用技术高效,但它仍然有其适用场景:
跨平台需求:如果你需要编写跨多种操作系统的代码,而不仅仅是Linux,select
是一个很好的选择,因为它在大多数Unix-like系统上都有实现。
简单性:对于简单的应用程序,或者学习网络编程的基础概念时,select
提供了一个易于理解和使用的接口。
资源限制较小的环境:在文件描述符数量不是非常多,且对性能要求不是特别高的场合,select
足够应付,且实现起来更直接。
各位小伙伴们,我刚刚为大家分享了有关“select linux tcp”的知识,希望对你们有所帮助。如果您还有其他相关问题需要解决,欢迎随时提出哦!