请教了 iocp 下,如何设置socket 的recv超时

im2web 2008-07-24 11:51:51
在写服务器程序,比较常见的一个问题是,当socket 连上来以后,它可能继没有数据发送也没有接受。
那么这个连接就会挂在那里。 假设这样的链接是恶意的, 那么他会大量消耗你的资源。

所以我希望进行一次WSARecv 投递以后, 能在一定时间内超时, 那么我就可以根据情况进行处理了。

len = 10 * 1000;
Ret = setsockopt(Accept, SOL_SOCKET, SO_RCVTIMEO , &len , sizeof(len));
printf("Ret %d %d\n", Ret, WSAGetLastError());
代码很简单, 在accept 以后, 设置socket 的recv 超时, 如果不使用iocp, 就使用见得的recv 肯定
能在超时以后,获得一个结果。

但是在iocp的例子里面,我发现超时以后,无法GetQueuedCompletionStatus 获得消息,那么这样我就很被动了

现在我起了线程定时扫描所有的socket ,效率很低,希望能解决这个问题。
...全文
3809 49 打赏 收藏 转发到动态 举报
写回复
用AI写文章
49 条回复
切换为时间正序
请发表友善的回复…
发表回复
lrboy 2010-11-04
  • 打赏
  • 举报
回复
MARK
ashamwolf 2010-09-17
  • 打赏
  • 举报
回复
用select?
qiuqingpo 2010-09-02
  • 打赏
  • 举报
回复
学习一下!
lijianli9 2010-06-24
  • 打赏
  • 举报
回复
路过学习,
ice50303 2010-03-29
  • 打赏
  • 举报
回复
好多人》
papaofdoudou 2009-11-23
  • 打赏
  • 举报
回复
mark
YouthWang 2009-10-15
  • 打赏
  • 举报
回复
哎 我也想通了 谢谢谢谢
im2web 2008-08-02
  • 打赏
  • 举报
回复
哦。 是我概念上有一些混淆,

其实我的概念上定时器是2个,一个是逻辑上的定时器, 比如每20秒,需要向对方发送一个hello 消息

还有一个定时器 是 现在这个120秒检测用户是否有消息发过来的定时器。

120秒这个定时器, 是不需要clock 住 socket 对象的, 而 20秒这个是需要 clock 住对象的。
而我把2者混在了一起。

WinEggDrop 2008-08-01
  • 打赏
  • 举报
回复
最后一贴关于这个讨论的,主要是说下我说过的几种方法.顶楼所说的,主要就是一种超时检测机制,很多服务器程序都需要这样的机制,因为太多空闲的连接还是使用一定量的系统资源的,有些服务器,象FTP服务器,有时还限制了最大登陆的连接数,万一有人恶意大量地连接,但这些连接不被系统定时断开的话,那么正常的用户有可能无法登陆FTP服务器(因为连接数到达上限)

1.使用setsockopt设置SO_RCVTIMEO
这种方法简单好用,但缺点是只用于阻塞的socket,而且有时因为对方的非正常断开而无法检测到.

2.在接收数据前使用select(),select()返回可读才调用recv()等API.
这种方法一样简单好用,但缺点还是主要适用于阻塞socket,一般非阻塞socket也可用,只不过要调用个死循环不断地检测select()返回值,很是浪费资源.

3.定时扫描所有客户socket的方法(楼主正采用的方法).这方法就是记录每次每个socket数据通讯时的时间,然后在扫描时再和当前时间比较,如果时间差高于超时机制的限制时间,就将socket断开.
这种方法使用起来也是很简单的,只要建一个线程定时地扫描所有客户socket列表.适用性很强,所有socket模式都可兼容的.需要注意的是这方法临界要做好,不然是挺容易出现问题(在扫描期间有socket正常的断开时资源被释放时,扫描列表时如果没做临界,那么扫描时就很有可能访问了非法的内存).这方法有个缺点就是超时机制的误差比较高,因为如果超时检测的时间设置为N,那么是有可能出现N-1秒的误差的.设置检测的时间越长,出现的误差时间就越长.由于每次都要扫描所有的客户socket列表,如果socket比较多时,设置这个检测时间就是个"鸡肋".检测时间设置得过短,频烦的扫描对系统资源和程序性能必然多少有是影响;而设置时间过长,又令误差时间过大.

