Unix网络编程-非阻塞IO

非阻塞IO

Unix所提供的IO默认都是阻塞IO,在高性能网络库中,一般不会使用阻塞IO,不论是普通的消息收发,甚至是connect函数,accept函数,此文重写str_cli(),以及改写connect,accpet,写成非阻塞版本。

str_cli()

/**
 * @file str_cli.c
 * 非阻塞版本
 * 不建议在网络通信中使用标准IO,虽然有缓冲区
 * 但是标准IO的缓冲区操纵难度大,弊大于利
 * 这里我们使用自己建立的缓冲区数组
 */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <math.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <time.h>
#include <sys/time.h>
#define BUFSIZE 1024

// 获取时间函数
char* gf_time()
{
    struct timeval tv;
    static char str[30];
    char *ptr;
    if (gettimeofday(&tv, NULL) < 0)
    {
        perror("gettimeofday");
        exit(-1);
    }
    ptr = ctime(&tv.tv_sec);
    strcpy(str, &ptr[11]); // 这是什么说法?这个11?
    // 这是和tcpdump一样的时间格式
    // 10:18:34.544636 .后面是微妙?
    snprintf(str + 8, sizeof(str) - 8, ".%06ld", tv.tv_usec);
    return str;
}

void str_cli(FILE *fp, int sockfd)
{
    char intosoBuf[BUFSIZE];        // 发送缓冲区,标准输入->sendbuf->socketfd
    char sotooutBuf[BUFSIZE];       // 接收缓冲区,socketfd->recvbuf->标准输出
    char *intosoPtrStart, *intosoPtrEnd;    // 介于start和end之间的是将要发送的数据
    char *sotooutPtrStart, *sotooutPtrEnd;
    int stdineof;   // 表示是否读到eof字符(这个只对文件有效)

    // 初始化四个指针
    intosoPtrStart = intosoPtrEnd = intosoBuf;  // 都指向buf头部
    sotooutPtrStart = sotooutPtrEnd = sotooutBuf;
    stdineof = 0;   // 未读到eof

    // 将sockfd,stdin,stdout设置为非阻塞
    int ops = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, ops | O_NONBLOCK);
    ops = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, ops | O_NONBLOCK);
    ops = fcntl(STDOUT_FILENO, F_GETFL, 0);
    fcntl(STDOUT_FILENO, F_SETFL, ops | O_NONBLOCK);

    // 求出最大的文件描述符
    int maxfd = max(sockfd, max(STDIN_FILENO, STDOUT_FILENO));

    // 设置文件描述符集,使用select
    fd_set rset, wset;  // 读,写
    while (1)
    {
        // 文件描述符集合初始化
        FD_ZERO(&rset);
        FD_ZERO(&wset);

        // 根据两个buf的状态,决定监听哪些文件描述符
        // 如果没有读到eof,且尾指针还没有到达缓冲区尾部,说明可以继续从stdin接收数据
        if (stdineof == 0 && intosoPtrEnd < &intosoBuf[BUFSIZE])
        {
            FD_SET(STDIN_FILENO, &rset);    // 从标准输入读
        }
        // 如果尾指针还没有到达sotooutBuf的尾部,说明还可以继续从sockfd接收数据
        if (sotooutPtrEnd < &sotooutBuf[BUFSIZE])
        {
            FD_SET(sockfd, &rset);  // 从套接字读
        }
        // 如果intosoBuf的头尾指针不相等,说明中间有可以发送到sockfd的数据
        if (intosoPtrStart != intosoPtrEnd)
        {
            FD_SET(sockfd, &wset);  // 往套接字里写
        }
        // 如果sotooutBuf的头尾指针不相等,说明中间有数据可以继续往标准输出输出
        if (sotooutPtrStart != sotooutPtrEnd)
        {
            FD_SET(STDOUT_FILENO, &wset);   // 可以往标准输出写
        }

        // 调用select
        select(maxfd + 1, &rset, &wset, NULL, NULL);    // 这一步被阻塞

        // 解除阻塞,依次判断

        // 如果标准输入有数据可以读
        if (FD_ISSET(STDIN_FILENO, &rset))
        {
            int n;
            // 从标准输入读,读到end指针之后,只剩下bufsize-end这么大的空间
            if ((n = read(STDIN_FILENO, intosoPtrEnd, &intosoBuf[BUFSIZE] - intosoPtrEnd)) < 0) // 暂时没有数据?
            {
                if (errno != EWOULDBLOCK)   // 如果errno不是假错
                {
                    fprintf(stderr, "read error on stdin.\n");
                    return;
                }
            }
            else if (n == 0)    // 如果客户端断开,就会从stdin读到eof,套接字也会收到eof
            {
                fprintf(stderr, "%s:EOF on stdin.\n", gf_time());
                stdineof = 1;   // 设置标志,已经输入eof
                // 如果发送缓冲区已经没有要发送给套接字的数据了
                if (intosoPtrStart == intosoPtrEnd)
                {
                    // 关闭套接字的写端
                    shutdown(sockfd, SHUT_WR);
                }
            }
            else    // 正常接收到数据
            {
                // 这里往stderr发,不是说出错了,是因为stdin和stdout都被占用
                // 如果再往stdin和stdout发数据,可能会导致紊乱,stderr默认也是绑定到终端的
                fprintf(stderr, "%s:read %d bytes from stdin.\n", gf_time());
                intosoPtrEnd += n;  // 指针后移
                FD_SET(sockfd, &wset);  // buf中有数据,就可以往套接字中发送了
            }
        }
        // 如果套接字可读,有数据从套接字发送过来
        if (FD_ISSET(sockfd, &rset))
        {
            int n;
            if ((n = read(sockfd, sotooutPtrEnd, &sotooutBuf[BUFSIZE] - sotooutPtrEnd)) < 0)
            {
                // 正常来讲这种情况是不会发生的,已经发生事件,却还是没有数据可读
                if (errno != EWOULDBLOCK)
                {
                    fprintf(stderr, "read error on sockfd.\n");
                    return;
                }
            }
            else if (n == 0)    // 读到EOF,说明没有数据要接受了
            {
                fprintf(stderr, "%s:EOF on sockfd.\n", gf_time());
                if (stdineof == 1)  // 如果没有数据要发送了
                {
                    return; // 既没有发送了,也没有接受了,就结束
                }
                // 否则就是服务器宕掉了
                fprintf(stderr, "str_cli:server terminated prematurely.");
                return;
            }
            else
            {
                fprintf(stderr, "%s:read %d bytes from sockfd.\n", gf_time(), n);
                sotooutPtrEnd += n;
                // 既然缓冲区有数据了,就可以往标准输出写了
                FD_SET(STDOUT_FILENO, &wset);
            }
        }
        // 如果标准输出可以写
        int n;
        if (FD_ISSET(STDOUT_FILENO, &wset) && (n = sotooutPtrEnd - sotooutPtrStart) > 0)
        {
            int nwriten;
            if ((nwriten = write(STDOUT_FILENO, sotooutPtrStart, n)) < 0)
            {
                // 正常来讲这种情况是不会发生的,已经发生事件,却还是没有数据可读
                if (errno != EWOULDBLOCK)
                {
                    fprintf(stderr, "write error on stdout.\n");
                    return;
                }
            }
            else
            {
                fprintf(stderr, "%s:write %d bytes to stdout.\n", gf_time(), nwriten);
                sotooutPtrStart += nwriten;
                if (sotooutPtrStart == sotooutPtrEnd)   // 没有要发送的数据了,重置缓冲区
                {
                    sotooutPtrStart = sotooutPtrEnd = sotooutBuf;
                }
            }
        }
        // 如果套接字可写
        int n;
        if (FD_ISSET(sockfd, &wset) && (n = intosoPtrEnd - intosoPtrStart) > 0)
        {
            int nwriten;
            if ((nwriten = write(sockfd, intosoPtrStart, n)) < 0)
            {
                // 正常来讲这种情况是不会发生的,已经发生事件,却还是没有数据可读
                if (errno != EWOULDBLOCK)
                {
                    fprintf(stderr, "write error on sockfd.\n");
                    return;
                }
            }
            else
            {
                fprintf(stderr, "%s:write %d bytes to sockfd.\n", gf_time(), nwriten);
                intosoPtrStart += nwriten;
                if (intosoPtrStart == intosoPtrEnd)   // 没有要发送的数据了,重置缓冲区
                {
                    sotooutPtrStart = sotooutPtrEnd = sotooutBuf;
                    if (stdineof == 1)  // 已经没有数据要发送了
                    {
                        shutdown(sockfd, SHUT_WR);
                    }
                }
            }
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橙子砰砰枪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值