Indy10 和 System.Net.Sockets 之间的联网

Networking between Indy10 and System.Net.Sockets

我和我的合作伙伴在一个项目上工作,我的合作伙伴在 Pascal 中开发并使用 Indy10 套接字进行网络连接,而我在 UWP 项目中使用 C# 进行开发。因此对于网络我必须使用 streamsocket 命名空间。

问题是,我的搭档使用带有-1 和false 参数的ReadStream 方法,这意味着接收流的长度是不确定的。因此,我必须发送从我的流中的数据(例如使用 DataWriter class)计算的长度前缀(在这种情况下我使用 BitConverter.GetBytes 方法)继续数据本身。 但是 Indy10 套接字什么也没有收到。好像一直卡在等包,不知道。

有人遇到过这个问题吗?请帮忙,我们一直在寻找解决方案大约两周。我已经在 Whosebug 中阅读了所有类似的问题,这是一个关于使用框架、定界符的博客。在 Indy Socket 源代码本身中搜索解决方案。 请帮忙!

更新 1
C# 代码。在这种情况下,我从 WPF 应用程序发送。在 UWP 中使用流的逻辑几乎相同,只是使用 StreamSocket 和 DataWriter。

TcpClient client = new TcpClient(hostTextBox.Text, int.Parse(portTextBox.Text));

Byte[] byteMessage = Encoding.Unicode.GetBytes(messageTextBox.Text);
Byte[] lenghtPrefix = BitConverter.GetBytes(byteMessage.Length);
Byte[] concatedData = new byte[lenghtPrefix.Length + byteMessage.Length];
lenghtPrefix.CopyTo(concatedData, 0);
byteMessage.CopyTo(concatedData, lenghtPrefix.Length);

NetworkStream stream = client.GetStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(concatedData);
writer.Flush();

stream.Close();
client.Close();

但我们也尝试过这个解决方案:

Byte[] byteMessage = Encoding.Unicode.GetBytes(messageTextBox.Text);
Byte[] lenghtPrefix = BitConverter.GetBytes(byteMessage.Length);

Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect(hostTextBox.Text, int.Parse(portTextBox.Text));

socket.Send(lenghtPrefix, SocketFlags.None);
socket.SendBufferSize = byteMessage.Length;
socket.Send(byteMessage, SocketFlags.None);

socket.Close();

服务器端的 Pascal 代码,等待流。以这种方式接收是至关重要的,因为软件的其他模块(也是使用 Indy10 用 Pascal 编写的)以这种方式与服务器通信。

function Receive(const AContext: TIdContext): string;
var
  Stream: TMemoryStream;
begin
  try
    Result := '';
    Stream := TMemoryStream.Create;
    if  AContext.Connection.Connected then
      AContext.Connection.IOHandler.ReadStream(Stream, -1, False);
  finally
    Result := UnPackStream(Stream);
    Stream.Free;
  end;
end;

When calling TIdIOHandler.ReadStream() in Indy, if AByteCount is -1 and AReadUntilDisconnect is False, ReadStream() will read the first 4 bytes (Int32) or 8 bytes (Int64) (depending on the value of the TIdIOHandler.LargeStream 属性) and interpret them as an integer in network byte order (big-endian), then read the number of bytes specified by that integer (if greater than 0).

The main problem with your first C# example is that you are using a StreamWriter, which is designed for sending text, not binary data. If you try to write a Byte[] array with a StreamWriter, it will not be sent as-is, it will be formatted to a textual representation that is not compatible with Indy. You don't need the StreamWriter, you can use the NetworkStream as-is, it has a Write() method for Byte[] data. Or use a BinaryWriter.

Another problem with both C# examples is that you are using BitConverter without taking endian into account. Its GetBytes(Int32) method creates an array of bytes in the same endian as the calling machine's architecture. Windows is primarily a little-endian environment, but ReadStream() is expecting the integer bytes in big-endian.

Try something more like this:

TcpClient client = new TcpClient(hostTextBox.Text, int.Parse(portTextBox.Text));

Byte[] byteMessage = Encoding.Unicode.GetBytes(messageTextBox.Text);
Byte[] lengthPrefix = BitConverter.GetBytes(byteMessage.Length);
if (BitConverter.IsLittleEndian)
     Array.Reverse(lengthPrefix);

NetworkStream stream = client.GetStream();
stream.Write(lengthPrefix, 0, lengthPrefix.Length);
stream.Write(byteMessage, 0, byteMessage.Length);

stream.Close();
client.Close();

Or this:

TcpClient client = new TcpClient(hostTextBox.Text, int.Parse(portTextBox.Text));

Byte[] byteMessage = Encoding.Unicode.GetBytes(messageTextBox.Text);
Byte[] lengthPrefix = BitConverter.GetBytes(byteMessage.Length);
if (BitConverter.IsLittleEndian)
     Array.Reverse(lengthPrefix);

NetworkStream stream = client.GetStream();
BinaryWriter writer = new BinaryWriter(stream);

// BinaryWriter.Write(Int32) writes in little-endian only,
// so you still need to use Write(Byte[]) for the length value...
writer.Write(lengthPrefix); 
writer.Write(byteMessage)
writer.Flush();

stream.Close();
client.Close();

最后,这是解决方案。我们为此工作了一整天。我们使用 Synapse 套接字作为参考。最后是 UWP 下的代码,我们也在其他桌面项目(WPF、Winforms)中测试过(TcpClientNetworkStream)。 BitConverter 并在 hole 流前加一个数字前缀不起作用,因为 Indy 等待二进制格式的长度,因此字节数组必须包含表示二进制长度的值。 希望对遇到类似问题的人有所帮助!

Windows.Networking.Sockets.StreamSocket socket = new Windows.Networking.Sockets.StreamSocket();
Windows.Networking.HostName serverHost = new Windows.Networking.HostName(hostTextBox.Text);
string serverPort = portTextBox.Text;
await socket.ConnectAsync(serverHost, serverPort);

//Write data to the server.
DataWriter writer = new DataWriter(socket.OutputStream);

byte[] data = Encoding.Unicode.GetBytes(messageTextBox.Text);
byte[] size = new byte[4];

ushort x = (ushort)(data.Length >> 16);
ushort y = (ushort)(data.Length);
size[0] = (byte)(x / 256);
size[1] = (byte)(x % 256);
size[2] = (byte)(y / 256);
size[3] = (byte)(y % 256);

writer.WriteBytes(size);
writer.WriteBytes(data);
await writer.StoreAsync();
await writer.FlushAsync();

writer.DetachStream();

//Read response.
var bytes = new byte[4];

DataReader reader = new DataReader(socket.InputStream);
await reader.LoadAsync(4);

reader.ReadBytes(bytes);
int length = bytes[0] * 256 * 256 * 256 + bytes[1] * 256 * 256 + bytes[2] * 256 + bytes[3];

byte[] dat = new byte[length];
var count = await reader.LoadAsync((uint)length);
reader.ReadBytes(dat);

responseTextBlock.Text = Encoding.Unicode.GetString(dat);

reader.DetachStream();
socket.Dispose();