1. 程式人生 > >Linux網路程式設計---I/O多路複用之epoll

Linux網路程式設計---I/O多路複用之epoll

/*
 TCP伺服器
 用法:./server port
 
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <assert.h>
#include <vector>
#include <fcntl.h>
#include <sys/epoll.h>
#include <algorithm>
using namespace std;
#define BUFSIZE 1024
#define MAXCONN 200
static void bail(const char *on_what)
{
    fputs(strerror(errno),stderr);
    fputs(": ",stderr);
    fputs(on_what, stderr);
    fputc('\n',stderr);
    exit(1);
}
struct sockfd_opt  //處理每個socket描述符的結構體
{
    int fd;    //描述符
    int (*do_task)(struct sockfd_opt *p_so); //回撥函式
};
vector<struct sockfd_opt*>HashHead[MAXCONN];  //連結串列元素
int epfd;
struct epoll_event *events;

//設定為非阻塞模式
void setnonblocking(int sock)
{
    int opts;
    opts=fcntl(sock, F_GETFL);
    if (opts<0)
        bail("fcntl");
    opts=opts|O_NONBLOCK;
    if (fcntl(sock, F_SETFL,opts)<0)
        bail("fcntl");
}

//生成hash值
int intHash(int key)
{
    key+=~(key<<15);
    key^=(key>>10);
    key+=(key<<3);
    key^=(key>>6);
    key+=~(key<<11);
    key^=(key>>16);
    return key;
}

//向客戶端發回日期時間
int send_reply(struct sockfd_opt *p_so)
{
    char reqBuf[BUFSIZE]; //接收快取
    char dtfmt[BUFSIZE];//日期-時間結果字串
    time_t td; //當前時間和日期
    struct tm tm;
    long z;
    unsigned int hash;
    if ((z=read(p_so->fd, reqBuf, sizeof(reqBuf)))<=0)
    {
        //此fd代表的客戶端關閉了連線,因此該fd將自動從epfd中刪除,於是我們僅需將其從散列表中刪除
        
        hash=intHash(p_so->fd)& MAXCONN;
        vector<struct sockfd_opt*>::iterator  it;
        HashHead[hash].erase(find(HashHead[hash].begin(), HashHead[hash].end(), p_so)); //刪除
        
        //關閉當前套接字描述符
        close(p_so->fd);
        free(p_so);
        //若讀操作返回-1且不是RST分段
        if (z<0 && (errno|=ECONNRESET))
            bail("read()");
    }
    else
    {
        reqBuf[z]=0;
        time(&td);
        tm=*localtime(&td);
        strftime(dtfmt, sizeof(dtfmt), reqBuf, &tm);
        
        //向客戶端發回結果
        z=write(p_so->fd, dtfmt, strlen(dtfmt));
        if (z<0)
            bail("write()");
    }
    
    return 0;
}

//接收TCP連線
int creat_conn(struct sockfd_opt *p_so)
{
    unsigned int hash;
    struct sockaddr_in client;  //客戶端ip地址
    int conn_fd;
    socklen_t sin_size;
    sin_size=sizeof(client);
    struct epoll_event ev;
    if ((conn_fd=accept(p_so->fd, (struct sockaddr*)&client, &sin_size))==-1)
    {
        fprintf(stderr, "Accept error:%s\a\n",strerror(errno));
        exit(1);
    }
    setnonblocking(conn_fd);
    fprintf(stdout, "server got connection from %s:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
    
    int ret;
    if ((p_so=(struct sockfd_opt*)malloc(sizeof(sockfd_opt)))==NULL)
    {
        perror("malloc");
        return -1;
    }
    p_so->fd=conn_fd;
    p_so->do_task=send_reply;
    hash=intHash(conn_fd)&MAXCONN;
    
    HashHead[hash].push_back(p_so);
  //  printf("fd2:%d hash2:%d size2:%d\n",conn_fd,hash,HashHead[hash].size());
    
    //向epoll上下文註冊此conn_fd
    ev.data.fd=conn_fd;
    ev.events=EPOLLIN;
    //ev.events=EPOLLIN|EPOLLET
    
    //新增此fd
    ret=epoll_ctl(epfd,EPOLL_CTL_ADD,conn_fd,&ev);
    if (ret)
        bail("epoll_ctl");
    return 0;
}



//初始化監聽套接字選項
int init(int fd)
{
    sockfd_opt *p_so;
    struct epoll_event ev;
    unsigned int hash;
    int ret;
    if ((p_so=(struct sockfd_opt*)malloc(sizeof(sockfd_opt)))==NULL)
    {
        perror("malloc");
        return -1;
    }
    //設定監聽套接字選項的回撥函式
    p_so->do_task=creat_conn;
    p_so->fd=fd;
    //將監聽套接字選項加入到連結串列尾
    
    
    hash=intHash(fd)&MAXCONN;
    HashHead[hash].push_back(p_so);
 //   printf("fd1:%d hash1:%d size1:%d\n",fd,hash,HashHead[hash].size());
    
    //向epoll上下文註冊此fd
    ev.data.fd=fd;
    ev.events=EPOLLIN;
    //ev.events=EPOLLIN|EPOLLET
    
    //新增此fd
    ret=epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);
    if (ret)
        bail("epoll_ctl");
    return 0;
    
}

int main(int argc,char *argv[])
{
    int listen_fd; //用於監聽的套接字描述符
    struct sockaddr_in server;
    int port;
    socklen_t optlen;
    epfd=epoll_create(MAXCONN); //epoll集合
    int nev;//epoll_wait返回的檔案描述符個數
    vector<struct sockfd_opt*>::iterator it;//迭代器
    struct sockfd_opt *p_so;
    unsigned int hash;
    port=atoi(argv[1]);
    if((listen_fd=socket(PF_INET, SOCK_STREAM, 0))==-1)
        bail("socket()");
    
    setnonblocking(listen_fd);
    
    //設定套接字選項
    int opt;
    optlen=sizeof(opt);
    int ret=setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, optlen);
    if (ret)
        bail("setsockopt()");
    //伺服器監聽地址準備
    memset(&server, 0, sizeof(server));
    server.sin_family=PF_INET;
    server.sin_addr.s_addr=htonl(INADDR_ANY);
    server.sin_port=htons(port);
    //繫結伺服器到監聽套接字
    if((bind(listen_fd, (struct sockaddr*)&server, sizeof(server)))==-1)
        bail("bind()");
    //開始監聽
    if(listen(listen_fd, 5)==-1)
        bail("listen()");
    
    if (init(listen_fd))
        bail("init()");
    
    events=(struct epoll_event*)malloc(sizeof(struct epoll_event)*MAXCONN);
    printf("server is waiting for acceptance of new client\n");
    for (; ; )
    {
        //等待註冊的事件發生
        nev=epoll_wait(epfd,events,MAXCONN,-1);
        if (nev<0)
        {
            free(events);
            bail("epoll_wait");
        }
        for (int i=0; i<nev; i++)
        {
            hash=intHash(events[i].data.fd)&MAXCONN;
            it=HashHead[hash].begin();
            while (it!=HashHead[hash].end())
             {
                if ((*it)->fd==events[i].data.fd)
                {
                    (*it)->do_task(*it);
                    break;   //跳出來,迭代器可能會失效(當刪除一個套接字描述符後)
                }
                ++it;
            }
        }
    }
    return 0;
}