Incomplete messages (C# TCP/IP Client)

2019-02-19 20:02发布

First of all, I'm absolutely not a network programmer. What I try to do, is a very simple TCP/IP communication between a Java server and a C# client.

Java server:

 public void run(){   
try {
       // Open server socket
       _server = new ServerSocket(SERVER_PORT);
       _client = _server.accept();
                System.out.println("ComInterface: client connected.");
                // Wait for a client data output stream
                while(true){

                    // Receive message from client
                    BufferedReader is =
                            new BufferedReader(new InputStreamReader(_client.getInputStream()));
                    msg = is.readLine();

                    // Process message
                    if(msg!=null){
                        System.out.println("ComInterface: Message Received : <" + msg + ">.");
                        processMessage(msg); // Independant method
                    }
                    else{
                        System.out.println("ComInterface: client closed connection.");
                        _client.close();
                        _client = _server.accept();
                        System.out.println("ComInterface: client connected.");
                    }

                }

            } catch (IOException e) {
                e.printStackTrace();
            }
}

public void sendMessage(String msg){
        try {

            // Out stream
            DataOutputStream os = new DataOutputStream(_client.getOutputStream());

            os.writeBytes((String)(msg+"\n"+(char)13));
            os.flush();
            System.out.println("ComInterface: Message <" + msg + "> sent");

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }   
    }

And here's the C# client:

public class ComInterface : MonoBehaviour
    {
        public const String SERVER_IP = "127.0.0.1"; // Localhost
        public const int PORT = 1100; // Default port 
        public const int READ_BUFFER_SIZE = 5000; // 4.8828125 kilobytes

        private TcpClient _client;
        private ASCIIEncoding _asen;
        private byte[] _readBuffer;
        private String _msg;

        public Boolean connected { get; internal set; } // setter is for internal use only

        /**
         * Initialize internal variables (buffer, socket...)
         */
        public ComInterface()
        {
            connected = false;
            _client = new TcpClient();
            _asen = new ASCIIEncoding();
            _readBuffer = new Byte[READ_BUFFER_SIZE];
            _msg = String.Empty;
        }

        /**
         * Connect to server at SERVER_IP:PORT
         * Return true if connection was a success, or false if failure.
         */
        public Boolean Connect()
        {
            try
            {

                _client.Connect(SERVER_IP, PORT);
                connected = true;
                Array.Clear(_readBuffer, 0, _readBuffer.Length);
                Debug.Log("TCPClient: <Connect> Connected to the server");
                // Start an asynchronous read invoking ReceiveComMessage
                _client.GetStream().BeginRead(_readBuffer, 0, READ_BUFFER_SIZE, new AsyncCallback(ReceiveComMessage), _client.GetStream());
            }
            catch (Exception ex)
            {
                Debug.Log("TCPClient: <Connect> Cannot connect to the server - " + ex.Message);
                connected = false;
            }
            // Return connection state
            return connected;
        }

 /**
         * Received a message from Communicator
         */
        private void ReceiveComMessage(IAsyncResult ar)
        {
            int BytesRead;
            String msg;
            try
            {
                BytesRead = _client.GetStream().EndRead(ar);
                if (BytesRead < 1)
                {
                    // if no bytes were read server has close.  
                    Debug.Log("TCPClient: <ReceiveComMessage> The server has closed (BytesRead<1)");
                    this.Disconnect();
                    return;
                }
                // Convert the byte array the message was saved into,
                msg = Encoding.ASCII.GetString(_readBuffer);
                Debug.Log("C# Message: \"" + msg + "\""); // Output example in log below
                BytesRead = 0;
                Array.Clear(_readBuffer, 0, _readBuffer.Length);

                // Start a new asynchronous read into readBuffer.
                _client.GetStream().BeginRead(_readBuffer, 0, READ_BUFFER_SIZE, new AsyncCallback(ReceiveComMessage), _readBuffer);

            }
            catch (Exception ex)
            {
                Debug.Log("TCPClient: <ReceiveComMessage> The server has closed (Exception):" + ex.Message + " see " + ex.StackTrace);
                this.Disconnect();
            }

The main problem is that all the message are arriving incomplete. Here's the log trace:

C#: Message "{
C#: Message ""sender":"Bob"",
C#: Message ""recipient":",
etc...

Instead of for instance

C#: Message "{"sender":"Bob","recipient":[1,2,3]}"

I'm a bit confused and I'd need some help to resolve this. Thank you very much!

3条回答
姐就是有狂的资本
2楼-- · 2019-02-19 20:31

TCP is a stream-oriented connection, not message-oriented. It has no concept of a message. When you write out your serialized string, it only sees a meaningless sequence of bytes. TCP is free to break up that stream up into multiple fragments and they will be received at the client in those fragment-sized chunks. It is up to you to reconstruct the entire message on the other end.

In your scenario, one would typically send a message length prefix. This way, the client first reads the length prefix so it can then know how large the incoming message is supposed to be.

I would seriously consider using something like Google's Protocol Buffers as a good way of declaring your messages and then streaming them with the size prefix option. The nice thing is that you define your set of messages once and then use the available tools to automatically generate C++, Java, C#, etc code from the message definitions. This will help in having a consistent messaging set that works between languages.

查看更多
Summer. ? 凉城
3楼-- · 2019-02-19 20:32

Take a look at this example...

Java TCP Server...

import java.net.*;
import java.io.*;

public class TcpServer
{
    public static void main(String h[])
    {
        try
        {
            ServerSocket serverSocket = new ServerSocket(1100);
            Socket socket = serverSocket.accept();
            System.out.println("Client Accepted");
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            System.out.println("Received: " + bufferedReader.readLine());
            PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
            printWriter.println("Hello Theo. Welcome to socket programming.");
        } catch (Exception e)
        {
            System.out.println(e);
        }
    }
}

C# TCP Client...

using System;
using System.IO;
using System.Net.Sockets;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var client = new TcpClient("localhost", 1100);
            var stream = client.GetStream();
            var streamWriter = new StreamWriter(stream);
            streamWriter.WriteLine("My name is Theo");
            streamWriter.Flush();
            var streamReader = new StreamReader(stream);
            Console.WriteLine("Received: " + streamReader.ReadLine());
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }

        Console.WriteLine("Press a key to continue.");
        Console.ReadKey();
    }
}
查看更多
何必那么认真
4楼-- · 2019-02-19 20:33

A message (any data, I mean), when sent through a socket, is divided into several packets. When printing each received packet, you don't see your whole message.

You should define an end of message string (something like ".#."). Until you receive this sequence, you keep concatenating the messages you receive.

This is what session protocols (that is, protocols that run on the top of TCP) do.

Hope this helps.

Regards, Calil

查看更多
登录 后发表回答