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

  • 相关阅读:
    003 01 Android 零基础入门 01 Java基础语法 01 Java初识 03 Java程序的执行流程
    002 01 Android 零基础入门 01 Java基础语法 01 Java初识 02 Java简介
    001 01 Android 零基础入门 01 Java基础语法 01 Java初识 01 导学
    001 Android Studio 首次编译执行项目过程中遇到的几个常见问题
    Dora.Interception,为.NET Core度身打造的AOP框架 [2]:以约定的方式定义拦截器
    Dora.Interception,为.NET Core度身打造的AOP框架 [1]:更加简练的编程体验
    监视EntityFramework中的sql流转你需要知道的三种方式Log,SqlServerProfile, EFProfile
    轻量级ORM框架——第二篇:Dapper中的一些复杂操作和inner join应该注意的坑
    轻量级ORM框架——第一篇:Dapper快速学习
    CF888G Xor-MST(异或生成树模板)
  • 原文地址:https://www.cnblogs.com/lcw/p/3159502.html
Copyright © 2011-2022 走看看