Socket程序发送PChar类型结构体的有趣问题(100分)

  • 主题发起人 主题发起人 北京男人
  • 开始时间 开始时间

北京男人

Unregistered / Unconfirmed
GUEST, unregistred user!
[:(] 我想通过Socket在局域网传输数据,而且这个数据可能是命令可能是字符串或者其他类型数据,
所以我定义了一个如下记录类型:
TSocketMessage = record
Msg: string[255]

end;
(其他数据域暂时忽略不谈,反正我觉得数据和命令同时传输时用记录类型方便一些)
然后在客户端把要传输的字符串装到这个记录里面,用ClientSocket1.Socket.SendBuf方法发出去,然后在
服务器端用ServerSocket1.Socket.ReceiveBuf方法接收这个记录,并将记录里的字符串显示出来一切正常,以下程序
能正确传输信息。
问题来了,因为我传输的数据量可能很大,所以记录域Msg string[255]太小了,所以我改类型为string或者
PChar类型,结果出现什么都发不过来或者是乱码的问题,请问为什么?以下是程序源码,unit1是服务器端,unit2是客户端,
unit3是公共单元大家需要将'192.168.100.148'这个改成自己机器的IP地址。
unit1程序窗口上放了ServerSocket1,Button1,memo1
unit2程序窗口上放了ClientSocket1,edit1,button1,button2,button3
--------------------------------
unit Unit3;

interface

type
TSocketMessage = record
Msg: string[255]
//我想把此行改成Msg: PChar;结果什么消息都不能发送了
end;

implementation

end.
--------------------------
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ScktComp, StdCtrls;

type
TForm1 = class(TForm)
ServerSocket1: TServerSocket;
Memo1: TMemo;
Button1: TButton;
procedure ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

uses Unit3;

{$R *.DFM}

procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
pack: TSocketMessage;
begin
Socket.ReceiveBuf(Pack, SizeOf(Pack));
memo1.lines.add( Pack.Msg );
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
ServerSocket1.Port:= 8090;
ServerSocket1.Open

end;

end.
------------------------------
unit Unit2;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ScktComp;

type
TForm2 = class(TForm)
ClientSocket1: TClientSocket;
Button1: TButton;
Edit1: TEdit;
Button2: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;


var
Form2: TForm2;

implementation

uses Unit3;

{$R *.DFM}

procedure TForm2.Button1Click(Sender: TObject);
begin
ClientSocket1.Port:= 8090;
ClientSocket1.Address:= '192.168.100.148';
ClientSocket1.Active:= true;
end;

procedure TForm2.Button2Click(Sender: TObject);
var
pack: TSocketMessage;
begin
pack.Msg := Edit1.Text
//此行改成pack.Msg := PChar(Edit1.Text);
ClientSocket1.Socket.SendBuf(Pack, SizeOf(Pack));
end;

procedure TForm2.Button3Click(Sender: TObject);
begin
if ClientSocket1.Active then
ShowMessage('true')
else
ShowMessage('False');
end;

end.
 
用string或pchar的话,是不是SizeOf(Pack)会有问题
随便说说,主要是听课!
 
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
S: string;
begin
Socket.ReceiveText(S);
memo1.lines.add(S);
end;

procedure TForm2.Button2Click(Sender: TObject);
var
pack: TSocketMessage;
begin
pack.Msg := PChar(Edit1.Text);
ClientSocket1.Socket.SendBuf(Pack.Msg^, StrLen(Pack.Msg));
end;

string[0..Len]已经是静态分配内存了,
而string or PChar,则无,所以
sizeof(s) // s[0..len]是求出长度
sizeof(s) // s: string or PChar,则永远=4
 
像Char, Byte, Word, Integer, DWORD以及静态分配的数组,为堆分配的内存,也就是一定义,就已经确定了它的内存范围,而string, PChar, Pointer, array of TDataType则不是,是需要在运行时刻,通过函数取得。所以。。。你在Client中发送的数据就是错的,那Server取的数据也自然就是错的。
一般动态类型的数据,一般是:

S: string
S[1] or PChar(S)^
C: PChar
C[0] or C^
P: Pointer
P^

最后说:不要使用动态数组,直接用PChar or Pointer就行了。
多多试验,就明白了。
 
不行啊,我那个记录类型里面还有其他好几个数据成员,我只是没写而已。
ClientSocket1.Socket.SendBuf(Pack.Msg^这个方法只能发送Msg这个数据域,如果还有两个数据域比如是这样定义的
type
TSocketMessage = record
MessageType: integer;
From: PChar;
Msg: PChar;
end;
那么如何发送呀?再说我接到信息后不能只通过
Socket.ReceiveText(S);
当字符串接收,因为我发送时是按照记录类型发送的,接收时也应该是拿记录来接收然后分析出MessageType,From,Msg三个数据域的值再继续处理。怎么办?
 
即然Integer怎么发你知道了,那怎么发送不定长的数据不难吧。
TCP是基于Stream发送数据的概念,不是说你发两次'abc',另一端就会触发两次,每次一个'abc',它是可能会是乱七八糟的触发的。所以,你自己还得管理一下接收的处理。
如:
Client:
Send Len(Integer);
Send Data(0..Len - 1)

Server:
Recv Len
Recv Data(0..Len - 1)

有了这个Data,怎么分隔那是另一回事。
明白上面的了,那两个PChar,三个也没问题啊。

不过,实际中,只是做一次Send PChar的上面的处理,不然,写起手累啊,因为,传输是传输,解析让另外的回调啊,事件啊,什么什么的去做吧。

不说了,费话多了。
 
我的目的就是把命令和数据在同一个包里发送,这样的话编程比较简单,思路比较清楚,如果记录类型不行那用什么方法发送好呢?
 
对于复杂结构,可以使用流来发送
var
tempLen: Integer;
TempStream : TMemoryStream;
begin
TempStream := TMemoryStream.Create;
// 发送定义格式类型时直接发送内容
tempInt := 10;
TempStream.WriteBuffer(@pack.MessageType, sizeof(Integer))

// 发送非定义格式类型时,数据前带有四个字节的长度内容
tempLen := Length(pack.Msg);
TempStream.WriteBuffer(@tempLen, sizeof(Integer));
TempStream.WriteBuffer(pack.Msg, tempLen);
...
TempStream.Position := 0;
ClientSocket1.Socket.SendStream(TempStream);
TempStream.Free;
end;

接收到数据时,使用同样的顺序取出记录项即可,这和FMC的串行化方式类似。
但在接收数据时,必须进行边界处理,因为你不一定会在一个包中得到完整的数据。
以上代码没调试过,可以有问题,但基本思想就在里面。
 
楼上说的有理,不过接收时候无法知道包里数据的长度呀!
tempLen := ??;
TempStream.ReadBuffer(pack.Msg, tempLen);
 
写的时候先写入长度啊:
TmpStream.Write(Length(Str), SizeOf(Integer))
// 先写入长度
TmpStream.Write(Str[1], SizeOf(Str));

读的时候就先读长度:
TmpStream.Read(tmpLen, SizeOf(Integer))
// 先读取长度
SetLength(pack.Msg, tmpLen)
// 记得先分配空间
TmpStream.Read(pack.Msg, tmpLen);
 
发送时先发送要发送流的长度然后再发送流,接收时先收长度,在按长度接收流,随便发什么都是这样,不会有问题。
如果长度太大可以分成几部分,每接收一部分返回成功信息否则返回失败信息。发送方收到成功信息后再发下一包否则重发当前包。

 
多人接受答案了。
 

Similar threads

S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
后退
顶部