Socket之read,write函数

在socket中,read,write函数不同于一般的文件IO操作

下面开始分析这两个函数在socket中的行为

write

1
2
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

从函数声明我们可以看到,第一个是一个文件描述符,第二个是一个指向缓存区的指针,第三个则是要写入的数据量

write函数把buf的count字节内容写入文件描述符

成功,就返回写的字节数。失败,返回-1,并设置errno变量

那么就会有两种情况

a)返回值大于0,表示写入了部分或者全部数据,由于有可能一次无法将所有数据写入,因此需要通过返回值来确定剩余数据量

b)返回值小于0,表示出现了错误,我们要根据errno的值进行处理

  • EINTR:写的时候出现了中断错误
  • EPIPE:网络连接出现了问题

因此,普通的write函数无法满足我们的需求,我们需要写一个能够将数据完全写入以及能够处理错误的加强版write函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ssize_t writen(int fd, const void *buf, size_t n) {
size_t nleft; //剩余数据
ssize_t nwritten; //已写入数据,用于移动指针
const const *ptr; //指向buf的指针

ptr = buf;
nleft = n;
while (nleft > 0) {
if ((nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR) {
nwritten = 0;
} else {
return -1;
}
}
nleft -= nwritten;
ptr += nwritten;
}
}

read

1
2
#include <unistd.h>
ssize_t read(int fd,void *buf,size_t count)

read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数

当读成功时,read返回实际所读的字节数

如果返回的值是0,表示已经读到文件的结束了

小于0表示出现了错误.并改写errno

  • EINTR:中断错误
  • ECONNREST:网络连接出了问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ssize_t readn(int fd, void *buf, size_t count) {
size_t nleft; //剩余数据量
ssize_t nread; //已读数据量
char *ptr; //指向缓存区的指针

ptr = buf;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, ptr, nleft)) < 0 ) {
if (errno == EINTR) {
nread = 0;
} else {
return -1;
}
} else if (nread == 0) {
break;
}
nleft -= nread;
ptr += nread;
}
return (n - nleft);
}

socket中的缓冲区与blocking

每个socket被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区

read,write函数都是和缓冲区进行IO互动

然而,缓冲区的大小是有限制的,其大小可以通过getsockopt获取,因此,当缓冲区满了,也就有了中断错误EINTR

缓冲区有以下特性

  • I/O缓冲区在每个TCP套接字中单独存在;
  • I/O缓冲区在创建套接字时自动生成;
  • 即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
  • 关闭套接字将丢失输入缓冲区中的数据

因为read和write的操作都是在缓冲区上进行,因而也有了所谓的阻塞模式(blocking)

试想,当缓冲区中没有数据的时候,read就会阻塞

那么当缓冲区的数据满了,那么write就会阻塞