基於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
		{
			//其他的處理
		}
	}
}

因爲讀寫是一個週而復始的過程,那麼服務器讀了之後下一時刻就是將數據寫回去

 

 

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