5、数据传输
由于socket末端使用文件描述符号来替代,当被连接起来的时候,我们就可以使用read和write来和socket进行通信了。需要记住的是如果我们使用connect函数的时候设置了默认的通信对等端,那么数据报的socket可以被认为是连接好了的。使用read和write与套接字描述符号进行通信是非常有意义的,因为它以为着我们可以把套接字描述符号传递给本来是用来操作本地文件的函数。我们也可以把套接字描述符号传递给子进程,子进程执行exec另外一个程序而那个程序却不用知道关于套接字的信息。
尽管我们能够使用read和write交换数据,但是这也只是我们使用这两个函数所能做的所有的事情了。如果我们想要指定选项,从多个客户端接受包,或者发送带外的数据,我们需要使用六个特定的socket函数来进行数据传输。
数据传输函数
有三个函数用来发送数据,同时也有三个函数用来接收数据。
发送数据
首先我们来看看发送数据的函数。
send
最简单的就是send,它和write的功能类似,但是允许我们指定标记来改变我们看待被传输的数据的方式。
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
返回:如果成功那么返回发送的字节数目,如果错误则返回1。
和write类似,套接字使用send的时候需要被连接。buf和nbytes参数和write中相应的参数的含义一样。
和write不同的是,send支持第四个参数flags。Single UNIX Specification定义了两个参数,但是一般实现都会定义一些额外的参数。大致如下面列出的,具体哪个系统支持,请参见参考资料,详细这里就不一一列举了。
- MSG_DONTROUTE 不在本地网络之外对包进行路由。
- MSG_DONTWAIT 打开非阻塞操作(与使用O_NONBLOCK等价)。
- MSG_EOR 如果协议支持,那么这个表示记录的结尾。
- MSG_OOB 如果协议支持,那么发送带外数据。
如果send返回成功,并不意味这另一端的进程接收到了数据。所有我们能够保证的只是:当send成功的时候,数据被发送到网络中了,并且没有错误。
对于一个支持消息边界的协议,如果我们尝试发送一个单个的消息,这个消息的大小比协议支持的最大大小要大,那么send将会返回失败并且设置errno为EMSGSIZE。对于一个字节流协议,send将会阻塞,直到整个数据都被传输完毕。
sendto
sendto函数和send函数类似。不同的是,sendto允许我们指定一个无连接套接字使用的目标地址。
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr *destaddr,
socklen_t destlen);
返回:如果成功返回字节数目,如果错误返回1。
使用面向连接的套接字,目标地址被忽略,因为目标已经隐含在了连接中。 使用无连接的套接字,如果我们不首先通过connect函数设置目标地址,那么我们就无法使用send发送 ,所以sendto函数给了我们一个可以选择的方法来发送消息。
sendmsg
我们有不只一种方法通过套接字传输数据。我们可以调用sendmsg,通过msghdr结构指定用来传输数据的多个缓存,这个和使用writev函数有点类似。
#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
返回:如果成功,返回发送的字节数目,如果错误返回1。
POSIX.1定义msghdr结构至少包含如下成员:
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* address size in bytes */
struct iovec *msg_iov; /* array of I/O buffers */
int msg_iovlen; /* number of elements in array */
void *msg_control; /* ancillary data */
socklen_t msg_controllen; /* number of ancillary bytes */
int msg_flags; /* flags for received message */
.
.
.
};
对于msg_iov成员,我们前面看见过iovec结构(14章讲解readv和writev的时候),我们将在后面看到辅助数据的使用。
接收数据
接下来我们看看接收数据的函数。
recv
函数recv和read函数类似,但是允许我们指定一些如何接收数据的控制选项。
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
返回:返回消息的字节长度,如果没有消息返回0并且对等端做顺序的shutdown(???),或者如果错误返回1。
传递给recv的flags参数通过一个表格的形式进行列出,其中只有三个是Single UNIX Specification定义的。这里只给出一个大致的列举,具体那个实现支持它们,请参见参考资料。
recv套接字调用使用的标记
+-------------------------------------------------------------------------------------------------+
| Flag | Description |
|-------------+-----------------------------------------------------------------------------------|
| MSG_OOB | 如果协议支持,那么获取带外数据。 |
|-------------+-----------------------------------------------------------------------------------|
| MSG_PEEK | 返回包内容但是不会消耗包。 |
|-------------+-----------------------------------------------------------------------------------|
| MSG_TRUNC | 返回一个包的实际长度,即使它被截断了。 |
|-------------+-----------------------------------------------------------------------------------|
| MSG_WAITALL | 等待,直到所有数据可用(只对SOCK_STREAM) |
+-------------------------------------------------------------------------------------------------+
当我们指定MSG_PEEK标记的时候,我们可以查看下一次将要读取的数据而不用消耗实际的数据(也就是说数据不会在被看到之后被清除掉)。下次调用read或者recv相关的函数的时候,将会返回我们这里查看到的数据。
通过使用SOCK_STREAM套接字,我们可以接收比我们请求少的数据。MSG_WAITALL标记会阻止这个现象发生,不让recv返回直至所有请求的数据被接收到。对于SOCK_DGRAM和SOCK_SEQPACKET套接字,MSG_WAITALL标记对它们的行为没有区别,因为这些基于消息的套接字类型在单个读取的时候已经返回了整个的消息。
如果发送者调用了shutdown函数来结束数据的传输,或者如果网络协议默认支持依次shutdown并且发送者关闭了套接字,那么recv将会在我们已经接收到所有的数据的时候返回0。
recvfrom
如果我们对发送者是谁很感兴趣,那么我们可以使用recvfrom来获得数据得以发送的源地址。
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags, struct sockaddr *restrict addr,
socklen_t *restrict addrlen);
返回:会返回消息的字节长度,如果没有消息返回0并且对等端会顺序调用shutdown,或者如果错误返回1。
如果addr参数非空,它将会包含发送数据所来自的套接字末端地址。当调用recvfrom的时候,我们需要设置addrlen参数指向一个整数,这个整数用来包含addr参数所指向的套接字缓存的字节大小。当返回的时候,这个整数会被设置成实际字节的大小。
因为通过这个函数我们可以获取发送者的地址,recvfrom实际和无连接的套接字一块使用。否则,recvfrom表现的行为和recv一样。
recvmsg
和readv类似地如果想要接收数据到多个缓存,或者我们想要接收辅助的数据,我们可以使用recvmsg。
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
返回:消息的字节长度,如果没有消息返回0并且对等端允许调用shutdown,或者如果错误返回1。
recvmsg函数使用msghdr结构(我们看到sendmsg中使用了)指定接收数据的输入缓存。我们可以设置flags参数来更改默认的recvmsg行为。返回的时候,msghdr结构的msg_flags成员会被设置,以表示接收数据的各种特性(msg_flags成员在进入recvmsg的时候被忽略)。对于recvmsg返回的可能值,在下面的表格中列出来了。后面会对recvmsg函数的使用举一个例子。Specification定义的。这里只给出一个大致的列举,具体那个实现支持它们,请参见参考资料。
recvmsg返回的msg_flags中的标记
+---------------------------------------------------------+
| Flag | Description |
|--------------+------------------------------------------|
| MSG_CTRUNC | 控制数据被截断。 |
|--------------+------------------------------------------|
| MSG_DONTWAIT | recvmsg 以一种非阻塞的模式被调用。 |
|--------------+------------------------------------------|
| MSG_EOR | 返回接收的记录结尾。 |
|--------------+------------------------------------------|
| MSG_OOB | 接收带外数据。 |
|--------------+------------------------------------------|
| MSG_TRUNC | 截断正常数据。 |
+---------------------------------------------------------+
网友评论