4.使用系统的Timer
标准的Timer:使用SetTimer()设置Timer,使用KillTimer()删除Timer.优点是适用于所有系统,也适用于所有socket模型.缺点是精确度不高,而且是消息机制的,如果太多消息要处理,Timer触发时间会被延迟.
NT系统内核Timer.优点是精确度高,缺点是只能用于NT系统.

所有上面的方法我都在以前写的服务器程序中尝试过,最终我是选用了NT系统内核Timer那种方法.这种方法是不是最高效的,我也不清楚,只是我自己倾向于这方法,自认为是比较高效的方法(事实上是不是高效的,我也无法测试).
im2web 2008-08-01
  • 打赏
  • 举报
回复
最后 完全爆一下我的基本设计。 请高手指点一下。

socket 对象
包含了2个buff read[]
send[]
链表 sendbuff,指向多个要发送的数据块
链表 recvbuff
int read投递计数器
int recv投递计数器
time_t 操作时间

指针 指向上层对象。



互斥量 A 和所有在线列表 LA

在使用的时候,有逻辑线程 x, 和iocp 工作线程y, 扫描线程z

逻辑线程需要投递的时候,先锁住互斥量A, 查看是否在LA中间,如果没有就加入LA
锁住 socket 对象直接将数据加在链表,或者sendbuff,然后投递, 相应的投递数+1。
如果是逻辑线程要短线, 就查看所有的投递数是否为0, 为0, 从LA 中间除去。然后socket 对象可以删除



iocp工作线程 GetQueuedCompletionStatus()
先检查是send 返回 还是read 返回
锁住互斥量A
锁住socket
如果是send 返回,相应投递数-1, 如果链表里有数据, 投递+1, 继续投递
如果是read 返回,相应投递数-1, 如果 返回数据!=0 呼叫逻辑操作
返回数据 =0 查看所有的投递数是否为0, 为0, 从LA 中间除去。然后socket 对象可以删除了。

扫描线程
锁住A 遍历LA
依次锁住socket 对象
扫描,如果超时,就closesocket.

我希望x 线程和y 可以多,而且不用相互互斥, 如果用了timer 好像的确可以省掉A 和LA, 可以比较大的提高性能。

如果将删除socket 对象的工作放在扫描里面做,也可以避免都要锁A 的情况,但是从逻辑上看,不太好。
WinEggDrop 2008-08-01
  • 打赏
  • 举报
回复
[Quote=引用 40 楼 im2web 的回复:]
很明白, 很透彻。 ,顶多就是再关闭了一个已经有可以已被关闭了的socket,根本不会出现任何问题.
唯一的bug 就是这个socket 可能被第二次使用, 而错关了一个bug.
[/Quote]

理论上是有机会出现的,但实际上几乎是不可能发生.
1.刚好出现Timer激活里那个socket被关闭的出现的概率是非常少.因为哪怕相差0.0000001秒,在系统的执行顺序中,也是有先后差别的.
2.这个被关闭的socket马上又被建立,而且刚好又是马上被系统重利用到.例如socket句柄对应的是0x1723,socket被关闭后,另一个socket又被建立,刚好得到的句柄又是0x1723,这样的概率也是非常的低.
im2web 2008-08-01
  • 打赏
  • 举报
回复
非常感谢。

我的感觉是timer 和扫描都存在灵界区的问题。 很简单的情况,无法那种方式,都是 2个线程在同时操作同一个socket.

一个是 扫描线程 和 GetQueuedCompletionStatus()线程
一个是 timer 和 GetQueuedCompletionStatus()线程

只是扫描线程涉及到的东西更多,所以更容易出问题。
我也感觉到timer 比较方便,在相同的情况下, timer 要简洁一些。

问一下 timer 具体的运行在那个线程?是否是 当激发的时候,timer 以回调的形式,中断目前线程的操作,然后运行,然后将控制权交回原有的线程呢?


还有一个问题,GetQueuedCompletionStatus() 返回以后,进行逻辑处理的时间可能比较长,如何做到更高性能呢? 如果此时要扫描的话,还是会被死锁的目前 我采用了 scan trylock,防止双方死锁。虽然排除了死锁,仍然感觉不够高效。


im2web 2008-08-01
  • 打赏
  • 举报
