一個簡單http_server的實現(1)

我的博客:startcraft.cn
最近在學習網絡編程的知識,所以準備實現一個http服務器,寫博客來記錄一下學習的過程

RIO

RIO是csapp中提到的一個健壯的I/O包,爲什麼要使用這個來進行文件的讀寫,是因爲接下來的socket編程中不適合使用c標準I/O

先貼出Rio包的實現

#ifndef __RIO_H__
#define __RIO_H__
#include <http_server.h> 
#define RIO_BUFSIZE 8192//緩衝區的大小
struct rio_t
{
    int rio_fd;//緩衝區對應的文件描述符
    int rio_cnt;//緩衝區中的元素個數
    char * rio_bufptr;//緩衝區頭指針
    char rio_buf [RIO_BUFSIZE];//緩衝區
    rio_t (int fd):rio_fd (fd),rio_cnt (0),rio_bufptr(rio_buf){}
};
class Rio 
{
    public:
        //從文件中讀取n個字節,存入usrbuf,讀到EOF返回0,出錯返回-1,正常返回讀入的字節數
        ssize_t rio_readn (int fd,void *usrbuf,size_t n);
        //將usrbuf中的n個字節寫入文件fd中
        ssize_t rio_writen (int fd,void *usrbuf,size_t n);
        //初始化緩衝區
        void rio_readinitb (rio_t * rp,int fd);
        //從緩衝區讀取一行文本,最多讀取maxlen的字節,超出部分截斷用'\0'結尾
        ssize_t rio_readlineb (rio_t * rp,void * usrbuf,size_t maxlen);
        //從緩衝區讀n個字節
        ssize_t rio_readnb (rio_t * rp,void *usrbuf,size_t n);

    private:
        //維護緩衝區,爲系統read的帶緩衝區版本
        ssize_t rio_read (rio_t *rp,char *usrbuf,size_t);

};
#endif //__RIO_H__

rio_readn函數和rio_writen函數和linux底層的read和write函數很像,不過處理了EINTR的錯誤,當read和write在阻塞時被某個信號中斷,這時沒有字節被讀取/寫入,系統會將錯誤代碼置爲EINTR
顯然這個錯誤是可以修復的,當產生這個錯誤時我們只需要繼續調用read和write即可

#include "rio.h" 
ssize_t Rio::rio_readn (int fd,void * usrbuf,size_t n)
{
    size_t isleft=n;
    size_t isread;
    char *buf=static_cast<char*>(usrbuf);
    while (isleft>0)
    {   
        if ((isread=read (fd,buf,isleft))<0)
        {
            if (errno==EINTR)
                isread=0;
            else
                return -1;//讀取出錯
        }else if (isread==0)
            break;
        isleft-=isread;
        buf+=isread;
    }   
    return (n-isleft);
}
ssize_t Rio::rio_writen (int fd,void *usrbuf ,size_t n)
{
    size_t isleft=n;
    size_t iswrite;
    char *buf=static_cast<char*>(usrbuf);
    while (isleft>0)
    {   
        if ((iswrite=write(fd,buf,isleft))<=0)
        {
            if (errno==EINTR)
                iswrite=0;
            else
                return -1;//寫入出錯                                                              
        }
        isleft-=iswrite;
        buf+=iswrite;
    }   
    return n;
}

緩衝區是爲了優化I/O速度,因爲底層I/O要切換到內核態進行,切換過程開銷很大
rio_t就是緩衝區的結構,rio_readinitb是將緩衝區與打開的文件關聯上
rio_readnb函數就是read的帶緩衝區版本
rio_readlineb是一次讀取一行,這兩個函數的核心是rio_read函數
rio_read函數是來維護緩衝區的,當緩衝區爲空時它會將緩衝區填滿,如果剩餘未讀取數據不夠將緩衝區填滿返回填入的字節數,只要緩衝區不爲空就將指定的n與緩衝區剩餘字節中小的那個值的字節數拷貝給用戶緩衝區

...
ssize_t Rio::rio_read (rio_t *rp,char *usrbuf,size_t n)
{   
    int cnt;
    while (rp->rio_cnt<=0)
    {
        //填滿緩衝區
        rp->rio_cnt=read (rp->rio_fd,rp->rio_buf,sizeof (rp->rio_buf));
        if (rp->rio_cnt<0)
        {
            if (errno!=EINTR)
                return -1;//讀取出錯
        }else if (rp->rio_cnt==0)//EOF
                return 0;
        else
            rp->rio_bufptr=rp->rio_buf;//重置緩衝區指針
    }
    cnt=std::min (n,(size_t)rp->rio_cnt);
    memcpy (usrbuf,rp->rio_bufptr,cnt);
    rp->rio_cnt-=cnt;
    rp->rio_bufptr+=cnt;
    return cnt;
}
ssize_t Rio::rio_readlineb (rio_t* rp,void *usrbuf,size_t maxlen)
{   
    int isread;
    int i;
    char c,*buf=static_cast<char*> (usrbuf);
    for (i=0;i<maxlen;++i)
    {
        if ((isread=rio_read (rp,&c,1))==1)
        {
            *buf++=c;
            if (c=='\n')
                break;
        }else if (isread==0)
        {
            if (i==0)
                return 0;//EOF,且未讀入任何數據
            else
                break;
        }else
            return -1;
    }
    *buf='\0';
    return i+1;
}
ssize_t Rio::rio_readnb (rio_t* rp,void *usrbuf,size_t n)
{
    int isleft=n;
    int isread;
    char *buf=static_cast<char*> (usrbuf);
    while(isleft>0)
    {
        if ((isread=rio_read(rp,buf,n))<0)
            return -1;
        else if (isread==0)//EOF
            break;
        isleft-=isread;
        buf+=isread;
    }
    return (n-isleft);
}

RIO同時是線程安全的,因爲它給每一個文件配置了一個緩衝區,當不同線程讀取不同文件的時候是線程安全的,當然當不同線程讀取同一個文件還是會出問題,這就要用鎖機制了
代碼會同步更新再github

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章