【unix网络编程】用select改进客户端服务器程序

待解决问题

  1. 之前的代码无法感知服务器进程发来的 FIN 段,如果服务器崩溃,客户需要花费很长时间才认识到问题。

因为有可能阻塞在fgets函数上,一直等待服务器端的数据回送。然后客户端不放弃一直重发,等到9分钟后终于放弃了。
RST的问题上一次改进解决了。

select函数

/* According to POSIX.1-2001 */
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

1.select参数详解

int select(int nfds, //整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1
           fd_set *readfds, //(可选)指针,指向一组等待可读性检查的套接口。
           fd_set *writefds, //(可选)指针,指向一组等待可写性检查的套接口。
           struct timeval *timeout//select()最多等待时间,对阻塞操作则为NULL
           );

后面两个指针依靠三个函数来设置。
fd_set 每一位的0/1代表着是否对该位感兴趣,第几个事件发生时需要关注的,第几位为1.

2.select返回值

返回值备注
>0 > 0 任何一个关注的事件就绪,套接字可读,是就绪的个数。可用于多并发
=0 = 0 对端TCP发送了一个FIN
<0 < 0 <script type="math/tex" id="MathJax-Element-18"><0</script>主机崩溃重启,对端发送RST,在errno中有确切的错误码

改进的代码阅读

#include    "unp.h"

void
str_cli(FILE *fp, int sockfd)
{
    int         maxfdp1;
    fd_set      rset;
    char        sendline[MAXLINE], recvline[MAXLINE];

    FD_ZERO(&rset);//所有位清零
    for ( ; ; ) {
        FD_SET(fileno(fp), &rset);//标准IO文件指针所在位
        FD_SET(sockfd, &rset);//套接字
        maxfdp1 = max(fileno(fp), sockfd) + 1;//只关注这两个,因此最大值就是两个最大值+1
        Select(maxfdp1, &rset, NULL, NULL, NULL);//只给出等待可读性检查的套接口

        if (FD_ISSET(sockfd, &rset)) {  /* socket is readable */
            if (Readline(sockfd, recvline, MAXLINE) == 0)
                err_quit("str_cli: server terminated prematurely");
            Fputs(recvline, stdout);
        }

        if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */
            if (Fgets(sendline, MAXLINE, fp) == NULL)//文件输入完了
            //但是可能是批量输入的,还没等到服务器的回复,或者还有请求在路上。
            //因此只能是发送FIN表没数据要发送了。可以改为shutdown
            //则可以进一步改进,这里不return,而是清除IO位,
                return;     /* all done */
            Writen(sockfd, sendline, strlen(sendline));
        }
    }
}

针对批量改进

#include    "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
    int         maxfdp1, stdineof;
    fd_set      rset;
    char        buf[MAXLINE];
    int     n;

    stdineof = 0;
    FD_ZERO(&rset);
    for ( ; ; ) {
        if (stdineof == 0)
            FD_SET(fileno(fp), &rset);
        FD_SET(sockfd, &rset);
        maxfdp1 = max(fileno(fp), sockfd) + 1;
        Select(maxfdp1, &rset, NULL, NULL, NULL);

        if (FD_ISSET(sockfd, &rset)) {  /* socket is readable */
            if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
                if (stdineof == 1)
                    return;     /* normal termination */
                else
                    err_quit("str_cli: server terminated prematurely");
            }

            Write(fileno(stdout), buf, n);
        }

        if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */
            if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
                stdineof = 1;
                Shutdown(sockfd, SHUT_WR);  /* send FIN */
                FD_CLR(fileno(fp), &rset);
                continue;
            }

            Writen(sockfd, buf, n);
        }
    }
}

把fgets改掉的原因

忠告:

不要将 stdio 库提供的 C 语言函数与 IO 复用混合使用!!

假设我们没有改掉,而是继续使用fgets(),模拟一下情景:
1. 监听了标准输入,某个时刻 select 监听到了标准输入上有数据,于是客户端调用 fgets 读取一行数据。
2. stdio 的函数是带有缓冲区。fgets 把 STDIN_FILENO 上的所有数据读完了,保存到了自己的缓冲,并返回一行给客户。
3. 程序又回到了 select 调用上。此时,select 永远再也不会触发标准输入上的 IO 事件,因为 select 认为,你的数据早已被读取完,它并不知道 stdio 使用了缓冲区。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值