抽空实现了一下CSAPP中实现的所谓RIO包,作为基础函数为复杂的程序做准备。
总共包含六个函数,除去静态的rio_read外,其他五个如下:
1 ssize_t rio_readn(int fd, void *buf, size_t n); 2 ssize_t rio_writen(int fd, void *buf, size_t n); 3 void rio_readinitb(rio_t *rp, int fd); 4 ssize_t rio_readlineb(rio_t *rp, void *buf, size_t n); 5 ssize_t rio_readnb(rio_t *rp, void *buf, size_t n);
后缀带n的表示读取或者写入n个字节,b代表包含自己的buffer。
头文件如下:
1 #ifndef _RIO_H_ 2 #define _RIO_H_ 3 4 #define MAXLEN 8192 5 6 typedef struct 7 { 8 int rio_fd; 9 int rio_cnt; 10 char *rio_bufptr; 11 char rio_buf[MAXLEN]; 12 }rio_t; 13 14 ssize_t rio_readn(int fd, void *buf, size_t n); 15 ssize_t rio_writen(int fd, void *buf, size_t n); 16 17 void rio_readinitb(rio_t *rp, int fd); 18 ssize_t rio_readlineb(rio_t *rp, void *buf, size_t n); 19 ssize_t rio_readnb(rio_t *rp, void *buf, size_t n); 20 21 #endif
其中结构体rio_t定义如下:
1 typedef struct 2 { 3 int rio_fd; 4 int rio_cnt; 5 char *rio_bufptr; 6 char rio_buf[MAXLEN]; 7 }rio_t;
rio_fd为待操作的fd,由函数rio_readinitb初始化。
rio_cnt表示当前内部缓冲区未读字节数。
rio_bufptr为下一个未读字符指针。
rio_buf为内部维护的缓冲区。
1.rio_readn
1 ssize_t rio_readn(int fd, void *buf, size_t n) 2 { 3 ssize_t nread; 4 size_t nleft = n; 5 char *bp = (char *)buf; 6 7 while(nleft > 0) 8 { 9 if((nread = read(fd, bp, nleft)) < 0) 10 { 11 if(errno == EINTR) 12 nread = 0; 13 else 14 return -1; 15 } 16 else if(nread == 0) 17 break; 18 nleft -= nread; 19 bp += nread; 20 } 21 return (n - nleft); 22 }
rio_readn的实现和经典的UNP的实现类似,在内部维持一个待读字节变量nleft和当前写入位置变量bp,循环读取直至nleft变为0或者无数据可读或者出错为止。并加入对信号处理中断而产生的EINTR进行处理。
2.rio_writen
rio_writen实现如下,内部和rio_readn的思想基本一样,只是write调用返回0也认为是错误:
1 ssize_t rio_writen(int fd, void *buf, size_t n) 2 { 3 ssize_t nwritten; 4 size_t nleft = n; 5 char *bp = (char *)buf; 6 7 while(nleft > 0) 8 { 9 if((nwritten = write(fd, bp, nleft)) <= 0) 10 { 11 if(errno == EINTR) 12 nwritten = 0; 13 else 14 return -1; 15 } 16 nleft -= nwritten; 17 bp += nwritten; 18 } 19 return n; 20 }
3.rio_readinitb
1 void rio_readinitb(rio_t *rp, int fd) 2 { 3 rp->rio_fd = fd; 4 rp->rio_cnt = 0; 5 rp->rio_bufptr = rp->rio_buf; 6 }
顾名思义,该函数负责对rp所指向的结构体初始化。
4.rio_read
1 static ssize_t rio_read(rio_t *rp, void *buf, size_t n) 2 { 3 ssize_t cnt; 4 while(rp->rio_cnt <= 0) 5 { 6 rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf)); 7 if(rp->rio_cnt < 0) 8 { 9 if(errno != EINTR) 10 return -1; 11 } 12 else if(rp->rio_cnt == 0) 13 return 0; 14 else 15 rp->rio_bufptr = rp->rio_buf; 16 } 17 18 cnt = n; 19 if(rp->rio_cnt < n) 20 cnt = rp->rio_cnt; 21 memcpy(buf, rp->rio_bufptr, cnt); 22 rp->rio_cnt -= cnt; 23 rp->rio_bufptr += cnt; 24 return cnt; 25 }
static意味着该函数只在该文件可见。(?)
函数首先判断结构体中的缓冲区是否为空,若为空则调用read写缓冲区,并记录读取的字节数rio_cnt,指针位置rio_bufptr。每次调用该函数时若缓冲区为空且读取到数据,则重置指针位置。同样也需要处理EINTR错误。
缓冲区填充完毕后判断所需的字节和所读取的字节中较小值作为变量cnt,然后调用memcpy将内部缓冲区写cnt个字节到参数中所指的缓冲区中。然后相应调整内部未读字节数和内部指针,返回cnt已读字节数(即copy到用户给定缓冲区的字节数)。
5.rio_readlineb
1 ssize_t rio_readlineb(rio_t *rp, void *buf, size_t n) 2 { 3 size_t i, ret; 4 char c; 5 char *bp = (char *)buf; 6 for(i = 1; i < n; ++i) 7 { 8 if((ret = rio_read(rp, &c, 1)) == 1) 9 { 10 *bp++ = c; 11 if(c == '\n') 12 break; 13 } 14 else if(ret == 0) 15 { 16 if(i == 1) 17 return 0; 18 else break; 19 } 20 else 21 return -1; 22 } 23 *bp = '\0'; 24 return i; 25 }
由于所谓的“readline”的实现需要每次读取一个字节并和‘\n’进行比对,若每次都需要调用read则开销太大。因此可以借用上面实现的rio_read来减少对read的调用。
rio_read每次会填写自己的缓冲区,而每次即使读取一个字节也仅仅是调用memcpy写入一个字节然后调整两个位置变量。
当次数在1~MAXLEN - 1时重复调用rio_read读取一个字符写入buf并何'\n'进行比较,若找到一个,则认为已经读取一行,跳出循环。
若rio_read返回0,意味无数据可读,分两个情况,在第一次返回0意味着没有读取到任何数据,直接返回0。另一种则是读取到EOF,则直接跳出循环。
跳出循环后需要将*bp所指赋值为0,即手动添加上结束标志'\0'。返回循环的次数即读取到的字节数i。
6.rio_readnb
1 ssize_t rio_readnb(rio_t *rp, void *buf, size_t n) 2 { 3 ssize_t nread; 4 size_t nleft = n; 5 char *bp = (char *)buf; 6 7 while(nleft > 0) 8 { 9 if((nread = rio_read(rp, bp ,nleft)) < 0) 10 { 11 if(errno == EINTR) 12 nread = 0; 13 else 14 return -1; 15 } 16 else if(nread == 0) 17 break; 18 nleft -= nread; 19 bp += nread; 20 } 21 return (n - nleft); 22 }
rio_readn的带buffer版本,仅仅将read改为rio_read即可。
最后加上一个echo测试小例子:
1 #include <unistd.h> 2 #include <sys/socket.h> 3 #include <arpa/inet.h> 4 #include <netinet/in.h> 5 #include <errno.h> 6 #include <string.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 10 #include "rio.h" 11 12 #define SA struct sockaddr 13 14 int main(void) 15 { 16 int listenfd, connfd; 17 socklen_t clilen; 18 struct sockaddr_in servaddr ,cliaddr; 19 int ret; 20 rio_t rio; 21 char buffer[2048]; 22 23 if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) <= 0) 24 { 25 perror("SOCKET"); 26 return -1; 27 } 28 memset(&servaddr, 0, sizeof(servaddr)); 29 servaddr.sin_family = AF_INET; 30 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 31 servaddr.sin_port = htons(4321); 32 33 if((ret = bind(listenfd, (SA *)&servaddr, sizeof(servaddr))) < 0) 34 { 35 perror("BIND"); 36 return -1; 37 } 38 39 if((ret = listen(listenfd, 24)) < 0) 40 { 41 perror("LISTEN"); 42 return -1; 43 } 44 45 for(;;) 46 { 47 clilen = sizeof(cliaddr); 48 if((connfd = accept(listenfd, (SA *)&cliaddr, &clilen)) < 0) 49 { 50 perror("ACCEPT"); 51 return -1; 52 } 53 rio_readinitb(&rio, connfd); 54 while((ret = rio_readlineb(&rio, buffer, sizeof(buffer))) > 0) 55 rio_writen(connfd, buffer, ret); 56 } 57 return 0; 58 }