0

I have written a simple server and client code in C#. As soon as the client is connected, the server will send a welcome message, a file size and a string to the client one by one and the client will display it. The client will convert the string into a string array and display it. After that the client will send an id to server and the server will display it. But the problem is, the client is not properly displaying. When I run the client program after running the server, it's displaying the following thing whereas it's supposed to display each message in a single line.

welcome1.cpp,.jpg,.png

Moreover, on the client side, The line which I have written to display the converted string array, is not working at all neither the lines after this are executing. Seems like,the code hangs. I have marked it in my code. My sample code is given below:

Server:

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Text;

namespace server
{
    class Program
    {
        static void Main(string[] args)
        {
            // Listen on port 1234.
            try
            {
                TcpListener tcpListener = new TcpListener(IPAddress.Any, 1234);
                tcpListener.Start();              
                byte[] data = new byte[1024];
                // Infinite loop to connect to new clients.
                while (true)
                {
                    // Accept a TcpClient
                    TcpClient tcpClient = tcpListener.AcceptTcpClient();

                    NetworkStream ns = tcpClient.GetStream();
                    //sending welcome message
                    string welcome = "welcome";
                    ns.Write(Encoding.ASCII.GetBytes(welcome), 0, welcome.Length);
                    ns.Flush();

                    //sending file size
                    string fsize = "1";
                    ns.Write(Encoding.ASCII.GetBytes(fsize), 0, fsize.Length);
                    ns.Flush();

                    //sending extensions
                    string[] extensions = { ".cpp", ".jpg", ".png" };
                    string str = string.Join(",", extensions);
                    Console.WriteLine(str);

                    ns.Write(Encoding.ASCII.GetBytes(str), 0, str.Length);
                    ns.Flush();

                    //receiving id
                    int recv = ns.Read(data, 0, data.Length);
                    string id = Encoding.ASCII.GetString(data, 0, recv);

                    Console.WriteLine(id);                   
                }
            }
            catch (Exception e)
            {
                Console.Write(e.Message);
            }
            Console.Read();
        }
    }
}

Client:

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Text;

namespace client
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {                
                TcpClient tcpClient = new TcpClient("127.0.0.1", 1234);               
                NetworkStream ns = tcpClient.GetStream();
                byte[] data = new byte[1024];

                StreamWriter sWriter = new StreamWriter(tcpClient.GetStream());

                //receiving welcome message
                int recv = ns.Read(data, 0, data.Length);
                string message = Encoding.ASCII.GetString(data, 0, recv);
                Console.WriteLine(message);

                //receive filesize
                int recv2 = ns.Read(data, 0, data.Length);
                string message2 = Encoding.ASCII.GetString(data, 0, recv2);
                Console.WriteLine(message2);

                //receiving extensions
                int recv1 = ns.Read(data, 0, data.Length);
                string message1 = Encoding.ASCII.GetString(data, 0, recv1);
                Console.WriteLine(message1);


                var array2 = message1.Split(',');


                foreach (string s in array2) //from this line the program      isn't working
                {
                    Console.WriteLine(s);
                }

                string input = Console.ReadLine();
                ns.Write(Encoding.ASCII.GetBytes(input), 0, input.Length);
                ns.Flush();
            }
            catch (Exception e)
            {
                Console.Write(e.Message);
            }
            Console.Read();
        }
    }
}

What's wrong in the code?

Black
  • 1
  • 1
  • 1
    If you set a breakpoint at `var array2 ...`, you will see that string message probably has all you data from server. Tcp connection is like a water hose, every data you put in comes out in the other end. It is you job to collect all data and process. There is no guarantee that one ns.Write is equal to one ns.Read. – rdyhalt Sep 29 '15 at 16:41
  • @togocoder I think the code already hangs at `int recv2` because all data will be received by the 1st `ns.read` and the 2nd call in the mentioned line is blocking as no more data is present. – Tobias Knauss Sep 29 '15 at 17:35
  • Yes, you are right. `ns.Read` will block until there is data or the connection is closed – rdyhalt Sep 29 '15 at 20:58
  • Related question: http://stackoverflow.com/questions/30468329/c-sharp-tcp-server-combining-received-data – Sergey Vyacheslavovich Brunov Sep 29 '15 at 21:03
  • Related question: http://stackoverflow.com/questions/14725215/c-sharp-tcp-client-cant-get-more-than-1-message-in-row – Sergey Vyacheslavovich Brunov Sep 29 '15 at 21:04

