我的博客: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