recv、send、read、write之间的联系与区别

参考:
https://blog.csdn.net/u010765526/article/details/89424103
https://blog.csdn.net/qq_36528114/article/details/79543668
https://blog.csdn.net/CODINGCS/article/details/51475820


1. read

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

read()函数是负责从fd中读取内容。当读成功时,read()返回实际所读的字节数,如果返回的值是0,表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。我们写一个自己的读函数:

int my_read(int fd,void *buffer,int length) 
{
    int bytes_left; 
    int bytes_read; 
    char *ptr; 

    bytes_left=length; 

    while(bytes_left>0) 
    {
        bytes_read = read(fd,ptr,bytes_read); 
        if (bytes_read < 0) 
        {
            if(errno==EINTR)
                bytes_read=0; 
            else 
                return(-1); 
        }
        else if(bytes_read==0) 
            break; 
        bytes_left-=bytes_read; 
        ptr+=bytes_read; 
    }

    return(length-bytes_left); 
}

2、write

include 

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

write()函数将buf中的nbytes字节内容写入文件描述符fd,成功时返回写的字节数,失败时返回-1并设置errno变量。在网络程序中,当我们向套接字文件描述符写时有两可能:
1)write的返回值大于0,表示写了部分或者是全部的数据,这样我们用一个while循环来不停的写入,但是循环过程中的buf参数和nbyte参数得由我们来更新。也就是说,网络写函数是不负责将全部数据写完之后在返回的。
2)返回的值小于0,此时出现了错误,我们要根据错误类型来处理:
如果错误为EINTR表示在写的时候出现了中断错误。
如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。
为了处理以上的情况,我们自己编写一个写函数来处理这几种情况。

int my_write(int fd,void *buffer,int length) 
{ 
    int bytes_left; 
    int written_bytes; 
    char *ptr; 

    ptr=buffer; 
    bytes_left=length; 

    while(bytes_left>0) 
    { 
        /* 开始写*/ 
        written_bytes=write(fd,ptr,bytes_left); 
        if(written_bytes<=0) /* 出错了*/ 
        {        
            if(errno==EINTR) /* 中断错误我们继续写*/ 
                written_bytes=0; 
            else             /* 其他错误没有办法,只好撤退了*/ 
                return(-1); 
        } 

        bytes_left-=written_bytes; 
        ptr+=written_bytes;     /* 从剩下的地方继续写  */ 
    } 

    return(0); 
} 

2.1 write 在阻塞和非阻塞下的区别

阻塞下
当内核缓冲区大小不足以容纳需要写入的 nbytes,write一直阻塞等待,直到缓冲区大小 >= nbytes 时,write一次性copy,等待的过程中,有可能会被信号中断。 当内核缓冲区大小能够容纳需要写入的nbytes,write copy到缓冲区,并返回 nbytes。

非阻塞
坚持的原则是有多少写多少,如果内核缓冲区空间足够,同阻塞情况,一次性copy;如果缓冲区不够,则缓冲区的大小是有多少就写入多少,并返回该值,显然返回值小于nbytes,此种情况需要循环的方式将nbytes数据写到缓冲区;如果缓冲区是满的,则返回-1,并置error值为EAGAIN 或 EWOULDBLOCK。

write 阻塞下 非阻塞
缓冲区满 一直等待 返回-1,并置error为EAGAIN
有缓冲区,但不够指定写的大小 一直等待,直到有足够空间 写入一部分,并返回该写入的部分大小
有缓冲区,而且大小也够 一次性写入 一次性写入

3、recv & send

recv和send函数提供了和read和write差不多的功能,不过它们提供了第四个参数来控制读写操作。

include 

include 

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

int send(int sockfd,void *buf,int len,int flags); 

前面的三个参数和一样,第四个参数可以是0或者是以下的组合:

| MSG_DONTROUTE | 不查找表 | 

| MSG_OOB | 接受或者发送带外数据 | 

| MSG_PEEK | 查看数据,并不从系统缓冲区移走数据 | 

| MSG_WAITALL | 等待所有数据 | 

MSG_DONTROUTE:是send函数使用的标志。这个标志告诉IP,目的主机在本地网络上面,没有必要查找表。这个标志一般用网络诊断和路由程序里面。

MSG_OOB:表示可以接收和发送带外的数据。关于带外数据我们以后会解释的。

MSG_PEEK:是recv函数的使用标志。表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容,这样下次读的时候仍然是一样的内容。一般在有多个进程读写数据时可以使用这个标志。

MSG_WAITALL:是recv函数的使用标志。表示等到所有的信息到达时才返回。使用这个标志的时候recv会一直阻塞,直到指定的条件满足或者是发生了错误。

1)当读到了指定的字节时,函数正常返回。返回值等于len;

2)当读到了文件的结尾时,函数正常返回。返回值小于len;

3)当操作发生错误时返回-1,且设置错误为相应的错误号(errno)。
MSG_NOSIGNAL:is a flag used by send() in some implementations of the Berkeley sockets API.

This flag requests that the implementation does not to send a SIGPIPE signal on errors on stream oriented sockets when the other end breaks the connection. The EPIPE error is still returned as normal.

Though it is in some Berkely sockets APIs (notably Linux) it does not exist in what some refer to as the reference implementation, FreeBSD, which instead uses a socket option SO_NOSIGPIPE?.

对于服务器端,我们可以使用这个标志。目的是不让其发送SIG_PIPE信号,导致程序退出。

如果flags为0,则和read、write一样的操作。还有其它的几个选项,不过我们实际上用的很少。可以查看 Linux Programmer’s Manual得到详细解释。

你可能感兴趣的:(网络编程)