#include "net.h" #include <poll.h> #define MAX_SOCK_FD 1024 int main(int argc, char *argv[]) { int i, j, fd, newfd; nfds_t nfds = 1; struct pollfd fds[MAX_SOCK_FD] = {}; Addr_in addr; socklen_t addrlen = sizeof(Addr_in); /*检查参数,小于3个 直接退出进程*/ Argment(argc, argv); /*创建已设置监听模式的套接字*/ fd = CreateSocket(argv); fds[0].fd = fd; fds[0].events = POLLIN; while(1){ if( poll(fds, nfds, -1) < 0) ErrExit("poll"); for(i = 0; i < nfds; i++){ /*接收客户端连接,并生成新的文件描述符*/ if(fds[i].fd == fd && fds[i].revents & POLLIN){ //判断服务端的fd是否有数据到来即第0个,并且文件描述符是数据可读,那么再执行把客户端接进来 if( (newfd = accept(fd, (Addr *)&addr, &addrlen) ) < 0) perror("accept"); fds[nfds].fd = newfd; //加入检查列表 fds[nfds++].events = POLLIN; printf("[%s:%d][nfds=%lu] connection successful.\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), nfds); } /*处理客户端数据*/ if(i > 0 && fds[i].revents & POLLIN){ //服务端不算,需要1开始 if(DataHandle(fds[i].fd) <= 0){ if( getpeername(fds[i].fd, (Addr *)&addr, &addrlen) < 0) perror("getpeername"); printf("[%s:%d][fd=%d] exited.\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), fds[i].fd); close(fds[i].fd); for(j=i; j<nfds-1; j++) //删除检查的目标,等于把后面的数组往前挪1 fds[j] = fds[j+1]; nfds--; i--; } } } } close(fd); return 0; }
时间: 2025-06-13 15:25:37 浏览: 15
<think>嗯,用户需要关于在C语言中使用poll函数实现多路复用TCP服务器的示例代码和解释。首先,我得回忆一下poll的基本用法。记得poll是用来同时监控多个文件描述符的状态的,比如检查是否有数据可读或者可以写入。和select相比,poll不需要使用位图,而是用结构体数组来管理文件描述符,这可能更方便一些。
接下来,用户要的是示例代码,所以我需要先整理一个简单的服务器框架。可能需要包括创建socket、绑定地址、监听连接,然后初始化poll的结构体。还要处理新的客户端连接和已有的客户端数据传输。
记得在代码中,服务器的主循环应该不断调用poll函数,等待事件发生。当poll返回后,要遍历所有被监控的文件描述符,检查是否有事件发生。比如,如果是监听socket有读事件,说明有新的连接到来,这时候要调用accept,并将新的客户端socket加入到poll的监控列表中。
对于已有的客户端连接,当它们的socket有读事件时,需要读取数据并回显。如果读取返回0,说明客户端关闭了连接,这时要关闭socket并从监控列表中移除。
还要注意处理错误情况,比如poll返回-1时可能需要处理错误码。此外,设置socket为非阻塞模式可能是个好主意,避免在read或write时阻塞整个服务器。
然后,解释部分需要分步骤说明每个关键部分的作用,比如初始化服务器、poll监控逻辑、处理新连接、处理客户端数据等。同时,要指出poll相对于select的优势,比如没有文件描述符数量限制,更直观的结构体使用等。
用户可能还会关心代码中的一些细节,比如为什么使用POLLRDNORM事件,或者如何处理部分读取的情况。不过示例代码可能不需要太复杂,保持简洁明了更重要。需要确保代码中的错误检查,比如bind或listen失败时的处理。
最后,根据用户提供的引用,特别是引用5中提到poll和select的区别,应该在解释部分强调这些点,帮助用户理解为何选择poll。可能还需要提到性能方面的考虑,比如在大量连接下poll的表现,但具体数据可能不在当前回答范围内。
总结来说,回答的结构应该是先给出示例代码,然后分步骤详细解释代码的各个部分,最后比较poll和select的优缺点,确保用户能够理解并实现一个基本的多路复用TCP服务器。</think>以下是一个基于`poll`实现的C语言多路复用TCP服务器的示例代码及分步解释,结合了网络编程的关键实践要点:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8080
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fd;
struct sockaddr_in address;
struct pollfd fds[MAX_CLIENTS + 1]; // +1 用于服务器套接字
int nfds = 1; // 当前监控的文件描述符数量
// 创建TCP套接字
if ((server_fd = 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(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("listen failed");
exit(EXIT_FAILURE);
}
// 初始化poll结构体
fds[0].fd = server_fd;
fds[0].events = POLLRDNORM; // 监控读事件
printf("Server listening on port %d\n", PORT);
while (1) {
int ret = poll(fds, nfds, -1); // 阻塞等待事件
if (ret < 0) {
perror("poll error");
break;
}
// 检查所有监控的文件描述符
for (int i = 0; i < nfds; i++) {
if (fds[i].revents & POLLRDNORM) {
// 处理服务器套接字事件(新连接)
if (fds[i].fd == server_fd) {
if ((client_fd = accept(server_fd, NULL, NULL)) < 0) {
perror("accept error");
continue;
}
printf("New connection: fd %d\n", client_fd);
// 将新客户端加入监控列表
if (nfds <= MAX_CLIENTS) {
fds[nfds].fd = client_fd;
fds[nfds].events = POLLRDNORM;
nfds++;
} else {
printf("Max clients reached\n");
close(client_fd);
}
}
// 处理客户端数据
else {
char buffer[BUFFER_SIZE];
ssize_t bytes_read = read(fds[i].fd, buffer, BUFFER_SIZE);
if (bytes_read <= 0) { // 连接关闭或错误
close(fds[i].fd);
printf("Connection closed: fd %d\n", fds[i].fd);
// 从监控列表移除
fds[i] = fds[nfds-1];
nfds--;
i--; // 调整循环计数器
} else {
// 回声数据
write(fds[i].fd, buffer, bytes_read);
}
}
}
}
}
close(server_fd);
return 0;
}
```
**关键步骤解释:**
1. **初始化服务器套接字**
- 使用`socket()`创建TCP套接字
- `bind()`绑定到指定端口
- `listen()`进入监听状态
- 将服务器套接字加入poll监控列表[^3]
2. **poll监控循环**
- `poll()`系统调用阻塞等待事件发生
- `POLLRDNORM`事件表示普通数据可读
- 参数`-1`表示无限期等待事件
3. **处理新连接**
- 当服务器套接字触发读事件时,调用`accept()`接收新连接
- 将新客户端套接字加入监控列表(最多支持$MAX_CLIENTS$个客户端)
4. **处理客户端数据**
- 对于触发读事件的客户端套接字,执行`read()`读取数据
- 读取成功时执行`write()`回显数据(实现回声协议)
- 检测到连接关闭时(读取返回$0$或$-1$),关闭套接字并从监控列表移除
**poll与select的对比优势:**
1. 使用结构体数组`struct pollfd`代替位图操作,更直观易用[^5]
2. 没有最大文件描述符数量限制(取决于系统配置)
3. 事件类型划分更细致(支持更多事件类型)
阅读全文
相关推荐
















