网络I/O函数

read   write
recv   send
readv writev
recvfrom  sendto
recvmsg   sendmsg
read   write
#include <unistd.h>
ssize_t  read(int  filedes, void *buf, size_t  nbytes);
返回:若成功则返回读到的字节数,若已到文件结尾则返回0,若出错则返回-1.
说明:
1.读操作从文件的当前偏移量处开始,在成功返回之前,该偏移量将增加实际读到的字节数。第一个参数是文件描述符,第二个参数是读上来的数据放在缓冲区的首地址,第三个参数是预计想要读的字节数。
2.有多种情况会使实际读到的字节数少于要求读的字节数:
* 读普通文件时,在读到要求字节数之前已到达了文件尾端。
* 当从终端设备读时,通常一次最多读一行。
* 当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。
* 当从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数。
* 当某一信号造成中断,而已经读了部分数据量时。
ssize_t  write(int  filedes, const void *buf, size_t nbytes);
返回:若成功则返回已写的字节数,若出错则返回-1.
说明:write()会把参数buf 所指的内存写入nbytes个字节到参数fd 所指的文件内. 当然, 文件读写位置也会随之移动.

recv   send
#include <sys/socket.h>
ssize_t  recv(int sockfd, void *buff, size_t nbytes, int flags);
ssize_t  send(int sockfd, const void *buff, size_t nbytes, int flags);
返回: 若成功则为读入或写出的字节数,若出错则为-1
说明:
1. recv和send的前3个参数等同于read和write的3个参数。flags参数的值或为0,或为下图列出的一个或多个常值的逻辑或:
下面一一对这些标志进行一下解释:
MSG_DONTROUTE: 本标志告知内核目的主机在某个直接连接的本地网络上,因而无需执行路由表查找。
MSG_DONTWAIT:    本标志在无需打开相应套接字的非阻塞标志的前提下,把单个I/O操作临时指定为非阻塞,接着执                                     行I/O操作,然后关闭非阻塞标志。
MSG_OOB:                对于send,本标志指明即将发送带外数据。对于recv,本标志指明即将读入的是带外数据而不是                                     普通数据。
MSG_PEEK:             本标志使用于recv和recvfrom,它允许我们查看已可读取的数据,而且系统不在recv或                                              recvfrom返回后丢弃这些数据。
MSG_WAITALL:         本标志告知内核不要在尚未读入请求数目的字节之前让一个读操作返回。但即使指定了                                                  MSG_WAITALL,如果发生下列情况之一:a.捕获一个信号,b.连接被终止,c.套接字发生一个                                      错误,这些时候相应的读函数仍有可能返回比所请求字节数要少的数据。

2. flags参数在设计上存在一个基本问题:它是按值传递的,而不是一个值-结果参数。因此它只能用于从进程向内核传递标志,而内核无法向进程传回标志。



recvfrom  sendto
#include <sys/socket.h>
ssize_t  recvfrom(int  sockfd, void  *buff, size_t  nbytes,  int  flags, struct sockaddr *from, socklen_t  *addrlen);
ssize_t  sendto(int  sockfd, const void  *buff, size_t  nbytes, int flags, const  struct  sockaddr  *to, socklen_t  *addrlen);
返回:若成功则为读或写的字节数,若出错则为-1.


readv    writev
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
返回:若成功则为读入或写出的字节数,若出错则为-1
说明:
1.这两个函数类似于read和write,但不同的是readv和writev支持分散读和集中写。何谓分散读和集中写呢,就是来自读操作的输入数据被分散到多个应用缓冲区中,而来自多个应用缓冲区的输出数据则被集中提供给单个写操作。
2.这两个函数的第二个参数都是指向某个iovec结构数组的一个指针,其中iovec结构在头文件<sys/uio.h>中定义:
             struct     iovec {
                   void          *iov_base;          /* starting  address  of  buffer */
                   size_t       iov_len;                 /* size  of  buffer */
             };
iovec结构数组中元素的数目存在某个限制,具体取决于实现。posix要求在头文件<sys/uio.h>中定义IOV_MAX常值,而且其值至少为16.
3.readv和writev这两个函数可用于任何描述符,而不仅限于套接字。另外writev是一个原子操作,意味着对于一个基于记录的协议(例如UDP)而言,一次writev调用只产生单个UDP数据报。


