linux网络编程之socket(六):利用recv和readn函数实现readline函数
来源:互联网 发布:淘宝客cms免费 编辑:程序博客网 时间:2024/06/01 17:27
在前面的文章中,我们为了避免粘包问题,实现了一个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++ Code1
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++ Code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19void 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》
- linux网络编程之socket(六):利用recv和readn函数实现readline函数
- linux网络编程之socket(六):利用recv和readn函数实现readline函数
- linux网络编程之socket(六):利用recv和readn函数实现readline函数
- linux网络编程之socket(六):利用recv和readn函数实现readline函数
- linux网络编程之socket(六):利用recv和readn函数实现readline函数
- UNIX网络编程——利用recv和readn函数实现readline函数
- readn, writen和readline函数
- readn,writen和readline函数
- unix网络编程readn,writen,readline包裹函数
- readn,writen,readline函数
- recv函数和readline实现思想
- socket 网络编程recv()函数错误
- readn和writen函数, socket编程常用函数
- readn writen readline函数源代码
- UNIX 网络编程学习(7)--readn,writen,readline的实现
- 《UNIX网络编程 卷1》 笔记: readn和writen函数
- Unix网络编程之readline函数
- Unix网络编程之读写操作recv和send函数
- Python中的X[:,0]和X[:,1]
- Bitmap 图像解码以及缩略图生成----BitmapFactory类
- 【CF768G】The Winds of Winter 可持久化线段树 DFS序
- UVa10537 Toll! Revisited
- Java的JVM GC(Garbage Collection)垃圾回收原理机制及算法 .
- linux网络编程之socket(六):利用recv和readn函数实现readline函数
- 安卓代码简单实现电商购物车
- 解读 Redux 中间件的原理
- [未完待续]Clipboard提取或替换Windows系统剪贴板的文本内容
- App 后端架构设计方案 设计思想与最佳实践
- slf4j+logback实现日志存放到不同文件
- ionic多选框
- NYOJ63 小猴子下落(二叉树性质)
- linux 数组和指针