首页
社区
课程
招聘
[讨论]Gh0st3.6 IOCP发送BUG
发表于: 2014-4-21 22:53 32587

[讨论]Gh0st3.6 IOCP发送BUG

2014-4-21 22:53
32587
测试发现有时客户端会发送重复数据包,感觉作者的IOCP发送处理逻辑不是太清晰,简单修改了下,初步测试没发现异常
话说我很想不通使用了TCP通信的Gh0st里面居然还有处理重发的代码,真是蛋疼啊,懒得删了,万一有个大坑呢?
另外里面频繁的new delete看得我也很不爽啊,不过我忍住了,能不动就不动
1、CIOCPServer::Send
void[B] CIOCPServer::Send[/B](ClientContext* pContext, LPBYTE lpData, UINT nSize)
{
	if (pContext == NULL)
		return;
        
	try
	{
                CLock cs(pContext->m_SndLock, "Send");
		if (nSize > 0)
		{
			// Compress data
			unsigned long	destLen = (double)nSize * 1.001  + 12;
			LPBYTE			pDest = new BYTE[destLen];
			int	nRet = compress(pDest, &destLen, lpData, nSize);
			
			if (nRet != Z_OK)
			{
				delete [] pDest;
				return;
			}

			//////////////////////////////////////////////////////////////////////////
			LONG nBufLen = destLen + HDR_SIZE;
			// 5 bytes packet flag
			pContext->m_WriteBuffer.Write(m_bPacketFlag, sizeof(m_bPacketFlag));
			// 4 byte header [Size of Entire Packet]
			pContext->m_WriteBuffer.Write((PBYTE) &nBufLen, sizeof(nBufLen));
			// 4 byte header [Size of UnCompress Entire Packet]
			pContext->m_WriteBuffer.Write((PBYTE) &nSize, sizeof(nSize));
			// Write Data
			pContext->m_WriteBuffer.Write(pDest, destLen);
			delete [] pDest;
			
			// 如果当前缓冲区无数据堆积,执行PostSend
			[B]if (pContext->m_WriteBuffer.GetBufferLen() == nBufLen)
				PostSend(pContext);[/B]

			// 发送完后,再备份数据, 因为有可能是m_ResendWriteBuffer本身在发送,所以不直接写入
			LPBYTE lpResendWriteBuffer = new BYTE[nSize];
			CopyMemory(lpResendWriteBuffer, lpData, nSize);
			pContext->m_ResendWriteBuffer.ClearBuffer();
			pContext->m_ResendWriteBuffer.Write(lpResendWriteBuffer, nSize);	// 备份发送的数据
			delete [] lpResendWriteBuffer;
		}
		else // 要求重发
		{
			pContext->m_WriteBuffer.Write(m_bPacketFlag, sizeof(m_bPacketFlag));
			pContext->m_ResendWriteBuffer.ClearBuffer();
			pContext->m_ResendWriteBuffer.Write(m_bPacketFlag, sizeof(m_bPacketFlag));	// 备份发送的数据	
		}

 		//OVERLAPPEDPLUS * pOverlap = new OVERLAPPEDPLUS(IOWrite);
 		//PostQueuedCompletionStatus(m_hCompletionPort, 0, (DWORD) pContext, &pOverlap->m_ol);

		pContext->m_nMsgOut++;
	}catch(...){}
}


2、添加函数CIOCPServer::PostSend
void [B]CIOCPServer::PostSend[/B](ClientContext* pContext)
{
	OVERLAPPEDPLUS * pOverlap = new OVERLAPPEDPLUS(IOWrite);
	WSABUF sndBuf;
	ULONG ulFlags = MSG_PARTIAL;
	
	m_pNotifyProc((LPVOID) m_pFrame, pContext, NC_TRANSMIT);
	
	sndBuf.buf = (char*) pContext->m_WriteBuffer.GetBuffer();
	sndBuf.len = pContext->m_WriteBuffer.GetBufferLen();
	
	int nRetVal = WSASend(pContext->m_Socket, 
		&sndBuf,
		1,
		&sndBuf.len, 
		ulFlags,
		&pOverlap->m_ol, 
		NULL);
	
	if (nRetVal == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING)
		RemoveStaleClient( pContext, FALSE );
}

