使用IO多路复用实现转发服务器

//以下代码仅供参考,如有错误请评论指正,谢谢!

IO多路复用:允许同时对多个输入输出进行控制。
原理:构建一张文件描述符表,使用函数select,不断地监听表中的文件描述符是否可读(是否有数据),如果有则返回对应的文件描述符,并擦除其余文件描述符。
使用步骤:
(1)定义一个文件描述符集合 fd_set set;
(2)将集合清空:FD_ZERO(&set);
(3)将想关注的文件描述符添加到集合中 FD_SET(fd, &set);
(4)监测集合中的文件描述符是否有动作(是否有数据)使用select()函数;
(5)对返回的文件描述符进行IO操作。
另:从集合中删除指定文件描述符FD_CLR(fd, &set);
判断指定文件描述符是否在集合中FD_ISSET(fd, &set);
//服务端
server.c

#include "head.h" //除内核链表所需头文件外的其他头文件
#include "kernel_list.h"//内核链表所需头文件

//设计客户端节点
struct client
{
   
   
	char ip[50];	//存客户端ip
	unsigned int port;	//存客户端端口
	int confd;	//存客户端连接套接字
	
	struct list_head list;	//定义内核链表
};

//链表头节点
struct client *head = NULL;


//初始化头节点
struct client *list_init(void)
{
   
   
	struct client *head = calloc(1, sizeof(struct client));
	if(head == NULL)
	{
   
   
		perror("calloc failed");
		exit(0);
	}
	INIT_LIST_HEAD(&head->list);	
}

//单发(发给指定的人)
bool send_single(char *msg, unsigned short port)
{
   
   
	struct list_head *pos = NULL;
	struct client *tmp = NULL;
	
	list_for_each(pos, &head->list)
	{
   
   
		tmp =list_entry(pos, struct client, list);
		if(tmp->port == port)
		{
   
   
			write(tmp->confd, msg, strlen(msg));
			return true;
		}
	}
	return false;
}