回复
很明白, 很透彻。 ,顶多就是再关闭了一个已经有可以已被关闭了的socket,根本不会出现任何问题.
唯一的bug 就是这个socket 可能被第二次使用, 而错关了一个bug.
WinEggDrop 2008-08-01
  • 打赏
  • 举报
回复
[Quote=引用 37 楼 im2web 的回复:]
非常感谢。

我的感觉是timer 和扫描都存在灵界区的问题。 很简单的情况,无法那种方式,都是 2个线程在同时操作同一个socket.

一个是 扫描线程 和 GetQueuedCompletionStatus()线程
一个是 timer 和 GetQueuedCompletionStatus()线程

只是扫描线程涉及到的东西更多,所以更容易出问题。
我也感觉到timer 比较方便,在相同的情况下, timer 要简洁一些。

问一下 timer 具体的运行在那个…
[/Quote]

Timer同时操作的只是一个socket值,只要知道那个socket就是.但扫描所有客户端的话,你要扫描的是记录着所有客户端的那个列表.看到不同了没有?扫描列表的话,你要扫描的是象IOHandle->ClientSocket或IOHandle->DataTime等的对象,你要访问这些对象.但Timer的话,传递的只是一个Socket的值,就算这个Socket对应的IOHandle等资源被释放了,我也只是访问了一个被关闭的Socket值,不会崩溃.但如果你扫描一个内存被释放的IOHandle,你是肯定要崩溃的.

如果是用链表的话,扫描列表代码基本如下:
while(IOHandle)
{
if ((GetTickCount() - IOHandle->LastTime) >= 120) // 高于120秒
{
shutdown(IOHandle->ClientSocket,SD_BOTH);
closesocket(IOHandle->ClientSocket);
}
IOHandle = IOHanle->Next;
}

如果是Timer
VOID CALLBACK TimeOutRoutine(PVOID lpParam, BOOL TimerOrWaitFired)
{
SOCKET DataSocket = (SOCKET)lpParam;
shutdown(DataSocket);
closesocket(DataSocket);
}

上面两个应该很清楚地说明问题了.如果扫描列表的,不做好临界,由于要访问到所有IOHandle列表中所有结点的东西,如果某个IOHandle在扫描期间被释放了,必然就访问到非法内存.
使用Timer,就算IOHandle等被释放掉,Timer激活时也根本没访问到任何IOhandle的结点,顶多就是再关闭了一个已经有可以已被关闭了的socket,根本不会出现任何问题.
im2web 2008-07-31
  • 打赏
  • 举报
回复
正常的做法是用一个优先级略低的线程,每隔几分钟扫描一次所有的socket,对有未决事件且长期没有响应的socket调用closesocket

我目前是这么做的,
fantiyu 2008-07-31
  • 打赏
  • 举报
回复
正常的做法是用一个优先级略低的线程,每隔几分钟扫描一次所有的socket,对有未决事件且长期没有响应的socket调用closesocket



------------------------------
C++网络服务器开发群(QQ)招新,不欢迎c++入门级新手,群号41356711
群上限200,目前群内170+人,加群请注明自己目前情况
主要讨论方向:网络和多线程服务器开发、IOCP等
WinEggDrop 2008-07-31
  • 打赏
  • 举报
回复
[Quote=引用 33 楼 fantiyu 的回复:]
正常的做法是用一个优先级略低的线程,每隔几分钟扫描一次所有的socket,对有未决事件且长期没有响应的socket调用closesocket


------------------------------
C++网络服务器开发群(QQ)招新,不欢迎c++入门级新手,群号41356711
群上限200,目前群内170+人,加群请注明自己目前情况
主要讨论方向:网络和多线程服务器开发、IOCP等
[/Quote]

扫描过程和释放资源都要做临界才能保证不会有什么问题.不然在扫描期间有socket正常断开,资源被释放的话,扫描就很有可能访问了不存在的内存地址.

如果可以的话,反汇编看下些商业软件,象serv-u等,是不是用这种"正常的做法".
WinEggDrop 2008-07-30
  • 打赏
  • 举报
回复
[Quote=引用 30 楼 im2web 的回复:]
引用 29 楼 WinEggDrop 的回复:
引用 26 楼 im2web 的回复:
谢谢解答。

