在网络编程系列文章中,我们实现了一个基于epoll的网络框架 , 并在此基础上开发了一个简单的HTTP服务 , 在那个系列文章中我们使用了读、写两个buffer将网络IO和数据的读写进行了分离,它们之间的扭转完全通过epoll事件通知,如果你认真研究过源码,会发现,所有针对网络IO的操作都是由事件触发的 。这种基于事件触发的网络模型通常我们叫做Reactor网络模型 。
由于网络编程系列文章中代码实现相对比较复杂,不太好讲清楚 。所以,我决定单独出几篇文章对那个系列文章进行一些拓展,主要涉及到网络编程思想和性能测试 。
这篇文章我们通过实现一个简单的网络框架,来说明Reactor网络模型实现的一般思路 , 其本质思想和x.NET项目基本上是一样的,只是在代码上做了非常大的精简,理解起来会轻松很多 。
首先,我们来看一段代码#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
int mAIn() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(struct sockaddr_in));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(2048);
if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
perror("bind fail");
return -1;
}
listen(sockfd, 10);
printf("sock-fd:%dn", sockfd);
int epfd = epoll_create(1);
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 (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 | EPOLLET;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
printf("clientfd: %dn", clientfd);
} else if (events[i].events & EPOLLIN) {
char buffer[10] = {0};
int count = recv(connfd, buffer, 10, 0);
if (count == 0) {
printf("discounnectn");
epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);
close(i);
continue;
}
send(connfd, buffer, count, 0);
printf("clientfd: %d, count: %d, buffer: %sn", connfd, count, buffer);
}
}
}
}
熟悉epoll的人应该对上面的代码比较熟悉 , 这段代码的核心在下面的while主循环,如果是当前Server的Socket说明有新的连接进来,调用accept拿到客户端的fd,将其放在epoll的events中,并注册EPOLLIN事件,一般我们理解为可读事件 。
如果不是sockfd,说明是客户端的fd可读,我们将数据读出来再原样发送回去 。
上面的代码存在的主要问题在于,套接字的accept和读写操作我们是直接写在主循环里了,这将会让代码的逻辑变得难以琢磨 。
对于一个套接字,最直接的操作就是读和写 。所以,最容易想到的就是将读和写分离开 。为了实现读和写分离我们封装两个回调函数,如下:int recv_callback(int fd, char *buffer, int size);
int send_callback(int fd, char *buffer, int size);
你可以想一下,这两个函数应该怎么写?下面是根据原有的逻辑将读和写封装在了recv_callback和send_callback两个函数中,代码如下:int recv_callback(int fd, char *buffer, int size) {
int count = recv(fd, buffer, size, 0);
send_callback(fd, buffer, count, 0);
return count;
}
int send_callback(int fd, char *buffer, int size) {
int count = send(fd, buffer, size, 0);
return count;
}
然后,在主循环中就可以这样使用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) {
推荐阅读
- 网络信息时代如何找到你想找的人呢
- 笔记本无线网卡无法连接网络怎么办
- 如花是谁啊,网络人物如花是谁啊?
- 小米手机怎么开网络加速,小米手机显示网络加速中
- 什么是蜂窝网络,什么是蜂窝网?
- 问道100五法怎么过,问道网络游戏如何游历师门
- 早期网络歌手近况:英年早逝、已被判刑、转战幕后,青春终会散场
- 如何渲染线框模型
- 网络用语需句斟字酌!避免“乌龙事件”避免踩雷
- 当女明星出演网络电影,“照妖镜”下丑态百出,年轻时却都是神颜