//群发
void send_broadcast(char *msg, struct client* new)
{
   
   
	struct list_head *pos = NULL;
	struct client *tmp = NULL;

	list_for_each(pos, &head->list)
	{
   
   
		tmp =list_entry(pos, struct client, list);
		if(tmp
<think>我们需要使用C语言和IO多路复用(如select、poll或epoll)来实现一个简单的HTTP服务器(httpd)。 这里我们选择使用epoll,因为它在处理大量连接时效率更高。但请注意,epoll是Linux特有的,而select和poll是跨平台的。 由于问题要求使用IO多路复用,我们将使用epoll来实现。 我们将实现一个简单的HTTP服务器,它可以处理多个客户端的连接,并返回一个简单的HTTP响应。 步骤: 1. 创建socket,绑定端口并监听。 2. 创建epoll实例,并将监听socket添加到epoll事件中。 3. 进入循环,等待epoll事件。 4. 当有新的连接时,接受连接并将新的socket加入epoll。 5. 当有数据可读时,读取HTTP请求,并发送HTTP响应。 6. 注意处理错误和关闭连接。 我们将实现一个简单的HTTP响应,例如返回一个"Hello, World!"的页面。 注意:为了简化,我们不考虑完整的HTTP协议解析,只处理GET请求,并且忽略请求内容,直接返回响应。 代码结构: - 创建socket,设置非阻塞(可选,但epoll通常使用非阻塞IO) - 绑定和监听 - 创建epoll实例 - 主循环:epoll_wait - 处理事件:监听socket事件和已连接socket事件 我们使用边缘触发模式(EPOLLET)需要非阻塞socket,并且需要循环读取直到EAGAIN。但为了简单,我们也可以使用水平触发(默认),这样就不需要非阻塞,但为了性能,我们使用边缘触发并设置非阻塞。 因此,我们将设置所有socket为非阻塞模式。 由于代码较长,我们分步骤写。</think>下面我将使用C语言结合epoll(Linux下的高效IO多路复用技术)实现一个简单的HTTP服务器。这个实现支持并发处理多个客户端连接,并返回基本的HTTP响应。 ### 解决方案代码 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <fcntl.h> #include <errno.h> #define MAX_EVENTS 1024 #define PORT 8080 #define BUFFER_SIZE 4096 // 设置非阻塞socket void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); } // 发送HTTP响应 void send_response(int client_fd) { const char *response = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Connection: close\r\n\r\n" "<html><body><h1>Hello from epoll HTTP server!</h1></body></html>"; send(client_fd, response, strlen(response), 0); } int main() { int server_fd, epoll_fd; struct sockaddr_in addr; struct epoll_event event, events[MAX_EVENTS]; // 创建TCP socket if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(EXIT_FAILURE); } // 设置SO_REUSEADDR int opt = 1; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 绑定地址和端口 memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { perror("bind"); close(server_fd); exit(EXIT_FAILURE); } // 开始监听 if (listen(server_fd, SOMAXCONN) == -1) { perror("listen"); close(server_fd); exit(EXIT_FAILURE); } printf("HTTP server listening on port %d\n", PORT); // 创建epoll实例 if ((epoll_fd = epoll_create1(0)) == -1) { perror("epoll_create1"); close(server_fd); exit(EXIT_FAILURE); } // 添加服务器socket到epoll event.events = EPOLLIN; event.data.fd = server_fd; set_nonblocking(server_fd); if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) { perror("epoll_ctl: server_fd"); close(server_fd); close(epoll_fd); exit(EXIT_FAILURE); } // 主事件循环 while (1) { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); break; } for (int i = 0; i < nfds; i++) { // 处理新连接 if (events[i].data.fd == server_fd) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len); if (client_fd == -1) { perror("accept"); continue; } // 设置非阻塞并添加到epoll set_nonblocking(client_fd); event.events = EPOLLIN | EPOLLET; // 边缘触发模式 event.data.fd = client_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) { perror("epoll_ctl: client_fd"); close(client_fd); } printf("Accepted new connection from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); } // 处理客户端数据 else { char buffer[BUFFER_SIZE]; int bytes_read = recv(events[i].data.fd, buffer, BUFFER_SIZE, 0); if (bytes_read <= 0) { // 连接关闭或错误 if (bytes_read == 0 || (bytes_read == -1 && errno != EAGAIN)) { epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL); close(events[i].data.fd); printf("Connection closed\n"); } } else { // 简单解析HTTP请求(实际项目需要完整解析) if (strstr(buffer, "GET") != NULL) { send_response(events[i].data.fd); } // 关闭连接(简化处理) epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL); close(events[i].data.fd); } } } } close(server_fd); close(epoll_fd); return 0; } ``` ### 代码解释 1. **核心组件** - `epoll`:Linux高效的IO多路复用机制,可监控大量文件描述符 - 非阻塞socket:避免`accept`/`recv`阻塞整个进程 - 边缘触发(EPOLLET):只在状态变化时通知,减少系统调用 2. **工作流程** ```mermaid graph TD A[创建socket] --> B[绑定端口8080] B --> C[创建epoll实例] C --> D[添加监听socket到epoll] D --> E{epoll_wait循环} E --> |新连接| F[accept并加入epoll] E --> |数据到达| G[recv请求并发送响应] F --> E G --> E ``` 3. **关键函数** - `set_nonblocking()`:设置非阻塞模式避免IO操作阻塞 - `send_response()`:发送预定义的HTTP响应 - `epoll_create1()`:创建epoll实例 - `epoll_ctl()`:管理监控的文件描述符 - `epoll_wait()`:等待IO事件发生 4. **性能优化点** - 使用`SO_REUSEADDR`避免TIME_WAIT状态 - 边缘触发模式减少epoll_wait调用次数 - 非阻塞IO防止单连接阻塞整个服务 - 直接关闭连接简化处理(实际应支持keep-alive) 5. **测试方法** ```bash # 编译程序 gcc httpd.c -o httpd -O2 # 运行服务 ./httpd # 测试请求 curl http://localhost:8080 telnet localhost 8080 ``` ### 注意事项 1. 当前实现是简化版,实际HTTP服务器需要: - 完整解析HTTP请求头 - 支持持久连接(keep-alive) - 实现路由和多请求方法 - 添加超时处理 2. 边缘触发模式要求必须读取所有可用数据(当前示例简化处理) 3. 生产环境建议使用成熟库如libevent/libuv
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖小邓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值