网络通讯问题整理

1.

Socket阻塞模式和非阻塞模式的区别


简单点说:

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

我们拿最常用的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来说意义更大。
当在阻塞式的Socket上调用recv时,如果这时网络栈上没有数据给你接收,那么这时线程将
会挂起,直到有报文给你接收才返回。
这样就造成你的应用程序在企图接收数据时候,而网络栈上没有数据的时候就会被锁住。
有什么办法解决这个问题呢? 我们来介绍IO
2、 IO复用, 就是在企图读写数据的时候先询问下是否可读写,如果不能,可以去干别的事情,不会造成死锁。
但是假如我们有大量的连接需要去频繁的查询可读写状态,每次查询都会和内核交互。这样会造成
效率低下。再介绍一种
3、 重叠IO. 就是一次查询多个Socket的状态。不用去来来回回的遍历。
另外,
在windows socket api 中还有一种消息机制,就是把Socket状态通知到窗口。然后用消息去处理。
对于重叠IO, 在windows上还有完成端口模型,他和重叠端口相比,不但能捕捉到IO事件, 而且内核已经替你完成了Socket IO, 比如read事件, 在内核通知你的时候,他已经帮你读好数据了,并放在你指定的缓存中(这里是指在用户态下,事先为每个Socket分配的内存)。
为什么有这么多socket模式呢? 哪个更好呢?
为什么有,我不知道,可能是出于需求吧,
说说哪个更好?
孤立的来说,其实没有哪个更好? 只有哪个更适合你的应用应用环境。
如: 阻塞式的比较简单,方便,稳定。适合比较简单的客户端程序。
IO复用我认为它适合SocketIO操作比较少的情况。
重叠IO就适合高性能的服务器的开发,另外完成端口是windows上比较公认的高性能服务器的网络开发模型。当然, windows 的IOCP也有个坏处,就是需要大量的内存,应为前面说了他需要事先指定缓存。不过高性能的服务器,一般都不用windows平台。
windows的消息模型就比较适合有UI的应用程序。
2.

非阻塞connect的实现

 

步骤1设置非阻塞,启动连接

实现非阻塞connect ,首先把 sockfd 设置成非阻塞的。这样调用

connect 可以立刻返回,根据返回值和errno 处理三种情况:

(1) 如果返回 0,表示 connect 成功。

(2) 如果返回值小于 0 errno EINPROGRESS,  表示连接

      建立已经启动但是尚未完成。这是期望的结果,不是真正的错误。

(3) 如果返回值小于0errno 不是 EINPROGRESS,则连接出错了。

 

步骤2:判断可读和可写

然后把sockfd 加入 select 的读写监听集合,通过 select 判断 sockfd

是否可写,处理三种情况:

(1) 如果连接建立好了,对方没有数据到达,那么 sockfd 是可写的

(2) 如果在 select 之前,连接就建立好了,而且对方的数据已到达,

      那么sockfd 是可读和可写的。

(3) 如果连接发生错误,sockfd 也是可读和可写的。

判断connect 是否成功,就得区别 (2) (3),这两种情况下 sockfd 都是

可读和可写的,区分的方法是,调用 getsockopt 检查是否出错。

 

步骤3:使用getsockopt 函数检查错误

getsockopt(sockfd, SOL_SOCKET, SO_ERROR,&error, &len)

sockfd 都是可读和可写的情况下,我们使用 getsockopt 来检查连接

是否出错。但这里有一个可移植性的问题。

如果发生错误,getsockopt源自 Berkeley 的实现将在变量 error

返回错误,getsockopt本身返回0;然而 Solaris 却让 getsockopt 返回 -1

并把错误保存在errno 变量中。所以在判断是否有错误的时候,要处理

这两种情况。

函数原型如下:

int connect(int sockfd, const structsockaddr &serv_addr, socklen_t addr_len);

sockfd是客户端创建的流式套接字;serv_addr指向存放服务器地址信息的结构体;addr_len是该结构体的长度。

实际上,我们通过抓包分析可以得知:客户端和服务器端通过如下三次握手后即建立好了连接。

                    SYN
        客户端-----------------> 服务器端
                            ACK,SYN
        服务器端-----------------> 客户端
                            ACK

        客户端-----------------> 服务器端


3.  select函数处理


select()函数:select函数能够同时监听多个文件描述符,若其中一个或多个文件描述符有反应(读或写),select就会返回。

原型:

int select(nfds, readfds, writefds, errfds, timeout)  

 

fd_set *readfds, *writefds, *errfds;     //第2,3,4个参数都是文件描述符集,对Socket编程比较有用的是 readfds。  
struct timeval *timeout;         //控制select()如何返回,是非阻塞,还是阻塞一定时间返回,还是直接有文件描述符有响应才返回。

 

函数参数:

    1.nfs:最大的文件描述符+1,这个不能错,若不能确定,写一个算法找出来,下面提供的代码有

    2.可读文件描述符集。什么意思呢,就是这个集中的文件描述符随时可能给服务器写数据,那作为服务器,就要监测这些文件描述符,若不关心可读,填NULL。

    3.可写文件描述符集。这里不用到,就不说了。若不关心,可填NULL

    4.异常文件描述符集。若不关心,可填NULL。

    5.时间控制结构体。这个结构体里面有2个成员。一个代表秒,一个代表毫秒。两个都设成0表示select将会非阻塞返回。

 

对此,还有一些列的宏提供给select用:

    1.FD_SET(); 用来把文件描述符加到文件描述符集中

    2.FD_ZERO(); 清空文件描述符集中的所有描述符

    3.FD_ISSET();判断某个文件描述符有没有响应。

     注意:select每返回一次后,都要重新清空文件描述符集,和重新把文件描述符加到文件描述符集中。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值