基于epoll的并发服务器编程

这一篇文章主要是理清服务器和客户端的建立通信的流程,整个通信是在网络层(即ip协议以及其上的传输层和应用层)。不明白的话需要先了解网络7层模型、对应的报文格式和不同层的封装。

下面主要围绕图11-14讲述并深入探讨

服务器

socket

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

// Example
serverfd = socket(AF_INET, SOCK_STREAM, 0) // 这里返回的描述符仅是部分打开,还不能用于读写

bind

用于将sockaddr与套接字描述符serverfd联系起来。套接字分两种:主动套接字和监听套接字,对应客户端和服务端。

#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr, int addrlen); // addrlen = sizeof(sockaddr_in)

listen

调用listen函数告诉内核,描述符是被服务器而不是客户端使用。因为通常情况下,socket函数创建的描述符对应于主动套接字,默认连接一个客户端。而listen将socket函数创建的描述符转化为一个监听套接字,该套接字可以接收来自客户端的连接请求。

#include <sys/socket.h>

int listen(int sockfd, int backlog);

accept

等待来自客户端的连接请求到达监听套接字listenfd,在addr中填写客户端的套接字地址,并返回一个已连接描述符,这个描述符可以与客户端通信。

#include <sys/socket.h>

int accept(int listenfd, struct sockaddr *addr, int *addrlen);

监听描述符和已连接描述符之间关系

可以实现并发,每次一个连接请求到达监听描述符,可以fork一个新的进程,它通过已连接描述符与客户端通信。

基于epoll的并发服务器

首先简单介绍一下epoll。epoll是IO多路复用的一种技术,还有就是select和poll。[select最大的不足之处是返回时会重新创建文件描述符集合,因此每次调用都必须重新开始初始化,FD_ZERO和 FD_SET]。下面介绍epoll怎么用

#include <sys/epoll.h>

int epoll_create(int size) //创建epoll实例,并返回与该实例关联的文件描述符

int epoll_ctl(int epfd,
              int op,
              int fd,
              struct epoll_event *event);

// epoll_ctl可以向指定的epoll加入或删除文件描述符
// op对fd的操作 EPOLL_CTL_ADD 将fd添加到epfd指向的epoll监听集合中
//             EPOLL_CTL_DEL 将fd从epfd指向的epoll监听集合中删除
//             EPOLL_CTL_MOD 使用event指定的更新事件修改已有的fd的监听行为

struct epoll_event {
        __u32 events;
        union {
                void *ptr;
                int fd;
                __u32 u32;
                __u64 u64;
        } data;
};

// events值 EPOLLET  开启边缘触发
//          EPOLLIN  表示对应的文件描述符可以读,用来设置或者检测
//          EPOLLOUT 表示对应的文件描述符可以写,用来设置或者检测
//          EPOLLPRI 表示存在带外(out-of-band)数据可读
// data是用户私有使用,当接收到请求的时间后,data会通过epoll_wait返回给用户。通常是将event.data.fd设为fd

int epoll_wait(int epfd,
               struct epoll_event *events,
               int maxevents,
               int timeout);

// 调用epoll_wait()时,最多可以有maxevents个事件,超时时间是timeout。成功返回时,events指向每个事件的epoll_event结构体,返回的是事件数

// example
#define MAX_EVENTS 64
int nr_events, i, epfd;

nr_events = epoll_wait (epfd, events, MAX_EVENTS, -1);
if (nr_events < 0) {}

for (i = 0; i < nr_events; i++) {
    printf ("event=%ld on fd=%d\n",
            events[i].events,
            events[i].data.fs);
}

就是将socket编程和epoll结合起来实现并发服务器

for( ; ; )
{
	nfds = epoll_wait(epfd,events,20,500);
	for(i=0;i<nfds;++i)
	{
		if(events[i].data.fd==listenfd) //有新的连接
		{
			connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接
			ev.data.fd=connfd;
			ev.events=EPOLLIN|EPOLLET;
			epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中
		}
		else if( events[i].events&EPOLLIN ) //接收到数据,读socket
		{
			n = read(sockfd, line, MAXLINE)) < 0    //读
			ev.data.ptr = md;     //md为自定义类型,添加数据
			ev.events=EPOLLOUT|EPOLLET;
			epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓
		}
		else if(events[i].events&EPOLLOUT) //有数据待发送,写socket
		{
			struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取数据
			sockfd = md->fd;
			send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据
			ev.data.fd=sockfd;
			ev.events=EPOLLIN|EPOLLET;
			epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据
		}
		else
		{
			//其他的处理
		}
	}
}

因为读写是一个周而复始的过程,那么服务器读了之后下一时刻就是将数据写回去

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章