Socket.Receive 方法 (Byte(), Int32, Int32, SocketFlags)
使用指定的 SocketFlags,从绑定的 Socket 接收指定的字节数,存入接收缓冲区的指定偏移量位置。
public int Receive( byte[] buffer, int offset, int size, SocketFlags socketFlags )
参数
buffer
类型:System.Byte()
Byte 类型的数组,它是存储接收到的数据的位置。
offset
类型:System.Int32
buffer 中存储所接收数据的位置。
size
类型:System.Int32
要接收的字节数。
socketFlags
类型:System.Net.Sockets.SocketFlags
SocketFlags 值的按位组合。
返回值
类型:System.Int32
接收到的字节数。
(以上部分来自MSDN)
接下来我的经验总结:
1,Socket实际接收到的字节数与预期想要接收字节数
我们从上面的返回值可以清晰的知道,虽然我们给Buffer定义了一个长度和要接收到的size,但是Socket接收到的字节数(即返回值)并不一定等于Size
所以我们在做这个的时候,需要判断,实际接收到的字节数是否等于要接收的字节数!这个很重要。
//定义接收数据的缓存 byte[] body = new byte[1024]; //第一次接收的实际数据 flag int flag = socket.Receive(body, 0, body.Length, SocketFlags.None); //如果没有接收到定长的数据,循环接收 while(flag != body.Length) { flag += socket.Receive(body, flag, body.Length - flag, SocketFlags.None); }
因此,Socket接收数据的过程应该是这样的!
2,服务器发送多次,客户端一次接收的问题。
当然在实际的编程中很少这么干的,但是我这么写是想说明一个问题:假如客户端想一次接收服务器多次发送过来的数据,客户端定义缓存body的长度等于服务端多次发送的数据长度和,客户端在服务器发送完成后开始一次接收,客户端肯定能接收到服务器发送过来的所有数据,不会只接收服务器发送过来的部分数据。
我的叙述很烂!打个比方,假如服务器分三次分别发送“Hell”,“0”,“World”。这样客户端一定能接受到“HelloWorld”。而不是“Hell”或者其他什么的东西。
最近在调试程序的时候,希望1秒钟调用一次 Socket.Receive() 来接收数据。
实际上,应该是说,如果没有数据到来,就是1秒钟一次,如果有数据到来,则收到数据后立即继续接收,然后继续是1秒钟接收一次。
C#的相关代码如下:
public bool ReadDataForMe(Socket sck, uint waitSec) { int rlen = 1; DateTime ttStart = DateTime.Now; //sck.ReceiveTimeout = 5; sck.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, -300); while (true) { try { PrintLog("start read socket.\n"); if (BaseTools.GetEclpseTime(ttStart, 0.4) >= waitSec) { break; } rlen = sck.Receive(m_bufRecv, 0, 1000, SocketFlags.None); if (rlen == 0) { BaseTools.Sleep(100); continue; } return true; } catch (IOException) { // 读取失败,原因:基础 System.Net.Sockets.Socket 被关闭。 return false; } catch (SocketException e) { if (e.ErrorCode == 10060) // 超时的时候错误号码是10060 continue; break; } catch (Exception e) { break; } }return false; }// 如下调用while(true) { if (ReadDataForMe(mySock, 1)) break; }
其中BaseTools为一个基本工具库,自己封装的,这里用到了两个功能,一个是Sleep(),用于休息多长时间,另外一个是 GetEclpseTime() 用于获取指定的一个时间到当前时间的秒数,这样用于计算时间差。
另外还有一个函数 PrintLog() 用于在控制台打印字符串,其中添加了时间信息,代码如下:
public static void PrintLog(string s) { Console.WriteLine($"[{ DateTime.Now.ToString("HH:mm:ss.fff")}] {s}"); }
在每一次打印都添加了时间,精确到毫秒,这样方便对照打印时的时间。
测试方法就是连上服务器之后,服务器不发送数据,此客户端一直调用Receive()接口来收取数据。在每一次接收数据之前都加一行打印:start read socket.
以上为测试的环境情况。
下面测试主要是对函数开始的前几行的socket超时设置以及对超时时间的观察。
如上面的代码那样,设置超时时间为 -300,则打印间隔为500ms
如果改成3,则打印间隔为503ms
前几日碰到一问题,当CSocket的Receive阻塞时,如何进行超时处理。由于程序是在多线程中使用Socket通信,开始时是在主线程中用定时监测Receive函数,当超时后,结束通信。但问题是CSocket对象无法释放。因此从网上搜索解决办法,直接在线程中对Receive进行超时处理。
不错,搜到以下内容,很多网站转载。
为CSocket配置Time-Out功能
CSocket操作,如Send(),Receive(),Connect()都属阻塞操作,即它们在成功完成或错误发生之前是不会返回的。
在某些情况下,某项操作可能永远不能成功完成,程序为了等待其完成就得永远循环下去。在程序中为某项操作限定一个成功完成的时间是个好主意。本文就是讨论此问题的。
一个办法是设计一个计时器,当操作费时过长时就触发。这个办法的关键是怎样处理计时器。虽然操作是"阻塞"的,但仍具处理传回的消息的能力。如果用SetTimer来设置计时器,就可截获WM_TIMER消息,当它产生时就终止操作。涉及到这个过程的主要函数是:Windows API ::SetTimer(),MFC函数CSocket::OnMessagePending()和CSocket:: CancelBlockingCall()。这些功能可包装到你的CSocket类中得以简化。
类中用到三个重要函数:
BOOL SetTimeOut(UINT uTimeOut) 它应在CSocket函数调用前被调用。uTimeOut以千分秒为单位。下面的实现只是简单的设置计时器。当设置计时器失败时返回False。参见Windows API中关于SetTimer的说明。
BOOL KillTimeOut() 此函数应在操作未完成被阻塞时被调用。它删除SetTimeOut所设置的计时器。如果调用KillTimer失败则返回False。参见Windows API中关于KillTimer的说明。
BOOL OnMessagePending() 它是一个虚拟回调函数,当等待操作完成时被CSocket类调用。它给你机会来处理传回的消息。这次我们用它来检查SetTimeOut所设置的计时器,如果超时(Time-Out),则它调用CancelBlockingCall()。参见MFC文档关于OnMessagePending()和CancelBlockingCall()的说明。注意调用CancelBlockingCall()将使当前操作失败,GetLastError()函数返回WSAEINTR(指出是中断操作)。
下面就是使用这个类的例子: ...
CTimeOutSocket sockServer; CAcceptedSocket sockAccept; sockServer.Create(777); sockServer.Listen(); // Note the following sequence: // SetTimeOut // <operation which might block> // KillTimeOut if(!sockServer.SetTimeOut(10000)) { ASSERT(FALSE); // Error Handling...for some reason, we could not setup // the timer. } if(!sockServer.Accept(sockAccept)) { int nError = GetLastError(); if(nError==WSAEINTR) AfxMessageBox("No Connections Arrived For 10 Seconds"); else ; // Do other error processing. } if(!sockServer.KillTimeOut()) { ASSERT(FALSE); // Error Handling...for some reason the timer could not // be destroyed...perhaps a memory overwrite has changed // m_nTimerID? // } ...
下面是示例代码:
// // HEADER FILE // class CTimeOutSocket : public CSocket { public: BOOL SetTimeOut(UINT uTimeOut); BOOL KillTimeOut(); protected: virtual BOOL OnMessagePending(); private: int m_nTimerID; }; // // END OF FILE // // // IMPLEMENTATION FILE // BOOL CTimeOutSocket::OnMessagePending() { MSG msg; if(::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_NOREMOVE)) { if (msg.wParam == (UINT) m_nTimerID) { // Remove the message and call CancelBlockingCall. ::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE); CancelBlockingCall(); return FALSE; // No need for idle time processing. }; }; return CSocket::OnMessagePending(); } BOOL CTimeOutSocket::SetTimeOut(UINT uTimeOut) { m_nTimerID = SetTimer(NULL,0,uTimeOut,NULL); return m_nTimerID; } BOOL CTimeOutSocket::KillTimeOut() { return KillTimer(NULL,m_nTimerID); }
我按照以上方式建类并完成代码。运行测试后发现并没有实现效果!OnMessagePending没有监测到WM_TIMER消息。
然后我对类进行了调整,一来使得封装更好,二来外部调用基本不用做任何变化,三来希望能使OnMessagePending能够起作用。
其实修改很简单,就是将SetTimeOut和KillTimeOut都修改为私有函数,并重载CSocket基类的Receive和Send函数,在收发前后启动和关闭定时器。
class CTimeOutSocket : public CSocket{// Attributespublic: // Operationspublic: CTimeOutSocket(); virtual ~CTimeOutSocket(); // Overridespublic: // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CTimeOutSocket) public: virtual BOOL OnMessagePending(); virtual int Receive(void* lpBuf, int nBufLen, int nFlags = 0); virtual int Send(const void* lpBuf, int nBufLen, int nFlags = 0); //}}AFX_VIRTUAL // Generated message map functions //{{AFX_MSG(CTimeOutSocket) // NOTE - the ClassWizard will add and remove member functions here. //}}AFX_MSG // Implementationprotected: int m_nTimerID;private: BOOL KillTimeOut(); BOOL SetTimeOut(int nTimeOut);}; BOOL CTimeOutSocket::OnMessagePending() { MSG msg; if(::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_NOREMOVE)) { if (msg.wParam == (UINT) m_nTimerID) { // Remove the message and call CancelBlockingCall. ::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE); CancelBlockingCall(); return FALSE; // No need for idle time processing. }; }; return CSocket::OnMessagePending();} int CTimeOutSocket::Receive(void* lpBuf, int nBufLen, int nFlags) { SetTimeOut(10000); int nRecv = CSocket::Receive(lpBuf, nBufLen, nFlags); KillTimeOut(); return nRecv;} int CTimeOutSocket::Send(const void* lpBuf, int nBufLen, int nFlags) { SetTimeOut(10000); int nSend = CSocket::Send(lpBuf, nBufLen, nFlags); KillTimeOut(); return nSend; } BOOL CTimeOutSocket::SetTimeOut(int nTimeOut){ m_nTimerID = SetTimer(NULL,0,nTimeOut,NULL); return m_nTimerID;} BOOL CTimeOutSocket::KillTimeOut(){ return KillTimer(NULL,m_nTimerID);}
经过修改的代码,运行后OnMessagePending得到了WM_TIMER消息,正确解决了Receive的阻塞问题,同时我只需将原来的CSocket对象改为CTimeOutSocket对象就可以了,其它代码不用变化。
如对本文有疑问,请提交到交流论坛,广大热心网友会为你解答!! 点击进入论坛