第14章——高级IO函数

1.套接字超时

  套接字IO函数设置超时的方法有三种:

(1)调用alarm。

(2)select

(3)使用SO_RECTIMEO和 SO_SNDTIMEO 选项

  上面三种方法适用于输入输出操作(read , write, sendto , recvfrom ..)

  (1)(2)适用于所有文件描述符,(3)适用于套接字

  connect 内置超时时间很长(典型为75秒),select可用于connect的前提是对应套接字处于 非阻塞模式。(3)不适于connect。

2.一些设置超时示例

2.1 使用alarm

  涉及信号就避免多线程,信号本身适中全局资源,所以要注意对其他信号使用的影响

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)
                err_msg("connect_timeo: alarm was already set");

        if ( (n = connect(sockfd, saptr, salen)) < 0) {
                close(sockfd);
                if (errno == EINTR)
                        errno = ETIMEDOUT;
        }
        alarm(0);                                       /* turn off the alarm */
        Signal(SIGALRM, sigfunc);       /* restore previous signal handler */

        return(n);
}

static void
connect_alarm(int signo)
{
        return;         /* just interrupt the connect() */
}
void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
        int     n;
        char    sendline[MAXLINE], recvline[MAXLINE + 1];

        Signal(SIGALRM, sig_alrm);

        while (Fgets(sendline, MAXLINE, fp) != NULL) {

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

                alarm(5);
                if ( (n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0) {
                        if (errno == EINTR)
                                fprintf(stderr, "socket timeout\n");
                        else
                                err_sys("recvfrom error");
                } else {
                        alarm(0);
                        recvline[n] = 0;        /* null terminate */
                        Fputs(recvline, stdout);
                }
        }
}

static void
sig_alrm(int signo)
{
        return;                 /* just interrupt the recvfrom() */
}

2.2 使用select

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));
                /* 4> 0 if descriptor is readable */
}
/* end readable_timeo */

2.3 使用SO_RCVTIMEO 

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 timeout\n");
                                continue;
                        } else
                                err_sys("recvfrom error");
                }

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

 

3. recv 和 send 函数

       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);

  特别的是flags参数:

  MSG_DONTROUTE:

    和SO_DONTROUTE的套接字选项类似,SO_DONTROUTE是修改套接字到达长期有效,MSG_DONTROUTE是单次有效。

    根据IPv4的正常机制,IP分组需要根据目的地址和路由表找到适当本地接口,如果无法确定本地接口,就会报错。

    可以使用本选项,以绕过上述机制,强制从某个接口发出。路由守护进程就是这么做的。

  MSG_DONTWAIT

    临时打开非阻塞标志,进行IO,关闭非阻塞标志

  MSG_OOB

    让本次调用,用于接受或发送带外数据。

    TCP连接上有一个字节可以作为带外数据发送。带外数据是加急数据。

  MSG_PEEK

    允许查看可读数据,且recv或recvfrom返回后,该数据仍然在缓冲区,不被丢弃。

  MSG_WAITALL

    通知内核,在尚未读到全部指定数量的数据前不返回。这样就可以简化readnzz

#define readn(fd, ptr, n)    recv(fd, ptr, n, MSG_WAITALL)

    即使这样,也有可能读到少的数据:(a)信号中断,(b)连接终止(c)套接字错误

 

4. writev 和 readv

       ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
       ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

           struct iovec {
               void  *iov_base;    /* Starting address */
               size_t iov_len;     /* Number of bytes to transfer */
           };

  这两个函数的作用是:

    集中写:将应用程序多个缓冲区的数据,集中提供给单个写操作。

    分散读:将读操作的数据,分散到多个应用程序缓冲。

 

  由于writev是原子操作,对于UDP而言,一次writev多个缓冲区数据,只发送一个UDP报文。

            对于TCP而言,通过writev将多个缓冲区放到一个分组发出,避免触发 Nagle 算法。

 5. 排队数据量

  (1)使用非阻塞IO,可以避免没有数据时的阻塞。

  (2)既想查看数据,又不想取出数据,可以用MSG_PEEK,又不希望数据没到达的阻塞,可以加MSG_DONTWAIT。对于TCP,MSG_PEEK读到的数据量和之后读到的数据量是可能不同,因为可能有新的数据来了,对于UDP,两次读到的数据量相同。

 

posted on 2020-03-12 07:40  开心种树  阅读(206)  评论(0编辑  收藏  举报