recv send 阻塞和非阻塞

nt send( SOCKET s, const char FAR *buf, int len, int flags );

不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。

 

该函数的第一个参数指定发送端套接字描述符;

 

第二个参数指明一个存放应用程序要发送数据的缓冲区;

 

第三个参数指明实际要发送的数据的字节数;

 

第四个参数一般置0。

 

这里只描述同步Socket的send函数的执行流程。当调用该函数时,

 

(1)send先比较待发送数据的长度len和套接字s的发送缓冲的长度, 如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;

(2)如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议 还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len

(3)如果len大于剩余空间大小,send就一直等待协议把s的发送缓冲中的数据发送完

(4)如果len小于剩余 空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。

 

 

如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。

 

要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如 果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执 行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回 SOCKET_ERROR)

 

注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。

 

通过测试发现,异步socket的send函数在网络刚刚断开时还能发送返回相应的字节数,同时使用select检测也是可写的,但是过几秒钟之后,再send就会出错了,返回-1。select也不能检测出可写了。

 

 

 

2. recv函数

int recv( SOCKET s, char FAR *buf, int len, int flags);

 

不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。该函数的第一个参数指定接收端套接字描述符;

 

第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;

 

第三个参数指明buf的长度;

 

第四个参数一般置0。

 

这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时,

 

(1)recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,

 

(2)如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数 据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),

 

recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。

 

注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。

阻塞就是干不完不准回来,   
非组赛就是你先干,我现看看有其他事没有,完了告诉我一声

我们拿最常用的send和recv两个函数来说吧... 
比如你调用send函数发送一定的Byte,在系统内部send做的工作其实只是把数据传输(Copy)到TCP/IP协议栈的输出缓冲区,它执行成功并不代表数据已经成功的发送出去了,如果TCP/IP协议栈没有足够的可用缓冲区来保存你Copy过来的数据的话...这时候就体现出阻塞和非阻塞的不同之处了:对于阻塞模式的socket send函数将不返回直到系统缓冲区有足够的空间把你要发送的数据Copy过去以后才返回,而对于非阻塞的socket来说send会立即返回WSAEWOULDDBLOCK告诉调用者说:"发送操作被阻塞了!!!你想办法处理吧..." 
对于recv函数,同样道理,该函数的内部工作机制其实是在等待TCP/IP协议栈的接收缓冲区通知它说:嗨,你的数据来了.对于阻塞模式的socket来说如果TCP/IP协议栈的接收缓冲区没有通知一个结果给它它就一直不返回:耗费着系统资源....对于非阻塞模式的socket该函数会马上返回,然后告诉你:WSAEWOULDDBLOCK---"现在没有数据,回头在来看看"

 

 

读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取:
while(rs)
{
buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);
if(buflen < 0)
{
    // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读
    // 在这里就当作是该次事件已处理处.
    if(errno == EAGAIN)
     break;
    else
     return;
   }
   else if(buflen == 0)
   {
     // 这里表示对端的socket已正常关闭.
   }
   if(buflen == sizeof(buf)
     rs = 1;   // 需要再次读取
   else
     rs = 0;
}

还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回-1表示出错。在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send()内部,但暂没有更好的办法.

ssize_t socket_send(int sockfd, const char* buffer, size_t buflen)
{
ssize_t tmp;
size_t total = buflen;
const char *p = buffer;

while(1)
{
    tmp = send(sockfd, p, total, 0);
    if(tmp < 0)
    {
      // 当send收到信号时,可以继续写,但这里返回-1.
      if(errno == EINTR)
        return -1;

      // 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满,
      // 在这里做延时后再重试.
      if(errno == EAGAIN)
      {
        usleep(1000);
        continue;
      }

      return -1;
    }

    if((size_t)tmp == total)
      return buflen;

    total -= tmp;
    p += tmp;
}

return tmp;
}

时间: 2024-08-27 11:20:04

recv send 阻塞和非阻塞的相关文章

科学知识:同步、异步、阻塞和非阻塞区别_其它综合

简单点说: 阻塞就是干不完不准回来,一直处于等待中,直到事情处理完成才返回: 非阻塞就是你先干,我先看看有其他事没有,一发现事情被卡住,马上报告领导. 我们拿最常用的send和recv两个函数来说吧... 比如你调用send函数发送一定的Byte,在系统内部send做的工作其实只是把数据传输(Copy)到TCP/IP协议栈的输出缓冲区,它执行成功并不代表数据已经成功的发送出去了,如果TCP/IP协议栈没有足够的可用缓冲区来保存你Copy过来的数据的话...这时候就体现出阻塞和非阻塞的不同之处了:

