SOCK_NONBLOCK,accept4 阻塞与非阻塞SOCKET

http://www.cnblogs.com/sunada2005/p/3591378.html

谓阻塞方式的意思是指,当试图对该文件描述符进行读写时,如果当时没有东西可读,或者暂时不可写,程序就进入等待状态,直到有东西可读或者可写为止。

而对于非阻塞状态,如果没有东西可读,或者不可写,读写函数马上返回,而不会等待。

非阻塞,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高

简单的讲,阻塞就是GET,非阻塞就是PUT

阻塞好控制,不发送完数据程序不会走下去。但是对性能有影响。
非阻塞不太好控制,可能和能力有关,但是性能会得到很大提升。

阻塞式的编程方便。
非阻塞的编程不方便,需要程序员处理各种返回值

阻塞处理简单,非阻塞处理复杂

阻塞效率低,非阻塞效率高

阻塞模式,常见的通信模型为多线程模型,服务端accept之后,对每个socket创建一个线程去recv。逻辑上简单,适用于并发量小(客户端数目少),连续传输大数据量的情况下,比如文件服务器。还有就是在客户端recv服务器消息的时候也经常用,因为客户端就一个socket,用阻塞模式不影响效率,而且编程逻辑上要简单得多。

非阻塞模式,常见的通信模型为select模型和IOCP模型。适用于高并发,数据量小的情况,比如聊天室。客户端多的情况下,如果采用阻塞模式,需要开很多线程,影响效率。另外,客户端一般不采用非阻塞模式。

 

一、socket阻塞和非阻塞有哪些不同
1. 建立连接
阻塞方式下,connect首先发送SYN请求到服务器,当客户端收到服务器返回的SYN的确认时,则connect返回,否则的话一直阻塞。

非阻塞方式,connect将启用TCP协议的三次握手,但是connect函数并不等待连接建立好才返回,而是立即返回,返回的错误码为EINPROGRESS,表示正在进行某种过程。

2. 接收连接
阻塞模式下调用accept()函数,而且没有新连接时,进程会进入睡眠状态,直到有可用的连接,才返回。

非阻塞模式下调用accept()函数立即返回,有连接返回客户端套接字描述符,没有新连接时,将返回EWOULDBLOCK错误码,表示本来应该阻塞。

3. 读操作
阻塞模式下调用read(),recv()等读套接字函数会一直阻塞住,直到有数据到来才返回。当socket缓冲区中的数据量小于期望读取的数据量时,返回实际读取的字节数。当sockt的接收缓冲区中的数据大于期望读取的字节数时,读取期望读取的字节数,返回实际读取的长度。

对于非阻塞socket而言,socket的接收缓冲区中有没有数据,read调用都会立刻返回。接收缓冲区中有数据时,与阻塞socket有数据的情况是一样的,如果接收缓冲区中没有数据,则返回错误号为EWOULDBLOCK,表示该操作本来应该阻塞的,但是由于本socket为非阻塞的socket,因此立刻返回。遇到这样的情况,可以在下次接着去尝试读取。如果返回值是其它负值,则表明读取错误。

因此,非阻塞的re

4.写操作
对于阻塞Socket而言,如果发送缓冲区没有空间或者空间不足的话,write操作会直接阻塞住,如果有足够空间,则拷贝所有数据到发送缓冲区,然后返回.

对于写操作write,原理和read是类似的,非阻塞socket在发送缓冲区没有空间时会直接返回错误号EWOULDBLOCK,表示没有空间可写数据,如果错误号是别的值,则表明发送失败。如果发送缓冲区中有足够空间或者是不足以拷贝所有待发送数据的空间的话,则拷贝前面N个能够容纳的数据,返回实际拷贝的字节数。
尤其注意非阻塞的socket,在建立连接时要兼容处理返回EINPROGRESS情况,在接收连接、读操作、写操作时要兼容处理返回EWOULDBLOCK错误码的情况,以上情况并非连接异常,而是网络延时或者套接字缓冲区满造成的,一般不宜做异常处理(如返回异常关闭套接字)。

简单点理解就是:

同步,就是我调用一个功能,该功能没有结束前,我死等结果。
异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)。
阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者
同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞
阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回
--------------------- 
原文:https://blog.csdn.net/mayue_web/article/details/82873115 

 

(一)socket阻塞与非阻塞问题

在创建socket函数中的第二个参数types用来指定服务类型,在Linux2.6.17版本起,除了之前的SOCK_STREAM(流式服务)、SOCK_UGRAM(数据报服务)以外,又增加了SOCK_NONBLOCK和SOCK_CLOEXEC,分别表示将创建的socket设置为非阻塞的和用fork创建的子进程在子进程中关闭该socket;

第一个问题:如何将socket设置为非阻塞的?

第一种,fcntl系统调用函数是用来控制文件描述符常用的属性和行为,通过此函数可以修改socket属性;

