如何编写一个功能完善的HTTP服务器

    YC服务器是可扩展的HTTP服务器,它的源码文件是ychttp.cpp。
    使用YSP语言生成的网页或数据可以通过YC服务器传给浏览器及各种客户终端。下面是该服务器的源码。

……
typedef void (*serverCall)(int userSock,const char *headBuf);
struct ychttpCLASS    //ychttp.cpp对外接口
{
        virtual void _stdcall start_server(int port=80,serverCall aFunc=nullptr);
        virtual void _stdcall stop_server();
        virtual int _stdcall check_server();
        virtual int _stdcall slot_number();
};
……

                        头文件(部分代码):yc.h

#define YCLEV 5              //编译语法错误提示级别(可去掉)
#define YCICON "water.jpg"   //独立运行时的文件图标(可去掉)
#include "yc.h"

#define INDEX_FILE  "index.ysp"   //主页文件
ychttpCLASS YCHTTP;

char root_path[MAX_PATH];    //主页路径
int root_len;                //主页路径长度

void main(void **pObj)
{
    if((int)pObj > 1000)   //ychttp.cpp被其它应用程序调用时运行的代码
      {
        *pObj = &YCHTTP;   //函数接口对象地址

        root_len = strlen(YC_path());
        strcpy(root_path,YC_path());
        strcat(root_path,INDEX_FILE);                 //主页文件

        if(YC_readfile(root_path,NULL) < 0)           //文件不存在
          {
            strcpy(&root_path[root_len],YC_PATHwww);  //使用子目录
            root_len = strlen(root_path);
          }
        else  root_path[root_len] = 0;
        return;
      }

    //ychttp.cpp独立运行时的代码
    int mport = 81;    //端口
    YCHTTP.start_server(mport);   //启动服务器
    printf(YCHTTP.check_server() ? "Web服务器启动成功! port=%d\n"
                                 : "Web服务器未能启动.\n", mport);

    char URLptr[8192] = "";
    while(1)
      {
        printf("--------------press 'Q' to exit-----------\n");
        printf("\n在浏览器地址栏输入: http://127.0.0.1:%d/%s\n",mport,URLptr);
        printf("可浏览文件  %s\n",URLptr);
        printf("\n输入文件名: ");

        scanf("%s",URLptr);                      //从控制台输入
        if(!strcmpi(URLptr,"q"))  break;         //是Q字符, 则退出程序
        printf("slot_number = %d\n\n",YCHTTP.slot_number());
        strlwr(URLptr);

        char *mptr = strchr(URLptr,'.');
        if(mptr && (strchr(mptr+1,'.') || !strcmp(mptr,".com")
                                       || !strcmp(mptr,".cn")
                                       || !strcmp(mptr,".htm")
                                       || !strcmp(mptr,".ysp")))
          {
            ycwinCLASS *pwin = < ycwin.cpp >;  //在窗口中显示网页
            pwin->createWindow( URLptr );
            pwin->loop();
          }
        else
          {
            char fileName[8192];
            sprintf(fileName,"http://127.0.0.1:%d/%s",mport,URLptr);
            printf("%s\n",fileName);
            YHTML *phtml = YC_htmlLoad(fileName);
            int glen;
            wchar_t *wibuf = YCHTML->sourceHTML(phtml,&glen);
            char *txtbuf = YC_wideToChar(&glen,wibuf,glen);
            YCWEB->MessageBoxA(phtml,txtbuf,fileName); //对话框显示文本
            free(txtbuf);
            YC_htmlFree(phtml);
          }
      }
    YCHTTP.stop_server();
}

enum  //本程序能处理的http类型
{
    HTTP_none,
    HTTP_GET,
    HTTP_POST,
    HTTP_HEAD,
};

int get_http_method(char *headPtr,int glen)
{
    switch(glen)
      {
        case 4:
             if(*(int*)headPtr == *(int*)"POST")  return HTTP_POST;
             if(*(int*)headPtr == *(int*)"HEAD")  return HTTP_HEAD;
             break;
        case 3:
             if(*(int*)headPtr == *(int*)"GET ")  return HTTP_GET;
             break;
      }
    return HTTP_none;
}

