首页 > 代码库 > Linux网络编程(附1)——封装read、write
Linux网络编程(附1)——封装read、write
原打算实践简单的模型的时候,主要专注于基本的模型,先用UNIX I/O糊弄下,可是未封装的read和write用起来实在心累,还是直接用前辈们已经实现好的高级版本号read、write。
UNIX I/O read、write
#include<unistd.h> ssize_t read(int fd, void* buf, size_t n); 若成功则为读的字节数,若EOF则为0,若出错则为-1。 ssize_t write(int fd, const void* buf, size_t n); 若成功则为写的字节数。若出错则为-1buf被拷贝的位置,n最大拷贝的字节数,fd描写叙述符
在某些情况下,read和write因为各种原因会传送比预估要少的字节数(short count)。可是有些时候这个并非因为错误造成的,还可能因为下面几个原因。
1、读时遇到EOF: 如果我们准备读一个文件,该文件从当前文件位置開始仅仅含有30个字节,而我们以60个字节的片进行读取(普通情况下反复调用read、write传入的n固定)。
这样下来,返回值为30,此后再读的时候通过返回0来发出EOF信号。
2、从终端读,每一个read函数将以此传送一个文本行。(此时会不断的返回,然后不断再次调用,封装read后能够直接智能处理这样的情况)。
3、读和写网络套接字(socket)。
内部缓冲约束、和网络延时(上面红色部分提到)会影响read和write提前返回。
非网络情况下。直接读本地文件,read、和write基本上不会遇到非EOf的(short count)情况。
高级I/O
非缓冲版本号(并非没有内核缓冲、是没有内核缓冲之上的二级缓冲)
这些函数直接在存储器和文件之间传送数据,没有应用级缓冲。它们对将二进制数据读写到网络和从网路读写二进制数据尤事实上用。
readn
ssize_t readn(int fd,void *usrbuf,size_t n) { size_t nleft = n; ssize_t nread; char *bufp = usrbuf; while(nleft > 0){ if((nread = read(fd,bufp,nleft)) < 0){ if(errno == EINTR){/*interrupted by sig handler return*/ nread = 0; }else{ return -1;/*error*/ } }else if(nread == 0){ break; /*EOF*/ }else{/*read content*/ nleft -= nread; bufp += nread; } } return (n - nleft); }writen
ssize_t rio_writen(int fd,void *usrbuf,size_t n) { size_t nleft = n; ssize_t nwritten; char *bufp = usrbuf; while(nwritten = write(fd,bufp,nleft) <= 0){ if(errno == EINTR){ nwritten = 0; }else{ return -1; } nleft -= nwritten; bufp += nwritten; } return n; }
作为工具函数、详细实践就临时不做了,反正以后会经经常使用到。
注意:writen的返回值事实上是固定的,每次为n。可是read不固定。
原因:简单理解 远端write --->远端计算机(内核缓冲) ----------(网络传输中)----------->本地计算机(内核缓冲)--------->read
上面能够看出,read是读远端的内容,所以不能确定究竟要读多少。可是write是往本地写的,应用程序知道要写多少数据。即便是一次没有写完。也能够通过重复调用写完心目中的数据。
带缓冲版本号(内核缓冲之上)
此类函数同意高效地从二进制文中读文本行和二进制数据,这些文件缓存在应用级缓存内。类似于像printf这种标准I/O函数提供的缓冲区。
缓冲区结构体(事实上就是一些信息加一个大的数组)
#define RIO_BUFSIZE 8192 typedef struct{ int rio_fd; /*To operate the file descriptor*/ int rio_cnt;/*unread bytes in internal buf*/ char *rio_bufptr;/*next unread byte int internal buf*/ char rio_buf[RIO_BUFSIZE];/*internal buf*/ }rio_t;初始化缓冲区(事实上就是将缓冲区和网络描写叙述符fd联系起来)
rio_init
没打开一个文件描写叙述符都会调用一次rio_init
void rio_readinitb(rio_t *rp,int fd) { rp->rio_fd = fd; rp->rio_cnt = 0; rp->rio_bufptr = rp->rio_buf; }
rio_read(带缓冲版本号的read, 和unix read(未封装的)具有一模一样的效果)
static ssize_t rio_read(rio_t *rp,char *usrbuf,size_t n) { int cnt; while(rp->rio_cnt <= 0){/*Read the file content if buf is empty*/ 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 {/*reset buf ptr*/ rp->rio_bufptr = rp->rio_buf; } } /*when n < rp->rio_cnt, need copy some times */ cnt = n; if(rp->rio_cnt < n){/*one time copy end*/ cnt = rp->rio_cnt; } memcpy(usrbuf,rp->rio_bufptr,cnt); rp->rio_bufptr += cnt; rp->rio_cnt -= cnt; return cnt; }rio_readlineb
每次第一行,最多读maxlen-1,最后一个字留给空字符,超过maxlen-1的话将被截断,并用空字符结束
ssize_t rio_readlineb(rio_t *rp, void *usrbuf,size_t maxlen) { int n,rc; char c,*bufp = usrbuf; for(n = 1; n < maxlen; n++){ if (( rc = rio_read(rp,&c,1)) == 1){ *bufp++ = c; if(c == ‘\n‘){ break; } }else if (rc == 0){ if(n == 1){/*EOF no data read*/ return 0; }else{/*EOF some data read*/ break; } }else{/*ERROR*/ return -1; } } *bufp = 0;/*string end sign :‘\0‘*/ return n; }rio_readnb(带缓冲版本号的readn)
ssize_t rio_readnb(rio_t *rp,void *usrbuf,size_t n) { size_t nleft = n; ssize_t nread; char *bufp = usrbuf; while(nleft > 0){ if((nread = rio_read(rp,bufp, nleft)) < 0){ if(errno == EINTR){/*interrupted by sig handler return*/ nread =0; }else{/*errno set by read() */ return -1; } }else if(nread == 0){/*EOF*/ break; } nleft -= nread; bufp += nread; } return (n-nleft);/*return >=0*/ }
Linux网络编程(附1)——封装read、write