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

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分包问题,后面写文章叙述。

时间: 2024-10-11 08:36:04

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

Linux网络编程-readn函数实现

readn函数功能:在网络编程的读取数据中,通常会需要用到一个读指定字节才返回的函数,linux系统调用中没有给出,需要自己封装. readn实现代码: int readn(int fd, void *vptr, size_t n) { size_t nleft = n; //readn函数还需要读的字节数 ssize_t nread = 0; //read函数读到的字节数 unsigned char *ptr = (char *)vptr; //指向缓冲区的指针 while (nleft >

网络编程中shut_down和close()函数的区别

在Linux C网络编程中,一共有两种方法来关闭一个已经连接好的网络通信,它们就是close函数和shutdown函数,它们的函数原型分别为: 1 #include<unistd.h> 2 int close(int sockfd) 3 //返回:0--成功, 1--失败 4   5 #include<sys/socket.h> 6 int shutdown(int sockfd, int howto) 7 //返回:0--成功, 1--失败 对一个tcp socket调用clos

一些在PHP网络编程中会遇到的函数简单介绍

PHP网络编程函数介绍 1.     Fgetcsv函数-解析读入的行并找出csv格式的字段. Fgetcsv函数可以解析读入的行并找出csv格式的字段,然后返回一个包含这些字段的数组. Fgetcsv()函数参数的说明: 参数 说明 Handle 必选参数.打开文件后返回文件标识指针 Length 必选参数.指定一行最多显示的字符数,该值大于csv文件最长行的字符数. Delimiter 可选参数.文件的分隔符 Enclosure 可选参数.文件的分隔符 [示例]获取文件中每行用逗号分隔后所获

Unix网络编程随手记——IP处理函数inet_aton()、gethostbyname()等

IP地址实质上就是一个32位的无符号整数,用如下结构体存放 1 struct in_addr 2 { 3 unsigned int s_addr; 4 }; 由于历史原因,虽然IP地址只是个标量,却用一个结构体来存储. 由于主机可以有不同的主机字节顺序,即大端机或小端机.但TCP/IP定义了统一的网络字节顺序,大端字节顺序. Unix提供了两个函数可在主机字节和网络字节间实现转换: 一.htonl()和ntohl() 在Linux系统下: #include <arpa/inet.h> 有些系统

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

IO复用是Linux中的IO模型之中的一个,IO复用就是进程预先告诉内核须要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程进程处理.从而不会在单个IO上堵塞了. Linux中.提供了select.poll.epoll三种接口函数来实现IO复用. 1.select函数 #include <sys/select.h> #include <sys/time.h> int select(int nfds, fd_set *readfds, fd_set *wr

C++中实现TCP/UDP网络编程的流程和主要函数使用方法说明

socket通讯 网络字节序 小端法: 高位存高地址,低位存低地址. (intel架构的存储方式) 大端法:高位存低地址,低位存高地址.(网络传输的方式) #include <arpa/inet.h> // 将本地转网络,转IP 转192.168.1.11->string->atoi->int->htonl->网络字节序,可以使用 int inet_pton(); 进行直接转换 uint32_t htonl(uint32_t hostlong); // 本地转网络

Linux网络编程中tcp_server和tcp_client函数的封装

本文的主要目的是将server套接字和client套接字的获取,做一个简易的封装,使用C语言完成.   tcp_server   服务器端fd的获取主要分为以下几步: 1.创建socket,这一步仅仅创建一个socket,没有任何特性的属性. 2.绑定网卡和port,一块主机可能有多块网卡,如果我们使用INADDR_ANY,意味着后面接受的TCP连接可以绑定在任意一块网卡上. 例如某台主机的ip地址有两个:192.168.44.136.10.1.1.4,假设绑定的ip采用INADDR_ANY,端

UNIX网络编程——客户/服务器心搏函数 (转)

下面是关于回送客户和服务器程序开发一些简单的心搏函数.这些函数可以发现对端主机或到对端的通信路径的过早失效.         在给出这些函数之前我们必须提出一些警告.首先,有人会想到使用TCP的保持存活特性(SO_KEEPALIVE套接字选项)来提供这种功能,然而TCP得在连接已经闲置2小时之后才发送一个保持存活探测段.意识到这一点以后,他们的下一个问题是如何把保持存活参数改为一个小得多的值(往往是在秒钟的量级),以便更快的检测到失效.尽管缩短TCP的保持存活定时器参数在许多系统上确实可行,但是

【python】网络编程-套接字常用函数