文章目录
一、网络io多路复用代码实现
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/poll.h>
#include <sys/epoll.h>
// block
void *client_thread(void *arg) {
int clientfd = *(int *)arg;
while (1) {
char buffer[128] = {0};
int count = recv(clientfd, buffer, 128, 0);
if (count == 0) {
break;
}
//
send(clientfd, buffer, count, 0);
printf("clientfd: %d, count: %d, buffer: %s\n", clientfd, count, buffer);
}
close(clientfd);
}
// tcp
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建一个socket
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(2048);
if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))) {
perror("bind");
return -1;
}
listen(sockfd, 10);
#if 0
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
printf("accept\n");
#if 0
char buffer[128] = {0};
int count = recv(clientfd, buffer, 128, 0);
send(clientfd, buffer, count, 0);
printf("sockfd: %d, clientfd: %d, count: %d, buffer: %s\n", sockfd, clientfd, count, buffer);
#else
while (1) {
char buffer[128] = {0};
int count = recv(clientfd, buffer, 128, 0);
if (count == 0) {
break;
}
send(clientfd, buffer, count, 0);
printf("sockfd: %d, clientfd: %d, count: %d, buffer: %s\n", sockfd, clientfd, count, buffer);
}
#endif
#elif 0
while (1) {
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
pthread_t thid;
pthread_create(&thid, NULL, client_thread, &clientfd);
}
#elif 0 // select
//int nready = select(maxfd, rset, wset, eset, timeout);
fd_set rfds, rset; // rfds用来设置,rset用来判断
FD_ZERO(&rfds);
FD_SET(sockfd, &rfds);
int maxfd = sockfd;
printf("loop\n");
while (1) {
rset = rfds;
int nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &rset)) {
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
printf("sockfd: %d\n", clientfd);
FD_SET(clientfd, &rfds);
maxfd = clientfd;
}
// close
// rfds --> i , FD_CLR
int i = 0;
for (i = sockfd+1;i <= maxfd;i ++) {
if (FD_ISSET(i, &rset)) { //
char buffer[128] = {0};
int count = recv(i, buffer, 128, 0);
if (count == 0) {
printf("disconnect\n");
//close(i);
FD_CLR(i, &rfds);
close(i); //
break;
}
send(i, buffer, count, 0);
printf("clientfd: %d, count: %d, buffer: %s\n", i, count, buffer);
}
}
}
#elif 0
struct pollfd fds[1024] = {0};
fds[sockfd].fd = sockfd;
fds[sockfd].events = POLLIN;
int maxfd = sockfd;
while (1) {
int nready = poll(fds, maxfd+1, -1);
if (fds[sockfd].revents & POLLIN) {
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
printf("sockfd: %d\n", clientfd);
fds[clientfd].fd = clientfd;
fds[clientfd].events = POLLIN;
maxfd = clientfd;
}
int i = 0;
for (i = sockfd+1;i <= maxfd;i ++ ) {
if (fds[i].revents & POLLIN) {
char buffer[128] = {0};
int count = recv(i, buffer, 128, 0);
if (count == 0) {
printf("disconnect\n");
fds[i].fd = -1;
fds[i].events = 0;
close(i);
continue;
}
send(i, buffer, count, 0);
printf("clientfd: %d, count: %d, buffer: %s\n", i, count, buffer);
}
}
}
#else
int epfd = epoll_create(1); // int size
//pthread_create();
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
struct epoll_event events[1024] = {0};
while (1) {
int nready = epoll_wait(epfd, events, 1024, -1);
int i = 0;
for (i = 0;i < nready;i ++) {
int connfd = events[i].data.fd;
if (sockfd == connfd) {
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
printf("clientfd: %d\n", clientfd);
} else if (events[i].events & EPOLLIN) {
char buffer[10] = {0};
int count = recv(connfd, buffer, 10, 0);
if (count == 0) {
printf("disconnect\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);
close(i);
continue;
}
send(connfd, buffer, count, 0);
printf("clientfd: %d, count: %d, buffer: %s\n", connfd, count, buffer);
}
}
}
#endif
getchar();
//close(clientfd);
}
二、问题总结:
1.为什么服务器上会出现大量的close_wait状态?
-
应用程序没有正确处理关闭连接的过程:当客户端主动关闭连接时,服务器会进入 CLOSE_WAIT 状态,等待应用程序调用 close() 函数来关闭连接。如果应用程序没有调用 close(),连接将一直处于 CLOSE_WAIT 状态。
-
资源泄漏:如果应用程序在处理连接时出现资源泄漏问题,导致连接无法被及时释放,可能会导致大量的 CLOSE_WAIT 状态的连接堆积。
-
高并发连接处理不当:在高并发环境下,如果服务器端处理连接的代码不能及时响应或处理所有的连接关闭请求,也会导致 CLOSE_WAIT 状态连接增多。
-
系统或网络问题:系统资源耗尽、网络延迟或其他网络问题也可能导致连接关闭过程中的延迟,从而出现大量的 CLOSE_WAIT 状态。
2.服务器出现大量 TIME_WAIT 状态的原因有哪些?
- 频繁地建立和关闭连接:当服务器处理大量短暂的TCP连接并频繁地关闭它们时,会产生大量的 TIME_WAIT 状态。这通常发生在高并发的Web服务器、代理服务器或其他类型的网络服务中。
- 连接关闭时的延迟:在TCP连接关闭时,可能会出现网络延迟或对端处理连接关闭的延迟,导致连接处于 TIME_WAIT 状态。这种情况通常发生在网络拥塞、高负载、或对端应用程序处理连接关闭的效率低下时。
- 大量短暂连接的重复利用:在一些情况下,服务器端会尝试重复利用已经建立的连接来处理新的请求,而不是每次都建立新的连接。这种重复利用可能会导致大量 TIME_WAIT 状态的连接堆积。
- 操作系统参数配置不当:操作系统的TCP参数配置不当,如过小的 TIME_WAIT 超时时间、过小的端口范围等,也可能导致 TIME_WAIT 状态连接积累。
- 攻击或恶意行为:恶意的网络活动或攻击可能会导致大量的短暂连接并快速关闭,从而产生大量的 TIME_WAIT 状态连接。
3.五种网络 IO 模型对比
阻塞IO,非阻塞IO,多路复用IO,信号驱动IO这四种的主要区别在第一阶段,他们在第二阶段是一样的:数据从内核缓冲区复制到调用者缓冲区期间都被阻塞住。他们都是同步IO,只有同步 IO 模型才考虑阻塞和非阻塞。异步 IO 肯定是非阻塞。
4.select优缺点
优点:
- 跨平台性:select 是一种标准的系统调用,在大多数操作系统上都得到了支持,包括 Unix/Linux、Windows 等。
- 简单直观:使用 select 可以相对简单地实现基于事件驱动的编程模型,不需要复杂的多线程或多进程管理。
- 高效利用资源:通过一次系统调用同时监视多个文件描述符,避免了轮询的开销,可以高效地利用系统资源。
- 适用于少量连接:在连接数量较少的情况下,select 的性能通常是可以接受的,而且实现也相对简单。
缺点:
- 文件描述符限制:select 通常受到操作系统对文件描述符数量的限制,当需要监视的文件描述符数量较大时,可能会导致性能下降或出现错误。
- 效率问题:当连接数量较大时,select 的效率可能会下降,因为每次调用 select 都需要遍历所有的文件描述符集合,导致系统开销增加。
- 无法扩展:select 是基于单个进程的模型,无法充分利用多核处理器的性能,也无法方便地扩展到分布式系统。
- 内存复制:select 在内核态和用户态之间需要进行大量的数据拷贝,特别是在调用 select 和返回结果之间,可能需要频繁地复制文件描述符集合,影响性能。
- 限制事件类型:select 通常只能监视可读、可写和异常等基本事件,对于更复杂的事件类型可能需要使用其他的机制。
4.epoll是否线程安全?
- 单线程环境下的安全性:
在单线程环境下,一个 epoll 实例可以被多个线程同时使用,而不需要额外的同步措施。因为 epoll 实例本身不维护任何状态,所有的状态信息都保存在内核中,而每个线程操作的是自己的文件描述符集合或事件集合。因此,在单线程环境下,不同线程之间对 epoll 实例的操作不会相互干扰。 - 多线程环境下的使用:
在多线程环境下,如果多个线程需要同时使用同一个 epoll 实例,就需要进行适当的同步措施,以避免竞态条件和数据不一致性。一种常见的做法是使用互斥锁(mutex)或其他同步机制来保护对 epoll 实例的操作,确保每次只有一个线程在操作 epoll 实例。 - 事件处理的线程安全性:
对于多线程环境下的事件处理,需要确保事件处理的逻辑是线程安全的。因为 epoll_wait() 函数返回的就绪事件是共享的,多个线程可能会同时处理这些事件。因此,在事件处理逻辑中需要注意线程安全性,避免多个线程同时操作共享资源导致的竞态条件和数据不一致性问题。
总结
网络 I/O 多路复用是一种高效的 I/O 模型,在处理多个连接或套接字的同时能够提高系统的性能和并发能力。通过理解其实现原理和常见问题,并采取适当的解决方法,可以有效地应用于实际的网络编程中,提高程序的性能和可靠性。