zoukankan      html  css  js  c++  java
  • 利用recv和readn函数实现readline函数

    前面的文章中,我们为了避免粘包问题,实现了一个readn函数读取固定字节的数据。如果应用层协议的各字段长度固定,用readn来读是非常方便

    的。例如设计一种客户端上传文件的协议,规定前12字节表示文件名,超过12字节的文件名截断,不足12字节的文件名用''补齐,从第13字节开始是

    文件内容,上传完所有文件内容后关闭连接,服务器可以先调用readn读12个字节,根据文件名创建文件,然后在一个循环中调用read读文件内容并存

    盘,循环结束的条件是read返回0。


    字段长度固定的协议往往不够灵活,难以适应新的变化。前面讲过的TFTP协议的各字段是可变长的,以''为分隔符,文件名可以任意长,再看blksize

    等几个选项字段,TFTP协议并没有规定从第m字节到第n字节是blksize的值,而是把选项的描述信息“blksize”与它的值“512”一起做成一个可变长的字

    段。


    因此,常见的应用层协议都是带有可变长字段的,字段之间的分隔符用换行' '的比用''的更常见,如HTTP协议。可变长字段的协议用readn来读就很

    不方便了,为此我们实现一个类似于fgets的readline函数。


    首先来看一个跟read 相似的系统函数recv。

     #include <sys/types.h>
     #include <sys/socket.h>
     ssize_t recv(int sockfd, void *buf, size_t len, int flags);


    recv函数与read函数类似,但只能读取套接字描述符,而不能是一般的文件描述符,且多了一个标志参数。

    flags参数比较重要的有两个,一个是MSG_OOB,即读取带外数据时候的选项,tcp头部有一个紧急指针16位的值。另一个是MSG_PEEK,即从缓冲区

    返回数据但不清空缓冲区,这点与read是不同的。

    下面使用封装后的recv函数实现readline函数:


     C++ Code 
    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
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
     
    /* recv()只能读写套接字,而不能是一般的文件描述符 */
    ssize_t recv_peek(int sockfd, void *buf, size_t len)
    {
        while (1)
        {

            int ret = recv(sockfd, buf, len, MSG_PEEK); // 设置标志位后读取后不清除缓冲区
            if (ret == -1 && errno == EINTR)
                continue;
            return ret;
        }
    }

    /* 读到' '就返回,加上' ' 一行最多为maxline个字符 */
    ssize_t readline(int sockfd, void *buf, size_t maxline)
    {
        int ret;
        int nread;
        char *bufp = buf;
        int nleft = maxline;
        int count = 0;

        while (1)
        {
            ret = recv_peek(sockfd, bufp, nleft);
            if (ret < 0)
                return ret; // 返回小于0表示失败
            else if (ret == 0)
                return ret; //返回0表示对方关闭连接了

            nread = ret;
            int i;
            for (i = 0; i < nread; i++)
            {
                if (bufp[i] == ' ')
                {
                    ret = readn(sockfd, bufp, i + 1);
                    if (ret != i + 1)
                        exit(EXIT_FAILURE);
                    
                    return ret + count;
                }
            }
            if (nread > nleft)
                exit(EXIT_FAILURE);
            nleft -= nread;
            ret = readn(sockfd, bufp, nread);
            if (ret != nread)
                exit(EXIT_FAILURE);

            bufp += nread;
            count += nread;
        }

        return -1;

    在readline函数中,我们先用recv_peek”偷窥“ 一下现在缓冲区有多少个字符并读取到bufp,然后查看是否存在换行符' '。如果存在,则使用readn连

    通换行符一起读取(清空缓冲区);如果不存在,也清空一下缓冲区, 且移动bufp的位置,回到while循环开头,再次窥看。注意,当我们调用readn读

    取数据时,那部分缓冲区是会被清空的,因为readn调用了read函数。还需注意一点是,如果第二次才读取到了' ',则先用count保存了第一次读取的

    字符个数,然后返回的ret需加上原先的数据大小。


    使用 readline函数也可以认为是解决粘包问题的一个办法,即以' '为结尾当作一条消息。对于服务器端来说可以在前面的fork程序的基础上把do_service函数更改如下:

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    void do_echoser(int conn)
    {
        char recvbuf[1024];
        while (1)
        {
            memset(recvbuf, 0, sizeof(recvbuf));
            int ret = readline(conn, recvbuf, 1024);
            if (ret == -1)
                ERR_EXIT("readline error");
            else if (ret  == 0)   //客户端关闭
            {
                printf("client close ");
                break;
            }

            fputs(recvbuf, stdout);
            writen(conn, recvbuf, strlen(recvbuf));
        }
    }

    客户端的更改也是类似的,不再赘述,测试输出也是正常的。

    参考:

    《Linux C 编程一站式学习》

    《TCP/IP详解 卷一》

    《UNP》

  • 相关阅读:
    Session服务器配置指南与使用经验
    string和byte[]的转换 (C#)
    错误1已授予对“SqlAccess...的友元访问解决方法
    网络视频会议 二
    Editplus配置环境变量
    TSC 条码打印机 Dll 说明
    源码C#事例网址
    C#中的日志类
    分页事例 比较好的
    dotnet 网络编程 tcp
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8472884.html
Copyright © 2011-2022 走看看