【问题标题】:Sending and receiving data over a network using TcpClient使用 TcpClient 通过网络发送和接收数据
【发布时间】:2011-04-06 06:35:36
【问题描述】:

我需要开发一个连接到 TCP 服务器的服务。主要任务是读取传入消息并在十分钟内向服务器发送命令,例如同步命令。例如,我使用了如下所示的 TcpClient 对象:

...
TcpClient tcpClient = new TcpClient();
tcpClient.Connect("x.x.x.x", 9999);
networkStream = tcpClient.GetStream();
clientStreamReader = new StreamReader(networkStream);
clientStreamWriter = new  StreamWriter(networkStream);
while(true)
{
   clientStreamReader.Read()
}

另外,当我需要用任何方法写出一些东西时,我会使用:

 clientStreamWriter.write("xxx");

这种用法正确吗?还是有更好的办法?

【问题讨论】:

    标签: c# network-programming tcpclient


    【解决方案1】:

    我开发了一个可能有用的 dotnet 库。我已经解决了如果数据超出缓冲区,则永远无法获取所有数据的问题,许多帖子都打折了。解决方案仍有一些问题,但效果很好https://github.com/NicholasLKSharp/DotNet-TCP-Communication

    【讨论】:

      【解决方案2】:

      请注意 - 这是一个非常陈旧且繁琐的“解决方案”。

      顺便说一句,您可以使用序列化技术来发送字符串、数字或任何支持序列化的对象(大多数 .NET 数据存储类和结构都是 [Serializable])。 在那里,您应该首先将四个字节的 Int32 长度发送到流中,然后将二进制序列化 (System.Runtime.Serialization.Formatters.Binary.BinaryFormatter) 数据发送到其中。

      在另一边或连接(实际上在两边)你定义应该有一个 byte[] 缓冲区,当数据到来时你将在运行时附加和修剪。

      我正在使用类似的东西:

      namespace System.Net.Sockets
      {
          public class TcpConnection : IDisposable
          {
              public event EvHandler<TcpConnection, DataArrivedEventArgs> DataArrive = delegate { };
              public event EvHandler<TcpConnection> Drop = delegate { };
      
              private const int IntSize = 4;
              private const int BufferSize = 8 * 1024;
      
              private static readonly SynchronizationContext _syncContext = SynchronizationContext.Current;
              private readonly TcpClient _tcpClient;
              private readonly object _droppedRoot = new object();
              private bool _dropped;
              private byte[] _incomingData = new byte[0];
              private Nullable<int> _objectDataLength;
      
              public TcpClient TcpClient { get { return _tcpClient; } }
              public bool Dropped { get { return _dropped; } }
      
              private void DropConnection()
              {
                  lock (_droppedRoot)
                  {
                      if (Dropped)
                          return;
      
                      _dropped = true;
                  }
      
                  _tcpClient.Close();
                  _syncContext.Post(delegate { Drop(this); }, null);
              }
      
              public void SendData(PCmds pCmd) { SendDataInternal(new object[] { pCmd }); }
              public void SendData(PCmds pCmd, object[] datas)
              {
                  datas.ThrowIfNull();
                  SendDataInternal(new object[] { pCmd }.Append(datas));
              }
              private void SendDataInternal(object data)
              {
                  if (Dropped)
                      return;
      
                  byte[] bytedata;
      
                  using (MemoryStream ms = new MemoryStream())
                  {
                      BinaryFormatter bf = new BinaryFormatter();
      
                      try { bf.Serialize(ms, data); }
                      catch { return; }
      
                      bytedata = ms.ToArray();
                  }
      
                  try
                  {
                      lock (_tcpClient)
                      {
                          TcpClient.Client.BeginSend(BitConverter.GetBytes(bytedata.Length), 0, IntSize, SocketFlags.None, EndSend, null);
                          TcpClient.Client.BeginSend(bytedata, 0, bytedata.Length, SocketFlags.None, EndSend, null);
                      }
                  }
                  catch { DropConnection(); }
              }
              private void EndSend(IAsyncResult ar)
              {
                  try { TcpClient.Client.EndSend(ar); }
                  catch { }
              }
      
              public TcpConnection(TcpClient tcpClient)
              {
                  _tcpClient = tcpClient;
                  StartReceive();
              }
      
              private void StartReceive()
              {
                  byte[] buffer = new byte[BufferSize];
      
                  try
                  {
                      _tcpClient.Client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, DataReceived, buffer);
                  }
                  catch { DropConnection(); }
              }
      
              private void DataReceived(IAsyncResult ar)
              {
                  if (Dropped)
                      return;
      
                  int dataRead;
      
                  try { dataRead = TcpClient.Client.EndReceive(ar); }
                  catch
                  {
                      DropConnection();
                      return;
                  }
      
                  if (dataRead == 0)
                  {
                      DropConnection();
                      return;
                  }
      
                  byte[] byteData = ar.AsyncState as byte[];
                  _incomingData = _incomingData.Append(byteData.Take(dataRead).ToArray());
                  bool exitWhile = false;
      
                  while (exitWhile)
                  {
                      exitWhile = true;
      
                      if (_objectDataLength.HasValue)
                      {
                          if (_incomingData.Length >= _objectDataLength.Value)
                          {
                              object data;
                              BinaryFormatter bf = new BinaryFormatter();
      
                              using (MemoryStream ms = new MemoryStream(_incomingData, 0, _objectDataLength.Value))
                                  try { data = bf.Deserialize(ms); }
                                  catch
                                  {
                                      SendData(PCmds.Disconnect);
                                      DropConnection();
                                      return;
                                  }
      
                              _syncContext.Post(delegate(object T)
                              {
                                  try { DataArrive(this, new DataArrivedEventArgs(T)); }
                                  catch { DropConnection(); }
                              }, data);
      
                              _incomingData = _incomingData.TrimLeft(_objectDataLength.Value);
                              _objectDataLength = null;
                              exitWhile = false;
                          }
                      }
                      else
                          if (_incomingData.Length >= IntSize)
                          {
                              _objectDataLength = BitConverter.ToInt32(_incomingData.TakeLeft(IntSize), 0);
                              _incomingData = _incomingData.TrimLeft(IntSize);
                              exitWhile = false;
                          }
                  }
                  StartReceive();
              }
      
      
              public void Dispose() { DropConnection(); }
          }
      }
      

      这只是一个示例,您应该对其进行编辑以供使用。

      【讨论】:

      • 为什么 BufferSize = 8 * 1024 ?
      【解决方案3】:

      首先,TCP 不保证您发送的所有内容都会以相同的读取在另一端被接收。它只保证您发送的所有字节都会以正确的顺序到达。

      因此,在从流中读取数据时,您需要不断构建缓冲区。您还必须知道每条消息有多大。

      最简单的方法是使用不可键入的 ASCII 字符来标记数据包的结尾并在接收到的数据中查找。

      【讨论】:

      • 感谢您的回复。我将尝试读取缓冲区中的字节...在我的问题中,我还想知道用 while(true) 监听传入数据的好方法..
      • 还有另一种解决方案。在将其转换为字节 [] 之前,也尝试将字符串长度与数据一起发送。我发送的数据格式如下 [content_length,content]。所以在两端你可以计算长度和匹配来检查接收到的数据包是否完成。对于长度,你可以使用 ASCIIEncoding.ASCII.GetByteCount(data).ToString("X4");
      【解决方案4】:

      我很幸运直接使用了套接字对象(而不是 TCP 客户端)。我创建了一个看起来像这样的 Server 对象(为简洁起见,我已经编辑了一些东西,例如异常处理,但我希望这个想法能够得到体现。)...

      public class Server()
      {
          private Socket sock;
          // You'll probably want to initialize the port and address in the
          // constructor, or via accessors, but to start your server listening
          // on port 8080 and on any IP address available on the machine...
          private int port = 8080;
          private IPAddress addr = IPAddress.Any;
      
          // This is the method that starts the server listening.
          public void Start()
          {
              // Create the new socket on which we'll be listening.
              this.sock = new Socket(
                  addr.AddressFamily,
                  SocketType.Stream,
                  ProtocolType.Tcp);
              // Bind the socket to the address and port.
              sock.Bind(new IPEndPoint(this.addr, this.port));
              // Start listening.
              this.sock.Listen(this.backlog);
              // Set up the callback to be notified when somebody requests
              // a new connection.
              this.sock.BeginAccept(this.OnConnectRequest, sock);
          }
      
          // This is the method that is called when the socket recives a request
          // for a new connection.
          private void OnConnectRequest(IAsyncResult result)
          {
              // Get the socket (which should be this listener's socket) from
              // the argument.
              Socket sock = (Socket)result.AsyncState;
              // Create a new client connection, using the primary socket to
              // spawn a new socket.
              Connection newConn = new Connection(sock.EndAccept(result));
              // Tell the listener socket to start listening again.
              sock.BeginAccept(this.OnConnectRequest, sock);
          }
      }
      

      然后,我使用单独的 Connection 类来管理与远程主机的单独连接。看起来像这样……

      public class Connection()
      {
          private Socket sock;
          // Pick whatever encoding works best for you.  Just make sure the remote 
          // host is using the same encoding.
          private Encoding encoding = Encoding.UTF8;
      
          public Connection(Socket s)
          {
              this.sock = s;
              // Start listening for incoming data.  (If you want a multi-
              // threaded service, you can start this method up in a separate
              // thread.)
              this.BeginReceive();
          }
      
          // Call this method to set this connection's socket up to receive data.
          private void BeginReceive()
          {
              this.sock.BeginReceive(
                      this.dataRcvBuf, 0,
                      this.dataRcvBuf.Length,
                      SocketFlags.None,
                      new AsyncCallback(this.OnBytesReceived),
                      this);
          }
      
          // This is the method that is called whenever the socket receives
          // incoming bytes.
          protected void OnBytesReceived(IAsyncResult result)
          {
              // End the data receiving that the socket has done and get
              // the number of bytes read.
              int nBytesRec = this.sock.EndReceive(result);
              // If no bytes were received, the connection is closed (at
              // least as far as we're concerned).
              if (nBytesRec <= 0)
              {
                  this.sock.Close();
                  return;
              }
              // Convert the data we have to a string.
              string strReceived = this.encoding.GetString(
                  this.dataRcvBuf, 0, nBytesRec);
      
              // ...Now, do whatever works best with the string data.
              // You could, for example, look at each character in the string
              // one-at-a-time and check for characters like the "end of text"
              // character ('\u0003') from a client indicating that they've finished
              // sending the current message.  It's totally up to you how you want
              // the protocol to work.
      
              // Whenever you decide the connection should be closed, call 
              // sock.Close() and don't call sock.BeginReceive() again.  But as long 
              // as you want to keep processing incoming data...
      
              // Set up again to get the next chunk of data.
              this.sock.BeginReceive(
                  this.dataRcvBuf, 0,
                  this.dataRcvBuf.Length,
                  SocketFlags.None,
                  new AsyncCallback(this.OnBytesReceived),
                  this);
      
          }
      }
      

      你可以使用你的 Connection 对象通过直接调用它的 Socket 来发送数据,就像这样......

      this.sock.Send(this.encoding.GetBytes("Hello to you, remote host."));
      

      正如我所说,我已尝试在此处编辑代码以进行发布,如果其中有任何错误,我深表歉意。

      【讨论】:

      • 您好,谢谢回复..我也有 2 个应用程序来连接第 3 方 tcp 服务器..我认为一个应用程序向第 3 方服务器发送命令,而其他应用程序(作为 Windows 服务)只会读取传入字节..在这个雕像中,当我使用另一个 TcpClient 对象在第一个应用程序上发送数据时,发送了数据但是当我使用第二个应用程序 tcpclient 对象读取数据时,传入的数据没有变化..在这个雕像中,我是否必须生成所有读取和写入Windows 服务和客户端应用程序中的方法应该与远程连接??多个客户端可以连接到服务..作为 Web 应用程序
      • 我通常会花费一个线程来创建每个连接。为此,在 Connection 构造函数中,不要直接调用 BeginReceive(),而是执行以下操作: Thread t = new Thread(new ThreadStart(this.BeginReceive)); t.Start();使用这样的方法,许多客户端可以同时连接。我是否正确理解了您的问题?您提到了“远程处理”,但我不确定您是否专门指的是 .Net Remoting,或者您是否只是指“从远程客户端连接”。
      • 嗨,我的应用程序架构将连接到第 3 方 tcp 服务器。我有两个主要任务。一个是发送命令(此任务可以通过我们网络中的任何内部客户端应用程序完成)和其他任务正在侦听传入数据,这可以通过集中式 Windows 服务应用程序来完成。因此,我们网络的问题命令发送者客户端应用程序应该连接到我们的 Windows 服务应用程序。通过远程发送命令或客户端应用程序应该使用自己的 tcpclient 对象连接到 3rd 方 tcp 服务器以直接发送命令?thnx
      • 抱歉耽搁了(我还没有弄清楚如何在添加 cmets 时收到电子邮件通知。)根据您的描述,听起来好像您可以使用远程处理(或其他以 Windows 为中心的WCF 等技术)将消息从客户端应用程序发送到 Windows 服务,然后让 Windows 服务使用套接字连接中继这些消息。或者,您可以让客户端应用程序直接连接,从而无需中间 Windows 服务。
      【解决方案5】:

      首先,我建议您使用 WCF、.NET Remoting 或其他一些更高级别的通信抽象。 “简单”套接字的学习曲线几乎与 WCF 一样高,因为直接使用 TCP/IP 时有很多不明显的陷阱。

      如果您决定继续使用 TCP/IP 路径,请查看我的 .NET TCP/IP FAQ,尤其是 message framingapplication protocol specifications 上的部分。

      另外,使用异步套接字 API。同步 API 无法扩展,在某些错误情况下可能会导致死锁。同步 API 只包含很少的示例代码,但实际生产质量的代码使用异步 API。

      【讨论】:

      • 奇怪的是,每个人都认为一切都必须本身扩展
      猜你喜欢
      • 1970-01-01
      • 2014-03-05
      • 1970-01-01
      • 2016-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多