//处理HTTP请求的函数
void response_proc(int userSock,char *headBuf,int rev_cnt,
                                    int sendLen,int keepAlive,int *server_stop)
{
    char *headPtr = strchr(headBuf,' '); //headBuf: "GET /hello.ysp HTTP/1.1..."
    if(!headPtr)  return;

    int m_method = get_http_method(headBuf, headPtr - headBuf);
    if(m_method == HTTP_none)    return;

    headPtr++;
    char *spacePtr = strchr(headPtr,' ');
    if(!spacePtr)   return;

    char *questPtr = strchr(headPtr,'?');
    char *url_ptr = YCWEB->decode_uri_file(headPtr,
                                   (questPtr ? questPtr : spacePtr) - headPtr);
    if(!url_ptr)    return;

    if(url_ptr[0] != '/')   //url_ptr: "/hello.ysp"
      {
        free(url_ptr);
        return;
      }

    char file_name[2048];  //存放HTTP请求文件名
    spacePtr = url_ptr[1] ? &url_ptr[1] : INDEX_FILE;
    int kk = strlen(spacePtr);
    if(kk > sizeof(file_name)-1-root_len)   kk = sizeof(file_name)-1-root_len;

    memcpy(file_name,root_path,root_len);
    memcpy(&file_name[root_len],spacePtr,kk);
    kk += root_len;
    file_name[kk] = 0;

    char *httphead_buf = nullptr;
    char *binary_buf = nullptr;

    spacePtr = &file_name[kk-4];
    strlwr(spacePtr);

    #define STR_VALUE(str)  str[0] + (str[1]<<8) + (str[2]<<16) + (str[3]<<24)
    int glen = -1;
    char *content_type_str;

    switch((spacePtr[0]=='.' || kk>5 && spacePtr[-1]=='.') ? *(int*)spacePtr:0)
      {
        default:
        case STR_VALUE(".htm"):                  //HTTP请求文件名的后缀为.htm
        case STR_VALUE("html"):                  //HTTP请求文件名的后缀为.html
             content_type_str = "Content-Type: text/html\r\n";
             break;
        case STR_VALUE(".jpg"):                  //HTTP请求文件名的后缀为.jpg
             content_type_str = "Content-Type: image/jpg\r\n";
             break;
        case STR_VALUE(".png"):                  //HTTP请求文件名的后缀为.png
             content_type_str = "Content-Type: image/png\r\n";
             break;
        case STR_VALUE(".gif"):                   //HTTP请求文件名的后缀为.gif
             content_type_str = "Content-Type: image/gif\r\n";
             break;
        case STR_VALUE(".bmp"):
        case STR_VALUE(".ico"):
             content_type_str = "Content-Type: image/bmp\r\n";
             break;
        case STR_VALUE(".ysp"):    //处理 YSP 文件
             glen = YCHTML->run_ysp_file(file_name,binary_buf,httphead_buf,
                                         headPtr,headBuf + rev_cnt - headPtr,
                                         questPtr,server_stop);
             content_type_str = httphead_buf ? httphead_buf : "";
             break;
      }

    if(glen < 0)  glen = YC_readfile(file_name,&binary_buf);   //读取非ysp文件数据
    char *userData = binary_buf;
    char *errbuf = null;

    int httpcode = 200;
    if(glen < 0)    //文件不存在,显示404错误
      {
        errbuf = (char*)malloc(256);
        if(errbuf)
          {
            snprintf(errbuf,256,`HTTP/1.1 404
                              <span style='color:rgb(100,10,0);font-size:26px;'>
                              文件没有找到 : %s</span>`,url_ptr);
            httpcode = 404;
            userData = errbuf;
            glen = strlen(errbuf);
          }
        else  glen = 0;
      }

    //为了方便在ycs.htm页面中显示HTTP请求和应答信息,将应答信息放在headBuf的后面。
    headBuf += rev_cnt;
    kk = snprintf(headBuf,sendLen, "\r\n\r\nHTTP/1.1 %d OK\r\n"
                                   "Content-Length: %d\r\n"
                                   "%s"
                                   "%s"
                                   "\r\n",
                                 httpcode,    //应答信息类型
                                 glen,        //发给客户端的数据的长度
                                 keepAlive ? "Connection: Keep-Alive\r\n" : "",
                                 content_type_str);   //设置发送数据

    send(userSock,&headBuf[4], kk - 4, 0);      //向客户端发送HTTP应答
    if(m_method != HTTP_HEAD)   send(userSock,userData,glen,0); //向客户端发数据

    if(errbuf)  free(errbuf);  //释放本函数所申请的所有内存
    free(url_ptr);
    free(binary_buf);
    free(httphead_buf);
}

