readn

 

在Linux中,read的声明为:

ssize_t read(int fd, void *buf, size_t count);

它的返回值有以下情形:

1.大于0,代表成功读取的字节数

2.等于0,代表读取到了EOF,一般是对方关闭了socket的写端或者直接close

3.小于0,出现错误。

我们编写一个readn函数,声明与read一致,但是,readn在未出错或者fd没有关闭的情况下,会读满count个字节

ssize_t readn(int fd, void *buf, size_t count)
{
size_t nleft = count; //剩余的字节数
ssize_t nread; //用作返回值
char *bufp = (char*)buf; //缓冲区的偏移量 while(nleft > 0)
{
nread = read(fd, bufp, nleft);
if(nread == -1)
{
if(errno == EINTR)
continue;
return -1; // ERROR
}
else if(nread == 0) //EOF
break; nleft -= nread;
bufp += nread;
} return (count - nleft);
}

readn的返回值含义如下:

1.小于0,出错

2.等于0,对方关闭

3.大于0,但是小于count,对方关闭

4.count,代表读满count个字节

 

writen

 

write函数的声明如下:

ssize_t write(int fd, const void *buf, size_t count);

man手册中对write的返回值描述如下:

       On success, the number of bytes written is returned (zero indicates nothing was  writ‐

       ten).  On error, -1 is returned, and errno is set appropriately.

       If  count  is  zero and fd refers to a regular file, then write() may return a failure

       status if one of the errors below is detected.  If no errors are detected, 0  will  be

       returned  without  causing any other effect.  If count is zero and fd refers to a file

       other than a regular file, the results are not specified.

解释如下:

成功时,返回成功写入的字节数,否则返回-1,并设置相应的errno。

如果count为0,并且fd指向一个普通文件,那么当探测到错误时返回-1.如果没有错误发生,返回0,不会产生任何影响。

如果count为0,并且fd指向的不是普通文件,那么结果未定义。

我们不去追究write为0的情形。编写write如下:

ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft = count;
ssize_t nwrite;
const char *bufp = (const char*)buf; while(nleft > 0)
{
nwrite = write(fd, bufp, nleft);
if(nwrite <= 0) // ERROR
{
if(nwrite == -1 && errno == EINTR)
continue;
return -1;
} nleft -= nwrite;
bufp += nwrite;
} return count;
}

从代码中可以看出,writen要么写满count字节,要么失败。

 

readline

 

在网络编程中,很多协议是基于文本行的,例如HTTP和FTP,还有telnet,他们的消息每行都是以\r\n作为结束标志的。于是我们开发一个readline函数,声明如下:

ssize_t readline(int sockfd, void *usrbuf, size_t maxlen)

readline函数的语义是:

如果碰不到\n,那么读取maxlen-1个字节,最后一个位置补充\0。

否则读取到\n,在后面加一个\0。如果中间遇到EOF,直接返回0,而不是已经读取的字节数。

我们先给出一种低效的实现:

ssize_t readline_slow(int fd, void *usrbuf, size_t maxlen)
{
char *bufp = usrbuf; //记录缓冲区当前位置
ssize_t nread;
size_t nleft = maxlen - 1; //留一个位置给 '\0'
char c;
while(nleft > 0)
{
if((nread = read(fd, &c, 1)) < 0)
{
if(errno == EINTR)
continue;
return -1;
}else if(nread == 0) // EOF
{
break;
} //普通字符
*bufp++ = c;
nleft--; if(c == '\n')
break;
}
*bufp = '\0';
return (maxlen - nleft - 1);
}

这个的思路很简单,每次读取一个字节,直到遇到换行符为止。

这种实现是低效的,因为每次读取一个字节,都要进行一次系统调用。

在网络编程中,还有一个函数叫做recv,如下:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

它相对于read,多了一个flags选项。

有一个选项为MSG_PEEK,描述如下:

This flag causes the receive operation to return data from the beginning of the

receive queue without removing that data from the queue.   Thus,  a  subsequent

receive call will return the same data.

大致意思是它从内核中读取数据,但并不会将数据移除,所以这个flag起到了一个预览内核数据的作用。这样我们就可以先从内核中读取一大块数据,检查其中是否存在\n,如果不存在,这么将这些数据全部读取,如果存在,则读取到\n为止。

我们先实现recv_peek函数:

ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
int nread;
do
{
nread = recv(sockfd, buf, len, MSG_PEEK);
}
while(nread == -1 && errno == EINTR); return nread;
}

readline函数的实现如下:

