2

我目前正在编写的程序有一点问题。

首先让我解释一下它应该实现什么。

它与聊天程序非常相似,因此它基本上有一个信息类(我称之为 Packet),它存储有关某些事情的数据,这些数据发生在客户端窗口上。该程序本身由两个不同的客户端窗口和一个服务器窗口组成。(还有一个窗口可以打开其中的每一个,但现在这并不重要。)

所以客户在他的窗口上改变了一些东西并按下发送。

然后我的数据包将存储所有这些信息。它由昵称、文本、一些不同的数值(基本上是掷骰子结果)和“种类”变量组成。

有三种不同类型的消息,这个“数据包”能够发送。

  1. 一条普通的短信(完美运行。)
  2. 一个骰子消息,由 3 个骰子结果、一个难度值和一个天赋值组成。
  3. 另一个骰子消息,这次只包含一个骰子结果。

每当发送消息(并因此被服务器接收)时,程序就会读出它是什么类型的消息(通过 kind-variable),并根据消息的内容启动不同的步骤来处理消息。

聊天消息只是在聊天中显示并发送给每个客户端以显示在那里。这没有任何错误或其他东西。

骰子消息都有一个特殊的形状来显示。它基本上是以这种形状生成的聊天消息,以显示掷骰子是否成功。


现在我的问题来了:

我在 LAN 版本中测试了该程序(使用我的语言环境 IP),它运行良好。没有问题,没有错误。

但现在我请朋友帮我通过互联网测试它。端口转发工作,客户端能够连接。

现在用于发送消息。聊天消息:工作正常 Dice1-消息:只发送昵称和两个骰子结果。第三个骰子结果没有显示出来,其他所有信息都只是在那里留下一个空白区域,它应该出现在哪里。第二个骰子消息也是如此。它只显示骰子结果和昵称,但不显示任何其他信息。

如果它有助于知道,该程序是用 Delphi 6.0 编写的(是的,有点旧,但我已经习惯了这个程序,我对编程还不是很好。所以我用它来学习基础知识和编写我的第一个更大的程序,比如那个。)

数据包本身定义如下:

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)

所以..就是这样。我希望那里的任何人都知道问题出在哪里(或者更好的是,在这种情况下,LAN 和 Internet 之间导致问题的区别是什么)并且知道一种方法来帮助我解决这个问题。

提前致谢~

PS:服务器只需获取信息,以相同的方式显示它,任何客户端都会这样做,然后将其直接发送给每个客户端,每个客户端都会接收并显示它..以防万一这可能很重要。


编辑:

嗯,我认为信息本来就很好,因为“其他信息”并没有真正不同。

不过没关系~更多细节:)

“其他信息”都具有字符串 [255] 类型,无论如何其中一个是布尔值。

Dice1 -Type使用以下内容:

昵称、结果 1、结果 2、结果 3、难度、技能点、天赋(只是天赋的名称,掷骰子测试成功或失败)和一个名为“成功”的布尔值。

骰子2使用:

昵称、结果、难度、天赋(天赋的名称。不要误认为值^^)、天赋点(基本上是天赋的值减去掷骰子的难度)以及布尔值“成功”以及字符串 [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;

我希望这让它更容易理解..我不擅长解释,我想:/

但是,感谢您在这里欢迎我,我希望我不会打扰你们,并且也可以在任何情况下提供帮助!

4

2 回答 2

2

嗯..如果它是我正在考虑的组件,那么 ClientRead 事件被触发并带有完整的数据消息并不是一个锁。

如果是这种情况,您需要一个基于 TCP 的协议来允许传输消息。TCP 本身不能传输消息,只能传输字节流。

TCustomWinSocket 文档,(我的斜体):

'使用 ReceiveBuf 从 Windows 套接字对象的 OnSocketEvent 事件处理程序或套接字组件的 OnRead 或 OnClientRead 事件处理程序中的套接字连接读取。

ReceiveBuf 返回实际读取的字节数可能小于调用中请求的字节数)。

如果未读取任何字节,则 ReceiveBuf 返回 –1。

因此,您仅声明超大缓冲区并始终传输批次以避免解析接收到的数据的狡猾计划没有奏效。

你需要正确地做到这一点。设计一个协议,无论 ClientRead() 事件是用整个消息、部分消息、组合消息还是多次触发,每次一个字节都可以工作。我的首选设计是使用带有 'byteLoad(thischar:char):boolean;' 的 'PDU' 类 使用内部状态机处理协议的方法,使用正确数据加载字段,并且仅在组装完整消息时返回 true。在每个 ClientRead 调用中,我迭代每个接收到的字节并调用 'byteLoad' 直到它返回 true,然后处理 PDU,(可能通过在某处排队),创建另一个 PDU 并开始加载它。

请注意,由于您使用的是异步、非线程 TServerSocket 选项,因此任何此类特定于客户端的数据(如 PDU 实例)或必须在 ClientRead 调用中保留的任何其他数据都必须存储为传入的 TCustomWinSocket 实例的字段。这意味着要么使用 TCustomWinSocket 后代来保存此数据,要么使用基本 TCustomWinSocket 的“数据”属性来保存另一个对象或记录。这些东西通常在 OnConnected 事件中创建/初始化/加载。当心 TCustomWinSocket 在断开连接时的行为 - 我有一种有趣的感觉,它试图释放数据字段中的任何非零指针 - 你可能不想要的东西。

于 2012-04-25T20:49:12.780 回答
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 来进行消息布局。

于 2012-04-25T20:05:17.543 回答