LINUX高性能服务器框架和ET/LT的代码区别

1.服务器模型:

1). C/S模型

 :


  此模型就是一个服务器能给多个客户端提供服务,但所以资源都被服务端所占有,客户端想要获取只能通过请求连接服务端去获取。由于客户端的请求访问是异步的,所以需要一个手段进行此类事件的监听,比如SELECT或者是EPOLL,可以只监听服务器套接字,将客户端的客户需求交给子进程去处理,也可以进行同时监听,将连接对面的套接字加入,进行一起监听。

2).P2P模式

 此模型放弃了以服务器为中心的观点,改为每个电脑的地位相同,既可以作为客户端,也可以作为服务端。

 

2.服务器大致框架:

不同差别服务器,其差别一般都于逻辑处理,但大体框架是一样的,如下图:

3.io模型:

I/O模型一般有如下几种:

阻塞I/O:即当客户端发起连接以后,服务端成功连接,但是服务/客户端因为某些原因无法立刻执行,从而被操作系统挂起,直到有消息或者要处理时,才会从等待队列中唤醒。可能被阻塞的I/O有accept(),connect(),send(),recv()等等。

非阻塞I/O:此类不会一直等待,比如可以设置一段事件进行等待,事件发生就立即进行返回。

同步/异步 I/O

4:两种高效的I/O模型:

1.): reactor模型(同步模型):

如上图:就绪事件准备好之后插入就绪队列,然后唤醒请求队列进行不同的工作状态进行工作。

 

以同步I/O模拟proactor模式

注意如此,用同步I/O来模拟异步I/O时,发生读事件,将数据进行封装然后储存在队列中,然后注册写事件就绪,而写事件则是队列取出进行回馈返回。

5.高效的并发模式(主要运用线程池):
1).半同步半异步:

半同步半异步指的就是同步用来处理用户逻辑,异步用来处理I/O事件:

但是上述模型有缺点:

8-11改良后的半同步/半异步模式,每个工作线程都可以通过epoll_wait()。来监听多个SOCKET进行服务。

2).领导/追逐者模式:读者能力有限,请自行 查阅资料

6.优先状态机,用来处理单元之间的逻辑问题:比如先处理什么,再处理什么,接着处理什么。

想象一个如下有限状态机:

下面是利用上述原理进行的分析HTTP请求的状态机(对面发送请求HTTP请求,当然可能分很多次发送,这里代码是多次接受,每一次接受都判断一次的)。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

#define BUFFER_SIZE 4096
enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };
enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };
enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, FORBIDDEN_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };
static const char* szret[] = { "I get a correct result\n", "Something wrong\n" };