//线程函数,用来接收浏览器或其它客户端发送的数据
unsigned long _stdcall user_request_thread(void *lpParameter)
{
    #define MAX_REV_SIZE  (2048 + 2)
    YSLOT *pSlot = (YSLOT*)lpParameter;  //槽口指针,表示服务器和客户端的每次连接

    char *headBuf = NULL;      //接收数据缓冲区
    int rev_cnt,rev_len = 0;

    while(1)
      {
        for(rev_cnt=0; ; )  //数据长度未知,可分多次接收
          {
            if(rev_cnt + MAX_REV_SIZE > rev_len)
              {
                if(!YC_realloc(&headBuf, rev_len+MAX_REV_SIZE))  goto  tr_p_err;
                rev_len += MAX_REV_SIZE;     //申请缓冲区内存
              }

            int kk = recv(pSlot->userSock,&headBuf[rev_cnt],MAX_REV_SIZE-2,0);
            if(kk<=0 || pSlot->servc->server_stop)   break; //无数据或用户终止程序时

            rev_cnt += kk;
            headBuf[rev_cnt] = 0;

            int postLen = YCWEB->http_get_content_length(headBuf);
            if(postLen>0 && postLen != rev_cnt -
                         (strstr(headBuf,"\r\n\r\n") - headBuf + 4))  continue;

            if(kk < MAX_REV_SIZE-2)   break;   //数据已接收完毕,退出循环
          }

        if(rev_cnt<=0 || pSlot->servc->server_stop)  break;  //退出线程
        int keepAlive = YCWEB->http_get_keep_alive(headBuf); //是否保持长连接

        if(rev_len-rev_cnt < MAX_REV_SIZE/2) //保证headBuf的后MAX_REV_SIZE/2字节可用
          {
            if(!YC_realloc(&headBuf, rev_cnt + MAX_REV_SIZE/2))  goto  tr_p_err;
            rev_len = rev_cnt + MAX_REV_SIZE/2;
          }

        //处理HTTP请求。目前,忽略其它请求
        response_proc(pSlot->userSock,headBuf,rev_cnt,
                          MAX_REV_SIZE/2,keepAlive,&pSlot->servc->server_stop);

        if(pSlot->servc->user_callback)  //若有回调函数,则执行它
                          pSlot->servc->user_callback(pSlot->userSock,headBuf);
        if(!keepAlive || pSlot->servc->server_stop)  break;    //短连接或用户终止
      }

    tr_p_err:
    free(headBuf);   //释放接收数据缓冲区所占内存

    pSlot->servc->slot_delete(pSlot); //删除槽口对象
    return 0;
}

//在双向链表pHead中插入pLink
template<class T>
void yc_add_link(T *pLink,T *&pHead)
{
    if(!pHead)  pHead = pLink->pLast = pLink;
    pLink->pLast = pHead->pLast;
    pLink->pNext = pHead;
    pHead->pLast->pNext = pLink;
    pHead->pLast = pLink;
}

