C++Builder 程序员博客
3 Jul
基于TCP/IP的报文通讯,具体为异步长连接方式
通讯协议格式如下:
报文字段 类型与长度 字段说明
length DWORD 包的总长度,为包控制信息和实际传送内容长度之和
factory BYTE 厂商编码
morePkt BYTE 是否还有后续包,’1’有,’0’无
cmdId CHAR[10] 命令字,长度为10的数字串,必须10位,右补\0
recordSep CHAR[2] ” ~”。
datastr CHAR[不定长] 数据内容
报文为string
假设 length=34
factory=10
morepkt=0
cmdid=1234
recordsep=” ~”
datastr=”123456789“
请问怎样组织这个报文呢?请大家不吝赐教
请教楼上的:
datastr CHAR[不定长]怎样定义呢?好象bcb里不能这样定义吧CHAR[] datastr , 定义成一个CHAR的指针呢还是定义成string?
我用的是tclientsocket控件,用它怎样发送这个结构呢?
如果应答报文不是结构体,我怎样去解析报文呢?比如报文长度是四个字节。应该怎样取这四个字节呢?
谢谢您,我刚做通讯,希望你别介意。
这样的结构顶多算是报文的数据体。报文起码要有特定的报文头,报文长度,报文结束位。
还有帧校验位,控制位、标志位等等。
我想楼主所说的报文应该是应用层的通信协议而不是报文交换方式里的报文吧:-)
对于最后那个不定长的CHAR,你可以看我上面给出的结构体最后一个成员CHAR szData[1]; 它的作用是让szData紧跟在结构体之后,尽管定义成szData[1],但它不是只是放一位,我后面写的代码里演示了往里放"123456789"的方法。
这种设计方法在Windows编程里会见到,是MS先用的,可以放心了吧:-)
请教,下面用#c写的组包和发送程序
它是先把每一个类的函数成员转化成字节数组存到一个字节可变字节数组里,然后就直接sendmessagebyte了。如果按你方法我直接用个结构来存放数据,把数据写入后我能直接发送结构体?用socket那个发送命令?而且我感觉那样整个结构体解析出来长度对不上。所以我还是打算用它这个办法,还是定义个类或结构体,然后把函数成员写如后组包,但我应该组成个字节数组包呢还是字符串包?如果组成字节包用socket哪个命令发送呢?请帮帮忙。压力大,在试用期啊。
我现在是定义了个类,函数成员就是下面这些,然后用_length=42+类->DataTrans.Length();memcp(result,&_length,4);
我本意是拷贝&_length 到result中,memcp(result+6,&CmdId,10);这是把CmdId拷贝到result的第7个位置开始;请问我这样行吗?而且运行的时候(类->DataTrans.Length())提示错误。看看吧。
/// <summary>
/// 取得发送的数据与信息.
/// </summary>
public byte[] SendMessageByte
{
get
{
int _length = 42 + Encoding.Default.GetByteCount(em.DataTrans);
byte[] result = new byte[_length];
byte[] tmp = null;
//length DWORD 包的总长度,为包控制信息和实际传送内容长度之和
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.Length));
Array.Copy(tmp, 0, result, 0, 4);
//factory BYTE 厂商编码(暂不处理)
result[4] = em.Factory;
//morePkt BYTE 是否还有后续包,’1’有,’0’无
result[5] = em.MorePkt;
//cmdId CHAR[10] 命令字,长度为10的数字串,必须10位,右补\0
tmp = null;
tmp = Encoding.Default.GetBytes(em.CmdId);
Array.Copy(tmp, 0, result, 6, 10);
//requestId DWORD 请求流水号,为异步通讯的标识,客户端生成,服务端在应答包中原样返回。说明:如果服务端返回的报文大于一个包,则这一组包的流水号相同
tmp = null;
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.RequestId));
Array.Copy(tmp, 0, result, 16, 4);
//answerId DWORD 应答流水号,EOC在回送包中将RequestId填写在此字段回送。说明:如果服务端返回的报文大于一个包,则这一组包的应答流水号相同
tmp = null;
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.AnswerId));
Array.Copy(tmp, 0, result, 20, 4);
//packetNo DWORD 包序号,从1开始
tmp = null;
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.PacketNo));
Array.Copy(tmp, 0, result, 24, 4);
//recordSep CHAR[2] 记录分隔符。目前暂不使用,EOC在回送包直接填” ~”。
tmp = null;
tmp = Encoding.Default.GetBytes(recordSep);
Array.Copy(tmp, 0, result, 28, 2);
//fieldSep CHAR[2] 字段分隔符。目前暂不使用,EOC在回送包直接填” ;”。
tmp = null;
tmp = Encoding.Default.GetBytes(fieldSep);
Array.Copy(tmp, 0, result, 30, 2);
//reserved DWORD[2] 保留字段
tmp = null;
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.Reserved[0]));
Array.Copy(tmp, 0, result, 32, 4);
tmp = null;
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.Reserved[1]));
Array.Copy(tmp, 0, result, 36, 4);
//errCode SHORT 返回码0表示成功,其它表示失败。表示请求是否被成功地处理,与具体的业务无关。当网络、应用通讯、数据库访问正常时,errorcode 应该为0。请求包中的errorcode可以随便填充。注意此处表示系统级的成功与失败(如报文实际长度与字段len中表示的不一致)。对数据的实际操作结果放在datatrans中
tmp = null;
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.ErrCode));
Array.Copy(tmp, 0, result, 40, 2);
//dataTrans CHAR[不定长] 传送内容,从datatrans开始依次为数据内容,实际使用时取其地址作为指针使用
tmp = null;
tmp = Encoding.Default.GetBytes(em.DataTrans);
Array.Copy(tmp, 0, result, 42, Encoding.Default.GetByteCount(em.DataTrans));
return result;
}
public bool SendMessage(ref string receiveStr)
{
string msg = Encoding.Default.GetString(SendMessageByte);
DataRow rw = dt.NewRow();
rw["ServerIP"] = SvrIP;
rw["ServerPort"] = SvrPort;
rw["Message"] = msg;
byte[] receiveMessage = new byte[4096];
SocketHelper.SendOnTcp(SvrIP, SvrPort, SendMessageByte,ref receiveMessage, 3);length,4)
/// <summary>
/// 取得发送的数据与信息.
/// </summary>
public byte[] SendMessageByte
{
get
{
int _length = 42 + Encoding.Default.GetByteCount(em.DataTrans);
byte[] result = new byte[_length];
byte[] tmp = null;
//length DWORD 包的总长度,为包控制信息和实际传送内容长度之和
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.Length));
Array.Copy(tmp, 0, result, 0, 4);
//factory BYTE 厂商编码(暂不处理)
result[4] = em.Factory;
//morePkt BYTE 是否还有后续包,’1’有,’0’无
result[5] = em.MorePkt;
//cmdId CHAR[10] 命令字,长度为10的数字串,必须10位,右补\0
tmp = null;
tmp = Encoding.Default.GetBytes(em.CmdId);
Array.Copy(tmp, 0, result, 6, 10);
//requestId DWORD 请求流水号,为异步通讯的标识,客户端生成,服务端在应答包中原样返回。说明:如果服务端返回的报文大于一个包,则这一组包的流水号相同
tmp = null;
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.RequestId));
Array.Copy(tmp, 0, result, 16, 4);
//answerId DWORD 应答流水号,EOC在回送包中将RequestId填写在此字段回送。说明:如果服务端返回的报文大于一个包,则这一组包的应答流水号相同
tmp = null;
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.AnswerId));
Array.Copy(tmp, 0, result, 20, 4);
//packetNo DWORD 包序号,从1开始
tmp = null;
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.PacketNo));
Array.Copy(tmp, 0, result, 24, 4);
//recordSep CHAR[2] 记录分隔符。目前暂不使用,EOC在回送包直接填” ~”。
tmp = null;
tmp = Encoding.Default.GetBytes(recordSep);
Array.Copy(tmp, 0, result, 28, 2);
//fieldSep CHAR[2] 字段分隔符。目前暂不使用,EOC在回送包直接填” ;”。
tmp = null;
tmp = Encoding.Default.GetBytes(fieldSep);
Array.Copy(tmp, 0, result, 30, 2);
//reserved DWORD[2] 保留字段
tmp = null;
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.Reserved[0]));
Array.Copy(tmp, 0, result, 32, 4);
tmp = null;
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.Reserved[1]));
Array.Copy(tmp, 0, result, 36, 4);
//errCode SHORT 返回码0表示成功,其它表示失败。表示请求是否被成功地处理,与具体的业务无关。当网络、应用通讯、数据库访问正常时,errorcode 应该为0。请求包中的errorcode可以随便填充。注意此处表示系统级的成功与失败(如报文实际长度与字段len中表示的不一致)。对数据的实际操作结果放在datatrans中
tmp = null;
tmp = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(em.ErrCode));
Array.Copy(tmp, 0, result, 40, 2);
//dataTrans CHAR[不定长] 传送内容,从datatrans开始依次为数据内容,实际使用时取其地址作为指针使用
tmp = null;
tmp = Encoding.Default.GetBytes(em.DataTrans);
Array.Copy(tmp, 0, result, 42, Encoding.Default.GetByteCount(em.DataTrans));
return result;
}
public bool SendMessage(ref string receiveStr)
{
string msg = Encoding.Default.GetString(SendMessageByte);
DataRow rw = dt.NewRow();
rw["ServerIP"] = SvrIP;
rw["ServerPort"] = SvrPort;
rw["Message"] = msg;
byte[] receiveMessage = new byte[4096];
SocketHelper.SendOnTcp(SvrIP, SvrPort, SendMessageByte,ref receiveMessage, 3);
解析报文长度不对,那是因为你的length这个值是随便设置的,实际上这个值发送时是要计算出来的。length DWORD 这个值,你要的是一帧报文的长度,而不是一包的长度。一包跟一帧是不同的,每发送一次就叫一包,有时候由于网络等原因,一包可能含有多帧,一帧也有可能分成两包发送。所以必须要有报文头,报文长度,报文结束位,才能判断数据的合法性完整性,以及解析报文时计算长度。例如我现在做的一套电力系统里面就是规定以68H开头,然后跟着4个字节的LL再跟一个68H,这就是个报文头,一个L就是报文长度,要重复两次。报文长度是用来计算数据长度的,特别是不定长的数组之类。按照你上面的定义,length=25字节才对。
我做这类开发的时候,将整个报文写到流里面,再发出去。
解析报文的时候,将收到的数据写到流里面,指针移到头部,先做合法性判断。如果前两个字节就定义成是长度,每个报文的长度都不固定,能进行判断吗?只有合法了才有做解析的必要。流可以使用TMemoryStream,用个PByte来保存当前指针,用Move函数将指针的内容移到自己的结构、或者变量里面,Inc函数来向后移指针。