前面的文章中,我们为了避免粘包问题,实现了一个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函数:

/* 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'就返回,一行最多为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”偷窥“ 一下现在缓冲区有多少个字符,然后查看是否存在换行符'\n',如果存在,则使用readn连通换行符一起读取,如果不存在,则也先将前面的数据读取进bufp, 且移动bufp的位置,回到while循环开头,再从当前bufp位置窥看,注意,当我们调用readn读取数据时,那部分缓冲区是会被清空的,因为readn调用了read函数,还需注意一点是,如果第二次才读取到了'\n',则先用count保存了第一次读取的字符个数,然后返回的ret需加上原先的数据大小。

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

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));
}
}

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

UNIX网络编程——利用recv和readn函数实现readline函数的更多相关文章

  1. UNIX网络编程——I/O复用:select和poll函数

    我们看到TCP客户同时处理两个输入:标准输入和TCP套接字.我们遇到的问题是就在客户阻塞于(标准输入上)fgets调用,服务器进程会被杀死.服务器TCP虽然正确的给客户TCP发送了一个FIN,但是既然 ...

  2. UNIX网络编程——利用ARP和ICMP协议解释ping命令

    一.MTU 以太网和IEEE 802.3对数据帧的长度都有限制,其最大值分别是1500和1492字节,将这个限制称作最大传输单元(MTU,Maximum Transmission Unit)      ...

  3. UNIX网络编程——select函数的并发限制和 poll 函数应用举例

    一.用select实现的并发服务器,能达到的并发数,受两方面限制 1.一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n来调整或者使用setrlimit函数设置,  ...

  4. UNIX网络编程——客户/服务器心搏函数

    阅读此博客时,可以参考以前的博客<<UNIX网络编程--socket的keep-alive>>和<<UNIX网络编程--套接字选项(心跳检测.绑定地址复用)> ...

  5. UNIX网络编程——UDP 的connect函数(改进版)

    上一篇我们提到,除非套接字已连接,否则异步错误是不会返回到UDP套接字的.我们确实可以给UDP套接字调用connect,然而这样做的结果却与TCP连接大相径庭:没有三次握手.内核只是检查是否存在立即可 ...

  6. UNIX网络编程——使用select函数编写客户端和服务器

    首先看原先<UNIX网络编程--并发服务器(TCP)>的代码,服务器代码serv.c: #include<stdio.h> #include<sys/types.h> ...

  7. 《Unix 网络编程》14:高级 I/O 函数

    高级 I/O 函数 ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ...

  8. UNIX网络编程——getsockname和getpeername函数

    UNIX网络编程--getsockname和getpeername函数   来源:网络转载   http://www.educity.cn/linux/1241293.html     这两个函数或者 ...

  9. 【unix网络编程第三版】阅读笔记(五):I/O复用:select和poll函数

    本博文主要针对UNP一书中的第六章内容来聊聊I/O复用技术以及其在网络编程中的实现 1. I/O复用技术 I/O多路复用是指内核一旦发现进程指定的一个或者多个I/O条件准备就绪,它就通知该进程.I/O ...

随机推荐

  1. POJ2774 很长的信息

    Description Little cat在Byterland的首都读物理专业.这些天他收到了一条悲伤地信息:他的母亲生病了.担心买火车票花钱太多(Byterland是一个巨大的国家,因此他坐火车回 ...

  2. poj 1279 半平面交核面积

    Art Gallery Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 6668   Accepted: 2725 Descr ...

  3. [BZOJ]1011 遥远的行星(HNOI2008)

    由eps引发的血案. Description 直线上N颗行星,X=i处有行星i,行星J受到行星I的作用力,当且仅当i<=A*J.此时J受到作用力的大小为 Fi->j=Mi*Mj/(j-i) ...

  4. bzoj1132[POI2008]Tro 计算几何

    1132: [POI2008]Tro Time Limit: 20 Sec  Memory Limit: 162 MBSubmit: 1722  Solved: 575[Submit][Status] ...

  5. [bzoj省选十连测推广赛2]T2七彩树

    抄自:http://blog.csdn.net/coldef/article/details/61412577 当时看了就不会,看了别人的题解不懂怎么维护,最后抄了个代码....... 给定一棵n个点 ...

  6. OpenCV环境搭建(一)

    此环境搭建是OpenCV的python(一下简称py)开发环境搭建,建立在py3的环境和语法上实现的. windows系统搭建 系统环境:windows 10 + python 3.6 + OpenC ...

  7. sb error

    width: $("#StudentManagement").parent().width(), height: $("#StudentManagement") ...

  8. MockHttpServletRequestBuilder中content和param的区别

    结论: Mock将URL的参数和通过使用param添加的参数添加到request中的parameter中(url参数) 而将content内容.类型并没有进行解析,直接添加到request的conte ...

  9. 面向对象+canvas 倒计时

    效果参照网上的,用面向对象改写了一下,只写了自己需要的部分. 1.效果: 实现: //html <canvas id="canvas" width="800px&q ...

  10. Linux系统格式化新磁盘并挂载分区

    Linux系统格式化新磁盘并挂载分区 在虚拟机的设置界面中,我们可以选择添加硬盘 添加好硬盘后,我们输入命令fdisk -l 看到有一个未经分区的硬盘 Fdisk命令编辑这个硬盘 输入n创建分区,p选 ...