TCP read and write from client and server

2020-05-09 22:57发布

问题:

I'm trying to make a client and server which can read and write info back and forth between each other. I can write from the client and server reads it but not vise versa and I have no idea why. Nobody on here seems to know when I asked before and I cant find anything online that works. If you know please tell me and not tell me to go read an article about TCP because it doesn't help at all.

Client:

namespace ExampleClient
{
public partial class Form1 : Form
{
public static bool IsConnected;
public static NetworkStream Writer;
public static NetworkStream Receiver;

public Form1()
{
    InitializeComponent();
}

private void BtnConnect_Click(object sender, EventArgs e)
{
    TcpClient Connector = new TcpClient();
    Connector.Connect(TxtIP.Text, Convert.ToInt32(TxtPort.Text));
    IsConnected = true;
    Writer = Connector.GetStream();
    Console.WriteLine("Connected");

    System.Threading.Thread Rec = new System.Threading.Thread(new System.Threading.ThreadStart(Receive));
    Rec.Start();
}

private void BtnWrite_Click(object sender, EventArgs e)
{
    try{
        byte[] Packet = Encoding.ASCII.GetBytes("Client Write Test");
        Writer.Write(Packet, 0, Packet.Length);
        Writer.Flush();}
    catch{
        try { Writer.Close();} catch { }}
}

public static void Receive()
{
    while (true){
        try{
            byte[] RecPacket = new byte[1000];
            Receiver.Read(RecPacket, 0, RecPacket.Length);
            Receiver.Flush();
            string Message = Encoding.ASCII.GetString(RecPacket);
            MessageBox.Show(Message);
        }
        catch { break;}
    }
}
}
}

Receiver/ Server:

namespace ExampleServer
{
public partial class Form1 : Form
{
public static NetworkStream Writer;
public static NetworkStream Receiver;

public Form1()
{
    InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
    TcpListener Server = new TcpListener(2002);

    Server.Start();
    TcpClient Connection = Server.AcceptTcpClient();
    Receiver = Connection.GetStream();
    System.Threading.Thread Rec = new System.Threading.Thread(new System.Threading.ThreadStart(Receive));
    Rec.Start();
}

public static void Receive()
{
    while (true)
    {
        try
        {
            byte[] RecPacket = new byte[1000];
            Receiver.Read(RecPacket, 0, RecPacket.Length);
            Receiver.Flush();
            string Message = Encoding.ASCII.GetString(RecPacket);
            MessageBox.Show(Message);

            try{
                byte[] Packet = Encoding.ASCII.GetBytes("Server Write Test");
                Writer.Write(Packet, 0, Packet.Length);
                Writer.Flush();}
            catch{
                try { Writer.Close(); } catch { }}
        }
        catch { break; }
    }
}
}
}

So when the server reads the message from the client, it displays it which works fine but when it goes to write a message back to the client, it crashes with the error "system.nullreferenceeexception: object reference not set to an instance of an object. at exampleserver.form1.receive()".

回答1:

The problem why your client never receives the response is due to that you never let it.

You never initialize the client's Receiver stream, the application isn't breaking because A) the error is thrown in a different thread, B) you just break the while loop when the error occurs, you really need to log the error somehow.

Use only one NetworkStream. The stream supports simultaneous receiving and sending, and it's much easier having one than two - you will not forget to initialize it as easily.

In the future you should also look into handling your messages. As TCP is stream based, you can get messages in any possible way; one third of a message, half a message, or just one byte at a time (etc.). There are a few ways to solve this, but the best method is length-prefixing (or length prefixing).

Length-prefixing combined with header-prefixing makes your messages as reliable as they get when it comes to receiving them properly. If done correctly you can always be sure that you get a message containing the correct, and correct amount of data.

Length- and header-prefixing means that you prefix each message with it's correct length, and a header telling what the message is for. This is how it's composed:

  1. Read the length of the message (the amount of bytes)
  2. Store the length in an Integer.
  3. Choose a header (usually a one-byte header is enough).
  4. Concatenate the length with the header, and then that with the rest of the message.

This will produce the following structure:

[Length (4 bytes)][Header (1 byte)][Message (?? byte(s))]

To read it:

  1. Read the first 4 bytes, and convert the returned array to an Integer.
  2. Read the next byte to get the header.
  3. Read until you've read the whole length of the message. Determine what you'll do with the message based on the header.

Reading that sequence you will always know how much to read and expect from the other endpoint, and thus you won't have a problem with partial or messages being lumped together.

Hope this helps!



回答2:

Only one usage of each socket address (protocol/network address/port) is normally permitted... don't start TcpListener.



回答3:

I don't think you are initializing Writer. Make sure that gets initialized before calling it.

You catch the first NullReferenceException, but then in the catch, you call Writer again, throwing a new one.