#include <fcntl.h>
int fcntl(int fd,int cmd,...)
fd是要被操作的文件描述符,cmd参数指定执行何种类型操作,常见的cmd有以下几种:

获取文件描述符标志  ----> F_GETFD   设置文件名描述符标志----->F_SETFD 

获取文件描述符状态标志--->F_GETFL 设置文件名描述符状态标志----->F_SETFL

修改socket为非阻塞socket

int flags = fcntl(sockfd,F_GETFL,0);

fcntl(sockfd,F_SETFL,flags | SOCK_NONOBLOCK);

第二种:直接在创建socket时设置

int sockfd= socket(AF_INET,SOCK_STREAM | SOCK_NONOBLOCK,0);

注意accept返回的socket也是阻塞的,应用accept4函数将最后一个参数设置为SOCK_NONOBLOCK

int accept4(sockfd,(struct sockaddr*)&cli,&len,SOCK_NONBLOCK);

第二个问题:在完成三次握手之后,网络异常(掉线)或者客户端正常退出,对accept是否会有影响?

netstat -nt | grep hostport 查看此端口的TCP连接状态

掉线以及客户端退出都不会影响accept调用返回,由于accept只是从监听队列中取出连接,而不论连接状态处于何种状态,更不关心网络状态.不会因为监听队列中socket状态的变化而决定accept是否能调用成功。

第三个问题:关于socket选项的知识

#include <sys/socket.h>

int getsockopt(int sockfd,int level,int option_name,void *option_value,socklen_t *restrict option_len);

int setsockopt(int sockfd,int level,int option_name,const void *option_value,socklen_t  option_len);

level指定要操作那个协议的选项,通用socket选项的level是SOL_SOCKET,option_name 是指定选项的名字

常用的通用socket选项有:SO_REUSEADDR 重用本地址 、SO_RCVBUF --->TCP接受缓冲区大小、SO_SNDBUF --->TCP发送缓冲区大小、SO_LINGER 若有数据待发送则延缓关闭、TCP_MAXSEG  ---> TCP最大报文段大小 上述这几个必须在listen调用之前设置才有效。

socket选项一:SO_REUSEADDR

通过设置socket此选项可以强制使用处于time_wait状态的连接占用的socket地址,也可以通过修改内核参数/proc/sys/net/ipv4/tcp_tw_recycle来快速回收被关闭的连接socket,从而使得TCP连接根本不进入time_wait状态。进而允许本次程序重用socket地址

socket选项二:SO_RCVBUF SO_SNDBUF

SO_RCVBUF:TCP接受缓冲区的大小最小值为256字节 ,如果我们修改此大小小于256字节时,系统会忽略此次设置,最小值还是256字节

SO_SNDBUF:TCP发送缓冲区大小最小值是2048字节,是为了保证一个TCP连接有足够的空闲缓冲区处理拥塞,如果我们修改此大小,系统会将我们设置的值翻倍

也可以修改内核参数:/proc/sys/net/ipv4/tcp_rmem、/proc/sys/net/ipv4/tcp_wmem来强制TCP接受和发送缓冲区的大小没有最小值限制。

选项三:SO_LINGER

SO_LINGER用于控制close系统调用在关闭TCP连接时的行为,默认情况下,当我们使用close系统调用关闭socket时会立即返回,TCP模块将该socket对应的发送缓冲区的残留数据发送给对方

在设置此选项时,需要向函数传递Linger结构体:

struct linger

{

  int l_onoff;//0未开启,1开启

  int l_linger;//滞留时间 

}

根据linger结构体中两个变量的值不同,close调用会有不同的方式

情况一:l_onoff==0;此时SO_LINGER不起作用,close使用默认行为关闭socket;

情况二:l_onoff != 0 && l_linger == 0;此时close调用立即返回,将该socket对应的发送缓冲区的数据发给对方,同时给对方发送一个复位报文段。这种情况给服务器提供了一个异常终止连接的方法;

情况三:l_onoff != 0 && l_linger >0;此时close的行为取决于两个条件:1.该socket对应的发送缓冲区是否还有残留数据;2.该socket是阻塞的还是非阻塞的;

对于阻塞的socket,close将等待一段长为l_linger的时间等TCP模块将发送缓冲区的数据发给对方并得到对方确认,返回-1并设置EWOULDBLOCK(不需要重新读写);

对于非阻塞的socket,close立即返回,此时需要根据返回值和error来判断残留数据是否发送完毕;如果tcp缓冲区还有数据需要发送,数据能够正确的发送到对端;在发送的最后一个包会加FIN标志;如果另一端也要关闭发FIN时,本端就会发RST,因为本端的SOCKET已经关了。
--------------------- 
作者:zhangyin_blog 
来源:CSDN 
原文:https://blog.csdn.net/weixin_41966991/article/details/81942349 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值