最近在学习网络编程的知识,所以准备实现一个http服务器,写博客来记录一下学习的过程
RIO
RIO是csapp中提到的一个健壮的I/O包,为什么要使用这个来进行文件的读写,是因为接下来的socket编程中不适合使用c标准I/O
先贴出Rio包的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #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: ssize_t rio_readn (int fd,void *usrbuf,size_t n); ssize_t rio_writen (int fd,void *usrbuf,size_t n); void rio_readinitb (rio_t * rp,int fd); ssize_t rio_readlineb (rio_t * rp,void * usrbuf,size_t maxlen); ssize_t rio_readnb (rio_t * rp,void *usrbuf,size_t n);
private: ssize_t rio_read (rio_t *rp,char *usrbuf,size_t);
}; #endif
|
rio_readn函数和rio_writen函数和linux底层的read和write函数很像,不过处理了EINTR的错误,当read和write在阻塞时被某个信号中断,这时没有字节被读取/写入,系统会将错误代码置为EINTR
显然这个错误是可以修复的,当产生这个错误时我们只需要继续调用read和write即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #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与缓冲区剩余字节中小的那个值的字节数拷贝给用户缓冲区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| ... 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) 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; 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) break; isleft-=isread; buf+=isread; } return (n-isleft); }
|
RIO同时是线程安全的,因为它给每一个文件配置了一个缓冲区,当不同线程读取不同文件的时候是线程安全的,当然当不同线程读取同一个文件还是会出问题,这就要用锁机制了
代码会同步更新再github