epoll实现TCP特大型并发服务器的流程: 一、创建套接字(socket函数): 通信域
选择IPV4
网络协议、套接字类型选择流式
;
int sock_fd = socket ( AF_INET, SOCK_STREAM, 0 ) ;
二、填充服务器和客户机的网络信息结构体: 1.分别定义服务器网络信息结构体变量serveraddr
和客户机网络信息结构体变量clientaddr
; 2.分别求出服务器和客户机的网络信息结构体变量的内存空间大小,以作备用; 3.网络信息结构体清0
; 4.使用IPV4
网络协议AF_INET
; 5.在终端预留服务器端主机的IP地址
:inet_addr(argv[1])
; 6.在终端预留服务器端网络字节序的端口号
:htons(atoi(argv[2]))
;
struct sockaddr_in serveraddr; struct sockaddr_in clientaddr; socklen_t serveraddr_len = sizeof ( serveraddr) ; socklen_t clientaddr_len = sizeof ( clientaddr) ; memset ( & serveraddr, 0 , serveraddr_len) ; memset ( & clientaddr, 0 , clientaddr_len) ; serveraddr. sin_family = AF_INET; serveraddr. sin_addr. s_addr = inet_addr ( argv[ 1 ] ) ; serveraddr. sin_port = htons ( atoi ( argv[ 2 ] ) ) ;
三、设置允许端口复用(setsockopt函数): setsockopt函数: 功能 :设置套接字属性;
# include <sys/types.h> # include <sys/socket.h> int setsockopt ( int sockfd, int level, int optname, const void * optval, socklen_t optlen) ;
特别注意: 使用setsockopt设置允许端口复用时,其在代码的位置在填充网络信息结构体和bind之间;
int reuse = 1 ; if ( - 1 == ( setsockopt ( sock_fd, SOL_SOCKET, SO_REUSEADDR, & reuse, sizeof ( reuse) ) ) ) { perror ( "setsockopt error" ) ; exit ( - 1 ) ; }
四、套接字和服务器的网络信息结构体进行绑定(bind函数):
int ret = bind ( sock_fd, ( struct sockaddr * ) & serveraddr, serveraddr_len) ;
int ret1 = listen ( sock_fd, 5 ) ;
# include <sys/epoll.h> int epoll_create ( int size) ;
int epfd = epoll_create ( 1 ) ; if ( - 1 == epfd) { perror ( "epoll_create error" ) ; exit ( - 1 ) ; }
七、定义事件结构体变量和存放就绪事件描述符的数组: 事件结构体 :epoll_event
用于描述一个文件描述符上的事件;
typedef union epoll_data { void * ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t ; struct epoll_event { uint32_t events; epoll_data_t data; } ;
struct epoll_event event; struct epoll_event events[ N] ;
八、将关心的文件描述符加入到红黑树(epoll_ctl函数): 功能 :epoll的控制操作或者用于向 epoll 实例中添加、修改、删除事件;epoll_ctl函数:
int epoll_ctl ( int epfd, int op, int fd, struct epoll_event * event) ;
event. events = EPOLLIN; event. data. fd = sock_fd; if ( - 1 == ( epoll_ctl ( epfd, EPOLL_CTL_ADD, sock_fd, & event) ) ) { perror ( "epoll_ctl error" ) ; exit ( - 1 ) ; }
九、等待文件描述符中的事件是否就绪,成功则返回就绪的文件描述符的个数(epoll_wait函数): epoll_wait函数:
int epoll_wait ( int epfd, struct epoll_event * events, int maxevents, int timeout) ;
if ( - 1 == ( ret = epoll_wait ( epfd, events, N, - 1 ) ) ) { perror ( "epoll_wait error" ) ; exit ( - 1 ) ; }
十、遍历就绪的文件描述符集,判断哪些文件描述符已经准备就绪:
for ( int i = 0 ; i < ret; ++ i) { . . . }
十一、找到实际就绪的事件的文件描述符,并且接收来自客户端的数据(recv函数)和给客户端发送应答消息(send函数):
if ( events[ i] . data. fd == sock_fd) { new_fd = accept ( sock_fd, ( struct sockaddr * ) & clientaddr, & clientaddr_len) ; if ( - 1 == new_fd) { perror ( "accept error" ) ; exit ( - 1 ) ; } printf ( "文件描述符[%d]客户端[%s:%d]连接到了服务器\n" , new_fd, inet_ntoa ( clientaddr. sin_addr) , ntohs ( clientaddr. sin_port) ) ; event. events = EPOLLIN; event. data. fd = new_fd; if ( - 1 == ( epoll_ctl ( epfd, EPOLL_CTL_ADD, new_fd, & event) ) ) { perror ( "epoll_ctl error" ) ; exit ( - 1 ) ; } printf ( "文件描述符[%d]成功挂载在红黑树上\n" , new_fd) ; } else { memset ( buf, 0 , sizeof ( buf) ) ; int old_fd = events[ i] . data. fd; if ( - 1 == ( nbytes = recv ( old_fd, buf, sizeof ( buf) , 0 ) ) ) { perror ( "recv error" ) ; exit ( - 1 ) ; } else if ( 0 == nbytes) { printf ( "文件描述符[%d]客户端断开了服务器\n" , old_fd) ; close ( old_fd) ; epoll_ctl ( epfd, EPOLL_CTL_DEL, old_fd, & event) ; } if ( ! strncmp ( buf, "quit" , 4 ) ) { printf ( "文件描述符[%d]客户端退出了服务器\n" , old_fd) ; close ( old_fd) ; epoll_ctl ( epfd, EPOLL_CTL_DEL, old_fd, & event) ; } printf ( "文件描述符[%d]客户端发来数据[%s]\n" , old_fd, buf) ; strcat ( buf, "-----k" ) ; send ( old_fd, buf, sizeof ( buf) , 0 ) ;
close ( sock_fd) ;