//在双向链表pHead中删除pLink
template<class T>
void yc_delete_link(T *pLink,T *&pHead)
{
    pLink->pLast->pNext = pLink->pNext;
    pLink->pNext->pLast = pLink->pLast;
    if(pHead == pLink)
      {
        pHead = pLink->pNext;
        if(pHead == pLink)   pHead = nullptr;
      }
}

//用函数pFunc遍历执行双向链表pHead
template<class T>
void yc_run_link(T *pHead,void (*pFunc)(T*))
{
    if(!pHead)   return;
    T *pLink = pHead;
    do{
        pFunc(pLink);
        pLink = pLink->pNext;
      } while(pLink != pHead);
}

struct YSLOT       //槽口对象结构,每次连接都创建一个槽口对象,所有槽口对象形成一个双向链表
{
        YSLOT  *pNext;           //下一个槽口对象执针
        YSLOT  *pLast;           //上一个槽口对象执针
        int    userSock;         //本槽口的套接字
        struct YSERVER  *servc;  //服务器对象指针
        void   *slotHandle;      //本槽口线程的句柄
};

struct YSERVER        //服务器对象
{
        serverCall user_callback;   //用户回调函数指针
        int server_stop;            //用户终止程序标志
        int listenSock;             //服务器侦听套接字

        YSERVER(int port=80,serverCall aFunc=null) : pHead(null),
                                                     slot_count(0),
                                                     user_callback(aFunc),
                                                     m_port(port)
          {
            listenSock = socket(AF_INET,SOCK_STREAM,0);  //创建侦听套接字
            if(listenSock == INVALID_SOCKET)  return;

            InitializeCriticalSection(&server_cris);     //初始化线程锁数据
            server_exit = server_stop = 0;               //程序终止标志
            srvHandle = NULL;                            //服务器侦听线程句柄

            sockaddr_in saddr;
            saddr.sin_family = AF_INET;
            saddr.sin_addr.s_addr = htonl(INADDR_ANY);
            saddr.sin_port = htons(m_port);          //服务器端口,默认为80

            if(bind(listenSock,(sockaddr*)&saddr,sizeof sockaddr)==SOCKET_ERROR)
              { 
                svr_err:   //若绑定或侦听设置失败,则退出
                closesocket(listenSock);   
                listenSock = INVALID_SOCKET;
                return;
              }
            if(listen(listenSock,100) == SOCKET_ERROR)    goto  svr_err;

            srvHandle = CreateThread(null,   //创建侦听线程
                                     0,
                                 [](void *lpParameter)  //侦听线程lambda函数
                                 {
                                   YSERVER *svc = (YSERVER*)lpParameter;
                                   while(1)
                                     {
                                       int newSok = accept(svc->listenSock,0,0);
                                       if(svc->server_stop) //判断服务器终止标志
                                         {
                                           closesocket(newSok); //关闭客户端连接
                                           break;   //退出侦听线程
                                         }
                                       svc->slot_add(newSok);   //处理新增连接
                                     }
                                   CloseHandle(svc->srvHandle);  //释放侦听句柄
                                   closesocket(svc->listenSock); //关闭侦听连接
                                   svc->server_exit = 1;   //设置服务器退出标志
                                   return (unsigned long)0;
                                 },
                                     this,   //传给侦听线程函数的服务器对象指针
                                     0,
                                     null);
          }

        ~YSERVER()
          {
            if(listenSock == INVALID_SOCKET)  return;
            server_stop = 1;

            //此时,阻塞函数accept()仍在执行中,需用下列代码退出accept()。
            int endSock = socket(AF_INET,SOCK_STREAM,0);
            sockaddr_in saddr;
            saddr.sin_family = AF_INET;
            saddr.sin_port = htons(m_port);
            saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
            connect(endSock,(sockaddr*)&saddr,sizeof(sockaddr));
            closesocket(endSock);
            while(!server_exit);    //等待accept()函数的退出

            server_lock();          //加锁,阻止其它线程同时修改链表
            yc_run_link(pHead,[](YSLOT *pLink)  //遍历执行双向链表的每一项
                               {
                                 closesocket(pLink->userSock);
                                 pLink->userSock = INVALID_SOCKET;
                               });
            server_unlock();       //解锁,允许其它线程修改链表
            while(pHead);          //等待双向链表的全部销毁

            DeleteCriticalSection(&server_cris);   //删除线程锁数据所占资源
          }