recvmsg   sendmsg
#include <sys/socket.h>
ssize_t  recvmsg(int  sockfd, struct  msghdr *msg,  int  flags);
ssize_t  sendmsg(int sockfd, struct  msghdr *msg,  int  flags);
返回:若成功则为读入或写出的字节数,若出错则为-1
说明:
1.这两个函数是最通用的I/O函数,实际上我们可以把所有read readv recv 和 recvfrom 调用替换成recvmsg调用。类似地,各种输出函数调用也可以替换成sendmsg调用。
2.这两个函数把大部分参数封装到一个msghdr结构中:
              struct  msghdr{
                   void                     *msg_name;                    /* protocol  address */
                    socklen_t           msg_namelen;               /*  size  of  protocol  address */
                    struct      iovec    *msg_iov;                      /*  scatter/gather  array */
                    int                        msg_iovlen;                   /*  # elements in msg_iov */
                   void                      *msg_control;               /*  ancillary  data  (cmsghdr  struct) */
                   socklen_t            msg_controllen;           /*  length  of  ancillary data */
                   int                         msg_flags;                    /*  flags  returned  by  recvmsg() */
             };
     下面一一对这些参数进行说明:
       msg_name和msg_namelen这两个成员用于套接字未连接的场合(譬如未连接UDP套接字)。他们类似recvfrom和sendto的第五个和第六个参数:msg_name指向一个套接字地址结构,调用者在其中存放接收者(对于sendmsg调用)或发送者(对于recvmsg调用)的协议地址。如果无需指明协议地址(例如对于TCP套接字或已连接UDP套接字),msg_name应置为空指针。msg_namelen对于sendmsg是一个值参数,对于recvmsg却是一个值-结果参数。
        msg_iov和msg_iovlen这两个成员指定输入或输出缓冲区数组(即iovec结构数组),类似readv或writev
的第二个和第三个参数。msg_controllen对于recvmsg是一个值-结果参数。
       对于recvmsg和sendmsg,我们必须区别他们的两个标志变量,一个是传递值的flags参数,另一个是所传递msghdr结构的msg_flags成员,它传递的是引用,因为传递给函数的是该结构的地址。
        只有recvmsg使用msg_flags成员。recvmsg被调用时,flags参数被复制到msg_flags成员,并由内核使用其值驱动接收处理过程。内核还依据recvmsg的结果更新msg_flags成员的值。
         sendmsg则忽略msg_flags成员,因为它直接使用flags参数驱动发送处理过程。这一点意味着如果想在某个sendmsg调用中设置MSG_DONTWAIT标志,那就把flags参数设置为该值,把msg_flags成员设置为该值不起作用。

这些标志中,内核只检查而不返回前4个标志,既检查又返回接下来的2个标志,不检查而只返回后4个标志。recvmsg返回的7个标志解释如下:
     MSG_BCAST:本标志的返回条件是本数据报作为链路层广播收取或者其目的IP地址是一个广播地址。与IP_RECVD-                                 STADDR套接字选项相比,本标志是用于判定一个UDP数据报是否发往某个广播地址的更好方法。
     MSG_MCAST:它的返回条件是本数据报作为链路层多播收取。
     MSG_TRUNC:本标志的返回条件是本数据报被截断,也就是说,内核预备返回的数据超过进程事先分配的空间(所有                                 iov_len成员之和)。
     MSG_CTRUNC:本标志的返回条件是本数据报的辅助数据被截断,也就是说,内核预备返回的辅助数据超过进程事先                                     分配的空间(msg_controllen)。
     MSG_EOR:      本标志的返回条件是返回数据结束一个逻辑记录。TCP不使用本标志,因为它是一个字节流协议。
     MSG_OOB:     本标志绝不为TCP带外数据返回。它用于其他协议族(例如OSI协议族)。
     MSG_NOTIFICATION:本标志由SCTP接收者返回,指示读入的消息是一个事件通知,而不是数据消息。

辅助数据:
辅助数据可通过调用sendmsg和recvmsg这两个函数,使用msghdr结构中的msg_control和msg_controllen这两个成员发送和接收。辅助数据的另一个称谓是控制信息。
首先说一下辅助数据的各种用途:



辅助数据由一个或多个辅助数据对象构成,每个对象以一个定义在头文件<sys/socket.h>中的cmsghdr结构开头。
              struct     cmsghdr  {
                        socklen_t               cmsg_len;               /* length  in  bytes, including this structure */
                        int                            cmsg_level;           /* originating  protocol  */
                        int                            cmsg_type;           /* protocol-specific  type */
                                                                                        /* followed by unsigned  char  cmsg_data[] */
}

