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;
//缓冲区没数据就调用读函数往缓冲区读数据
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;
}