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

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

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

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

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

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

段。

因此,常见的应用层协议都是带有可变长字段的,字段之间的分隔符用换行'\n'的比用'\0'的更常见,如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;
    }
}

/* 读到'\n'就返回,加上'\n' 一行最多为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] == '\n')
            {
                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,然后查看是否存在换行符'\n'。如果存在,则使用readn连

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

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

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

使用 readline函数也可以认为是解决粘包问题的一个办法,即以'\n'为结尾当作一条消息。对于服务器端来说可以在前面的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\n");
            break;
        }

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

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

参考:

《Linux C 编程一站式学习》

《TCP/IP详解 卷一》

《UNP》

利用recv和readn函数实现readline函数的更多相关文章

  1. UNIX网络编程——利用recv和readn函数实现readline函数

    在前面的文章中,我们为了避免粘包问题,实现了一个readn函数读取固定字节的数据.如果应用层协议的各字段长度固定,用readn来读是非常方便的.例如设计一种客户端上传文件的协议,规定前12字节表示文件 ...

  2. Linux网络编程-readn函数、writen函数、readline函数实现

    readn函数功能:在网络编程的读取数据中,通常会需要用到一个读指定字节才返回的函数,linux系统调用中没有给出,需要自己封装. readn实现代码: int readn(int fd, void ...

  3. TCP粘包问题的解决方案02——利用readline函数解决粘包问题

      主要内容: 1.read,write 与 recv,send函数. recv函数只能用于套接口IO ssize_t recv(int sockfd,void * buff,size_t len,i ...

  4. 网络编程readn、writen和readline函数的编写

    readn   在Linux中,read的声明为: ssize_t read(int fd, void *buf, size_t count); 它的返回值有以下情形: 1.大于0,代表成功读取的字节 ...

  5. 自己封装一个readline函数实现服务器客户端回射

    实现的功能:一次只能读取一行,客户端输入之后,一回车,马上字符串传到服务器端并显示在终端,然后服务器端将字符串又传回给客户端. 服务器端可以接收多个客户端的连接请求,并fork一个子进程来进行服务. ...

  6. Python中readline()函数 去除换行符

    从Python中readline()函数读取的一行内容中含有换行符\n,很多时候我们需要处理不含有换行符的字符串,此时就要去掉换行符\n. 方法是使用strip()函数. 例子如下: f = open ...

  7. 利用闭包特性改写addEventListener的回调函数

    var numClicks = 0; document.addEventListener("click",function(){ alert( ++numClicks); },fa ...

  8. 利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法

    1.利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法 首先判断字符串的长度是否为0,如果是,直接返回字符串 第二,循环判断字符串的首部是否有空格,如 ...

  9. python练习题:利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法

    方法一: # -*- coding: utf-8 -*- # 利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法: def trim(s): whil ...

随机推荐

  1. CentOS 7 开放防火墙端口命令

    CentOS 7 开放防火墙端口 命令 最近公司新的server要求用CentOS7, 发现以前CentOS 6 系列中的 iptables 相关命令不能用了,查了下,发现Centos 7使用fire ...

  2. 【WCF】HTTP 无法注册 URL 进程,不具有此命名空间的访问权限

    背景   如题,在运行WCF宿主主机时,出现了问题.   捕获异常为:HTTP 无法注册 URL http://+:8000/WCF/.进程不具有此命名空间的访问权限(有关详细信息,请参见 http: ...

  3. mysql 数据库远程访问设置方法

    摘自: http://www.iteye.com/topic/418151 mysql数据库远程访问设置方法 1.修改localhost更改 "mysql" 数据库里的 " ...

  4. python3 使用openpyxl库读写excel(续)

    官网:https://openpyxl.readthedocs.io/en/stable/

  5. C/C++语言中变量作用域:局部变量,全局变量,文件级变量

    C/C++语言中的变量分为全局变量和局部变量. 这样的划分方式的根据是变量的可见范围或者叫做作用域. 1 局部变量 局部变量指的是定义在{}中的变量,其作用域也在这个范围内.尽管常见的局部变量都是定义 ...

  6. elasticSearch nested exist与missing查询

    elasticSearch nested查询,简单意义上,你可以理解为,它不会被索引,只是被暂时隐藏起来,而查询的时候,开关就是使用nested query/filter去查询 下面我有一个例子,是查 ...

  7. [Node.js] Child Process with fork() to handle heavy calculation process

    When build server, if we have a API endpoint requires some heavy calculation process, it will block ...

  8. 6. Activity life cycle

    An activity can exist in essentially three states: Resumed The activity is in the foreground of the ...

  9. (LeetCode 203)Remove Linked List Elements

    Remove all elements from a linked list of integers that have value val. ExampleGiven: 1 --> 2 --& ...

  10. TotalCommander使用方法,如何对图片批量重命名

    1 文件或文件夹重命名 F2 2 计算所有文件夹的大小 A/t+Shift+Enter.(这样对于文件的更新操作就更加快捷有效了,比如我的文档里面只有若干个子文件夹有更新,则别的都不用动,只要修改那些 ...