【问题标题】:C# client-server protocol/model questionC# 客户端-服务器协议/模型问题
【发布时间】:2010-10-27 11:25:09
【问题描述】:

我正在尝试在概念上为我用 c#(客户端和服务器)编写的客户端-服务器套接字应用程序模型工作。我的服务器需要同时处理多个客户端,最好是同时处理来自客户端的多个请求。我已经为我的通信设计了一个容器,我将在每条消息的开头发送一个固定长度的标头,其中包含(除其他外)消息的长度。我有一些使用 c# 进行套接字编程的经验,所以我很喜欢使用异步套接字。

从概念上讲,我遇到的主要问题是我需要客户端和服务器都能够随时接收消息。客户端将建立一个连接,并保持“登录”状态(就像一个 IM 客户端),它需要在任意时间接收数据并在任意时间发出请求。作为协议的一部分,我还希望接收对每个请求的响应(无论是从服务器到客户端,还是从客户端到服务器)。

如果可能,我希望能够使用单个套接字。我相信我可以使用两个套接字来完成这项工作,一个用于发出服务器->客户端请求,一个用于客户端->服务器请求,但我不希望处理两个端口/连接的额外复杂性。但是,使用单个套接字时,我不确定如何管理发送请求并在可以交错时获取响应。

我在搜索中找不到任何类似服务器或客户端的示例。感谢任何提供任何想法的人。

【问题讨论】:

    标签: c# sockets model tcp client-server


    【解决方案1】:

    套接字是双向的。您只需要一个套接字即可在两个端点之间来回发送数据。但是在客户端上,您可能需要一个线程不断地从套接字读取并通知某些组件或将它接收到的数据排队,以便在其他地方进行处理,而另一个线程通过同一个套接字发送数据。这样你就有了异步通信。

    但在服务器端,您需要打开一个 ServerSocket,它将绑定到 TCP 端口并接受该端口上的连接;您接受的每个连接都是一个新的 Socket。因此,每个客户端都有一个套接字,并且每个客户端可能需要一个线程。

    您需要为要通过套接字传递的消息建立协议。您可以自己制作协议或查找协议,具体取决于您想要做什么。通常,您将首先发送两个或四个字节以及消息其余部分的长度,以便对方知道从流中读取那么多字节;那是一条信息。消息的开头通常是消息类型;在一个非常简单的场景中,您可以说“1”是客户端请求,“2”是服务器响应,“3”是客户端回显,“4”是服务器回显响应,“5”是服务器echo,“6”是客户端回显响应等。例如,您收到一条消息,对其进行解析,然后您就知道它是来自客户端的请求还是对从服务器发送的消息的响应。

    【讨论】:

      【解决方案2】:

      如果您使用 TCP,那么服务器端的每个客户端都必须有一个套接字,以及用于侦听连接的套接字。对于 UDP,您可以使用一个套接字来完成这一切。无论如何,为您的一个 UDP 套接字或每个 TCP 客户端套接字执行以下操作:

      有一个BeginReceive 一直在运行,BeginWrite 在您发生必要的事件时(即用户按下按钮或收到您需要处理的消息)。

      编辑:针对您的评论,我处理它的方式是在您的标头中包含一个请求 ID。每当发送请求(从任一端)时,都包含该消息的唯一值。使用Dictionary<RequestId, RequestState>(对类型进行适当的替换)并查看传入消息是否与预先存在的请求相关。此外,您可以指定所有设置了高位的 ID 都是来自客户端的请求,而清除了高位的 ID 来自服务器以避免冲突:

      Server                      Client
      Request 0x00 -------------> Starts processing
      
      Starts processing <-------  (unrelated) Request 0x80
      
      Request 0x00 complete <---- Sends response to 0x00
      
      Sends response to 0x80 ---> Request 0x80 complete
      

      这是 AOL 的 OSCAR 协议用于(可能很慢)有状态请求的系统。

      EDIT2:嗯,好的……你真的想阻止它吗?您可以做的是让一个单独的线程处理Send/Receive 调用。该线程将通过线程安全的“发送消息”队列和类似的“收到消息”伪队列与主线程通信。我将后者称为伪队列的原因是您希望能够从队列中获取乱序消息。主线程将消息放入发送队列,然后阻塞接收队列。每次套接字线程更新接收队列时,主线程都会唤醒并检查它想要的消息是否在那里。如果是,它将接受它(无序)并完成所需的操作。如果消息还没有,它只会再次阻塞在队列中。当操作最终完成时,它将恢复正常操作并按顺序从接收队列中获取消息并处理它们或执行其他任何操作。

      这有意义吗?如果有点混乱,我很抱歉......

      【讨论】:

      • 是的。但是假设服务器从客户端请求一些需要一段时间的东西。同时,客户端向服务器请求某些东西。在我的服务器的 BeginReceive 中,我怎么知道这个传入的数据是来自客户端的请求,还是对服务器请求的响应?这就是我正在努力解决的问题。我掌握了异步套接字的基础知识。谢谢。
      • 对不起,所有关于套接字的讨论让我认为您的问题在那个级别;这次更新怎么样?
      • 抱歉,我很难提出我的问题……但这正是我一直试图解决的问题。我曾想过使用请求 ID 和字典,但想知道如何实现一种方法来发出请求并阻止直到结果到来(并返回它)而不在我的请求状态对象中使用 ResetEvent 之类的东西。为每个请求创建那些会很糟糕,不是吗?谢谢。
      • 是的,这是有道理的。谢谢你,你帮了大忙!
      猜你喜欢
      • 1970-01-01
      • 2016-03-30
      • 2011-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-14
      • 1970-01-01
      • 2014-07-27
      相关资源
      最近更新 更多