Chinaunix首页 | 论坛 | 博客
  • 博客访问: 122008
  • 博文数量: 41
  • 博客积分: 2564
  • 博客等级: 少校
  • 技术积分: 455
  • 用 户 组: 普通用户
  • 注册时间: 2007-09-20 19:17
文章分类

全部博文(41)

文章存档

2009年(41)

我的朋友

分类: LINUX

2009-04-16 11:02:11

对于socket,最基本的输入输出函数就是,read和write。它们最基本,同样功能也是最少的。Unix中有几个函数是read/write的变种,在基本的输入输出功能上,还增加了一些非常使用的功能和特性,它们是:recv/send、readv/writev和recvmsg/sendmsg。

1、socket超时的实现

一般来说,要在对socket的I/O操作实现超时,有3种方式:

·注册SIGALRM信号的处理函数,然后调用alrm,则若对socket的I/O操作阻塞时间大于alrm注册的时间,则在alrm时间到时会阻塞会被SIGALRM打断。这样做有个缺点,就是alrm的调用会影响到同一进程的其他alrm调用。某些系统中,系统调用被信号打断后,会自动重新调用,在这种系统中可以使用setlongjmp/siglongjmp来跳出。

·使用SO_RCVTIMEO/SO_SNDTIMEO选项。注意,并不是所有的系统都支持这两个选项。

·用select代替read/write。

前面两种方式只对socket进行输入/输出有效,而最后一种除了输入/输出以外,还对connect有效(socket必须处于nonblocking模式下)。

2、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);
 
Both return: number of bytes read or written if OK, –1 on error

可以看到,前3个参数和read/write是一样的,很好理解。最后一个flag参数的值可以是一下几个常量中的一个或多个的或:

·MSG_DONTROUTE 适用于send,表示此次发送的数据包不经过系统的路由过程,直接发送。

·MSG_DOWAIT 适用于recv/send,表示此次操作采用非阻塞方式(不一定所有的系统都支持此项)。

·MSG_OOB 适用于recv/send,表示发送或接收“out-of-band data”(只能发送1个字节的out-of-band data)。

·MSG_PEEK 适用于recv,表示此次读取的是缓存中数据的副本。

·MSG_WAITALL 适用于recv,表示调用将直接阻塞知道指定的指定的字节数被读出(若中途遇到EOF或出错,则也会少于指定字节数)。

2、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);
 
Both return: number of bytes read or written, –1 on error

这两个函数名字和read/write只多一个字母'v',功能更强大。readv可以将数据读入到多个buffer中,而writev可以将多个buffer的数据输出。iov指向一个iovec的数组,iovcnt为数组大小(linux中最大为1024)。

iovec结构如下:

struct iovec {
  void *iov_base; /* starting address of buffer */
  size_t iov_len; /* size of buffer */
};

iov_base指向buffer的起始地址,iov_len为buffer的大小。

3、recvmsg/sendmsg

这对函数可以说是socket输入/输出的“万金油”,它们可以替代之前提到过的输入/输出函数。原型如下:

#include <sys/socket.h>
 
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
 
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);
 
Both return: number of bytes read or written if OK, –1 on error

大部分重要的都放在msg指向的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指向相应的socket地址,msg_namelen为地址大小,这两个成员仅在socket未连接的情况下使用(需设置IP_RECVDSTADDR),对于已连接的socket(如TCP和已连接的UDP),msg_name应置为NULL,msg_namelen为0。在使用recvmsg时,msg_namelen是值-结果类型。

msg_iov与msg_iovlen和readv/writev表示的意义一样。

msg_flags仅对recvmsg有用,当recvmsg调用时,将flags赋值给msg_flags,然后内核按照msg_flags进行操作,返回时将更新(有可能)后的flags通过这个值返回;sendmsg仅使用flags。

附表,flag总结

msg_control成员很重要,下一节将详细分析;msg_controllen为msg_control大小。

4、辅助数据

辅助数据是指msghdr中的msg_control和msg_controllen成员,它们又称谓“控制信息”。在某些情况下,这些信息是非常有用的。

附表,辅助数据总结(直接截图,凑活着看)

辅助数据可以包含多条信息,每条信息存放在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[] */
};

注意,数据区cmsg_data是可变的,按需求分配。

在处理cmsghdr时,有5个实用的宏:

#include <sys/socket.h>
 
#include <sys/param.h> /* for ALIGN macro on many implementations */
 
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mhdrptr) ; 
#Returns: pointer to first cmsghdr structure or NULL if no ancillary data
 
struct cmsghdr *CMSG_NXTHDR(struct msghdr *mhdrptr, struct cmsghdr *cmsgptr) ;
#Returns: pointer to next cmsghdr structure or NULL if no more ancillary data objects
 
unsigned char *CMSG_DATA(struct cmsghdr *cmsgptr) ;
#Returns: pointer to first byte of data associated with cmsghdr structure
 
unsigned int CMSG_LEN(unsigned int length) ;
#Returns: value to store in cmsg_len given the amount of data
 
unsigned int CMSG_SPACE(unsigned int length) ;
#Returns: total size of an ancillary data object given the amount of data

图例:

代码片段:

struct msghdr msg;
struct cmsghdr *cmsgptr;

/* fill in msg structure */

/* call recvmsg() */

for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL;
     cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
    if (cmsgptr->cmsg_level == ... &&
        cmsgptr->cmsg_type == ... ) {
        u_char *ptr;

        ptr = CMSG_DATA(cmsgptr);
        /* process data pointed to by ptr */
    }
}

5、如何得知当前缓冲区中有多少数据?

使用MSG_PEEK即可,这样可以获取缓冲区数据的副本,而不是“消耗”,副本的大小即为当前缓冲区中的数据量。需注意的是,网卡每时每刻都有可能收到数据,因此缓冲区的数据量是一直在变化的。

6、socket I/O与标准I/O

之前提到的函数(read/write、recv/send等)都是系统调用,除了系统调用之外,还可以使用标准I/O库操作socket。标准I/O库自带缓冲机制,同时考虑了一些细节,这给使用者带来一定的方便。但是,方便的同时,它带来了新的问题,这些问题的根源就是缓冲机制!避免一些“奇怪”问题的建议就是,不要使用标准I/O库来处理socket。(见unpv13e 14.8)

阅读(1297) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~