2 Answers2

2

After looking at your post again, I am sure that my comment is the actual answer:

The code already hangs at int recv2 because all data will be received by the 1st ns.read and the 2nd call in the mentioned line is blocking as no more data is present.
You need to add a 'high level protocol' that lets you identify the data of each packet.
Sample:

000007welcome

6 bytes (4 could be enough though) at the Start of each message specify the length of the user data. So you can easily separate your data which will look like

000007welcome0000011000014.cpp,.jpg,.png

You have to create the functions for creating/adding and separating/interpreting your 6 bytes header (which actually is just a length info, but could be enhanced to contain multiple info) by yourself of course, but that's quite easy.
In general you should always consider timing issues on EVERY operation that is not just adding 2 integers: file access, data transfer over network or other media, ... really any type of hardware access. Therefore sending data over network means that the packets could

  • arrive in correct order, as desired, with some delay so that you could read them separately. This is quite unlikely though.
  • arrive in correct order, but at the same time or with major delay (which is a couple of 100ms, so not really large for a human, but indeed in terms of networking). This is very common, see your case.
  • arrive partially. Packet fragmentation usually occurs only on large packets (greater than MTU), but you should have a routine that stores and concats incoming data, while another routine checks the stored data for complete messages, removes complete messages from the stored data and processes them. For this, use a loop (in a separate thread) and call ns.read when ns.DataAvailable is true.
  • arrive in different order. This may happen on long transmission paths (internet; rarely on LAN). You would have to enhance your protocol by an incrementing sequence number.
  • get lost! On UDP, the packets will NOT be resent. On TCP they will, but actually I cannot tell what happens on the sender side if the resend fails, too...

This means, depending on the importance of your data you may need to take safety measures for these cases.
And finally you might want to handle (unexpected) connection losses...
I also suggest you take a look on my answers on other networking questions for advices on debugging, testing, common issues, ...

Tobias Knauss
  • 3,361
  • 1
  • 21
  • 45
  • 1
    A nice Web site that really is doing a good job explain it in a nice way. http://blog.stephencleary.com/2009/04/tcpip-net-sockets-faq.html – rdyhalt Sep 29 '15 at 21:03
1

Analysis

The code:

// Receive the welcome message.
int recv = ns.Read(data, 0, data.Length);
string message = Encoding.ASCII.GetString(data, 0, recv);
Console.WriteLine(message);

// Receive the file size.
int recv2 = ns.Read(data, 0, data.Length);
string message2 = Encoding.ASCII.GetString(data, 0, recv2);
Console.WriteLine(message2);

// Receive the extensions.
int recv1 = ns.Read(data, 0, data.Length);
string message1 = Encoding.ASCII.GetString(data, 0, recv1);
Console.WriteLine(message1);

does not work properly because the Stream.Read(Byte[], Int32, Int32) method has the following return value:

Return Value

Type: System.Int32

The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.

Stream.Read Method (Byte[], Int32, Int32), MSDN.

The Stream class does not guarantee the "data correspondence" between the Stream.Write() and the Stream.Read() method calls. For example, if the Stream.Write() method call writes 6 bytes, the first Stream.Read() method call could return 1 byte (first byte).

Conceptual solution

Because of "streaming" it is necessary to define the logical messages to "extract" ("detect") them from the stream.

It is required to "join" the messages using the "separator" when sending and "split" the messages using the "separator" when receiving. One of the following alternatives could be considered to implement the "separator" concept:

  • Introduce the "end-of-the-message" marker.
  • Introduce the message header which contains the length of the message.

The small article, TCP/IP client-server application: exchange with string messages, may be useful to understand the mentioned alternatives.