        void _stdcall slot_add(int newSock)   //增加一个槽口
          {
            if(newSock == INVALID_SOCKET)   return;
            YSLOT *pLink = (YSLOT*)malloc(sizeof YSLOT); //为槽口对象申请内存
            if(!pLink)  return;

            server_lock();             //加锁,阻止其它线程修改链表
            yc_add_link(pLink,pHead);  //将新增槽口对象(pLink)加入双向链表
            slot_count++;              //槽口计数增1
            server_unlock();           //解锁,允许其它线程修改链表

            pLink->userSock = newSock; //设置槽口套接字成员
            pLink->servc = this;       //设置槽口所属的服务器对象之指针
            //创建槽口数据接收线程
            pLink->slotHandle = CreateThread(0,0,user_request_thread,pLink,0,0);
          }

        void _stdcall slot_delete(YSLOT *pLink)   //删除一个槽口
          {
            server_lock();      //加锁,阻止其它线程同时修改链表
            slot_count--;       //槽口计数减1
            yc_delete_link(pLink,pHead);  //将槽口对象(pLink)从双向链表中删除
            server_unlock();              //解锁,允许其它线程修改链表

            closesocket(pLink->userSock); //关闭本槽口的服务器和客户端之间的连接
            CloseHandle(pLink->slotHandle);   //释放槽口线程句柄
            free(pLink);                      //释放槽口对象所占内存
          }

        void server_lock()   //加锁函数
          {
            EnterCriticalSection(&server_cris);
          }
        void server_unlock()  //解锁函数
          {
            LeaveCriticalSection(&server_cris);
          }
        int slot_number()     //获得槽口数,即连接数
          {
            return slot_count;
          }

        private:
        YSLOT          *pHead;          //双向链表头
        unsigned short m_port;          //服务器端口号
        void           *srvHandle;      //服务器侦听线程句柄
        int            slot_count;      //槽口计数器
        int            server_exit;     //服务器退出标志
        CRITICAL_SECTION  server_cris;  //线程同步锁结构数据
};

YSERVER *gServer;  //服务器对象指针

int ychttpCLASS::check_server()    //检查服务器是否已启动
{
    return gServer != NULL;
}

int ychttpCLASS::slot_number()    //获得服务器连接数
{
    if(!gServer)  return 0;
    return gServer->slot_number();
}

void ychttpCLASS::start_server(int port,serverCall aFunc) //启动服务器
{
    if(gServer)  return;
    gServer = new YSERVER(port,aFunc);
    if(gServer && gServer->listenSock==INVALID_SOCKET)  stop_server();
}

void ychttpCLASS::stop_server()   //终止服务器
{
    if(!gServer)  return;
    delete gServer;
    gServer = NULL;
}

                        代码文件:ychttp.cpp

编译:用YC命令:ycc ychttp.cpp 生成 ychttp.exe
运行:在cmd界面执行ychttp.exe后,输出下列文字:

    Web服务器启动成功! port=81
    --------------press ‘Q’ to exit-----------

    在浏览器地址栏输入: http://127.0.0.1:81/
    可浏览文件

输入文件名:

    在提示“输入文件名:”后,键入一些字符后按Enter键,
    如果键入的是文本文件,将在对话框中显示其内容;
    如果键入的是网址,则显示其页面。

    在Chrome浏览器的地址栏输入 http://127.0.0.1:81 后,浏览器将进入一个研究生管理系统界面。
    YC服务器中,ychttp.cpp被ycs.htm调用,在界面上能够显示各种HTTP请求应答信息,这有助于调式分析web服务程序、观察浏览器向服务器发送的信息格式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值