同步、异步、阻塞和非阻塞的概念

在进行网络编程时,我们常常见到同步.异步.阻塞和非阻塞四种调用方式.这些方式彼此概念并不好理解.下面是我对这些术语的理解.    同步         所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回.按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等).但是一般而言,我们在说同步.异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务.最常见的例子就是 SendMessage.该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返

再谈IO的异步,同步,阻塞和非阻塞

原本转过一个<六种Socket I/O模型幽默讲解>,里面用比喻的方法讲解各种IO,但说到底那个时候我对同步异步这些还是只知其表.还未能完全理解异步和同步,现在觉得清晰一些了.总结一下. 前提概要: IO的过程: 整个IO的过程其实是应用发起IO的请求,到应用获取到IO请求数据的中间过程. 这个中间,其实主要的时间就是系统准备数据的过程.这也是异步技术的优化所在. 对系统调用的理解: 首先,我们要明确一点.IO的操作属于一种系统调用.也就是应用在运行中,进入到内核代码来执行某些重要的操作. 其

同步与异步、阻塞与非阻塞

这是两对概念,用在不同的语境会有一些不同的含义,不能一概而论. 整体来说,同步就是两种东西通过一种机制实现步调一致,异步是两种东西不必步调一致.   一.同步调用与异步调用: 在用在调用场景中,无非是对调用结果的不同处理. 我理解同步调用就是调用一但返回,就能知道结果,而异步是返回时不一定知道结果,还得通过其他机制来获知结果,如: a.   状态b.   通知c.   回调函数 这里的同步调用不一定会阻塞,例如立即返回失败的结果. 而异步调用立即返回时,你还拿不到结果的.   二.同步线程与异步

并发、并行、同步、异步、阻塞、非阻塞

最近在写爬虫 ,对于这几个概念比较模糊,所以特意学习了一下. 进程(process):进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进 行资源分配和调度的一个独立单位. 线程(thread):线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的 基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存 器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 一个线程可以创建和撤销另一个线

深入CSocket编程之阻塞和非阻塞模式

本文适合初中级读者阅读 有时,花上几个小时阅读.调试.跟踪优秀的源码程序,能够更快地掌握某些技术关键点和精髓.当然,前提是对这些技术大致上有一个了解. 我通过几个采用 CSocket 类编写并基于 Client/Server (客户端 / 服务端)的网络聊天和传输文件的程序 ( 详见: 源代码参考 ) ,在调试这些程序的过程中,追踪深入至 CSocket 类核心源码 Sockcore.cpp , 对于CSocket 类的运行机制可谓是一览无遗,并且对于阻塞和非阻塞方式下的 socket 程序的编

js同步、异步、阻塞与非阻塞的知识点

  当你们在说同步,异步,阻塞,非阻塞的时候到底在说什么? 一个术语的描述应该有一个目标对象吧 那些说同步,异步的家伙,从来不说这个目标对象是谁,在程序的运行过程中,有很多参与者,到底这个同步,异步用来描述谁的呢? 个人理解,同步与异步描述的是一种行为方式,阻塞与非阻塞描述的是一种状态. 在js中,我们说同步,是说同步调用,举个栗子:  代码如下   function A(){           B();           C();   } 那么A对B,C发起了同步调用,这里的主体是A.在执

同步和异步, 阻塞和非阻塞, Reactor和Proactor

http://www.artima.com/articles/io_design_patterns.html http://note.sdo.com/u/1434409594/n/lvRFW~kjR2N0LX0nE004_r http://www.cnblogs.com/xuxm2007/archive/2011/08/15/2139808.html, 图示   我看了些blog, 很少有人能说清楚的, 而且很多人的理解根本就不对 通俗的说 阻塞和非阻塞, 等不等的问题, 傻等就是阻塞, 不等就

网络IO之阻塞、非阻塞、同步、异步总结

1.前言 在网络编程中,阻塞.非阻塞.同步.异步经常被提到.unix网络编程第一卷第六章专门讨论五种不同的IO模型,Stevens讲的非常详细,我记得去年看第一遍时候,似懂非懂,没有深入理解.网上有详细的分析:http://blog.csdn.net/historyasamirror/article/details/5778378.我结合网上博客和书总结一下,加以区别,加深理解. 2.数据流向 网络IO操作实际过程涉及到内核和调用这个IO操作的进程.以read为例,read的具体操作分为以下两个