ssize_t readline(int sockfd, void *usrbuf, size_t maxlen)
{
//
size_t nleft = maxlen - 1;
char *bufp = usrbuf; //缓冲区位置
size_t total = 0; //读取的字节数 ssize_t nread;
while(nleft > 0)
{
//预读取
nread = recv_peek(sockfd, bufp, nleft);
if(nread <= 0)
return nread; //检查\n
int i;
for(i = 0; i < nread; ++i)
{
if(bufp[i] == '\n')
{
//找到\n
size_t nsize = i+1;
if(readn(sockfd, bufp, nsize) != nsize)
return -1;
bufp += nsize;
total += nsize;
*bufp = 0;
return total;
}
} //没找到\n
if(readn(sockfd, bufp, nread) != nread)
return -1;
bufp += nread;
total += nread;
nleft -= nread;
}
*bufp = 0;
return maxlen - 1;
}

 

我们编写的这三个函数后面可以用于处理TCP分包问题,后面写文章叙述。

网络编程readn、writen和readline函数的编写的更多相关文章

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

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

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

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

  3. 【Socket】Socket网络编程常用的结构及函数小结

    名词解析 IP地址的作用是标示计算机的网卡地址,每台计算机都有一个IP地址: 端口是指计算机中为了标示在计算机中访问网络的不同程序而设的编号,并不是网卡接线的端口,而是不同程序的逻辑编号,并不是实际存 ...

  4. 网络编程API-下 (I/O复用函数)

    IO复用是Linux中的IO模型之中的一个,IO复用就是进程预先告诉内核须要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程进程处理.从而不会在单个IO上堵塞了. Linu ...

  5. UNIX网络编程读书笔记:shutdown函数

    终止网络连接的通常方法是调用close函数.不过close有两个限制,却可以使用shutdown来避免. close 把描述字的引用计数减1,仅在该计数变为0时才关闭套接口.使用shutdown可以不 ...

  6. TCP/IP网络编程之多种I/O函数

    send和recv函数 在之前的学习中,我们在不少示例中用到send和recv这两个函数,但一直没有详细解释过着两个函数中每个参数的含义.本节将介绍Linux平台下的send&recv函数 # ...

  7. UNIX网络编程读书笔记:poll函数

    poll函数提供的功能与select类似,不过在处理流设备时,它能够提供额外的信息. poll函数原型 #include <poll.h> int poll(struct pollfd * ...

  8. UNIX网络编程读书笔记:pselect函数

    函数原型 pselect函数是由POSIX发明的,其原型如下: #include <sys/select.h> #include <signal.h> #include < ...

  9. UNIX网络编程读书笔记:select函数

    select函数概况: select函数允许进程指示内核等待多个事件中的任何一个发生,并仅在有一个或多个事件发生或经历一段指定的时间后才唤醒它. 作为一个例子,我们可以调用select,告知内核仅在下 ...

随机推荐

  1. zlib编译不过(Error A2070)解决方法(转)

    原文转自 http://dearymz.blog.163.com/blog/static/2056574200871010027435/ 1.zlib是个很牛的东东,从http://www.zlib. ...

  2. 和菜鸟一起学c之gcc编译过程及其常用编译选项【转】

    转自:http://blog.csdn.net/eastmoon502136/article/details/8162626 版权声明:本文为博主东月之神原创文章,未经博主允许不得转载. 上篇文章,知 ...

  3. Daemon Process

    Daemon Process 守护进程(Daemon)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待    处理某些发生的事件.守护进程是一种很有用的进程.     Lin ...

  4. 获取Json对象的长度以及判断json对象是否为空

    (如有错敬请指点,以下是我工作中遇到并且解决的问题) = = = = = = = = = = = = = = = =  获取Json对象的长度  = = = = = = = = = = = = = = ...

  5. SpringBoot无XML配置

    SpringBoot,自己研究了好几天,以前也是没有接触过这类的框架,不过原理吧,也就是那么些个原理,毕竟都是Spring开源下的子框架. 好了,回归正题,今天晚上研究了好久,写出来了无配置文件的ja ...

  6. 【linux高级程序设计】(第十一章)System V进程间通信 2

    消息队列 消息队列是消息的链式队列,模型如下: 包括两种数据结构: msqid_ds消息队列数据结构 msg消息队列数据结构 struct msg_msg{ struct list_head m_li ...

  7. C#中使用7Z进行压缩解压

    SevenZipSharp相关文档下载地址: http://sevenzipsharp.codeplex.com/releases/view/51254 1. 解决方案中添加引用:SevenZipSh ...

  8. MVC5的坑

    事情是这样的,今天在写一个功能模块的时候,创建的方法,到controller里,死活为null 以前从没出现这种情况啊,但是区别是这个代码是多层跳转进来的,难道是页面跳转太多,还记得之前的model, ...

  9. 理解Java垃圾回收

    stop-the-world 原文链接:http://www.cubrid.org/blog/de... 了解Java的垃圾回收(GC)原理能给我们带来什么好处?对于软件工程师来说,满足技术好奇心可算 ...

  10. vue插槽slot的理解与使用

    一.个人理解及插槽的使用场景 刚开始看教程我的疑惑是为什么要用插槽,它的使用场景是什么,很多解释都是“父组件向子组件传递dom时会用到插槽”,这并不能很好的解决我的疑惑.既然你用了子组件,你为什么要给 ...