/*
* @Author: kerwang
* @Date: 2021-01-17 19:44:33
* @LastEditTime: 2021-01-18 20:43:11
* @LastEditors: Please set LastEditors
* @Description: 多路IO复用,SOCKET并发服务器设计,epoll实现
* @FilePath: /vscodespace/home/wang/socket/epoll.c
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <ctype.h>
#include <arpa/inet.h>
/**
* @funcation: int epoll_create(int size);
* @description: 创建epoll句柄,底层采用红黑树结构存储后续的文件描述符
* @param :size表示大小,告诉内核帮我创建多大的epoll模型,即将来监听多少个文件描述符
* @return :返回一个文件描述符,该文教描述符指向一个红黑树的树根
*/
/**
* @funcation: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
* @description: 对底层的这可红黑树进行什么样的控制
* @param :
* int epfd:epoll_create()函数的返回值
* op:进行什么样的控制(常用)
* EPOLL_CTL_ADD:增加/添加
* EPOLL_CTL_MOD:修改
* EPOLL_CTL_DEL:删除
* fd:文件描述符,对那个文件描述付进行操作
* struct epoll_event *:
* struct epoll_event{
* uint32_t events; /* Epoll events */
/* epoll_data_t data; /* User data variable */
/* } :告诉内需需要监听的事件
* events:①EPOLLIN:读事件、②EPOLLOUT:写事件、③EPOLLERR:socket文件描述符出错
*
* typedef union epoll_data {
* void *ptr;
* int fd; 文件描述符,跟第二个参数fd对应
* uint32_t u32;
* int64_t u64;
* } epoll_data_t;
*
* @return :成功返回0,失败返回-1并设置errno
*
******/
/**
* @funcation:
* int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
* @description: 等待哪个文件描述符发生事件
* @param:
* epfd:红黑树的树根,即int epoll_create(int size)函数的返回值
struct epoll_event*:这个参数是一个struct epoll_event类型的数组,和epoll_ctl函数的第二个参数区别是epoll_ctl
参数传递的是struct epoll_event的变量的地址,而在这个函数中是一个数组,是传出参数。传出满足事件的文件描述
符放入到这个数组中、
maxevents:该数组的容量/大小
timeout:超时,-1:表示阻塞。0:立即返回。>0表示等待多少ms后返回
* @return :成功返回有多少个文件描述符发生满足的事件,失败-1
*/
/*************************************以上三个函数更详细的查看Linux man手册******************************************/
#define SERVER_PORT 8000 //服务器端口
#define OPEN_MAX 1000 //最大能打开的文件描述符个数
#define IPBUF_LEN 128 //存放客户端IP地址的缓存区的大小
void PrintClientInfo(struct sockaddr_in info,int count,int flag); //打印客户端的信息
void perror_exit(const char *str); //错误处理函数
int main(int argc, char const *argv[])
{
//建立监听套接字
int listenfd = socket(AF_INET,SOCK_STREAM,0);
if(listenfd<0){
perror_exit("socker create error");
}
//设置端口复用
int opt = 1;
int temp = setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if(temp <0){
perror_exit("setsockopt error");
}
//服务器自身的IP和端口,建立连接上的客户端的ip和端口
struct sockaddr_in server_addr,client_addr;
int clientAddrLen;
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//将IP和端口绑定
temp = bind(listenfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
if(temp<0)
{
perror_exit("bind error");
}
//设置同时建立连接的最大上限
temp = listen(listenfd,20);
if(temp<0){
perror_exit("listen error");
}
/********创建epoll模型*********/
int efdroot; /*epoll底层红黑树的根节点*/
int nready; /*该变量存储有几个socket发生了事件(读、写、error)*/
/*
* eptep:设置监听哪个文件描述的什么事件的变量
* epbuf:存储的事那些文件描述符socket满足监听事件的缓冲区
*/
struct epoll_event eptep,epbuf[OPEN_MAX];
/*创建epoll句柄*/
efdroot = epoll_create(OPEN_MAX);
if(efdroot<0){
perror_exit("epoll_create error");
}
/*指定listenfd为读事件,即listenfd读事件来表示有新连接*/
eptep.data.fd = listenfd;
eptep.events = EPOLLIN;
/*将监听套接字的读事件加入到监听集合(底层红黑树)中*/
temp = epoll_ctl(efdroot,EPOLL_CTL_ADD,listenfd,&eptep);
if(temp<0){
perror_exit("epoll_ctl error");
}
/*客户端建立连接后新的fd,以及统计客户端的数量,和客户端交互数据的缓存区*/
int connfd;
int count = 0;
char dataBuf[BUFSIZ];
while(1)
{
/*阻塞等待efdroot集合(即监听socket的集合)中有满足监听条件(socket可读/error/可写)的事件发生。
如果满足监听条件的事件发生,将这些描述符放入到epbuf缓冲区中,同时返回共有几个socket描述符发生了事件*/
nready = epoll_wait(efdroot,epbuf,OPEN_MAX,-1);
if(nready<0){
perror_exit("epoll_wait error");
}
for(int i = 0; i< nready; i++)
{
if(epbuf[i].data.fd == listenfd) //有新连接
{
bzero(&client_addr,sizeof(client_addr));
clientAddrLen = sizeof(client_addr);
//这里accept不会再阻塞,能进入到这个if条件里说明一定有新的client请求建立链接
connfd = accept(listenfd,(struct sockaddr *)&client_addr,&clientAddrLen);
if(connfd<0){
perror_exit("accept error");
}
PrintClientInfo(client_addr,++count,1);
/*将新建立的客户端connfd文件描述符添加到监听集合中,监听集合为efdroot为根节点的红黑树中*/
eptep.events = EPOLLIN;
eptep.data.fd = connfd;
/*将建立连接后的socket添加到监听集合中*/
temp = epoll_ctl(efdroot,EPOLL_CTL_ADD,connfd,&eptep);
if(temp<0){
perror_exit("epoll_ctl error");
}
}
else //其他socket文件描述符中有数据到来
{
//定义临时读写fd
int rwfd = epbuf[i].data.fd;
temp = read(rwfd,dataBuf,sizeof(dataBuf));
//读到0说明客户端断开链接
if(temp == 0)
{
//将该文件描述符符从监听集合中删除(底层红黑二叉树中剔除)
int rec = epoll_ctl(efdroot,EPOLL_CTL_DEL,rwfd,NULL);
if(rec<0){
perror_exit("close epoll_ctl");
}
close(rwfd);
PrintClientInfo(client_addr,--count,0); //这里的client_addr并不是已经关闭的客户端的ip和port,这里只是站位参数
}
//小于0说明读出错
else if(temp<0)
{
perror("read error");
int rec = epoll_ctl(efdroot,EPOLL_CTL_DEL,rwfd,NULL);
if(rec<0){
perror_exit("close epoll_ctl");
}
close(rwfd);
PrintClientInfo(client_addr,--count,0); //这里的client_addr并不是已经关闭的客户端的ip和port,这里只是站位参数
printf("read 出错,服务器以主动断开上面的连接\n");
}
//读到数据,处理数据,回写给客户端
else
{
for(int j = 0; j<temp; j++)
{
//处理数据,将小写转换为大写
dataBuf[j] = toupper(dataBuf[j]);
}
//回写给客户端
write(rwfd,dataBuf,temp);
}
}
}
}
close(efdroot);
close(listenfd);
return 0;
}
//打印新建立连接客户端的ip和端口
void PrintClientInfo(struct sockaddr_in info,int count,int flag)
{
char IPbuf[IPBUF_LEN];
if(flag){
printf("Welecome Client ip:%s\tPort:%d\tCount:%d\n",
inet_ntop(AF_INET,&info.sin_addr,IPbuf,IPBUF_LEN),
ntohs(info.sin_port),count);
}
else
{
printf("Bye Count:%d\n",count);
}
}
//错误处理
void perror_exit(const char *str)
{
perror(str);
exit(-1);
}
epoll多路IO复用、高并发模型
最新推荐文章于 2022-05-25 21:52:01 发布