zoukankan      html  css  js  c++  java
  • 【Socket】linux下http服务器开发

    1.mystery引入
      1)超文本传输协议(HTTP)是一种应用于分布式、合作式、多媒体信息系统的应用层协议
      2)
    工作原理
       1)客户端
    一台客户机与服务器建立连接后,会发送一个请求给服务器,请求方式的格式为:统一资源定位符(URL)、协议版本号,后边是MIME信息,包括请求修饰符、客户机信息和可能的内容。
       2)
    服务器端
        1)服务器接收到客户机的请求后,首先解析请求信息,根据不同的请求模式给予相应的响应信息。HTTP中规定了6种请求格式,但最常用到的是GET和POST请求
        2)任何服务器除了包括HTML文件以外,还有一个HTTP驻留程序,用于响应用户的请求。
        3)服务器端驻留程序接收到请求后,在进行必要的操作后回送所要求的文件,在这一过程中,在网络上发送和接收的数据已经被分成一个或多个数据包(Packet),每个数据包包括:要传送的数据;控制信息,即告诉网络怎样处理数据包。
      3)在HTTP请求格式中,最重要的信息有两个,一个是请求的内容,另一个是请求的方法。
      4)数据对是由字段组成,其格式为:valuename=value;数据对与数据对之间由&连接,这是HTTP中定义的请求规则。
      5)在通常的WEB应用中,大数据量的提交一般选用POST方法,小数据量的提交,如查询操作,一般采用GET方法。
      6)请求实体由实体名和实体值组成,它的格式为:Entity:value


    2.HTTP服务器设计

      1)实现功能
       1)在HTTP服务器中,经常要处理的请求是GET,MesteryServer中将要实现针对GET请求的处理
         服务器并不支持CGI功能,所以针对GET中的CGI部分的请求处理,在本版本中不予支持。
       2)利用Gtk编写可视化的界面,三个按钮用于启动、关闭和暂停服务,一个标签用于显示服务状态。
       3)文件传输功能是本 服务器最基本的功能,负责可靠传送用户请求的资源文件
       4)具有日志功能,会将客户的请求信息存储到本地的日志文件里,以XML格式进行存储。
      2)
    业务功能
       1)针对GET请求功能,在这里只支持资源文件的下载,并且可以断点下载。
       2)如果是请求资源,则予以响应;如果涉及CGI的请求,则不予以响应。
       3)系统只支持GET请求,对于POST、HEAD、TRACE等请求,都予以忽略。
       4)对于HTTP服务器,它的运行模式是基于请求、响应机制,而下面的文件传输功能,其实是请求的具体执行过程,当服务器端成功解析请求命令后,会针对请求的类型,生成相应的响应码,并传送相应的资源文件。
       5)当请求工作执行完毕时,会形成一条日志记录,并插入到日志文件中。
       6)考虑到服务的关联性,服务器将为每一个请求开启一个单独的线程进行服务,该线程实现客户端请求接受、请求分析、响应码生成与传输、响应文件查找与传输、客户套接字关闭、日志记录写入等功能
      3)
    可视化界面
        其设计与业务的逻辑采用松耦合,双方只是通过消息的方式,传递控制命令与状态信息。
      4)
    主服务功能
        提供端口绑定(HTTP默认端口是80)、服务侦听、客户端套接字维护、业务线程创建等。
      5)
    界面模块
       1)由两个子模块组成:界面显示子模块->绘出程序的运程界面;按钮事件处理子模块
       2)界面模块与主服务模块之间的消息传递采用全局变量共享的方式。
      6)
    主服务模块
       1)以线程身份存在。
       2)不断轮询全局变量gServerStatus,并通过这个状态来动态调整真实服务状态。
      7)
    业务处理模块
       1)程序核心部分。
       2)由请求分析子模块、响应处理子模块、文件传输子模块、日志添加子模块等几个子模块组成。


    3.测试效果

       1)本地测试

      2)文件下载测试


      3)下载数据预览


    4.疑问解答

       前几天群里有人在讨论TCP和UDP,然后又引出了HTTP服务器,于是我就给他们推荐了这篇文章,但是大家看过之后还是有很多疑问,这里我根据自己的理解简单描述下。

    ❶主服务模块的设计原理

       可以看见,程序界面是用gtk写的,当点击“开始”按钮的时候,会动态创建该线程,其线程回调函数原型为

    void* server_process(void *p)
    {
        int serverSocket;
        struct sockaddr_in server_addr;
        struct sockaddr_in clientAddr;
        int addr_len = sizeof(clientAddr);
        if((serverSocket = socket(AF_INET,SOCK_STREAM,0)) < 0)
        {
            perror( "error: create server socket!!!");
            exit(1);
        }
        bzero(&server_addr,sizeof(server_addr));
        server_addr.sin_family =AF_INET;
        server_addr.sin_port = htons(SERVER_PORT);
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        if(bind(serverSocket,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
        {
            perror("error: bind address !!!!");
            exit(1);
        }
        if(listen(serverSocket,5)<0)
        {
            perror("error: listen !!!!");
            exit(1);
        }
        gIsRun = 1;
        printf("MesteryServer is running.....
    ");
        while(gIsRun)
        {    
            int clientsocket;
            clientsocket = accept(serverSocket,(struct sockaddr *)&clientAddr,(socklen_t*)&addr_len);
            if(clientsocket < 0)
            {
                perror("error: accept client socket !!!");
                continue;
            }
            if(gServerStatus == 0)
            {
                close(clientsocket);
            }
            else if(gServerStatus == 1)
            {
                pthread_t threadid;
                int temp;
                temp = pthread_create(&threadid, NULL, processthread, (void *)&clientsocket);
                /*if(threadid !=0)
                {                
                     pthread_join(threadid,NULL);
                }*/
            }
        }
        close(serverSocket);
    }

       从程序中可以看见,当绑定本地服务地址和端口后,便调用listen()函数进行侦听,while(gIsRun)表示主服务模块已经启动;然后采用阻塞式等待用户连接的到来,在连接到来的时候,还需要判断gServerStatus的值,即系统是否允许提供服务,如果允许,则创建服务线程。

       pthread_create(&threadid, NULL, processthread, (void *)&clientsocket);该线程的回调函数为processthread(),具体如下

    void* processthread(void *para)
    {
        int clientsocket;
        char buffer[1024];
        int iDataNum =0;
        int recvnum=0;
        clientsocket = *((int *)para);
        printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<BEGIN [%d]>>>>>>>>>>>>>>>>>>>>>>>
    ",clientsocket);
        struct HttpRequest httprequest;
        httprequest.content = NULL;
        httprequest.path = NULL;
        httprequest.path = (char *)malloc(1024);
        httprequest.rangeflag = 0;
        httprequest.rangestart = 0;
        while(1)
        {
            iDataNum = recv(clientsocket,buffer+recvnum,sizeof(buffer)-recvnum-1,0);
            if(iDataNum <= 0)
            {
                close(clientsocket);
                pthread_exit(NULL);
                return 0;
            }
            recvnum += iDataNum;
            buffer[recvnum]='';
            if(strstr(buffer,"
    
    ")!=NULL || strstr(buffer,"
    
    ")!=NULL)
                break;
        }
        printf("request: %s
    ",buffer);
                                                                                                                                                                                                                                                                                  
        //解析请求信息并处理请求信息
        switch(getrequest(buffer,&httprequest))
        {
        case GET_COMMON:
            processgetcommon(clientsocket,&httprequest);
            break;
        case GET_CGI:
            processgetcgi(clientsocket,&httprequest);
            break;
        case POST:
            processpost(clientsocket,&httprequest);
            break;
        case HEAD:
            processhead(clientsocket,&httprequest);
            break;
        default:
            break;
        }        
        insertlognode(pfilelog,&httprequest);
        if(httprequest.path != NULL)
            free(httprequest.path);
        if(httprequest.content != NULL)
            free(httprequest.content);
        close(clientsocket);
        printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<END [%d]>>>>>>>>>>>>>>>>>>>>>>>
    ",clientsocket);
        pthread_exit(NULL);
    }

       可以看见,在这个线程里面,便开始对请求进行业务分析了。

       ❷协议解析

       这个比较简单,因为HTTP协议的格式是固定的,所以只用对其按照HTTP的格式进行逐步解析就可以了。

       ❸文件传输

       文件传输是归在GET_COMMON类的

    //解析请求信息并处理请求信息
    switch(getrequest(buffer,&httprequest))
    {
    case GET_COMMON:
        processgetcommon(clientsocket,&httprequest);
        break;
    case GET_CGI:
        processgetcgi(clientsocket,&httprequest);
        break;
    case POST:
        processpost(clientsocket,&httprequest);
        break;
    case HEAD:
        processhead(clientsocket,&httprequest);
        break;
    default:
        break;
    }

       processgetcommon()函数实现如下

    void processgetcommon(int s,struct HttpRequest *prequest)
    {
        //先判断文件是否存在
        FILE *fp = isexistfile(prequest->path);
        printf("%s
    ",prequest->path);
        struct stat finfo;
        if(fp == NULL)
        {
            responsecode(s,404,prequest);
        }
        else
        {    
            if(prequest->rangeflag == 0)
            {        
                stat(prequest->path,&finfo);
                prequest->rangetotal = finfo.st_size;
            }
            responsecode(s,200,prequest);
            transferfile(s,fp,prequest->rangeflag,prequest->rangestart,prequest->rangetotal);
            fclose(fp);
        }
    }

       它先会判断有没有这个文件,如果没有,就生成404响应码,如果有,就返回200响应码,然后首先对prequest->rangeflag进行一个判断,看是否是断点续传,然后便开始传输文件,传输文件函数transferfile()如下

    int transferfile(int s,FILE *fp,int type,int rangstart,int totallength)
    {
        if(type == 1)
        {
            //为1,则表示当前从指定的位置传送文件
            fseek(fp,rangstart,0);   
        }
        int sendnum = 0;
        int segment = 1024;
        while(!feof(fp)&&sendnum < totallength)
        {
            char buf[segment];
            memset(buf,0,1024);
            int i = 0;
            while(!feof(fp) && i < segment && sendnum+i < totallength)
            {    
                buf[i++] = fgetc(fp);                
            }
            if(sendsegment(s,buf,i) == 0)
                return 0;
            sendnum += i;
        }
        return 1;
    }

       可以看见,具体的传输文件,是调用sendsegment()函数来实现的。

    int sendsegment(int s, char *buffer,int length)
    {
        if(length <= 0)
            return 0;
        printf("%s
    ",buffer);
        int result = send(s,buffer,length,0);
        if(result < 0)
            return 0;
        return 1;
    }

       而sendsegment()函数里面,就是用的socket里面的send()函数来实现的。

       ❹其它功能

       对于其它的功能,比如日志操作的,就是属于文件类的了;响应码则是属于对返回信息的一个格式处理,只要按照HTTP协议来就可以了;界面则是用gtk绘制就行了,这个空间比较大,只有绑定相应按钮和处理函数就行了。


    5.源代码
     

       请到我原博客附件下载:http://infohacker.blog.51cto.com/6751239/1155176

  • 相关阅读:
    js中__proto__和prototype的区别和关系?
    Vue绑定事件v-on(@)与修饰符
    Vue状态(视图共享)管理:Vuex
    Vue UI框架对比:Element UI、Ant Design Vue、iView
    搞清楚Vue-router中的Router、Router实例、Router对象的区别与联系
    Vue-router高级进阶知识
    Vue-router基础知识
    FloatHelper
    scrollHelper
    DialogHelper
  • 原文地址:https://www.cnblogs.com/lcw/p/3159502.html
Copyright © 2011-2022 走看看