在 Linux 中,使用
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] = '