Socket阻塞模式和⾮阻塞模式
阻塞I/O模型:
简介:进程会⼀直阻塞,直到数据拷贝完成
应⽤程序调⽤⼀个IO函数,导致应⽤程序阻塞,等待数据准备好。如果数据没有准备好,⼀直等待….数据准备好了,从内核拷贝到⽤户空间,IO函数返回成功指⽰。
阻塞I/O模型图:在调⽤recv()/recvfrom()函数时,发⽣在内核中等待数据和复制数据的过程。
当调⽤recv()函数时,系统⾸先查是否有准备好的数据。如果数据没有准备好,那么系统就处于等待状态。当数据准备好后,将数据从系统缓冲区复制到⽤户空间,然后该函数返回。在套接应⽤程序中,当调⽤recv()函数时,未必⽤户空间就已经存在数据,那么此时recv()函数就会处于等待状态。
当使⽤socket()函数和WSASocket()函数创建套接字时,默认的套接字都是阻塞的。这意味着当调⽤Windows Sockets API不能⽴即完成时,线程处于等待状态,直到操作完成。
并不是所有Windows Sockets API以阻塞套接字为参数调⽤都会发⽣阻塞。例如,以阻塞模式的套接字为参数调⽤bind()、listen()函数时,函数会⽴即返回。将可能阻塞套接字的Windows Sockets API调⽤分为以下四种:
1.输⼊操作: recv()、recvfrom()、WSARecv()和WSARecvfrom()函数。以阻塞套接字为参数调⽤该函数接收数据。如果此时套接字缓冲区内没有数据可读,则调⽤线程在数据到来前⼀直睡眠。
2.输出操作: send()、sendto()、WSASend()和WSASendto()函数。以阻塞套接字为参数调⽤该函数发送数据。如果套接字缓冲区没有可⽤空间,线程会⼀直睡眠,直到有空间。
3.接受连接:accept()和WSAAcept()函数。以阻塞套接字为参数调⽤该函数,等待接受对⽅的连接请求。如果此时没有连接请求,线程就会进⼊睡眠状态。
4.外出连接:connect()和WSAConnect()函数。对于TCP连接,客户端以阻塞套接字为参数,调⽤该函数向服务器发起连接。该函数在收到服务器的应答前,不会返回。这意味着TCP连接总会等待⾄少到服务器的⼀次往返时间。
使⽤阻塞模式的套接字,开发⽹络程序⽐较简单,容易实现。当希望能够⽴即发送和接收数据,且处理的套接字数量⽐较少的情况下,使⽤阻塞模式来开发⽹络程序⽐较合适。
阻塞模式套接字的不⾜表现为,在⼤量建⽴好的套接字线程之间进⾏通信时⽐较困难。当使⽤“⽣产者-消费者”模型开发⽹络程序时,为每个套接字都分别分配⼀个读线程、⼀个处理数据线程和⼀个⽤于同步的事件,那么这样⽆疑加⼤系统的开销。其最⼤的缺点是当希望同时处理⼤量套接字时,将⽆从下⼿,其扩展性很差
⾮阻塞IO模型:
简介:⾮阻塞IO通过进程反复调⽤IO函数(多次系统调⽤,并马上返回);在数据拷贝的过程中,进程是阻塞的;
我们把⼀个SOCKET接⼝设置为⾮阻塞就是告诉内核,当所请求的I/O操作⽆法完成时,不要将进程睡眠,⽽是返回⼀个错误。这样我们的I/O操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为⽌。在这个不断测试的过程中,会⼤量的占⽤CPU的时间。
把SOCKET设置为⾮阻塞模式,即通知系统内核:在调⽤Windows Sockets API时,不要让线程睡眠,⽽应该让函数⽴即返回。在返回时,该函数返回⼀个错误代码。图所⽰,⼀个⾮阻塞模式套接字多次调⽤recv()函数的过程。前三次调⽤recv()函数时,内核数据还没有准备好。因此,该函数⽴即返回WSAEWOULDBLOCK错误代码。第四次调⽤recv()函数时,数据已经准备好,被复制到应⽤程序的缓冲区
中,recv()函数返回成功指⽰,应⽤程序开始处理数据。
当使⽤socket()函数和WSASocket()函数创建套接字时,默认都是阻塞的。在创建套接字之后,通过调⽤ioctlsocket()函数,将该套接字设置为⾮阻塞模式。Linux下的函数是:fcntl().
套接字设置为⾮阻塞模式后,在调⽤Windows Sockets API函数时,调⽤函数会⽴即返回。⼤多数情况下,这些函数调⽤都会调⽤“失败”,并返回WSAEWOULDBLOCK错误代码。说明请求的操作在调⽤期间内没有时间完成。通常,应⽤程序需要重复调⽤该函数,直到获得成功返回代码。
需要说明的是并⾮所有的Windows Sockets API在⾮阻塞模式下调⽤,都会返回WSAEWOULDBLOCK错误。例如,以⾮阻塞模式的套接字为参数调⽤bind()函数时,就不会返回该错误代码。当然,在调⽤WSAStartup()函数时更不会返回该错误代码,因为该函数是应⽤程序第⼀调⽤的函数,当然不会返回这样的错误代码。
要将套接字设置为⾮阻塞模式,除了使⽤ioctlsocket()函数之外,还可以使⽤WSAAsyncselect()和WSAEventselect()函数。当调⽤该函数时,套接字会⾃动地设置为⾮阻塞⽅式。
由于使⽤⾮阻塞套接字在调⽤函数时,会经常返回WSAEWOULDBLOCK错误。所以在任何时候,都应仔细检查返回代码并作好对“失败”的准备。应⽤程序连续不断地调⽤这个函数,直到它返回成功指⽰为⽌。上⾯的程序清单中,在While循环体内不断地调⽤recv()函数,以读⼊1024个字节的数据。这种做法很浪费系统资源。
要完成这样的操作,有⼈使⽤MSG_PEEK标志调⽤recv()函数查看缓冲区中是否有数据可读。同样,这种⽅法也不好。因为该做法对系统造成的开销是很⼤的,并且应⽤程序⾄少要调⽤recv()函数两次,才
能实际地读⼊数据。较好的做法是,使⽤套接字的“I/O模型”来判断⾮阻塞套接字是否可读可写。
⾮阻塞模式套接字与阻塞模式套接字相⽐,不容易使⽤。使⽤⾮阻塞模式套接字,需要编写更多的代码,以便在每个Windows Sockets API函数调⽤中,对收到的WSAEWOULDBLOCK错误进⾏处理。因此,⾮阻塞套接字便显得有些难于使⽤。
recv函数但是,⾮阻塞套接字在控制建⽴的多个连接,在数据的收发量不均,时间不定时,明显具有优势。这种套接字在使⽤上存在⼀定难度,但只要排除了这些困难,它在功能上还是⾮常强⼤的。通常情况下,可考虑使⽤套接字的“I/O模型”,它有助于应⽤程序通过异步⽅式,同时对⼀个或多个套接字的通信加以管理。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论