recv send 阻塞和非阻塞


recv send 阻塞和非阻塞
   

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

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

 

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

 

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

 

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

 

第四个参数一般置0

 

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

 

1send先比较待发送数据的长度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到剩余空间里(注意并不是sends的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copys的发送缓冲区的剩余空间里)。

 

 

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

 

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

 

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

 

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

 

 

 

2. recv函数

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

 

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

 

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

 

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

 

第四个参数一般置0

 

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

 

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

 

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

 

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

 

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

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

我们拿最常用的sendrecv两个函数来说吧...
比如你调用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)
{
    //
由于是非阻塞的模式,所以当errnoEAGAIN,表示当前缓冲区已无数据可读
    //
在这里就当作是该次事件已处理处.
    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,errnoEAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间的阻塞在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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录第1章 在PC上安装双系统 1 1.1前期准备 1 1.2安装Fedora10过程详解 1 1.3安装Fedora10后的配置 9 1.3.1外观及Gnome桌面的使用 9 1.3.2网卡配置及连接IPV6网站 14 1.3.3升级及安装软件 17 1.3.4系统服务配置 18 1.3.5安装Linux下的虚拟机 19 1.4修复双系统启动 22 第2章 针对ARM目标板的交叉开发 24 2.1交叉编译环境 24 2.2交叉调试方法 25 2.3共享库相关 28 2.3.1基本的共享库使用知识点 28 2.3.2交叉编译的共享库 29 第3章 Linux下的串口使用 32 3.1 Linux下的串口设备 32 3.1.1串口简介 32 3.1.2串口与Linux终端设备、控制台 34 3.2 Linux下的“超级终端”工具 38 3.2.1 C-kermit的安装与使用 38 3.2.2简单的串口单双工通信实验 40 3.3串口编程总结 42 3.3.1相关数据结构 42 3.3.2相关函数 46 3.3.2串口编程实例 54 第4章 NFS交叉开发环境 59 4.1配置NFS服务 59 4.1.1设置共享目录 59 4.1.2启动和停止nfs 服务 59 4.2 NFS根文件系统启动 59 4.3 NFS交叉编译 60 第5章 创建根文件系统 62 5.1移植Busybox 62 5.2移植Glibc库 63 5.3完善根文件系统 63 5.4制作/烧写yaffs映像文件 66 第6章 MiniGUI移植与开发 67 6.1 MiniGUI在PC机上的开发环境的建立 67 6.1.1安装QVFB 67 6.1.2在PC上安装MiniGUI 68 6.2 MiniGUI在S3C2440上的开发环境的建立 71 6.2.1交叉编译相关的开源库 71 6.2.2针对本系统的MiniGUI输入引擎设计 73 6.2.3交叉编译并安装MiniGUI 74 6.2.4在mini2440开发板上运行MiniGUI示例程序 76 6.3 MiniGUI编程小结 78 6.3.1创建MiniGUI程序 78 6.3.2编译MiniGUI程序 81 6.3.3对话框程序 83 6.3.4自定义控件编写 88 6.3.5加入自己的线程 92 第7章 燃气监测系统的开发、调试过程 94 7.1项目简介 94 7.2开发设计 94 7.3调试过程 96 参考文献 100 附录 102 附录1电子科技大学清水河校区802.x上网认证脚本——作者lili(可以用来做学习Shell编程的实例哦) 102 附录2针对mini2440开发板的输入引擎文件mini2440.h和mini2440.c(原创) 108 附录3 VIM配置文件~/.vimrc内容 116
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值