代码之家  ›  专栏  ›  技术社区  ›  Navaneeth K N

C#中的套接字编程入门-最佳实践

  •  30
  • Navaneeth K N  · 技术社区  · 16 年前

    我在这里看到了很多关于套接字的资源。我相信他们都没有涉及我想知道的细节。在我的应用程序中,服务器执行所有处理并定期向客户端发送更新。

    本文的目的是涵盖开发socket应用程序所需的所有基本思想,并讨论最佳实践。以下是几乎所有基于socket的应用程序的基本功能。

    1个- 在套接字上绑定和侦听

    我正在使用以下代码。它在我的机器上很好用。当我在一个真正的服务器上部署它时,我需要考虑其他事情吗?

    IPHostEntry localHost = Dns.GetHostEntry(Dns.GetHostName());
    IPEndPoint endPoint = new IPEndPoint(localHost.AddressList[0], 4444);
    
    serverSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, 
                         ProtocolType.Tcp);
    serverSocket.Bind(endPoint);
    serverSocket.Listen(10);
    

    2个- 接收数据

    我使用了255大小的字节数组。所以当我接收到超过255字节的数据时,我需要调用receive方法直到得到完整的数据,对吗?一旦我得到了完整的数据,我需要追加到目前为止收到的所有字节,以获得完整的消息。是这样吗?还是有更好的方法?

    3个- 发送数据并指定数据长度

    由于TCP中无法找到要接收的消息的长度,因此我计划将该长度添加到消息中。这将是数据包的第一个字节。所以客户机系统知道有多少数据可供读取。

    还有更好的办法吗?

    4个- 关闭客户端

    当客户端关闭时,它将向服务器发送一条消息,指示关闭。服务器将从其客户端列表中删除客户端详细信息。下面是客户端用于断开套接字连接的代码(未显示消息传递部分)。

    client.Shutdown(SocketShutdown.Both);
    client.Close();
    

    有什么建议或问题吗?

    5个- 关闭服务器

    服务器向所有客户端发送指示关闭的消息。每个客户端在收到此消息时都将断开套接字的连接。客户端将向服务器发送关闭消息并关闭。一旦服务器从所有客户端接收到close消息,它就会断开套接字并停止侦听呼叫 处置 在每个客户端套接字上释放资源。这是正确的方法吗?

    6个- 未知的客户端断开连接

    有时,客户端可能在不通知服务器的情况下断开连接。我的计划是:当服务器向所有客户端发送消息时,检查套接字状态。如果未连接,请从客户端列表中删除该客户端并关闭该客户端的套接字。

    任何帮助都很好!

    3 回复  |  直到 16 年前
        1
  •  26
  •   jerryjvl    16 年前

    因为这是“入门”我的答案将坚持一个简单的实现,而不是一个高度可伸缩的实现。在使事情变得更复杂之前,最好先对简单的方法感到舒服。

    1-捆绑和倾听
    你的代码对我来说很好,我个人使用:

    serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444));
    

    而不是走DNS的路线,但我认为这两种方式都没有真正的问题。

    1.5-接受客户端连接
    只是为了完整起见。。。我想你是在做这件事,否则你就无法进入第二步。

    2-接收数据
    我将使缓冲区的长度略大于255字节,除非您可以预期所有服务器消息最多为255字节。我认为您需要一个可能大于TCP数据包大小的缓冲区,这样您就可以避免多次读取以接收单个数据块。

    我认为选择1500个字节应该没问题,甚至2048个字节也可以。

    或者,你可以避免使用 byte[] 存储数据片段,并将服务器端客户端套接字包装在 NetworkStream ,包裹在 BinaryReader ,以便可以直接从套接字读取消息的组件,而不必担心缓冲区大小。

    3-发送数据并指定数据长度
    您的方法可以很好地工作,但它显然要求在开始发送数据包之前很容易计算出它的长度。

    或者,如果您的消息格式(其组件的顺序)是以某种方式设计的,以便客户机能够在任何时候确定是否应该有更多的数据跟随(例如,代码0x01表示next将是int和string,代码0x02表示next将是16字节等)。结合 网络流 在客户端,这可能是一个非常有效的方法。

    为了安全起见,您可能需要添加对所接收组件的验证,以确保只处理正常值。例如,如果接收到长度为1TB的字符串的指示,则可能在某个地方发生了数据包损坏,关闭连接并强制客户端重新连接并“重新启动”可能更安全。这种方法为您提供了一个非常好的捕获所有行为,以防意外失败。

    4/5-关闭客户端和服务器
    我个人会选择 Close 没有进一步的消息;当一个连接被关闭时,在连接的另一端的任何阻塞读/写操作都会出现异常,您必须处理这个异常。

    因为你必须迎合“未知断开”无论如何得到一个稳健的解决方案,使任何更复杂的断开通常是没有意义的。

    6-未知断开
    我甚至不相信插座的状态连接可能会在客户机/服务器之间的某个路径上断开,而客户机或服务器都不会注意到。

    唯一可靠的方法是在下次尝试 发送 有点联系。在这一点上,如果连接出现任何问题,您将始终收到一个异常,指示失败。

    因此,检测所有意外连接的唯一方法是实现“ping”机制,理想情况下,客户端和服务器将定期向另一端发送消息,这只会导致指示接收到“ping”的响应消息。

    为了优化不必要的ping,您可能希望有一个“超时”机制,它只在一段时间内没有从另一端接收到其他流量时发送ping(例如,如果来自服务器的最后一条消息超过x秒,则客户端发送ping以确保连接没有在没有通知的情况下终止)。

    更先进的
    如果您想获得高的可伸缩性,就必须为所有套接字操作(Accept/Send/Receive)寻找异步方法。这些是“开始/结束”变体,但使用起来要复杂得多。

    我建议不要尝试这个,直到你有简单的版本和工作。

    另外请注意,如果您不打算扩展到几十个客户,这实际上不会是一个问题,无论。只有当您打算扩展到数千或数十万个连接的客户机时,而不是让您的服务器彻底崩溃,异步技术才是真正必要的。

    我可能已经忘记了其他一些重要的建议,但这应该足以让您从一个相当健壮和可靠的实现开始

        2
  •  13
  •   Community CDub    8 年前

    1个- 在套接字上绑定和侦听

    在我看来很好。不过,代码将只将套接字绑定到一个IP地址。如果您只想监听任何IP地址/网络接口,请使用 IPAddress.Any :

    serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444));
    

    为了保证将来的安全,您可能需要支持IPv6。要监听任何IPv6地址,请使用 IPAddress.IPv6Any 代替 IP地址。任何 .

    请注意,不能同时侦听任何IPv4和任何IPv6地址,除非使用 Dual-Stack Socket . 这将要求您解除 IPV6_V6ONLY 插座选项:

    serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0);
    

    启用 Teredo 用你的插座,你需要设置 PROTECTION_LEVEL_UNRESTRICTED 插座选项:

    serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)23, 10);
    

    2个- 接收数据

    我建议使用 NetworkStream 把插座包在 Stream 而不是手动读取数据块。

    读取固定数量的字节有点尴尬:

    using (var stream = new NetworkStream(serverSocket)) {
       var buffer = new byte[MaxMessageLength];
       while (true) {
          int type = stream.ReadByte();
          if (type == BYE) break;
          int length = stream.ReadByte();
          int offset = 0;
          do
             offset += stream.Read(buffer, offset, length - offset);
          while (offset < length);
          ProcessMessage(type, buffer, 0, length);
       }
    }
    

    在哪里? 网络流 真正的亮点是你可以像其他人一样使用它 溪流 . 如果安全性很重要,只需将 网络流 在一个 SslStream 使用X.509证书对服务器和(可选)客户端进行身份验证。压缩也一样。

    var sslStream = new SslStream(stream, false);
    sslStream.AuthenticateAsServer(serverCertificate, false, SslProtocols.Tls, true);
    // receive/send data SSL secured
    

    3个- 发送数据并指定数据长度

    你的方法应该是有效的,尽管你可能不想在重新发明轮子和设计一个新的协议这条路上走下去。看一看 BEEP 或者是一些简单的事情 protobuf .

    根据您的目标,可能需要考虑在套接字(如WCF或其他RPC机制)之上选择一个抽象。

    4/5/6年- 关闭未知断开连接

    什么 jerryjvl 唯一可靠的检测机制是ping或在连接空闲时发送keep alives。

    尽管在任何情况下都必须处理未知的断开连接,但我个人会保留一些协议元素,以便在双方同意的情况下关闭连接,而不是在没有警告的情况下关闭它。

        3
  •  2
  •   Dzmitry Huba    16 年前