字节流套接字上的read和write函数所表现的行为不同于通常的文件IO。字节流套接字上调用read和write输入或输出的字节数可能比请求的数量少,因为内核中用于套接字的缓冲区是有限制的,需要调用者多次调用read或write函数。
提示:readn、writen和readline是对read和wirte的封装。
readn(int fd,void *vptr, size_t n)
从描述符fd中读取n个字节,存入vptr指针的位置。思路如下:
- 当剩余长度大于0的时候就一直读啊读
- 当read的返回值小于0的时候,做异常检测
- 当read的返回值等于0的时候,退出循环
- 当read的返回值大于0的时候,拿剩余长度减read的返回值,拿到新的剩余长度,读的入口指针加上read的返回值,进入步骤1
- 返回参数n减去剩余长度,即实际读取的总长度
/* include readn */
#include "unp.h"
ssize_t /* Read "n" bytes from a descriptor. */
readn(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0; /* and call read() again */
else
return(-1);
} else if (nread == 0)
break; /* EOF */
nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}
/* end readn */
ssize_t
Readn(int fd, void *ptr, size_t nbytes)
{
ssize_t n;
if ( (n = readn(fd, ptr, nbytes)) < 0)
err_sys("readn error");
return(n);
}
writen(int fd,const void* vptr, size_t n)
像描述符fd中写入n个字节,从vptr位置开始写。思路如下:
- 当要写入的剩余长度大于0的时候就一直写啊写
- 当write的返回值小于0的时候,做异常检测
- 当write的返回值等于0的时候,出错退出程序
- 当write的返回值大于0的时候,拿剩余长度减去write的返回值,拿到新的剩余长度,写的入口指针加上write的返回值,进入步骤1
- 返回参数n的值,即期望写入的总长度
/* include writen */
#include "unp.h"
ssize_t /* Write "n" bytes to a descriptor. */
writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0; /* and call write() again */
else
return(-1); /* error */
}
nleft -= nwritten;
ptr += nwritten;
}
return(n);
}
/* end writen */
void
Writen(int fd, void *ptr, size_t nbytes)
{
if (writen(fd, ptr, nbytes) != nbytes)
err_sys("writen error");
}
readline(int fd,void *vptr, size_t maxlen)
从描述符fd中读一行文本,长度不超过maxlen,一次读1个字节。思路如下:
- 当读取的次数小于maxlen的时候就一直读啊读
- 当read的返回值等于1的时候,将读到的值赋值给
ptr++
,判断是不是n
- 当read的返回值等于0的时候,读完了
- 当read的返回值小于0的时候,做异常检测
- 进入步骤1
- 返回实际读取的长度
/* include readline */
#include "unp.h"
/* PAINFULLY SLOW VERSION -- example only */
/* 痛苦 慢速版 -- 仅供示例 */
ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
again:
if ( (rc = read(fd, &c, 1)) == 1) {
*ptr++ = c;
if (c == 'n')
break; /* newline is stored, like fgets() */
} else if (rc == 0) {
*ptr = 0;
return(n - 1); /* EOF, n - 1 bytes were read */
} else {
if (errno == EINTR)
goto again;
return(-1); /* error, errno set by read() */
}
}
*ptr = 0; /* null terminate like fgets() */
return(n);
}
/* end readline */
ssize_t
Readline(int fd, void *ptr, size_t maxlen)
{
ssize_t n;
if ( (n = readline(fd, ptr, maxlen)) < 0)
err_sys("readline error");
return(n);
}
readline(int fd,void *vptr, size_t maxlen)
my_read(int fd,char *ptr) 替换 read(fd,&c,1),实现一个较快速版本。思路如下:
- 当读取的次数小于maxlen的时候就一直读啊读
- 进入
my_read
函数,这个函数每次最多读MAXLINE个字符,然后每次返回一个字符 - 将读到的值赋值给
ptr++
,判断是不是n
- 当read的返回值等于0的时候,读完了
- 当read的返回值小于0的时候,做异常检测
- 进入步骤1
- 返回实际读取的长度
/* include readline */
#include "unp.h"
static int read_cnt;
static char *read_ptr;
static char read_buf[MAXLINE];
static ssize_t
my_read(int fd, char *ptr)
{
if (read_cnt <= 0) {
again:
if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
if (errno == EINTR)
goto again;
return(-1);
} else if (read_cnt == 0)
return(0);
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return(1);
}
ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ( (rc = my_read(fd, &c)) == 1) {
*ptr++ = c;
if (c == 'n')
break; /* newline is stored, like fgets() */
} else if (rc == 0) {
*ptr = 0;
return(n - 1); /* EOF, n - 1 bytes were read */
} else
return(-1); /* error, errno set by read() */
}
*ptr = 0; /* null terminate like fgets() */
return(n);
}
ssize_t
readlinebuf(void **vptrptr)
{
if (read_cnt)
*vptrptr = read_ptr;
return(read_cnt);
}
/* end readline */
ssize_t
Readline(int fd, void *ptr, size_t maxlen)
{
ssize_t n;
if ( (n = readline(fd, ptr, maxlen)) < 0)
err_sys("readline error");
return(n);
}
参考文献:《UNIX网络编程 卷1:套接字联网API》