LINE_STATUS parse_line( char* buffer, int& checked_index, int& read_index )
{
    char temp;
    for ( ; checked_index < read_index; ++checked_index )
    {
        temp = buffer[ checked_index ];
        if ( temp == '\r' )
        {
            if ( ( checked_index + 1 ) == read_index )
            {
                return LINE_OPEN;
            }
            else if ( buffer[ checked_index + 1 ] == '\n' )
            {
                buffer[ checked_index++ ] = '\0';
                buffer[ checked_index++ ] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
        else if( temp == '\n' )
        {
            if( ( checked_index > 1 ) &&  buffer[ checked_index - 1 ] == '\r' )
            {
                buffer[ checked_index-1 ] = '\0';
                buffer[ checked_index++ ] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    return LINE_OPEN;
}

HTTP_CODE parse_requestline( char* szTemp, CHECK_STATE& checkstate )
{
    char* szURL = strpbrk( szTemp, " \t" );
    if ( ! szURL )
    {
        return BAD_REQUEST;
    }
    *szURL++ = '\0';

    char* szMethod = szTemp;
    if ( strcasecmp( szMethod, "GET" ) == 0 )
    {
        printf( "The request method is GET\n" );
    }
    else
    {
        return BAD_REQUEST;
    }

    szURL += strspn( szURL, " \t" );
    char* szVersion = strpbrk( szURL, " \t" );
    if ( ! szVersion )
    {
        return BAD_REQUEST;
    }
    *szVersion++ = '\0';
    szVersion += strspn( szVersion, " \t" );
    if ( strcasecmp( szVersion, "HTTP/1.1" ) != 0 )
    {
        return BAD_REQUEST;
    }

    if ( strncasecmp( szURL, "http://", 7 ) == 0 )
    {
        szURL += 7;
        szURL = strchr( szURL, '/' );
    }

    if ( ! szURL || szURL[ 0 ] != '/' )
    {
        return BAD_REQUEST;
    }

    //URLDecode( szURL );
    printf( "The request URL is: %s\n", szURL );
    checkstate = CHECK_STATE_HEADER;
    return NO_REQUEST;
}

HTTP_CODE parse_headers( char* szTemp )
{
    if ( szTemp[ 0 ] == '\0' )
    {
        return GET_REQUEST;
    }//为神马这里就是 GET
    else if ( strncasecmp( szTemp, "Host:", 5 ) == 0 )
    {
        szTemp += 5;
        szTemp += strspn( szTemp, " \t" );
        printf( "the request host is: %s\n", szTemp );
    }
    else
    {
        printf( "I can not handle this header\n" );
    }

    return NO_REQUEST;
}


HTTP_CODE parse_content( char* buffer, int& checked_index, CHECK_STATE& checkstate, int& read_index, int& start_line )
{
    LINE_STATUS linestatus = LINE_OK;//行状态和主机状态。
    HTTP_CODE retcode = NO_REQUEST;
    while( ( linestatus = parse_line( buffer, checked_index, read_index ) ) == LINE_OK )
    {
        char* szTemp = buffer + start_line;//定位分析的指针----重要
        start_line = checked_index;//---------重要
        switch ( checkstate )
        {
            case CHECK_STATE_REQUESTLINE:
            {
                retcode = parse_requestline( szTemp, checkstate );//因为要转换状态
                if ( retcode == BAD_REQUEST )
                {
                    return BAD_REQUEST;
                }
                break;//断开switch 继续进行while读行
            }
            case CHECK_STATE_HEADER:
            {
                retcode = parse_headers( szTemp );
                if ( retcode == BAD_REQUEST )
                {
                    return BAD_REQUEST;
                }
                else if ( retcode == GET_REQUEST )
                {
                    return GET_REQUEST;
                }
                break;
            }
            default:
            {
                return INTERNAL_ERROR;
            }
        }
    }
    if( linestatus == LINE_OPEN )//没有读到结束符
    {
        return NO_REQUEST;//NO_REQUEST 只用来继续读
    }
    else
    {
        return BAD_REQUEST;
    }
}

int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
        printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi( argv[2] );
    
    struct sockaddr_in address;
    bzero( &address, sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );
    
    int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
    assert( listenfd >= 0 );
    
    int ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );
    
    ret = listen( listenfd, 5 );
    assert( ret != -1 );
    
    struct sockaddr_in client_address;
    socklen_t client_addrlength = sizeof( client_address );
    int fd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
    if( fd < 0 )
    {
        printf( "errno is: %d\n", errno );
    }
    else
    {
        char buffer[ BUFFER_SIZE ];
        memset( buffer, '\0', BUFFER_SIZE );
        int data_read = 0;
        int read_index = 0;
        int checked_index = 0;
        int start_line = 0;
        CHECK_STATE checkstate = CHECK_STATE_REQUESTLINE;//初始化状态
        while( 1 )
        {
            data_read = recv( fd, buffer + read_index, BUFFER_SIZE - read_index, 0 );
            if ( data_read == -1 )
            {
                printf( "reading failed\n" );
                break;
            }
            else if ( data_read == 0 )
            {
                printf( "remote client has closed the connection\n" );
                break;
            }
            //接受一次就记录read_index
    
            read_index += data_read;//记录更新read_index;
            /*每读取一次 先更新read_index 在进行分析*/
            HTTP_CODE result = parse_content( buffer, checked_index, checkstate, read_index, start_line );
            if( result == NO_REQUEST )//NO_REQUEST就代表继续接受信息继续分析
            {
                continue;
            }
            else if( result == GET_REQUEST )//不是GET的其他请求也继续分析
            {
                send( fd, szret[0], strlen( szret[0] ), 0 );
                break;//接受GET 断开
            }
            else
            {
                send( fd, szret[1], strlen( szret[1] ), 0 );
                break;
            }
        }
        close( fd );
    }
    
     close( listenfd );
    return 0;
}

只需注意一点的是通过while循环不断读取请求,根据读取数据进行请求行和消息头的寻找和分析。

下面是简单的,通过epoll_wait()调用的有限状态机(四种情况)和recator模型,将监听到的事件分为读写事件的简单服务端,代码如下:(只需要明白EPOLL_MOD后,读事件改为写事件后,就自动触发了写事件的情况,下一次调用就会直接进行调用写事件的函数)

//recator
#include <sys/socket.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <iostream>
#include <netinet/in.h>
#include <assert.h>

const int MAX_EVENT=1000;
const int MAX_BUFSIZE=1024;
const int clinetnum=99999;
int epollfd=0;
struct clinet_data;

typedef int(*REPLAY_LETTER)(int,clinet_data*);
void epollset(int,int);
void clintset(clinet_data*,int);

struct clinet_data{
    int fd;
    char bufread[MAX_BUFSIZE];
    char bufwrite[MAX_BUFSIZE];
    int readend;
    int writeend;
    REPLAY_LETTER readevent;
    REPLAY_LETTER writevent;
    REPLAY_LETTER closeevent;
    clinet_data():fd(0),readend(0),writeend(0){};
};

clinet_data* dataset=new clinet_data[clinetnum];

 void closeevent(int sockfd){
  close(sockfd);
  std::cout<<"close sockfd :"<<sockfd<<std::endl;
}
void epoll_event_change(int sockfd,int mode){
    switch (mode){
    
      case 1:
      {
        epoll_ctl(epollfd,EPOLL_CTL_DEL,sockfd,NULL);
        break;
      }
      case 2:
      {
        epoll_event ee;
        ee.data.fd=sockfd;
        ee.events=EPOLLOUT;
        epoll_ctl(sockfd,EPOLL_CTL_MOD,sockfd,&ee);
       break;
      }
      case 3:
      {
          epoll_event ee;
        ee.data.fd=sockfd;
        ee.events=EPOLLIN;
        epoll_ctl(sockfd,EPOLL_CTL_MOD,sockfd,&ee);  
        break;
      }
       case 4:
       {
                struct sockaddr_in addr;
                socklen_t size=sizeof(addr);
                int connect=accept(sockfd,(struct sockaddr*)&addr,&size);
                epollset(epollfd,connect);
                clintset(dataset,connect);
                break;
       }
      default:
      break;
    }
}
int readevent(int sockfd,clinet_data* dataset){
    //size<MAX_BUFFSIZE,事件改为写 然后发送回去

    int size=recv(sockfd,dataset[sockfd].bufread,MAX_BUFSIZE-1,0);
    dataset[sockfd].readend=size;
    dataset[sockfd].writeend=0;
      if(size==0){//对面关闭
        memset(dataset[sockfd].bufread,'\0',MAX_BUFSIZE);
        std::cout<<"nodata,over"<<std::endl;
        closeevent(sockfd);
        dataset[sockfd].fd=0;
        epoll_event_change(sockfd,1);
        return 0;
    }else{
    std::cout<<"recv data  :"<<dataset[sockfd].bufread<<std::endl;
    memcpy(dataset[sockfd].bufwrite,dataset[sockfd].bufread,size);
    memset(dataset[sockfd].bufread,'\0',MAX_BUFSIZE);
    epoll_event_change(sockfd,2);
    }
    return size;
}

int writeevent(int sockfd,clinet_data* dataset){
    dataset[sockfd].writeend=dataset[sockfd].readend;
    dataset[sockfd].readend=0;
    int count=send(sockfd,dataset[sockfd].bufwrite,dataset[sockfd].writeend,0);
    epoll_event_change(sockfd,3);
    return count;
}

void  clintset(clinet_data* con,int fd){
    con[fd].readevent=readevent;
    con[fd].writevent=writeevent;
    con[fd].fd=fd;
}

int setsocknoblock(int fd){
int old=fcntl(fd,F_GETFL);
int newfd=old|O_NONBLOCK;
fcntl(old,F_SETFL,newfd);
return old;
}

void epollset(int epollfd,int fd){
    epoll_event even;
    even.data.fd=fd;
    even.events=EPOLLET|EPOLLIN;
    epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&even);
    setsocknoblock(fd);
}

int main(int argc,char* argv[]){

    if(argc<2){
        std::cout<<"not enough"<<std::endl;
        exit(1);
    }
    int ret=0;
     struct sockaddr_in address;
      epoll_event events[MAX_EVENT]; 
    memset(&address,0,sizeof(address));
    address.sin_family=AF_INET;
    address.sin_addr.s_addr=inet_addr("127.0.0.1");
    address.sin_port=htons(atoi(argv[1]));
    int severfd=socket(PF_INET,SOCK_STREAM,0);
    assert(severfd>=0);

    epollfd=epoll_create(5);
    epollset(epollfd,severfd);
    
    ret=bind(severfd,(struct sockaddr*)&address,sizeof(address));
    assert(ret>=0);

    ret=listen(severfd,5);
    assert(ret>=0);
    clinet_data* dataset=new clinet_data[clinetnum];
    while(1){
      int numm=epoll_wait(epollfd,events,MAX_EVENT-1,5*1000);
      if(numm==0){
        std::cout<<"timeout"<<std::endl;
      }else if(numm<0){
      std::cout<<"epoll_wait error"<<std::endl;
      exit(1);
      }else{
        for(int i=0;i<numm;i++){
            int fd=events[i].data.fd;
            if(events[i].events&EPOLLIN){
                if(fd==severfd){
                epoll_event_change(fd,4);
                }else{
                     readevent(fd,dataset);
                }
            }
            else if(events[i].events&EPOLLOUT){
                writeevent(fd,dataset);
            }
        }
      }
}
delete [] dataset;
close(severfd);
return 0;
}

结合客户端代码(输入Q后,客户端断开自行测试:(作者已经测试过了,没有问题)。

#include<iostream>
#include<sys/socket.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
const int a=1024;
using namespace std;
int main(int argc,char* argv[]){
    char message[a];
struct sockaddr_in times;
if(argc!=3){
    cout<<"error()"<<endl;
    exit(1);
}
int cilsock;
int str_len;
cilsock=socket(PF_INET,SOCK_STREAM,0);
if(cilsock==-1){
    cout<<"socket()error"<<endl;
}
memset(&times,0,sizeof(times));
times.sin_family=AF_INET;
times.sin_addr.s_addr=inet_addr(argv[1]);
times.sin_port=htons(atoi(argv[2]));
if(connect(cilsock,(struct sockaddr *)&times,sizeof(times))==-1){
cout<<"connext()error()"<<endl;
}else{
puts("connect........");
}
 
while(1){
    fputs("input message",stdout);
    fgets(message,a,stdin);
 
    if(!strcmp(message,"q/n")||!strcmp(message,"Q/n"))
    break;
 
    write(cilsock,message,strlen(message));
    str_len=read(cilsock,message,a-1);
    message[str_len]=0;
    cout<<message<<endl;
}
close(cilsock);
return 0;
 
}

下面是ET/LT的代码选择模式,只需要进行注意两个区别即可。LT读取时,每一次读取都要设置while()循环,因为TCP传输类别时,当数据量过大时,可能会分段进行传输,所以要一直循环,将对面传来数据全部读取完毕。但是ET不用,直接进行总体循环检测即可。

在ET模式下要设置非阻塞。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10

int setnonblocking( int fd )
{
    int old_option = fcntl( fd, F_GETFL );
    int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
    return old_option;
}

void addfd( int epollfd, int fd, bool enable_et )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN;
    if( enable_et )
    {
        event.events |= EPOLLET;
    }
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}
//LT模式 只要事件发生 可以留在下一次在进行执行,这次不进行,只要没被处理(管道里有数据),就会一直触发,LT设置阻塞
void lt( epoll_event* events, int number, int epollfd, int listenfd )
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, false );//对面发送请求 我方加入监听
        }
        else if ( events[i].events & EPOLLIN )
        {
            printf( "event trigger once\n" );//一直进行触发 
            memset( buf, '\0', BUFFER_SIZE );
            int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
            if( ret <= 0 )
            {
                close( sockfd );
                continue;
            }
            printf( "get %d bytes of content: %s\n", ret, buf );
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}