3、CIOCPServer::OnClientWriting
bool [B]CIOCPServer::OnClientWriting[/B](ClientContext* pContext, DWORD dwIoSize)
{
	try
	{
		//////////////////////////////////////////////////////////////////////////
		static DWORD nLastTick = GetTickCount();
		static DWORD nBytes = 0;
		
		nBytes += dwIoSize;
		
		if (GetTickCount() - nLastTick >= 1000)
		{
			nLastTick = GetTickCount();
			InterlockedExchange((LPLONG)&(m_nSendKbps), nBytes);
			nBytes = 0;
		}
		//////////////////////////////////////////////////////////////////////////
               
                TRACE("IOCP Send DONE %d bytes Remain:%d bytes\n", 
			dwIoSize, 
			pContext->m_WriteBuffer.GetBufferLen());

		// Finished writing - tidy up
                if (dwIoSize > 0){
		        pContext->m_WriteBuffer.Delete(dwIoSize);
			[B]if (pContext->m_WriteBuffer.GetBufferLen() > 0)
				PostSend(pContext);[/B]
			else
				pContext->m_WriteBuffer.ClearBuffer();
                }

	}catch(...){}
	return false;			// issue new read after this one
}

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
免费 7
支持
分享
最新回复 (47)
雪    币: 10
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
boy怎么研究起Ghost来了
2014-4-21 23:45
0
雪    币: 11
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
出错重发的确是蛋疼。。。。
2014-4-22 00:29
0
雪    币: 60
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
我还是挺佩服软件作者的,他的面向对象思想很超前,一个gh0st就有很多可以学习的地方。
2014-4-22 01:21
0
雪    币: 30050
活跃值: (2377)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
XXXX
2014-4-22 02:16
0
雪    币: 185
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
对,WSASend MSDN上只描述了这两种情况,我也没有碰到过返回其他值的情况,

If no error occurs and the send operation has completed immediately, this function returns zero. The overlapped structures are updated with the receive results, and the associated event is signalled


有这种想法的主要是受制于send 函数

linux 下的 send 函数的Return 描述

On success, these calls return the number of characters sent. On error, -1 is returned, and errno is set appropriately


windows 下的send 函数的 return 描述

If no error occurs, send returns the total number of bytes sent, which can be less than the number requested to be sent in the len parameter




不过从逻辑上讲,确实应该有可能只发送了半截数据,然后断掉,WSASend返回失败,结果对方收到了部份数据,然后导致了数据重复的情况
2014-4-22 09:01
0
雪    币: 1233
活跃值: (907)
能力值: ( LV12,RANK:750 )
在线值:
发帖
回帖
粉丝
7
还真没注意过这个细节!受教了
2014-4-22 09:24
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
tcp协议发数据就不会失败,发送方发出去,没有收到对方ACK,TCP驱动自己也会重传,如果数据变化了,接收方校验到CRC不匹配,也会重传,不需要编码实现重传的。

但是会有网络不稳定的情况,出现这边SEND一次,对面收到多次RECV的情况。TCP没有固定的包界限,需要自己分包。
2014-4-22 09:51
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
另外,如果你是要写个IOCP服务器,用gh0st那个IOCP还不如用boost.asio
2014-4-22 09:52
0
雪    币: 220
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
建议对IOCP特别熟悉的人,搞个C/S的实例出来让大家学习。现在网上流传的大部分都有各种缺陷
2014-4-22 10:06
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/example/cpp03/chat/chat_server.cpp