1.closesocket()
2.等待所有相应这个socket的还处于pending的I/O操作从GetQueuedCompletionStatus()中返回结果
3.当IOHandle记录相应pending I/O(还没完成的操作)的变量复位为0时,才释放所有资源.(这个值是当有WSARec()或WSASend()投递时就加1,当WSARecv()或WSASend()操作完成时就减1).

当IOHandle记录相应pending I/O(还没完成的操作)的变量…
[/Quote]

你意思是Timer激发和这个Timer相应的socket也刚好有投递了的WSaSend()或WSARecv()刚好从GetQueuedCompletionStatus()中返回.

由于IOCP的消息机制也是有序的,也就是先进来的,先被GetQueuedCompletionStatus()接收.那么上面的情况,那么可以出现几种可能.

1.closesocket()先被执行了,然后GetQueuedCompletionStatus()后进行的投递都出现失败(由于socket被关闭了)
2.GetQueuedCompletionStatus()返回后又有投递被执行了,然后closesocket()才被执行到,那么这些投递马上就会返回错误.
3.closesocket()和WSASend()或WSARecv()等投递同时被"执行",这里理论上可以认为是同时,但真到了系统内核中,还是有序地被执行的.如果是真正地"同时"执行,那么一般的网络程序,也很容易遭遇到客户断开连接的同时,程序刚好执行发送或接收数据操作.

无论哪种情况,由于资源根本还没释放(这里的资源是指IOHandle,还有对应的内存buff等),根本不会存在任何崩溃的情况出现,唯一释放的只是socket,但任何WSASend()或WSARecv()的投递,如果投递在一个被关闭了的socket上,也只会在API中返回错误,但不会出现程序崩溃.
im2web 2008-07-30
  • 打赏
  • 举报
回复
我相信你一直没有明白我说的话

一直在超时的时间内(例如120秒)都有数据通讯的,Timer是不可能被激活的。 你说的是普通情况。

在普通情况下, 如果time 在激发 ==== > closescoket 这段时间,没有GetQueuedCompletionStatus 返回的情况,我们可以认为是安全的。 因为closesocket 完成,在此期间 还没有GetQueuedCompletionStatus, 基本上就没有问题了。


我举的例子 是一个socket 在120秒内没数据交互, 此时timer 激活, 而正在此时GetQueuedCompletionStatus()
获取返回,正好有数据进入的情况,都是在第120秒的时候发生,考虑到内部线程切换的情况,就可以出现
这种临界状态下。

time 在激发 ==== > closescoket 这段时间。这个socket 正好有GetQueuedCompletionStatus 返回.
大家的操作就会交错, 就会出现我的疑问。 我的图描述的是120秒那瞬间的情况。

im2web 2008-07-30
  • 打赏
  • 举报
回复
[Quote=引用 29 楼 WinEggDrop 的回复:]
引用 26 楼 im2web 的回复:
谢谢解答。

1.closesocket()
2.等待所有相应这个socket的还处于pending的I/O操作从GetQueuedCompletionStatus()中返回结果
3.当IOHandle记录相应pending I/O(还没完成的操作)的变量复位为0时,才释放所有资源.(这个值是当有WSARec()或WSASend()投递时就加1,当WSARecv()或WSASend()操作完成时就减1).

当IOHandle记录相应pending I/O(还没完成的操作)的变量复位为0时,才释放所有资源

非常巧,…
[/Quote]


我相信你一直没有明白我说的话

一直在超时的时间内(例如120秒)都有数据通讯的,Timer是不可能被激活的。 你说的是普通情况。

在普通情况下, 如果time 在激发 ==== > closescoket 这段时间,没有GetQueuedCompletionStatus 返回的情况,我们可以认为是安全的。如果在这个时间中,发生了GetQueuedCompletionStatus


我举的例子 是一个socket 在120秒内没数据交互, 此时timer 激活, 而正在此时GetQueuedCompletionStatus()
获取返回,正好有数据进入的情况。 都是在第120秒的时候发生,考虑到内部线程切换的情况,就可以出现

在这种临界状态下 就会出现我说的情况。

time 在激发 ==== > closescoket 这段时间。这个socket 正好有GetQueuedCompletionStatus 返回.
大家的操作就会交错, 就会出现我的疑问。

加载更多回复(29)

18,363

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