为了实现上面的效果我们要修改两个地方 , 一个是recv_callback中我们要注册一下EPOLLOUT事件,代码如下:int recv_callback(int fd) {
int count = recv(fd, conn_map[fd].rbuf + conn_map[fd].rlen, BUF_LEN - conn_map[fd].rlen, 0);
// do something
memcpy(conn_map[fd].wbuf, conn_map[fd].rbuf, conn_map[fd].rlen);
conn_map[fd].wlen = conn_map[fd].rlen;
conn_map[fd].rlen = 0;
struct epoll_event ev;
ev.events = EPOLLOUT;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
return count;
}
我们在rbuf拷贝到wbuf之后,给当前fd注册了EPOLLOUT事件,然后我们在主循环中要处理EPOLLOUT事件,代码如下:int main() {
...
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 (events[i].events & EPOLLIN && sockfd == connfd) {
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
ev.events = EPOLLIN;
ev.data.fd = clientaddr;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
conn_map[clientfd].fd = clientfd;
conn_map[clientfd].rlen = 0;
conn_map[clientfd].wlen = 0;
conn_map[clientfd].recv_call = recv_callback;
conn_map[clientfd].send_call = send_callback;
memset(conn_map[clientfd].rbuf, 0, BUF_LEN);
memset(conn_map[clientfd].wbuf, 0, BUF_LEN);
printf("clientfd:%d\n", clientfd);
} else if (events[i].events & EPOLLIN) {
int count = conn_map[connfd].recv_call(connfd);
printf("recv-count:%d\n", count);
} else if (events[i].events & EPOLLOUT) { // 处理EPOLLOUT事件
int count= conn_map[connfd].send_call(connfd);
printf("send-count:%d\n", count);
}
}
}
}
要注意的是,epfd是在main函数中定义的,而我们在recv_callback中有使用,所以我们可以暂时将epfd声明成一个全局变量,放在外面 。
上面的代码有一个问题,EPOLLOUT事件触发之后你会发现再向当前fd发送数据,就没响应了,这是因为epoll事件被我们修改了,为了解决这个问题我们可以在send_callback执行完之后再设置回去 , 如下:int send_callback(int fd) {
int count = send(fd, conn_map[fd].wbuffer, conn_map[fd].wlen, 0);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
return count;
}
这样,我们就将IO操作给屏蔽了 , 在主循环中我们只关注事件,不同的事件调用不同的回调函数 。在对应的回调函数中只做自己该做的,做完之后注册事件通知其它的回调函数 。
但是,上面的代码还不够优雅,对于accept和读事件来讲在epoll中都是EPOLLIN事件,这两个是不是可以合并在一起处理呢?答案是可以的,首先,我们要将accept相关的逻辑给拆出来,拆解之后的代码如下:int accept_callback(int fd) {
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
ev.events = EPOLLIN;
ev.data.fd = clientaddr;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
conn_map[clientfd].fd = clientfd;
conn_map[clientfd].rlen = 0;
conn_map[clientfd].wlen = 0;
conn_map[clientfd].recv_call = recv_callback;
conn_map[clientfd].send_call = send_callback;
memset(conn_map[clientfd].rbuf, 0, BUF_LEN);
memset(conn_map[clientfd].wbuf, 0, BUF_LEN);
return clientfd;
}
我们发现,accept_callback和recv_callback以及send_callback的签名是一样的,这样我们可以在conn_channel用一个union,将accept_callback也放到conn_channel中来 。如下:struct conn_channel {
int fd;
union {
callback accept_call;
callback recv_call;
} call_t;
callback send_call;
char wbuf[BUF_LEN];
int wlen;
char rbuf[BUF_LEN];
int rlen;
};
在主循环中,我们就可以先给sockfd注册好accept回调函数,然后我们只需要在主循环中保留两个逻辑就可以了,代码如下:int main() {
int sockfd = create_serv(9000);
if (sockfd == -1) {
推荐阅读
- 网络信息时代如何找到你想找的人呢
- 笔记本无线网卡无法连接网络怎么办
- 如花是谁啊,网络人物如花是谁啊?
- 小米手机怎么开网络加速,小米手机显示网络加速中
- 什么是蜂窝网络,什么是蜂窝网?
- 问道100五法怎么过,问道网络游戏如何游历师门
- 早期网络歌手近况:英年早逝、已被判刑、转战幕后,青春终会散场
- 如何渲染线框模型
- 网络用语需句斟字酌!避免“乌龙事件”避免踩雷
- 当女明星出演网络电影,“照妖镜”下丑态百出,年轻时却都是神颜