主要讨论一下数据的接受:
1.NMUDP控件
这个控件使用起来比较简单,设定监听端口,然后响应DataReceived事件就可以了,例如:
void __fastcall TMoniter::NMUDPDataReceived(TComponent *Sender,
int NumberBytes, AnsiString FromIP, int Port)
{
/* 用一个标志变量控制控件受信后是否执行需要的操作 */
if (recvFlag)
{
int rl;
/* 用于接受数据的内存 */
unsigned char rbuf[1024 * 9];
/* 控件的ReadBuffer方法,把接受到的数据存储到rbuf */
NMUDP -> ReadBuffer(rbuf , sizeof(rbuf) , rl);
/* 字符串结束 */
rbuf[rl]=0;
/* stream是事先定义的文件指针 */
if (stream != NULL)
{
/* 自编doLog函数,把接收数据写入日志文件 */
doLog( false , rbuf ,rl );
}
}
}
这个控件的优点是使用简单、效率比较高,但是只支持2K的缓冲,所以上面开辟的9K内存是多余的。2K的限制使我在项目中不得不放弃了这个控件。
2.IdUDPServer控件
使用方法跟NMUDP差不多,响应UDPRead事件就可以了,例如:(注释参考1)
void __fastcall TMoniter::IdUDPServer1UDPRead(TObject *Sender,
TStream *AData, TIdSocketHandle *ABinding)
{
if (recvFlag)
{
int r1;
unsigned char rbuf[1024 * 9];
r1 = AData->Size;
/* 接受到的数据是存放在数据流AData中的,把它们读到rbuf里去 */
AData->Read(rbuf , r1);
rbuf[r1] = 0;
if (stream != NULL)
{
doLog( false , rbuf ,r1);
}
}
}
这个控件支持了9K的缓冲,但是效率……我需要1秒钟接收150个1K多的数据包并解码后逐行显示在StringGrid中,虽然主要是对StringGrid的描绘浪费时间,但IdUDPServer还是不能令人满意。
3.回归自然吧——Socket
两个控件都不能满足我的需要,那么只能回头考虑底层的socket(我的C不好,对这个方法现在还不是很明白,所以注释很少,不过通过代码能大约猜出其功能)。
先定义这三个东东:
SOCKET sock
WSADATA wsaData
SOCKADDR_IN sockaddr
然后在需要开始受信的地方进行socket初始化,这里我用了一个按钮:
int result;
WORD wVersionRequested;
wVersionRequested = MAKEWORD(1,1);
if((result = WSAStartup(wVersionRequested,&wsaData))!=0)
{
Application->MessageBoxA("Socket Initial Error","Error",MB_OK);
WSACleanup();
return;
}
memset(&sockaddr,0,sizeof(sockaddr));
/* 设置端口号 */
sockaddr.sin_port=htons(3000);
sockaddr.sin_family=AF_INET;
sockaddr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock == INVALID_SOCKET)
{
Application->MessageBoxA("Socket Open failed","Error",MB_OK);
WSACleanup();
return;
}
result = bind(sock,(LPSOCKADDR)&sockaddr,sizeof sockaddr);
if(result == SOCKET_ERROR)
{
Application->MessageBoxA("Bind Error","Error",MB_OK);
WSACleanup();
return;
}
/* 自写函数getFileReady打开一个日志文件等待记录数据 */
if( !getFileReady() )
{
WSACleanup();
return;
}
/* 把StringGrid编辑区域清理一下 */
sgLog -> RowCount = 2;
sgLog -> Rows[1] -> Clear();
sgLog -> Cells[0][1] = "1";
lineCount = 1;
/* 启动线程,接受数据 */
recvFlag = true;
tudpr = new TUDPR(true);
tudpr->Resume();
}
TUDPR是负责受信的线程,其类定义如下:
class TUDPR : public TThread
{
private:
protected:
void __fastcall Execute();
public:
__fastcall TUDPR(bool CreateSuspended);
};
线程内的完整处理如下:
#include <vcl.h>
#pragma hdrstop
#include <winsock.h>
#include "TUDPR.h"
#include "Monitor.h"
extern int m_sendRcvFlag;
extern SOCKET sock;
extern WSADATA wsaData;
extern SOCKADDR_IN sockaddr;
#pragma package(smart_init)
__fastcall TUDPR::TUDPR(bool CreateSuspended)
: TThread(CreateSuspended)
{
}
void __fastcall TUDPR::Execute()
{
int result;
unsigned char rbuf[SNDRCVDATALEN];
/* 受信标志变量为真时接收数据 */
while(recvFlag)
{
result = recvfrom(sock,
rbuf,
SNDRCVDATALEN,
0,
NULL,
NULL
);
if( !recvFlag )
{
break;
}
if(result == SOCKET_ERROR)
{
Application->MessageBoxA("Receive Error","Error",MB_OK);
WSACleanup();
return;
}
rbuf[result] = 0;
/* 参考1中的doLog注释 */
Moniter -> doLog(false , rbuf , result);
}
}
第三种方法在效率上可以满足要求了,但是需要管理线程,实现起来也明显要麻烦许多。