I'm looking for a way to handle a disconnect, because every time I close a client, the server stops working. I get an error message, that it is "unable to read beyond the end of the stream" in this line:
string message = reader.ReadString();
Also I need a way to delet the disconnected client from the clients list. Here is my code: Server
using System;
using System.Threading;
using System.Net.Sockets;
using System.IO;
using System.Net;
using System.Collections.Generic;
namespace Server
{
class Server
{
public static List<TcpClient> clients = new List<TcpClient>();
static void Main(string[] args)
{
IPAddress ip = IPAddress.Parse("127.0.0.1");
TcpListener ServerSocket = new TcpListener(ip, 14000);
ServerSocket.Start();
Console.WriteLine("Server started.");
while (true)
{
TcpClient clientSocket = ServerSocket.AcceptTcpClient();
clients.Add(clientSocket);
handleClient client = new handleClient();
client.startClient(clientSocket);
}
}
}
public class handleClient
{
TcpClient clientSocket;
public void startClient(TcpClient inClientSocket)
{
this.clientSocket = inClientSocket;
Thread ctThread = new Thread(Chat);
ctThread.Start();
}
private void Chat()
{
while (true)
{
BinaryReader reader = new BinaryReader(clientSocket.GetStream());
while (true)
{
string message = reader.ReadString();
foreach (var client in Server.clients)
{
BinaryWriter writer = new BinaryWriter(client.GetStream());
writer.Write(message);
}
}
}
}
}
}
Client
using System;
using System.Net.Sockets;
using System.IO;
using System.Threading;
namespace Client
{
class Client
{
public static void Write()
{
TcpClient client = new TcpClient("127.0.0.1", 14000);
while (true)
{
string str = Console.ReadLine();
BinaryWriter writer = new BinaryWriter(client.GetStream());
writer.Write(str);
}
}
public static void Read()
{
TcpClient client = new TcpClient("127.0.0.1", 14000);
while (true)
{
BinaryReader reader = new BinaryReader(client.GetStream());
Console.WriteLine(reader.ReadString());
}
}
static void Main(string[] args)
{
Thread Thread = new Thread(Write);
Thread Thread2 = new Thread(Read);
Thread.Start();
Thread2.Start();
}
}
}
This is, in some sense, completely normal. That is, when using
BinaryReader
, its normal behavior is to throwEndOfStreamException
when reaching the end-of-stream.Why did it reach end-of-stream? Well, because the client disconnected and that's what happens to the stream. At the socket level, what really happens is that a read operation completes with 0 as the count of bytes read. This is the indication that the client has gracefully closed the socket and will not be sending any more data.
In the .NET API, this is translated to the end of the
NetworkStream
thatTcpClient
uses to wrap theSocket
object that's actually handling the network I/O. And thisNetworkStream
object is in turn being wrapped by yourBinaryReader
object. AndBinaryReader
throws that exception when it reaches the end of the stream.Note that your code does not actually provide a graceful way for the user to close a client. They will have to use Ctrl+C, or kill the process outright. Using the former has the serendipitous effect of performing a graceful shutdown of the socket, but only because .NET is handling the termination of the process and runs finalizers on your objects, such as the
TcpClient
object used to connect to the server, and the finalizer callsSocket.Shutdown()
to tell the server it's closing.If you were to kill the process (e.g. using Task Manager), you'd find an
IOException
was thrown instead. Good network code should always be prepared to see anIOException
; networks are unreliable and failures do occur. You want to do some reasonable, such as removing the remote endpoint from your connections, rather than just having the whole program crash.Now, all that said, just because the
EndOfStreamException
is "normal", that does not mean the code you posted is, or is in any way an example of the right way to do network programming. You have a number of issues:Network I/O provides a normal way to close connections, which involves handshaking on both endpoints to indicate when they are done sending, and when they are done receiving. One endpoint will indicate it's done sending; the other will note this (using the 0-byte-read mentioned above) and then itself indicate it's done both sending and receiving.
TcpClient
andNetworkStream
don't expose this directly, but you can use theTcpClient.Client
property to get theSocket
object to do a better graceful closure, i.e. one endpoint can indicate it's done sending, and still be able to wait until the other endpoint is also done sending.Using the
TcpClient.Close()
method to disconnect is like hanging up the phone on someone without saying "good-bye". UsingSocket.Shutdown()
is like finishing a phone call with a polite "okay, that's everything I wanted to say…was there anything else?"BinaryReader
but not handling theEndOfStreamException
correctly.Network I/O uses the
Socket
object, which supports full-duplex communications. There is no need to create a second connection just to do both reading and writing. A single connection suffices, and is better because when you split the send and receive into two connections, then you also need to add something to your protocol so that the server knows those two connections represent a single client (which your code does not actually do).Chat()
method has an extra "while (true)" in it.I have modified your original example to address all of the above, which I've presented here:
Server Program.cs:
Server handleClient.cs:
Client Program.cs:
Note that I did not, for the most part, address the inconsistent and unconventional naming you've used in your code. The only exception was for the thread variables in the client code, because I really don't like capitalized local variables that exactly match the name of a type.
You have some other problems as well, which the above revision of your code does not address. These include:
BinaryReader
. This is in a lot of ways an annoying class to use. I recommend, especially for a chat-server scenario where you're only dealing with text anyway, that you switch to usingStreamReader
/StreamWriter
.Program
class has server code, and the server code knows about theProgram
class. It would be much better to encapsulate both the server and client implementations into their own classes, separate from the main entry-point of the program, and to further decouple the top-level server code with the per-client data structure (use C#'sevent
to allow the top-level server code to be notified of important events, such as the need to remove a client from the list, without having the per-client data structure have to actually know about the top-level server object, never mind its client list).Normally, I would say that these are outside the scope of an answer like this, which is already quite long. I've addressed the immediate concern in your code and then some, and that is nominally sufficient.
However, I've been meaning to write an updated version of the basic network programming example I'd written some years ago, as a sort of "intermediate" example, adding multiple client support, asynchronous operation, and using the latest C# features (like
async
/await
). So, I went ahead and took some time to do that. I guess I'll post it to my blog eventually…that's a whole other project. In the meantime, here's that code (note that this is an entirely from-scratch example…it made more sense to do that than to try to rework the code you had)…Most of the grunt work for this implementation is in a single class shared by the server and client:
A client program would use that class directly. The server side is encapsulated in another class, found in the same DLL with the above:
And that is, for the most part, all you need. The DLL code above is referenced (in my example) by two different programs, a server and a client.
Here's the server:
And here's the client:
In my opinion, one of the most important things for you to note in the above is that the
ConnectedEndPoint
andChatServer
classes have zero dependency on the classes which use them. Through the use of callback delegates and events, the code that depends on these classes are able to interact bi-directionally without these supporting classes having to know about the types that code resides in (see "inversion of control", which this is a variation of).The more you can make your code relationships look like a tree with only single-direction references, the easier it will be to write the code, and to maintain it later.
Note: I used both events and callback delegates for the sake of illustration. Either approach works fine on its own. The main trade-offs are complexity vs. flexibility. Using events makes the code more flexible — event handlers can be added and removed as necessary — but if one implements events using the .NET convention of a method signature with a
sender
and anEventArgs
parameter, it is somewhat more "heavy-weight" than just passing a simple callback delegate when creating the object in question. I put an example of each in the code, and you can decide which approaches you prefer in which situations.You'll also note that the above makes heavy use of C#'s asynchronous features. At first, this may make the code seem harder to read. But in fact, it's actually much easier to get everything working using these features, than if I were to try to use the older
BeginXXX()
/EndXXX()
methods or, heaven forbid, dedicate a single thread to each connection (which scales very poorly as the client count goes up). It's absolutely worth getting used to thinking of operations which are inherently asynchronous, such as network I/O, in this way.