前面的文章中,我们为了避免粘包问题,实现了一个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. iOS开发-UI基础Demo

    现在更多的学习资料都是xCode4.X的,发现xCode6.1还是很多东西,如果有正在学习iOS开发的可以通过Demo简单了解下iOS的UI开发~ 1.新建单视图文件: 2.新建项目名称,语言选择OC ...

  2. 内存泄漏 Memory Leaks 内存优化 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  3. Spring boot基础:配置文件配置变量、多环境的配置

    一.配置 resources下面application.properties 1.普通配置 resources下面application.properties,比如写上:server.port=909 ...

  4. PHP OAuth 2.0 Server

    PHP OAuth 2.0 Server PHP OAuth 2.0 Server ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️ ...

  5. 微信小程序app配置指南

    //app.json页面 { //页面注册,有几个页面都要在pages里面注册 "pages":[ "pages/index/index", "pag ...

  6. 计算两端yuv视频流中每一帧的ssim值

    方法同上一篇,仅仅不多这里在计算的时候用了opencv1的接口,出现了一些问题.最后总算攻克了. 程序: #include <stdlib.h> #include <stdio.h& ...

  7. 在线sass编译器

    工作中,我们可能遇到突发情况(无法安装考拉,gulp以及webpack以及其它的自动化工具),我们这时就要用即时编译工具了,那么它就是你的首选: http://tool.oschina.net/

  8. 从零开始学JavaScript一(简介)

    概要:JavaScript的组成. 各个组成部分的作用 .   一.JavaScript是一种专为与网页交互而设计的脚本语言,它的的组成  Javascript  ECMAScript(核心) DOM ...

  9. VScode-Go can't load package: package .: no buildable Go source files in

    在VScode中调试Go程序时提示: can't load package: package .: no buildable Go source files in d:\my_workspace\go ...

  10. jQuery之前端国际化jQuery.i18n.properties[转]

    http://www.ibm.com/developerworks/cn/web/1305_hezj_jqueryi18n/ jQuery.i18n.properties是一款轻量级的jQuery国际 ...