由recvmsg返回的辅助数据可含有任意数目的辅助数据对象,为了对应用程序屏蔽可能出现的填充字节,头文件<sys/socket.h>中定义了以下5个宏,以简化对辅助数据的处理。

#include     <sys/socket.h>
#include     <sys/param.h>                    /* for ALIGN macro on many implementations */
struct     cmsghdr   *CMSG_FIRSTHDR( struct msghdr *mhdrptr )
返回:指向第一个cmsghdr结构的指针,若无辅助数据则为NULL
struct     cmsghdr    *CMSG_NXTHDR(struct msghdr *mhdrptr, strruct cmsghdr *cmsgptr )
返回:指向下一个cmsghdr结构的指针,若不再有辅助数据对象则为NULL
unsigned   char  *CMSG_DATA(struct  cmsghdr  *cmsgptr);
返回:指向与cmsghdr结构关联的数据的第一个字节的指针
unsighed   int  CMSG_LEN(unsigned  int  length);
返回:给定数据量下存放到cmsg_len中的值
unsigned int CMSG_SPACE(unsigned  int  length);
返回:给定数据量下一个辅助数据对象总的大小
注:POSIX定义了前3个宏,RFC3542定义了后2个宏。

说明:CMSG_FIRSTHDR返回指向第一个辅助数据对象的指针,然而如果在msghdr结构中没有辅助数据(或者msg_control为一个空指针,或者csmg_len小于一个cmsghdr结构的大小),那就返回一个空指针。
当控制缓冲区中不再有下一个辅助数据对象时,CSMG_NXTHDR也返回一个空指针。
CMSG_LEN和CMSG_SPACE的区别在于,前者不计辅助数据对象中数据部分之后可能的填充字节,因而返回的是用于存放在cmsg_len成员中的值,后者计上结尾处可能的填充字节,因而返回的是为辅助数据对象动态分配空间的大小值。

排队的数据量(其实下面这一段我也不是太懂 慢慢理解吧)
有时候我们想要在不真正读取数据的前提下知道一个套接字上已有多少数据排队等着读取。有3个技术可用于获悉已排队的数据量。
1.如果获悉已排队数据量的目的在于避免读操作阻塞在内核中(因为没有数据可读时我们还有其他事情可做),那么可以使用非阻塞式I/O。
2.如果我们既想查看数据,又想数据仍然留在接收队列中以供本进程其他部分稍后读取,那么可以使用MSG_PEEK标志。如果我们想这么做,既然不能肯定是否真有数据可读,那么可以结合非阻塞套接字使用该标志,也可以组合使用MSG_DONTWAIT标志和MSG_PEEK标志。需注意的是,就一个字节流套接字而言,其接收队列中的数据量可能在两次相继的recv调用之间发生变化。举例来说,假设指定MSG_PEEK标志以一个长度为1024字节的缓冲区对一个TCP套接字调用recv,而且其返回值为100.如果再次调用同一个recv,返回值就有可能超过100(假设指定的缓冲区长度大于100),因为在这两次调用之间TCP可能又收到了一些数据报。
           就一个UDP套接字而言,假设其接收队列中已由一个数据报,如果我们指定MSG_PEEK标志调用recvfrom一次,稍后不指定该标志再调用recvfrom一次,那么即使另有数据报在这两次调用之间加入该套接字的接收队列,这两个调用的返回值(数据报大小  内容及发送者地址)也完全相同。(当然这里假设没有其他进程共享该套接字并从中读取数据。)
3.一些实现支持ioctl的FIONREAD命令,该命令的第三个ioctl参数是指向某个整数的一个指针,内核通过该整数返回的值就是套接字接收队列的当前字节数。该值是已排队字节的总和,对于UDP套接字而言包括所有已排队的数据报。

C标准I/O函数也可以用在套接字上,不过这么做将在已经由TCP提供的缓冲级别之上新增一级缓冲。实际上,对由标准I/O函数库执行的缓冲缺乏了解是使用这个函数库最常见的问题。既然套接字不是终端设备,这个潜在问题的常用解决办法就是把标准I/O流设置成不缓冲,或者干脆不要在套接字上使用标准I/O.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值