我现在写的程序有点问题。
首先让我解释它应该实现的目标。
它与聊天程序非常相似,所以它基本上有一个信息类(我称之为数据包),它存储有关客户端窗口上发生的某些事情的数据。该程序本身由两个不同的客户端窗口和一个服务器窗口组成。 (还有一个窗口可以打开每个窗口,但现在这些并不重要。)
所以客户在他的Window上更改了一些内容并按下发送。
然后我的数据包将存储所有这些信息。它由一个昵称,一个文本,一些不同的数字值(基本的Dice-Roll结果)和一个'Kind'-Variable组成。
有三种不同类型的消息,这种“数据包”能够发送。
每当发送一条消息(并因此被服务器接收)时,程序就会读出,它是什么类型的消息(通过kind-variable)并根据它是什么来启动处理消息的不同步骤。 / p>
聊天消息只是在聊天中显示,并发送到每个客户端以便在那里显示。这没有任何错误或其他原因。
Dice-Messages都有一个特殊的形状显示出来。 它基本上是一个以这种形状生成的聊天消息,以显示骰子掷骰是否成功。
现在我的问题出现了:
我在局域网版本中测试了该程序(使用我的locale-IP),它运行得很好。没有问题,没有错误。
但现在我请朋友帮我通过互联网进行测试。 Portforwarding工作,客户能够连接。
现在发送消息。 Chatmessage:工作正常 Dice1-Message:仅发送昵称和两个骰子结果。第三个骰子结果没有显示出来,其他所有信息只会在那里留下一个空白区域。 第二个骰子消息也是如此。它仅显示骰子结果和昵称,但不显示任何其他信息。
如果有用的话,该程序是用Delphi 6.0编写的(是的,有点老了,但是我已经习惯了这个程序,我对编程还不是很好..所以我用它来学习基础知识和写我的第一个更大的程序,比如那个。)
Packet本身的定义如下:
type
TPacket = Record
Nickname : string[255];
Text : string[255];
OtherInfo: VarType;
end;
等。 然后消息引用这样的信息:
ServerSocket.Socket.ReceiveBuf (Packet, SizeOf(Packet));
MemMessage.Lines.Add(Packet.Nickname + 'succeeded his dice roll for ' + Packet.Talent + 'Restofmessage ' + Packet.OtherInformation)
所以..那是怎么回事。我希望那里的任何人知道这个问题是什么(或者更好的是在这种情况下局域网和互联网之间的问题导致的差异),并知道一种方法来帮助我解决这个问题。
先谢谢〜
PS:服务器只需获取信息,以相同的方式显示,任何客户端,然后将其直接发送到每个客户端,接收并显示它。以防万一这可能很重要。
修改:
我认为这些信息很好,因为“其他信息”并不是真的不同。但好的〜更多细节:)
'其他信息'都有类型字符串[255],反正其中一个是布尔值。
Dice1 -Type使用以下内容:
昵称,结果1,结果2,结果3,难度,技能点,天赋(只是天赋的名称,骰子滚动测试成功或失败)和一个名为“成功”的布尔值。
Dice2 使用:
昵称,结果,难度,天赋(天赋的名字。不要误认为^^),TalentPoints(基本上是Talent的值减去掷骰子的难度)以及布尔'成功'为以及一个名为'Kind'的字符串[255]。 Kind的功能如上所述。
关于协议,我认为我不使用任何特殊的协议。
我只需通过SendBuf和ReceiveBuf发送整个类TPacket。 我对编程很新,所以很抱歉我不能给你更多的信息:/
让我给出程序的代码,服务器用于接收和重定向TPacket。
我希望我不犯任何错误,因为我用德语编写程序,所以我现在必须翻译它。^^
procedure TFormServer.ServerSocketClientRead (Sender:TObject; Socket: TCustomWinSocket);
var
Message : TPacket;
i : ShortInt;
begin
{receive Message}
Socket.ReceiveBuf (Message, SizeOf(Message))
{checking, what kind the message is of}
if Message.Kind = 'Chat' then begin
MemMessage.Lines.Add ('By' + Message.Nickname + ': ' + Message.Text);
{Redirecting to other clients}
with ServerSocket.Socket do begin
for i := 0 to ActiveConnections -1 do
Connections[I].SendBuf(Message, SizeOf(Message))
end;
end;
if Message.Kind = 'DiceRoll_1' then begin
if Message.Success = true
then MemMessage.Lines.Add (Message.Nickname + ' succeeded his Dice Roll for ' + Message.Talent + ' (Difficulty: ' + Message.Difficulty + ') with the results: (' + Message.Result1 + '/' + Message.Result2 + '/' + Message.Result3 + '), Skill lvl: ' + Message.Skillpoints);
//The Message should look like that
{Dummy succeeded his Dice Roll for climbing (Difficulty: 4) with the results: (12/14/13), Skill lvl: 8}
//For Comparison, the buggy message looks like that
{Dummy succeeded his Dice Roll for (Difficulty: ) with the results: (12/14/ ), Skill lvl: }
else {The same as when it succeeded, just saying it did 'not' succeed..}
{redirection}
with ServerSocket.Socket do begin
for i := 0 to ActiveConnections -1 do
Connections[i].SendBuf (Message, SizeOf(Message))
end;
end; {if kind = DiceRoll_1}
{Same goes for the DiceRoll_2 now. This time it uses: Message.Nickname, Message.Talent, Message.Difficulty, Message.Result, Message.TalentPoints, Message.Success}
现在再次宣传我的TPacket ..可能很重要
type TPacket = Result
Kind : string[255];
Nickname : string[255];
Text : string[255];
Result : string[255];
Result1 : string[255];
Result2 : string[255];
Result3 : string[255];
Difficulty : string[255];
Talent : string[255];
Skillpoints : string[255];
TalentPoints: string[255];
Succeed : boolean;
end;
我希望这让它更容易理解.. 我想解释不是那么擅长:/
但是,谢谢你在这里欢迎我,我希望我不会为你们打扰,也可以在一个或另一个案例中提供帮助!
答案 0 :(得分:2)
嗯......如果它是我正在考虑的组件,那么ClientRead事件就会被一个充满数据的完整消息触发,这不是一个锁。
如果是这种情况,您需要在TCP之上的协议以允许传输消息。 TCP本身不能传输消息,只能传输字节流。
TCustomWinSocket docs,(我的斜体):
'使用ReceiveBuf从Windows套接字对象的OnSocketEvent事件处理程序或套接字组件的OnRead或OnClientRead事件处理程序中读取套接字连接。
ReceiveBuf返回实际读取的字节数(可能小于通话中请求的数量)。
如果没有读取字节,则ReceiveBuf返回-1。'
所以,你的狡猾计划只是声明超大缓冲区并且总是为了避免解析收到的数据而转移该批次并没有奏效。
你需要正确地做。设计一个协议,无论ClientRead()事件是使用整个消息,部分消息,组合消息还是每次都用一个字节进行多次激活,它都会起作用。我的首选设计是使用带有'byteLoad(thischar:char)的'PDU'类:boolean;'使用内部状态机来处理协议的方法,使用corect数据加载字段,并且仅在组装完整消息时返回true。在每个ClientRead调用中,我迭代每个接收到的字节并调用'byteLoad'直到它返回true,然后处理PDU,(可能通过在某处将它排队),创建另一个PDU并开始加载它。
请注意,由于您使用的是异步,非线程TServerSocket选项,因此必须将任何此类特定于客户端的数据(如PDU实例或必须在ClientRead调用中保留的任何其他数据)存储为TCustomWinSocket实例的字段,即传入。这意味着TCustomWinSocket后代用于保存此数据,或者使用基本TCustomWinSocket的“data”属性来保存另一个对象或记录。通常在OnConnected事件中创建/初始化/加载这些东西。谨防TCustomWinSocket在断开连接时的行为 - 我有一种有趣的感觉,它试图释放数据字段中的任何非零指针 - 这可能是你不想要的。
答案 1 :(得分:0)
您使用的是哪种协议?
如果这是UDP,则这些问题是“按设计”的协议。
缺乏可靠性,UDP应用程序通常必须愿意接受一些丢失,错误或重复。
请参阅http://en.wikipedia.org/wiki/User_Datagram_Protocol
通过本地网络,它可以正常运行,因为条件是最佳的。
但是在互联网上,一些数据包丢失了。
如果您想要路由数据,您将要么通过UDP在应用程序中添加确认+重发机制(请参阅TFTP协议以获取原始数据),或使用更高级的协议,如TCP,将在数据包丢失的情况下重试发送数据 - 但会产生更大的开销。
对于普通聊天程序,使用TCP(如果需要,甚至使用HTTP布局 - 这对防火墙更友好),确实有意义;即使此协议不是双工的,您也可以定期(例如1秒)提取数据,向服务器询问待处理的消息。
顺便说一句,使用string[255]
对于应用程序层来说有点粗鲁。使用#13#10(换行符)终止文本,甚至是#0终止文本都是有意义的。在聊天过程中,大部分数据包都将被闲置,您的文本中将无法使用超过255个字符。或者依赖于标准格式,如XML或JSON,用于您的消息布局。