发个参考代码,boost.asio的,用的reactor的设计方式,现在很多高性能服务器都是用的这种设计方式,好像是学的ACE(没看过ACE代码,那个有点大)
2014-4-22 10:20
0
雪    币: 30050
活跃值: (2377)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
12
XXXX
2014-4-22 11:20
0
雪    币: 30050
活跃值: (2377)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
这个其实已经是TCP协议栈要处理的问题了,应用层无须去考虑这些东西。
2014-4-22 11:21
0
雪    币: 30050
活跃值: (2377)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
14
XXXX
2014-4-22 11:42
0
雪    币: 185
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
没太懂你举这个例表达啥意思?

是想说这种情况不会发生?   我投递一个10KB的包,假如把网速相像的非常的慢。 前面5K传完了,对方也收到了,然后网络断掉了。

确实WSASend是返回失败了,但对方确实是收到重复的数据了吧?

不明白微软不允许是怎么个不允许法。他能控制对端?
2014-4-22 12:25
0
雪    币: 30050
活跃值: (2377)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
16
XXXX
2014-4-22 12:44
0
雪    币: 185
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
INVALID_SOCKET,再怎么处理也是无力回天,只有重新开一个连接。所以原来的连接是不会收到重复的数据的


这里怎么推出来对方不会收到重复数据的?   我们在不考虑复杂逻辑判断的情况下,

假如就是把一个10K的数据扔过来。服务端是不知道数据长度的,只会将收到的数据依次存储。(假如我们把服务器想的很笨,他只是依次把收到的数据存储到缓存,没有任何的逻辑)

假如先发了5K,服务器收了,保存了。  然后Socket掉了, 客户端不知道,重新又把10K全部重发了。

这样是不就导致了服务器重复了?  

我主要是说这种情况,不知道有没有说清楚~~~ 这种加逻辑是可以处理掉的。 比如不是一个完整的包丢弃。
2014-4-22 13:17
0
雪    币: 30050
活跃值: (2377)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
18
XXXX
2014-4-22 14:36
0
雪    币: 272
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
当然会出现只传送部分数据的情况,WSASend其中一个参数就是返回实际传送的字节,遇到这种情况得接着传剩下的数据!
2014-4-22 14:58
0
雪    币: 272
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
。。。。。
2014-4-22 15:04
0
雪    币: 185
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
你仔细看我原话。 我说的就是服务器端不告诉 客户端 我收到了哪些数据 。  

所以前一次部分成功的数据。 在返回值上并没有体现。 (发送成功了5K,但是函数返回值告诉你失败,你以为全部都是失败,所以又全部重发。)

也就是WSASend 返回失败 , 但实际上有部分字节是发送到对方了。

如果不在应用层上加控制的话。单纯的重连,再次投递,这部份就重复了。  

不知道我说清楚了没有?
2014-4-22 18:34
0
雪    币: 30050
活跃值: (2377)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
22
XXXX
2014-4-22 21:06
0
雪    币: 185
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
1,这种情况肯定会发生的,我用WSASend 没出现过,可能是我代码跑的场景不是太充分,但send 确实出现过  。而且 从逻辑上讲也是可能的。

2,确实socket 关掉了,所以我要重新连接再发送啊。   

下面是你的原话,我觉得是不正确的。当返回失败, 已发送字节数大于零时,就是这种情况了。

要想你这种场景应该也不难,假设是一根1k的网线, 然后我们投递了一个1000M的数据包(假如),然后去发送, 中途拔掉网线,或者杀掉服务端程序。

函数肯定返回失败,但数据只发送了部分

为什么不会出现只发送一部分的情况呢?
2014-4-22 21:12
0
雪    币: 30050
活跃值: (2377)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
24
XXXX
2014-4-22 21:16
0
雪    币: 30050
活跃值: (2377)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
25
XXXX
2014-4-22 21:24
0
游客
登录 | 注册 方可回帖
返回
//