参考:《UNIX 网络编程 · 卷1 : 套接字联网API》
一旦,我们建立好了 TCP 连接之后,我们就可以把得到的 fd 当作文件描述符来使用。由此网络程序里最基本的函数就是 read 和 write 函数了。其定义如下:
#include
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
write 函数将 buf 中的 count 字节内容写入文件描述符 fd。成功时返回写的字节数。失败时返回 -1。并设置 errno 变量。
在网络程序中,当我们向套接字文件描述符写时有两可能:
为了处理以上的情况,我们自己编写一个写函数来处理这几种情况。
int my_write(int fd, void *buffer, int length)
{
int bytes_left;
int written_bytes;
char *ptr;
ptr = buffer;
bytes_left = length;
while (bytes_left > 0)
{
written_bytes = write(fd, ptr, bytes_left);
if (written_bytes <= 0)
{
if (errno == EINTR)
written_bytes = 0;
else
return (-1);
}
bytes_left -= written_bytes;
ptr += written_bytes;
}
return (0);
}
read 函数是负责从 fd 中读取内容。当读成功时,read 返回实际所读的字节数。
如果返回的值是 0 表示已经读到文件的结束了;小于 0 表示出现了错误;如果错误为 EINTR 说明读是由中断引起的;如果是 ECONNREST 表示网络连接出了问题。
和上面一样,也写一个自己的读函数。
int my_read(int fd, void *buffer, int length)
{
int bytes_left;
int bytes_read;
char *ptr;
bytes_left = length;
while (bytes_left > 0)
{
bytes_read = read(fd, ptr, bytes_left);
if (bytes_read < 0)
{
if (errno == EINTR)
bytes_read = 0;
else
return (-1);
}
else if (bytes_read == 0)
break;
bytes_left -= bytes_read;
ptr += bytes_read;
}
return (length - bytes_left);
}
recv 和 send 函数提供了和 read 和 write 差不多的功能。不过它们提供了第四个参数来控制读写操作。recv 和 send 函数需要一个额外的参数,其定义如下:
#include
#include
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
前面的三个参数和 read、write 一样,第四个参数可以是 0 或者是以下的组合:
参数 | 含义 |
---|---|
MSG_PEEK | 查看数据,并不从系统缓冲区移走数据 |
MSG_OOB | 接受或者发送带外数据 |
MSG_WAITALL | 等待所有数据 |
MSG_DONTWAIT | 仅本操作非阻塞 |
MSG_DONTROUTE | 不查找表 |
MSG_DONTROUTE
:是 send 函数使用的标志。这个标志告诉 IP 目的主机在本地网络上面,没有必要查找表,这个标志一般用网络诊断和路由程序里面。
MSG_OOB
:表示可以接收和发送带外的数据。关于带外数据我们以后会解释的。
MSG_PEEK
:是 recv 函数的使用标志,表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容。这样下次读的时候,仍然是一样的内容。一般在有多个进程读写数据时可以使用这个标志。
MSG_WAITALL
:是 recv 函数的使用标志,表示等到所有的信息到达时才返回。使用这个标志的时候 recv 会一直阻塞,直到指定的条件满足,或者是发生了错误:
当读到了指定的字节时,函数正常返回。返回值等于 len
当读到了文件的结尾时,函数正常返回。返回值小于 len
当操作发生错误时,返回 -1,且设置错误为相应的错误号 (errno)
MSG_DONTWAIT
:在无需打开相应套接字的非阻塞标志下,把单个 IO 操作临时指定为非阻塞,接着执行 I/O 操作,然后关闭非阻塞标志。
flags 参数在设计上存在一个基本问题:它是按值传递的,而不是一个值-结果参数。因此它只能用于从进程向内核传递标志。内核无法向进程传回。但对于 TCP/IP 协议不需要从测内核向进程传回标志。
但是之后的标准却指出了随输入操作向进程返送 MSG_EOR 标志的需求。其做出的决定是保持常用的输入函数 recv 和 recvfrom 参数不变,而改变 recvmsg 和 sendmsg 所用的 msghdr 结构,该结构按引用传递,内核返回时会修改其中的 msg_flags 成员标志,如果一个进程需要内核更新标志,就需要调用 recvmsg,而不是调用 recv 或 recvfrom。
有一种情况就是我们不想读取数据,但想知道一个套接字缓冲区中有多少数据可以读取。有以下单个方法可以用于获得排队的数据量: