linux socket高性能服务器处理框架

思考一种高性能的服务器处理框架

一、首先须要一个内存池,目的在于:
·减小频繁的分配和释放,提升性能的同时,还能避免内存碎片的问题;
·可以存储变长的数据,不要很傻瓜地只能预分配一个最大长度;
·基于SLAB算法实现内存池是一个好的思路:分配不一样大小的多个块,请求时返回大于请求长度的最小块便可,对于容器而言,处理固定块的分配和回收,至关 容易实现。固然,还要记得须要设计成线程安全的,自旋锁比较好,使用读写自旋锁就更好了。
·分配内容的增加管理是一个问题,好比第一次须要1KB空间,随着数据源源不断的写入,第二次就须要4KB空间了。扩充空间容易实现,但是扩充的时候必然 涉及数据拷贝。甚至,扩充的需求很大,上百兆的数据,这样就很差办了。暂时没更好的想法,能够像STL同样,指数级增加的分配策略,拷贝数据虽不可避免, 可是起码重分配的概率愈来愈小了。
·上面提到的,若是是上百兆的数据扩展须要,采用内存映射文件来管理是一个好的办法:映射文件后,虽然占了很大的虚拟内存,可是物理内存仅在写入的时候才 会被分配,加上madvice()来加上顺序写的优化建议后,物理内存的消耗也会变小。
·用string或者vector去管理内存并不明智,虽然很简单,但服务器软件开发中不适合使用STL,特别是对稳定性和性能要求很高的状况下。

二、第二个须要考虑的是对象池,与内存池相似:
·减小对象的分配和释放。其实C++对象也就是struct,把构造和析构脱离出来手动初始化和清理,保持对同一个缓冲区的循环利用,也就不难了。
·能够设计为一个对象池只能存放一种对象,则对象池的实现实际就是固定内存块的池化管理,很是简单。毕竟,对象的数量很是有限。

三、第三个须要的是队列:
·若是能够预料到极限的处理能力,采用固定大小的环形队列来做为缓冲区是比较不错的。一个生产者一个消费者是常见的应用场景,环形队列有其经典的“锁无 关”算法,在一个线程读一个线程写的场景下,实现简单,性能还高,还不涉及资源的分配和释放。好啊,实在是好!
·涉及多个生产者消费者的时候,tbb::concurent_queue是不错的选择,线程安全,并发性也好,就是不知道资源的分配释放是否也管理得足 够好。

四、第四个须要的是映射表,或者说hash表:
·由于epoll是事件触发的,而一系列的流程多是分散在多个事件中的,所以,必须保留下中间状态,使得下一个事件触发的时候,可以接着上次处理的位置 继续处理。要简单的话,STL的hash_map还行,不过得本身处理锁的问题,多线程环境下使用起来很麻烦。
·多线程环境下的hash表,最好的仍是tbb::concurent_hash_map。

五、核心的线程是事件线程:
·事件线程是调用epoll_wait()等待事件的线程。例子代码里面,一个线程干了全部的事情,而须要开发一个高性能的服务器的时候,事件线程应该专 注于事件自己的处理,将触发事件的socket句柄放到对应的处理队列中去,由具体的处理线程负责具体的工做。

六、accept()单独一个线程:
·服务端的socket句柄(就是调用bind()和listen()的这个)最好在单独的一个线程里面作accept(),阻塞仍是非阻塞都无所谓,相 比整个服务器的通信,用户接入的动做只是很小一部分。并且,accept()不放在事件线程的循环里面,减小了判断。

七、接收线程单独一个:
·接收线程从发生EPOLLIN事件的队列中取出socket句柄,而后在这个句柄上调用recv接收数据,直到缓冲区没有数据为止。接收到的数据写入以 socket为键的hash表中,hash表中有一个自增加的缓冲区,保存了客户端发过来的数据。
·这样的处理方式适合于客户端发来的数据很小的应用,好比HTTP服务器之类;假设是文件上传的服务器,则接受线程会一直处理某个链接的海量数据,其余客 户端的数据处理产生了饥饿。因此,若是是文件上传服务器一类的场景,就不能这样设计。

八、发送线程单独一个:
·发送线程从发送队列获取须要发送数据的SOCKET句柄,在这些句柄上调用send()将数据发到客户端。队列中指保存了SOCKET句柄,具体的信息 还须要经过socket句柄在hash表中查找,定位到具体的对象。如同上面所讲,客户端信息的对象不但有一个变长的接收数据缓冲区,还有一个变长的发送 数据缓冲区。具体的工做线程发送数据的时候并不直接调用send()函数,而是将数据写到发送数据缓冲区,而后把SOCKET句柄放到发送线程队列。
·SOCKET句柄放到发送线程队列的另外一种状况是:事件线程中发生了EPOLLOUT事件,说明TCP的发送缓冲区又有了可用的空间,这个时候能够把 SOCKET句柄放到发送线程队列,一边触发send()的调用;
·须要注意的是:发送线程发送大量数据的时候,当频繁调用send()直到TCP的发送缓冲区满后,便没法再发送了。这个时候若是循环等待,则其余用户的 发送工做受到影响;若是不继续发送,则EPOLL的ET模式可能不会再产生事件。解决这个问题的办法是在发送线程内再创建队列,或者在用户信息对象上设置 标志,等到线程空闲的时候,再去继续发送这些未发送完成的数据。

九、须要一个定时器线程:
·一位将epoll使用的高手说道:“单纯靠epoll来管理描述符不泄露几乎是不可能的。彻底解决方案很简单,就是对每一个fd设置超时时间,若是超过 timeout的时间,这个fd没有活跃过,就close掉”。
·因此,定时器线程按期轮训整个hash表,检查socket是否在规定的时间内未活动。未活动的SOCKET认为是超时,而后服务器主动关闭句柄,回收 资源。

十、多个工做线程:
·工做线程由接收线程去触发:每次接收线程收到数据后,将有数据的SOCKET句柄放入一个工做队列中;工做线程再从工做队列获取SOCKET句柄,查询 hash表,定位到用户信息对象,处理业务逻辑。
·工做线程若是须要发送数据,先把数据写入用户信息对象的发送缓冲区,而后把SOCKET句柄放到发送线程队列中去。
·对于任务队列,接收线程是生产者,多个工做线程是消费者;对于发送线程队列,多个工做线程是生产者,发送线程是消费者。在这里须要注意锁的问题,若是采 用tbb::concurrent_queue,会轻松不少。

十一、仅仅只用scoket句柄做为hash表的键,并不够:
·假设这样一种状况:事件线程刚把某SOCKET因发生EPOLLIN事件放入了接收队列,但是随即客户端异常断开了,事件线程又由于EPOLLERR事 件删除了hash表中的这一项。假设接收队列很长,发生异常的SOCKET还在队列中,等到接收线程处理到这个SOCKET的时候,并不能经过 SOCKET句柄索引到hash表中的对象。
·索引不到的状况也好处理,难点就在于,这个SOCKET句柄当即被另外一个客户端使用了,接入线程为这个SCOKET创建了hash表中的某个对象。此 时,句柄相同的两个SOCKET,其实已是不一样的两个客户端了。极端状况下,这种状况是可能发生的。
·解决的办法是,使用socket fd + sequence为hash表的键,sequence由接入线程在每次accept()后将一个整型值累加而获得。这样,就算SOCKET句柄被重用,也 不会发生问题了。

十二、监控,须要考虑:
·框架中最容易出问题的是工做线程:工做线程的处理速度太慢,就会使得各个队列暴涨,最终致使服务器崩溃。所以必需要限制每一个队列容许的最大大小,且须要 监视每一个工做线程的处理时间,超过这个时间就应该采用某个办法结束掉工做线程。

对于linux socket与epoll配合相关的一些心得记录 2008-07-29 17:57

setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
一、经过上面语句能够简单设置缓冲区大小,测试证实:跟epoll结合的时候只有当 单次发送的数据全被从缓冲区读完毕以后才会再次被触发,屡次发送数据若是没有 读取完毕当缓冲区未满的时候数据不会丢失,会累加到后面。
二、 若是缓冲区未满,同一链接屡次发送数据会屡次收到EPOLLIN事件。 单次发送数据>socket缓冲区大小的数据数据会被阻塞分次发送,因此循环接收可 以用ENLIGE错误判断。
三、若是缓冲区满,新发送的数据不会触发epoll事件(也无异常),每次recv 都会为缓冲区腾出空间,只有当缓冲区空闲大小可以再次接收数据epollIN事件可 以再次被触发 接收时接收大小为0表示客户端断开(不可能有0数据包触发EPOLLIN),-1表示异 常,针对errorno进行判断能够肯定是合理异常仍是须要终止的异常,>0而不等于 缓冲区大小表示单次发送结束。
四、 若是中途临时调整接收缓存区大小,而且在上一次中数据没有彻底接收到 用户空间,数据不会丢失,会累加在一块儿 因此总结起来,系统对于数据的完整性仍是作了至关的保正,至于稳定性没有做更 深一步的测试

新增长:
五、若是主accept监听的soctet fd也设置为非阻塞,那么单纯靠epoll事件来驱 动的服务器模型会存在问题,并发压力下发现,每次accept只从系统中取得第一 个,因此若是恰冯多个 链接同时触发server fd的EPOLLIN事件,在返回的event数 组中体现不出来,会出现丢失事件的现象,因此当用ab等工具简单的压载就会发现 每次都会有最后几条信息得 不处处理,缘由就在于此,我如今的解决办法是将 server fd的监听去掉,用一个线程阻塞监听,accept成功就处理检测client fd, 而后在主线程循环监听client事件,这样epoll在边缘模式下出错的几率就小,测 试代表效果明显
六、对于SIG部分信号仍是要作屏蔽处理,否则对方socket中断等正常事件都会引发 整个服务的退出
七、sendfile(fd, f->SL->sendBuffer.inFd, (off_t *)&f->SL->sendBuffer.offset, size_need);注意sendfile函数的地三个变量是传 送地址,偏移量会自动增长,不须要手动再次增长,不然就会出现文件传送丢失现象
八、单线程epoll驱动模型误解:之前我一直认为单线程是没法处理web服务器这样 的有严重网络延迟的服务,但nginx等优秀服务器都是机遇事件驱动 模型,开始我 在些的时候也是担忧这些问题,后来测试发现,当client socket设为非阻塞模式 的时候,从读取数据到解析http协议,到发送数据均在epoll的驱动下速度很是 快,没有必要采用多线程,个人单核 cpu(奔三)就能够达到 10000page/second,这在公网上是远远没法达到的一个数字(网络延迟更为严 重),因此单线程的数据处理能力已经很 高了,就不须要多线程了,所不一样的是 你在架构服务器的时候须要将全部阻塞的部分拆分开来,当epoll通知你能够读取 的时候,实际上部分数据已经到了 socket缓冲区,你所读取用的事件是将数据从 内核空间拷贝到用户空间,同理,写也是同样的,因此epoll重要的地方就是将这 两个延时的部分作了相似 的异步处理,若是不须要处理更为复杂的业务,那单线 程足以知足1000M网卡的最高要求,这才是单线程的意义。 我之前构建的web服务器就没有理解epoll,采用epoll的边缘触发之程处后怕事件 丢失,或者单线理阻塞,因此本身用多线程构建了一个任务调度器, 全部收 到的事件通通压进任无调度器中,而后多任务处理,我还将read和write分别用两 个调度器处理,并打算若是中间须要特殊的耗时的处理就增长一套 调度器,用少许线程+epoll的方法来题高性能,后来发现read和write部分调度器是多余 的,epoll原本就是一个事件调度器,在后面再次缓存 事件分部处理还不如将 epoll设为水平模式,因此画蛇添足,可是这个调度起仍是有用处的 上面讲到若是中间有耗时的工做,好比数据库读写,外部资源请求(文 件,socket)等这些操做就不能阻塞在主线程里面,因此我设计的这个任务调度器 就有 用了,在epoll能处理的事件驱动部分就借用epoll的,中间部分采用模块化 的设计,用函数指针达到面相对象语言中的“委托”的做用,就能够知足不一样 的需 要将任务(fd标识)加入调度器,让多线程循环执行,若是中间再次遇到阻塞就会 再次加入自定义的阻塞器,检测完成就加入再次存入调度器,这样就能够将 多种 复杂的任务划分开来,至关于在处理的中间环节在本身购置一个相似于epoll的事 件驱动器
九、多系统兼容:我如今却是以为与其构建一个多操做系统都支持的服务器不 如构建特定系统的,若是想迁移再次改动,由于一旦兼顾到多个系统的化会大大增 加系 统的复杂度,而且不能最优性能,每一个系统都有本身的独有的优化选项,所 以我以为迁移的工做量远远小于兼顾的工做量
10模块化编程,虽然用c仍是要讲求一些模块化的设计的,我如今才发现几乎面相 对想的语言所能实现的全部高级特性在c里面几乎都有对应的解决办法(暂时发现 除了操做符重载),全部学太高级面相对象的语言的朋友不放把模式用c来实现, 也是一种乐趣,便于维护和本身阅读
十一、养成注释的好习惯


setsockopt -设置socket

1.closesocket(通常不会当即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));linux


2. 若是要已经处于链接状态的soket在调用closesocket后强制关闭,不经历
TIME_WAIT的过程:
BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));nginx


3.在send(),recv()过程当中有时因为网络情况等缘由,发收不能预期进行,而设置收发时限:
int nNetTimeout=1000;//1秒
//发送时限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收时限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));web


4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节
(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程当中发送数据
和接收数据量比较大,能够设置socket缓冲区,而避免了send(),recv()不断的循环收发:
// 接收缓冲区
int nRecvBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//发送缓冲区
int nSendBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));算法


5. 若是在发送数据的时,但愿不经历由系统缓冲区到socket缓冲区的拷贝而影响
程序的性能:
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));数据库


6.同上在recv()完成上述功能(默认状况是将socket缓冲区的内容拷贝到系统缓冲区):
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));编程


7.通常在发送UDP数据报的时候,但愿该socket发送的数据具备广播特性:
BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));缓存


8.在client链接服务器过程当中,若是处于非阻塞模式下的socket在connect()的过程当中可
以设置connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程当中有显著的
做用,在阻塞的函数调用中做用不大)
BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));安全


若是在发送数据的过程当中(send()没有完成,还有数据没发送)而调用了closesocket(),之前咱们
通常采起的措施是"从容关闭"shutdown(s,SD_BOTH),可是数据是确定丢失了,如何设置让程序知足具体
应用的要求(即让没发完的数据发送出去后在关闭socket)?
struct linger {
u_short l_onoff;
u_short l_linger;
};
linger m_sLinger;
m_sLinger.l_onoff=1;//(在closesocket()调用,可是还有数据没发送完毕的时候允许逗留)
// 若是m_sLinger.l_onoff=0;则功能和2.)做用相同;
m_sLinger.l_linger=5;//(允许逗留的时间为5秒)
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));服务器

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////网络

线程于进程的好处在于:
方便通讯,线程共享了代码与数据空间,因此对共享空间提供了最原始的支持
能够用线程运行完销毁的方式而不须要回收线程资源,只要进程退出,全部线程就销毁了,不须要担忧有僵尸进程的出现,也就是资源不能回收的问题。
对于并发比较高的服务器,而且每一个处理时间又不是太长的状况下,能够采用线程池的方式
在同等状况下,线程所占资源略少于进程,由于线程在访问一共享变量时,在物理内存中仅有一份此变量所占空间,如果进程间须要改写同一全局变量时,此时就会产生“写时复制”,会产生两份空间(对一个变量的改写,会形成多占用大于等于4K的物理空间)

进程于线程的好处在于:
不须要担忧太多由于访问共享资源而形成的各类同步与互斥问题,若是须要共享某部份内容,须要走专用的进程间通讯手段,也就是说对于共享空间是可控制的,不会出现随机性
不用担忧一不当心就形成函数重入的问题
在不一样的进程中,可使用不一样的ELF文件做为执行体,固然,在线程中也能够再进行fork+execv来实现这种方案

不管是线程仍是进程,其调度方式是同样的

长链接,用poll/select/epoll作多路复用的方式优缺点:
因为不会形成多线程与多进程,因此全部代码都在一个执行体内,都在同一调度单元中,节省了资源的开销,如内存的占用,进程切换的开销。
因为全部处理都在同一个调度单元内,也就是多个链接共用一个进程的时间片,若是系统中还有不少其它优先级较高进程或者实时进程,平均下来每一个链接所占用的 CPU时间就较少,且若是一个链接处于死循环中,若不加其它控制,其它链接就永远得不到响应,也就是说每一个链接的响应实时性会受到其它链接的影响。
若是对于每一个链接的处理方式不一样,会形成代码的很差控制,由于会有太多的逻辑判断。

以上三种方式,各有优缺点,主要是楼主的需求,这三种方式并不是是互斥的,能够交叉使用,灵活控制,从而最优化你的软件。固然,若是并发链接达到2000个 以上,并发处理也达到几百上千个以上(且每一个处理过程会执行很长),那么推荐你采用分布式处理,单个PC机是没法承受这种负荷的(若使用专用服务器,性能 会好一些,这个相对限制会宽一些),至少会形成响应时间过长

、、、、、、、、、、、、、、、、、、、、、、、、、、、

设置套接口的选项。    #include <winsock.h>    int PASCAL FAR setsockopt( SOCKET s, int level, int optname,    const char FAR* optval, int optlen);    s:标识一个套接口的描述字。    level:选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次。    optname:需设置的选项。    optval:指针,指向存放选项值的缓冲区。    optlen:optval缓冲区的长度。 注释: setsockopt()函数用于任意类型、任意状态套接口的设置选项值。尽管在不一样协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。选项影响套接口的操做,诸如加急数据是否在普通数据流中接收,广播数据是否能够从套接口发送等等。    有两种套接口的选项:一种是布尔型选项,容许或禁止一种特性;另外一种是整形或结构选项。容许一个布尔型选项,则将optval指向非零整形数;禁止一个选 项optval指向一个等于零的整形数。对于布尔型选项,optlen应等于sizeof(int);对其余选项,optval指向包含所需选项的整形数 或结构,而optlen则为整形数或结构的长度。SO_LINGER选项用于控制下述状况的行动:套接口上有排队的待发送数据,且 closesocket()调用已执行。参见closesocket()函数中关于SO_LINGER选项对closesocket()语义的影响。应用 程序经过建立一个linger结构来设置相应的操做特性:    struct linger { int l_onoff; int l_linger;    };    为了容许SO_LINGER,应用程序应将l_onoff设为非零,将l_linger设为零或须要的超时值(以秒为单位),而后调用setsockopt()。为了容许SO_DONTLINGER(亦即禁止SO_LINGER),l_onoff应设为零,而后调用setsockopt()。    缺省条件下,一个套接口不能与一个已在使用中的本地地址捆绑(参见bind())。但有时会须要“重用”地址。由于每个链接都由本地地址和远端地址的组 合惟一肯定,因此只要远端地址不一样,两个套接口与一个地址捆绑并没有大碍。为了通知WINDOWS套接口实现不要由于一个地址已被一个套接口使用就不让它与 另外一个套接口捆绑,应用程序可在bind()调用前先设置SO_REUSEADDR选项。请注意仅在bind()调用时该选项才被解释;故此无需(但也无 害)将一个不会共用地址的套接口设置该选项,或者在bind()对这个或其余套接口无影响状况下设置或清除这一选项。    一个应用程序能够经过打开SO_KEEPALIVE选项,使得WINDOWS套接口实如今TCP链接状况下容许使用“保持活动”包。一个WINDOWS套 接口实现并非必需支持“保持活动”,可是若是支持的话,具体的语义将与实现有关,应遵照RFC1122“Internet主机要求-通信层”中第 4.2.3.6节的规范。若是有关链接因为“保持活动”而失效,则进行中的任何对该套接口的调用都将以WSAENETRESET错误返回,后续的任何调用 将以WSAENOTCONN错误返回。    TCP_NODELAY选项禁止Nagle算法。Nagle算法经过将未确认的数据存入缓冲区直到蓄足一个包一块儿发送的方法,来减小主机发送的零碎小数据 包的数目。但对于某些应用来讲,这种算法将下降系统性能。因此TCP_NODELAY可用来将此算法关闭。应用程序编写者只有在确切了解它的效果并确实需 要的状况下,才设置TCP_NODELAY选项,由于设置后对网络性能有明显的负面影响。TCP_NODELAY是惟一使用IPPROTO_TCP层的选 项,其余全部选项都使用SOL_SOCKET层。    若是设置了SO_DEBUG选项,WINDOWS套接口供应商被鼓励(但不是必需)提供输出相应的调试信息。但产生调试信息的机制以及调试信息的形式已超出本规范的讨论范围。 setsockopt()支持下列选项。其中“类型”代表optval所指数据的类型。 选项        类型   意义 SO_BROADCAST BOOL 容许套接口传送广播信息。 SO_DEBUG BOOL 记录调试信息。 SO_DONTLINER BOOL 不要由于数据未发送就阻塞关闭操做。设置本选项至关于将SO_LINGER的l_onoff元素置为零。 SO_DONTROUTE BOOL 禁止选径;直接传送。 SO_KEEPALIVE BOOL 发送“保持活动”包。 SO_LINGER struct linger FAR*   如关闭时有未发送数据,则逗留。 SO_OOBINLINE BOOL 在常规数据流中接收带外数据。 SO_RCVBUF int 为接收肯定缓冲区大小。 SO_REUSEADDR BOOL 容许套接口和一个已在使用中的地址捆绑(参见bind())。 SO_SNDBUF int 指定发送缓冲区大小。 TCP_NODELAY BOOL 禁止发送合并的Nagle算法。 setsockopt()不支持的BSD选项有: 选项名    类型 意义 SO_ACCEPTCONN BOOL 套接口在监听。 SO_ERROR int 获取错误状态并清除。 SO_RCVLOWAT int 接收低级水印。 SO_RCVTIMEO int 接收超时。 SO_SNDLOWAT int 发送低级水印。 SO_SNDTIMEO int 发送超时。 SO_TYPE     int 套接口类型。 IP_OPTIONS    在IP头中设置选项。 返回值:    若无错误发生,setsockopt()返回0。不然的话,返回SOCKET_ERROR错误,应用程序可经过WSAGetLastError()获取相应错误代码。 错误代码:    WSANOTINITIALISED:在使用此API以前应首先成功地调用WSAStartup()。    WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。    WSAEFAULT:optval不是进程地址空间中的一个有效部分。    WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。    WSAEINVAL:level值非法,或optval中的信息非法。    WSAENETRESET:当SO_KEEPALIVE设置后链接超时。    WSAENOPROTOOPT:未知或不支持选项。其中,SOCK_STREAM类型的套接口不支持SO_BROADCAST选项,SOCK_DGRAM 类型的套接口不支持SO_DONTLINGER 、SO_KEEPALIVE、SO_LINGER和SO_OOBINLINE选项。    WSAENOTCONN:当设置SO_KEEPALIVE后链接被复位。    WSAENOTSOCK:描述字不是一个套接口。 参见:    bind(), getsockopt(), ioctlsocket(), socket(), WSAAsyncSelect().

相关文章
相关标签/搜索