[UNP]:高级IO

1. 套接字超时

在涉及套接字的I/O操作上设置超时的方法有三种

  • 使用alarm()
  • 使用select()
  • 使用套接字选项: SO_RCVTIMEO, SO_SNDTIMEO

1. alarm

接下来使用alarm()connect()设置超时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* include connect_timeo */
#include "unp.h"

static void connect_alarm(int);

int
connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
Sigfunc *sigfunc;
int n;

sigfunc = Signal(SIGALRM, connect_alarm);
/*************************************************/
if (alarm(nsec) != 0) // 设置超时事件为nesec
err_msg("connect_timeo: alarm was already set");
/************************************************/
if ( (n = connect(sockfd, saptr, salen)) < 0) {
close(sockfd);
if (errno == EINTR)
errno = ETIMEDOUT;
}
/************************************************/
alarm(0); // 超时时间之前connect()调用返回,关闭alarm
/***********************************************/
Signal(SIGALRM, sigfunc); /* restore previous signal handler */

return(n);
}

static void
connect_alarm(int signo)
{
return; /* just interrupt the connect() */
}
/* end connect_timeo */

void
Connect_timeo(int fd, const SA *sa, socklen_t salen, int sec)
{
if (connect_timeo(fd, sa, salen, sec) < 0)
err_sys("connect_timeo error");
}

2. select

接下来使用select()recvfrom()设置超时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* include readable_timeo */
#include "unp.h"

int
readable_timeo(int fd, int sec)
{
fd_set rset;
struct timeval tv;

FD_ZERO(&rset);
FD_SET(fd, &rset);

tv.tv_sec = sec;
tv.tv_usec = 0;

return(select(fd+1, &rset, NULL, NULL, &tv)); // 使用select()自带的超时参数
/* 4> 0 if descriptor is readable */
}
/* end readable_timeo */

int
Readable_timeo(int fd, int sec)
{
int n;

if ( (n = readable_timeo(fd, sec)) < 0)
err_sys("readable_timeo error");
return(n);
}

3. 套接字选项

使用套接字选项SO_RCVTIMEOrecvfrom()设置超时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include	"unp.h"

void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
struct timeval tv;

tv.tv_sec = 5;
tv.tv_usec = 0;
/***********************************************************/
Setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
/**********************************************************/
while (Fgets(sendline, MAXLINE, fp) != NULL) {

Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
if (n < 0) {
if (errno == EWOULDBLOCK) {
fprintf(stderr, "socket timeoutn");
continue;
} else
err_sys("recvfrom error");
}

recvline[n] = 0; /* null terminate */
Fputs(recvline, stdout);
}
}

如果I/O操作超时,那么读取操作(recvfrom())将会返回一个EWOULDBLOCK错误

2. recv(), send()

这两个函数类似于read()write(), 但是支持更多的flag

  • recv, send

    1
    2
    3
    4
    #include<sys/socket.h> 
    /* 返回:成功返回读入写出的字节数,出错返回 -1 */
    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);
    • sockfd

      指定描述符

    • buff

      读/写缓冲区

    • nbytes

      缓冲区大小

    • flags

      flags可选值如下

      1

      对其中的几个参数稍微解释一下

      • MSG_DONROUTE

        本标志告诉内核目的主机在本地网络上,不需要通过路由表进行查找

      • MSG_WAITALL

        该标志告诉内核不要在尚未读取完请求数目的字节之前就让操作返回

3. readv(), writev()

这两个函数允许单个I/O调用读出或写入多个缓冲区, 这也被称作分散读集中写

  • iovec

    1
    2
    3
    4
    struct iovec{
    void *iov_base; /* buf 的开始地址 */
    size_t iov_len; /* buf 的大小 */
    }
  • readv, writev

    1
    2
    3
    4
    #include<sys/uio.h> 
    ssize_t readv(int fileds, const struct iovec* iov, int iovcnt);
    ssize_t writev(int fields, const struct iovec* iov, int iovcnt);
    /* 返回:成功返回读入或写出的字节数,出错返回 -1 */
    • fileds

      描述符(文件描述符/套接字描述符)

    • iov

      读取/写入缓冲区数组

    • iovcnt

      数组长度

4. recvmsg(), sendmsg()

这两个函数是最通用的I/O函数,几乎什么都能干

  • msghdr

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /* msghdr结构 用来保存大部分参数 */
    struct msghdr{
    void *msg_name;
    socklen_t msg_namelen;
    struct iovec *msg_iov;
    int msg_iovlen;
    void *msg_control;
    socklen_t msg_controllen;
    int msg_flags;
    }
    • msg_name

      该字段通常只用于未连接UDP套接字,用于存放套接字地址结构

      对于recvmsg()来说, 该字段用于存储发送者的地址结构

      对于sendmsg()来说,该字段用于存储接受者的地址结构

      当该字段不需要(TCP套接字或已连接UDP套接字)时,该字段应该被置为空

    • msg_namelen

      msg_name的长度

    • msg_iov

      缓冲区数组

    • msg_iovlen

      缓冲区数组元素个数

    • msg_control

      辅助数据

    • msg_controllen

      msg_control的长度

    • msg_flags

      该字段只用于recvmsg(), 是一个值-结果类型参数

      传入时recvmsg()flags会被复制到其中

      传出时内核会根据接受到的结果来更新其值

    一个典型的msghdr结构看起来就像这样

    2

  • recvmsg, sendmsg

    1
    2
    3
    4
    #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
    • sockfd

      指定套接字描述符

    • msg

      各种参数集合

    • flags

      标志, 下面是它的总结图

      3

      • 标志解释

        4

5组I/O函数的比较

5

5. 辅助数据

struct msghdr结构当中存在辅助数据字段,接下来对其进行介绍

  • cmsghdr

    辅助数据由一个或多个辅助数据对象构成,每个对象由一个cmsghdr结构开头,用于描述该对象的属性

    1
    2
    3
    4
    5
    6
    7
    8
    #include <sys/socket.h>

    struct cmsghdr{
    socklen_t cmsg_len;
    int cmsg_level;
    int cmsg_type;
    // 后面紧跟着cmsg_data[]
    }
    • cmsg_len

      用于指明辅助数据对象的长度

    • cmsg_level + cmsg_type

      这两个字段组合用于用于指明辅助数据的用途

      6

  • 辅助数据示意图

    7

    辅助数据有着对其要求,在cmsghdr与辅助数据之间有着填充字节,在辅助对象之间也有着填充字节

为了屏蔽填充字节的细节,头文件<sys/socket.h>中定义了以下5个宏,用于简化对辅助数据的处理

8


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!