网络编程,根本上来说是对网络IO技巧。在对多客户端的网络编程的一个很拥有的方式是通过epoll来管理socfd,通过epoll_create创建epoll对象,通过epoll_ctl加入想要关心sockfd,可以在sockfd相应事件触发时调度sockfd去处理网络数据。通过recv或send发送数据。
ractor模型,巧妙的利用epoll_event.data.ptr指针,引入较为复杂的数据结构,可以再触发相应事件的时候,通过回调对事件进行处理
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.ptr = si;
下面是用于reactor模型的简易数据结构
struct sockitem{
int sockfd;
int (*callback)(int fd,int events,void*arg);
char recvbuff[BUFFER_LEBGTH];
char sendbuff[BUFFER_LEBGTH];
int rlength;
int slength;
int status;
};
在上面的数据结构中,通过绑定sockfd与相应的回调处理函数,同时封装recvbuff,sendbuff。从而更方便的处理网络数据,下面是服务端初始化一个reactor简易服务端
if(argc < 2){
return -1;
}
int port = atoi(argv[1]);
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0){
return -1;
}
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if(bind(sockfd,(struct sockaddr*)&addr,sizeof(addr)) < 0){
return -2;
}
if(listen(sockfd,5) < 0){
return -3;
}
eventloop = (struct reactor*)malloc(sizeof(struct reactor));
eventloop->epfd = epoll_create(1);
struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));
si->sockfd = sockfd;
si->callback = accept_cb;
si->status = WS_INIT;
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.ptr = si;
epoll_ctl(eventloop->epfd,EPOLL_CTL_ADD,sockfd,&ev);
这样,accept与recv同属EPOLLIN事件,其各自事件触发后,可以方便的调用其回调函数,对网络数据进行处理,主处理代码如下
while(1){
int nready = epoll_wait(eventloop->epfd,eventloop->events,1024,-1);
if(nready < -1){
break;
}
int i = 0;
for(i = 0; i < nready;i++){
if(eventloop->events[i].events & EPOLLIN ){
struct sockitem *item = (struct sockitem*)eventloop->events[i].data.ptr;
item->callback(item->sockfd,events[i].events,item);
}
if(eventloop->events[i].events & EPOLLOUT){
struct sockitem *item = (struct sockitem*)events[i].data.ptr;
item->callback(item->sockfd,events[i].events,item);
}
}
}
这样,在主函数中,只要关注监听后处理accept事件,在accept回调中加入对客户端sockfd的关注,如下代码
int accept_cb(int fd,int events,void *arg){
struct sockaddr_in clientaddr;
memset(&clientaddr,0,sizeof(clientaddr));
socklen_t client_len = sizeof(clientaddr);
int clientfd = accept(fd,(struct sockaddr*)&clientaddr,&client_len);
if(clientfd <= 0){
return -1;
}
char str[INET_ADDRSTRLEN] = {0};
printf("recv from %s at port %d\n",inet_ntop(AF_INET,&clientaddr.sin_addr,str,sizeof(str)),
ntohs(clientaddr.sin_port));
//ET 边缘触发 LT 有数据则一直触发 小块数据ET 循环读 LT大块
struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));
si->sockfd = clientfd;
si->callback = recv_cb;
si->status = WS_HANDSHARK;
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.ptr = si;
epoll_ctl(eventloop->epfd,EPOLL_CTL_ADD,clientfd,&ev);
}
在accept的回调中关注EPOLLIN事件,当服务端收到客户端数据时,触发recv_cb,如下代码
int recv_cb(int fd,int events,void* arg){
struct sockitem *si = (struct sockitem*)arg;
int ret = recv(fd,si->recvbuff,1024,0);
struct epoll_event ev;
if(ret < 0){
if(errno == EAGAIN || errno == EWOULDBLOCK){
//
return -1;
} else{
//断开
close(fd);
ev.events = EPOLLIN;
ev.data.fd = fd;
epoll_ctl(eventloop->epfd,EPOLL_CTL_DEL,fd,&ev);
free(si);
}
}else if(ret == 0){
//断开
close(fd);
ev.events = EPOLLIN;
ev.data.fd = fd;
epoll_ctl(eventloop->epfd,EPOLL_CTL_DEL,fd,&ev);
free(si);
}
else{
buffer[ret] = '\0';
printf("recv %s\n",buffer);
/*memcpy(si->sendbuff,si->recvbuff,si->rlength);
si->slength = si->rlenth;*/
if(si->status == WS_HANDSHARK){
handshark(si,eventloop);
}else if(si->status == WS_DATATRANSFORM){
}else if(si->status == WS_DATATRANSFORM){
}else{
}
ev.events = EPOLLOUT | EPOLLET;
si->sockfd = fd;
si->callback = send_cb;
ev.data.ptr = si;
epoll_wait(eventloop->epfd,EPOLL_CTL_MOD,fd,&ev);
}
}