unix网络编程2 读写函数介绍
2015-04-09 17:31
435 查看
Inroduction
这一节首先介绍必要的基础知识,比如网络字节序和主机字节序,套接字地址结构,然后详细叙述各个套接字api,最后给出一个线程安全的读写函数,读写函数非常重要,其中还会写一个带缓冲的读函数,用于处理文本行,减少上下文切换。基本函数介绍
ipv4套接字介绍(这里没把套接字结构里面所有内容写出来)#include<netinet/in.h> struct sockaddr{ sa_famliy_t sin_family;//协议族,ipv4是AF_INET in_port_t sin_port;//端口号 struct in_addr sin_addr;//网络字节序32位ip地址 }
套接字地址有很多种如下图
不仅仅是ipv4套接字地址结构,还有ipv6,unix域等,每一种协议的地址结构是不同的,后面使用的套接字函数如bind是ANSI C之前定义的,以前是定义的通用套接字结构,为了使用这些函数,必须进行强制类型转换。
通用套接字地址结构
struct sockaddr{ sa_family_t sin_family; }
字节排序函数
#include<netinet/in.h> 下面两个函数为主机字节序转网络字节序 htons 16位 htonl 32位
void bzero(void *dest,size_t nbytes); 目标字符串指定数目字节置为0,常用来把套接字地址结构置为0
I/O包
下面介绍读写函数,上一节介绍了应用进程的缓冲区可以无限,但是在TCP层的缓冲区是由对端告知的有限大小,所以read和write会返回不足值所以网络数据可能会反复使用read,write,下面会介绍一个健壮I/O包解决多次多写,而且这些函数是线程安全的。首先介绍基本读写函数
注意ssize_t与size_t的区别 ssize_t 被定义为int有符号 size_t 被定义为unsigned int 无符号整型 #include<unistd.h> 从fd描述符关联的文件读数据到buf 返回值:成功返回读的字节数,若是EOF返回0(没有数据可读了),出错-1 ssize_t read(int fd,void *buf,size_t n); ssize_t write(int fd,const void *buf,size_t n); 从buf写n个字节到fd关联的文件 返回值:成功返回写的字节数,出错-1
无缓冲区输入输出函数
直接在存储器和文件之间交换数据,没有缓冲。把2进制数据写到网络很有用
下面这个读函数在网络数据很有用,对于一个很大的数据可以反复调用read函数,而且出现中断返回,每个函数手动重启read
ssize_t rio_readn(int fd,void *buf,size_t n){ size_t nleft;//剩下多少没有读完的数据 char *pbuf; nleft = n; pbuf = buf;//用来指示当前读到缓冲区哪一个字节的数据 while(nleft > 0){ if((nread = read(fd,buf,n))< 0){ if(errno == EINTR)//中断 nread = 0 //重新再读 else return -1; }else if(nread == 0)//没数据读了这种情况造成不足值 break; pbuf += nread; nleft -= nread; } return (n-nleft);//返回读了的多少字节的数据 }
ssize_t rio_written(int fd,const void *buf,size_t n){ size_t nleft = n; char *pbuf = buf; while(nleft > 0){ if((nwritten = write(fd,buf,n)) < 0 ){ if(errno == EINTR) nwritten = 0; else return -1; } pbuf += nwritten; nleft -= nwritten; } return n; }
带缓冲的读函数
这样做的目的减少read的反复读,减少陷入内核,上下文切换的开销
下面一读函数是缓冲的,它从fd出读10240字节(1024的倍数)到缓冲区。这样做我们需要定义一个结构,如下
#define RIO_BUFSIZE 10240 typedef struct { int rio_fd;//用来关联的文件描述符 int rio_cnt;//内部缓冲区中剩余字节数 char rio_buf[RIO_BUFSIZE];//内部缓冲区 char *rio_bufp;//如果内部缓冲区已经读了一部分那么rio_bufp的作用就是指向已读完的字节的下一个 }rio_t
初始化函数
用来把文件描述符和上面的结构联系起来,并初始化
void rio_readinit(rio_t *rp,int fd){ rp->rio_fd = fd; rp->cnt = 0; rp->rio_bufp = rp->rio_buf; }
带缓冲的read函数
ssize_t rio_read(rio_t *rp,char *buf,size_t n){ int cnt; //缓冲 4000 区没数据就调用读函数往缓冲区读数据 while(rp->rio_cnt <=0){ if((rp->rio_cnt = read(rp->rio_fd,rp- >rio_buf,sizeof(rp->rio_buf)))<0){ if(errno != EINTR) return -1; }else if(rp->rio_cnt == 0){//没数据读了EOF return 0; }else rp->rio_bufp = rp->rio_buf;//每一次重新填充缓冲区,就把rio_bufp置"0" } cnt = n; if(rp->rio_cnt < n) cnt = rp->rio_cnt; memcpy(buf,rp->rio_bufp,cnt); rp->rio_bufp += cnt; rio_cnt -= cnt; return cnt; }
带缓冲区的readn函数
ssize_t readnb(rio_t *rp,void *buf,size_t n){ size_t nleft = n; ssize_t nread; char *bufp = buf; while(nleft > 0){ if((nread = rio_read(rp->fd,buf,n)) < 0){ if(errno == EINTR) nread = 0; }else if(nread == 0){ break; } bufp += nread; nleft -= nread; } return (n-nleft); }
带缓冲的readline
void readlineb(rio_t *rp,void *buf,size_t maxlen){ ssize_t rc;//读一个字节 char c ,*bufp = buf for(n = 1; n < maxlen ;n++){ if((rc = rio_read(rp->fd,&c,1)) == 1){ *bufp++ = c; if(c == '\n') break; }else if(rc == 0){ if(n == 1) return 0;//没有数据读 else break;//读到数据,遇到EOF }else return -1; } *bufp = 0; return n; }