void et( epoll_event* events, int number, int epollfd, int listenfd )//ET 只会被触发一次
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, true );
        }
        else if ( events[i].events & EPOLLIN )
        {
            printf( "event trigger once\n" );//触发一次 因为是ET 所以是事件发生就要处理完毕
            while( 1 )
            {
                memset( buf, '\0', BUFFER_SIZE );
                int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
                if( ret < 0 )
                {
                    if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
                    {
                        printf( "read later\n" );
                        break;
                    }
                    close( sockfd );
                    break;
                }
                else if( ret == 0 )//对面关闭客户端
                {
                    close( sockfd );
                }
                else
                {
                    printf( "get %d bytes of content: %s\n", ret, buf );
                }
            }
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}

int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
        printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi( argv[2] );

    int ret = 0;
    struct sockaddr_in address;
    bzero( &address, sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );

    int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
    assert( listenfd >= 0 );

    ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );

    ret = listen( listenfd, 5 );
    assert( ret != -1 );

    epoll_event events[ MAX_EVENT_NUMBER ];
    int epollfd = epoll_create( 5 );
    assert( epollfd != -1 );
    addfd( epollfd, listenfd, true );//套接字监听必须是ET 这个注意

    while( 1 )
    {
        int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
        if ( ret < 0 )
        {
            printf( "epoll failure\n" );
            break;
        }
    
       // lt( events, ret, epollfd, listenfd );
       et( events, ret, epollfd, listenfd );
    }

    close( listenfd );
    return 0;
}
//不论是那种方式,在关闭客户端时,都会触发一次,因为关闭客户端时,其向服务端发送请求关闭报文,也发生了事件,所以会被检测关闭

ET模式下,由于触发只触发一次,所以要将数据全部读完,这就要用while()循环,一次性全部处理读完。是先触发了读事件,然后再是循环读,假设第n此读,读完后返回读的字节数,这次循环不会退出,下一次while()由于没数据了,则返回-1设置errno为  EAGAIN 或  EWOULDBLOCK 。这就代表数据读完了,退出。其他返回-1的情况关闭套接字,退出。

LT模式下,只要有数据,就会触发,所以不用while循环。如果不设置非阻塞,epoll和recv会阻塞。上述代码LT设置非阻塞。假设第n此读完,读完后返回读的字节数,这次循环不会退出,下一次while()由于没数据了,则返回-1,直接进行关闭客户端。

ET/LT还有个区别就是,LT退出是直接通过recv()函数返回值<=0,而ET退出后,还要进行返回值和errno的情况分类讨论。(未设置reactor模型。)

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值