I'm creating a chat application in which multiple users can enter their messages and send them to the server over tcp (networkstream).
I have the following code for receiving messages:
private void ReceiveData()
{
Task.Run(() =>
{
int bufferSize = 1024;
string message = "";
byte[] buffer = new byte[bufferSize];
_NetworkStream = _TcpClient.GetStream();
AddMessage("Connected!");
while (true)
{
int readBytes = _NetworkStream.Read(buffer, 0, bufferSize);
message = Encoding.ASCII.GetString(buffer, 0, readBytes);
Console.WriteLine("I received a message: " + message);
if (message.Equals("bye"))
{
break;
}
AddMessage(message);
}
buffer = Encoding.ASCII.GetBytes("bye");
_NetworkStream.Write(buffer, 0, buffer.Length);
_NetworkStream.Close();
_TcpClient.Close();
AddMessage("Connection closed!");
});
}
Now when I call AddMessage (which I call from a different Thread, thus a different context) My application crashes. Which is quite logical given my AddMessage code:
private void AddMessage(string message)
{
this.ListBox_Messages.Items.Add(message);
}
My question is, is the addmessage function responsible for executing this on the ui thread or the function caller, in this case ReceiveData() and what would be the best and most modern way to implement it?
Thank you in advance!
Did your teacher actually TEACH you any of these "modern methods"?!
Tell him or her there is nothing wrong with going back to basics:
private void AddMessage(string message)
{
if (this.ListBox_Messages.InvokeRequired)
{
this.ListBox_Messages.Invoke((MethodInvoker)delegate {
this.ListBox_Messages.Items.Add(message);
});
}
else
{
this.ListBox_Messages.Items.Add(message);
}
}
It's simple, and it just works.
As a computer science teacher myself, I hate seeing these types of questions. Makes me wonder what the teachers are doing.
Teacher: "You must use this magical stuff that I haven't taught you yet!"
Giving your teacher the benefit of the doubt, are there any requirements for this application that you haven't shared with us?
Related
I'm currently using the ReadAsync method in NetworkStream, and thus far its been pretty stable. The problem occurs when the data received from the server is larger than the buffer itself.
ReadCallBack assumes that the buffer contains a complete and valid message, as a new buffer is created on every call to BeginAsyncRead. Thus, when the response is too large for one buffer, it gets split between multiple reads and thus multiple buffers.
My ReadCallBack then assumes each buffer is an independent message (instead of adding them together) and ends up failing the deserialization on both.
My question is, what is the correct way to handle messages that are bigger than the size of the buffer, specially if you want to do the reads asynchronously?
A character itself could be split between two buffers so this gets quite tricky.
Below is my code for ReadAsync and ReadCallBack.
public async Task ReadForever() {
while(this.IsConnected) {
await BeginAsyncRead();
}
}
public async Task BeginAsyncRead() {
if (this.Stream.CanRead) {
try {
ReceiverState readState = new ReceiverState(this.Stream);
var task = this.Stream.ReadAsync(readState.buffer, 0, ReceiverState.bufferSize);
await task.ContinueWith(bytesRead => ReadCallBack(bytesRead.Result, readState));
}
catch (Exception e) {
Console.WriteLine("Error");
}
} else {
Console.WriteLine("Unreadable stream");
}
}
private void ReadCallBack(int numBytesRead, ReceiverState state) {
if (numBytesRead > 0) {
string payload = Encoding.ASCII.GetString(state.buffer, 0, numBytesRead);
string[] responses = payload.Split(new string[] {"\n"}, StringSplitOptions.RemoveEmptyEntries);
foreach (string res in responses){
var t = Task.Run(() => this.OnRead(res));
}
} else {
Console.WriteLine("Corrupted number of bytes received");
}
}
Note : All responses sent by the server contain a newline at the end.
Note : ReadForever is called inside Task.Run, my application can receive messages from the server as notifications, thus I must always be reading for incoming messages (I don't know when a notification may arrive).
So I'm very interested in learning more sophisticated programming techniques and languages, and I've decided to further my study into C# by learning sockets and server programming. I stumbled upon a video that i bet a lot of you have already seen or heard of, and it's, in my opinion, pretty good at explaining most of what's going on.
So my problem is specifically with essentially taking this code and making it a chat system that takes info from a client, sends it to the server, then from the server to ALL clients. I've looked up any tutorials and other stack overflow pages to no avail, at least to me. I can't seem to figure it out, and when someone has an answer they've simply written "Found a solution" and more or less left it at that.
I am aware of this thread on SO, and I've tried what's been posted in there to no avail. I've also done my fair share of searching through google and whatnot, and with being new to sockets and all that, it's definitely not the easiest thing.
The Actual Problem
Here's my code for the server:
using static AppNameHere.ChatCommands;
namespace AppNameHere
{
public partial class Host : Form
{
public static Host host = null;
private static string response;
private static byte[] _buffer = new byte[1024];
private static List<Socket> _ClientSk = new List<Socket>();
private static Socket _Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
public Host()
{
InitializeComponent();
}
private void Host_Load(object sender, EventArgs e)
{
SetupServer();
}
private static void SetupServer()
{
_Socket.Bind(new IPEndPoint(IPAddress.Any, HostJoinSelect.portSelected));
_Socket.Listen(HostJoinSelect.playerTotal + 2);
_Socket.BeginAccept(new AsyncCallback(AccCallback), null);
}
private static void AccCallback(IAsyncResult iar)
{
Socket s = _Socket.EndAccept(iar);
_ClientSk.Add(s);
Console.WriteLine("Client Connected.");
s.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(RecCallback), s);
_Socket.BeginAccept(new AsyncCallback(AccCallback), null);
}
private static void RecCallback(IAsyncResult iar)
{
Socket s = (Socket)iar.AsyncState;
int received = s.EndReceive(iar);
byte[] dataBuf = new byte[received];
Buffer.BlockCopy(_buffer, 0, dataBuf, 0, received);
string t = Encoding.ASCII.GetString(dataBuf);
foreach (Socket socket in _ClientSk)
{
response = ChatCommandParser(t);
byte[] data = Encoding.ASCII.GetBytes(response);
socket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(RecCallback), socket);
}
}
private static void SendCallback(IAsyncResult iar)
{
Socket s = (Socket)iar.AsyncState;
s.EndSend(iar);
}
}
}
The using at the top is just a class I made for converting text to something else, so it's no obstruction. What really rustles my jimmies is the foreach loop that's, in theory, supposed to make it work, and it makes sense as it should be going through all the clients and sending them all messages, but if a client sends a message, nothing happens on the other clients. Only the original client gets the message back.
So both debugging and troubleshooting have shown that it runs through BeginSend and such, but it still only sends a message to that same client that i typed into which is just weird.
Thanks in advance, and please, if you have an answer then explain it to me cause I'm still learning, so a fix and an explanation would be nice.
P.S. Yes I know I'm using TCP and not UDP.
EDIT: The code I used for my client is in this pastebin link: https://pastebin.com/4WicEu2x
EDIT 2: I have a feeling the main issue is that in my client code, I have the client listening for messages only AFTER it sends a message. If this is it, then i'm dumber than a rock and would like to thank anyone who posted here telling me how to improve my program.
EDIT 3: What I wrote in my second edit was correct. If you're having an issue and you've followed the same guide I did in that youtube video, then just know that you should have Asynchronous Receiving in your client-side program as well as your server. I didn't think too much of the client side, but always check your code! Here's my edited client-side code (yes i know it needs LOTS of work):
namespace AppNameHere
{
public partial class Join : Form
{
public static Join join = null;
private static Socket _ClientSk = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
private static byte[] recBuf = new byte[1024];
public Join()
{
InitializeComponent();
}
private void Join_Load(object sender, EventArgs e)
{
}
private void Join_FormClosed(object sender, FormClosedEventArgs e)
{
if (HostJoinSelect.hjs != null)
{
HostJoinSelect.hjs.Show();
}
else
{
HostJoinSelect.hjs = new HostJoinSelect();
HostJoinSelect.hjs.Show();
}
}
private void Join_Shown(object sender, EventArgs e)
{
LoopConnect();
}
private static void Send()
{
string req = join.textBox1.Text;
byte[] buffer = Encoding.ASCII.GetBytes(req);
_ClientSk.Send(buffer);
}
private static void ConnectedCallback()
{
_ClientSk.BeginReceive(recBuf, 0, recBuf.Length, SocketFlags.None, new AsyncCallback(ReceivedCallback), _ClientSk);
}
private static void ReceivedCallback(IAsyncResult iar)
{
Socket s = (Socket)iar.AsyncState;
int rec = s.EndReceive(iar);
byte[] dataBuf = new byte[rec];
Buffer.BlockCopy(recBuf, 0, dataBuf, 0, rec);
string q = Encoding.ASCII.GetString(dataBuf);
join.Invoke(new MethodInvoker(delegate () {
join.listBox1.Items.Add(Form1.namesave + ": " + q);
join.listBox1.TopIndex = join.listBox1.Items.Count - 1;
}));
s.BeginReceive(recBuf, 0, recBuf.Length, SocketFlags.None, new AsyncCallback(ReceivedCallback), s);
}
private static void LoopConnect()
{
int attempts = 0;
while (!_ClientSk.Connected)
{
if (attempts < 4)
{
try
{
attempts++;
if (attempts <= 4)
{
_ClientSk.Connect(HostJoinSelect.IPSelectedJoin, HostJoinSelect.portSelectedJoin);
Console.WriteLine("Connected");
ConnectedCallback();
}
else
{
attempts = 0;
break;
}
}
catch (SocketException s)
{
if (attempts <= 4)
{
Console.WriteLine(s.Message + " | Connection attempts: " + attempts.ToString());
}
else
{
attempts = 0;
break;
}
}
}
else
{
MessageBox.Show("You failed to connect to " + HostJoinSelect.IPSelectedJoin + ":" + HostJoinSelect.portSelectedJoin + ", please ensure you have a means of connecting to this address.");
join.Close();
break;
}
}
}
private void button1_Click(object sender, EventArgs e)
{
Send();
}
}
}
I changed the code from accepting messages only after it sent one itself to having an asynchronous BeginReceive to take in data sent by the server. Always review your code and logic!
Besides the fact that your code requires some fixes it still works. I executed it in my test project and it worked as you want (almost, short of concurrency issues).
Required concurrency fixes:
As Emir Curtovic mentioned you shouldn't use a common buffer. Use local variable instead. If you want you can utilize the same buffers between connections using ThreadLocal or AsyncLocal class-level fields. It will be both thread-safe and reduce a heap fragmentation hazard.
You cannot just use List for sockets. It is not thread safe. There are at least two possible fixes: a) use a lock {} around the _ClientSk.Add(s) call or b) use concurrent collections.
If your client connections come simultaneously the last bug may cause the behavior that you described. However, there is also a possibility that your client code has some issues as well. If after introducing the fixes you still experience the issue please post your client code as well.
In general, there is more work to be done with your code. For example you should handle client disconnections. Currently it will just throw exceptions on int received = s.EndReceive(iar) call any time your client decides to close the connection. Besides handling the exception you will need to add to your protocol some way to perform a graceful connection closing. For example, by handling EOF symbol. Here you will find a nice code sample from MSFT.
I'm in the progress of writing a client/server socket library in .NET core, just a basic model for use inside another project.
In the client, I've got three threads, one listening, one sending, and one which passes the received messages back to the consumer.
I'm trying to implement a Shutdown function to close the client. Sending and receiving functions are both consumers, so they're easy to just tell to check for a ManualResetEvent.
However, the only way I can find to close the receiving thread is to run socket.Shutdown() since the tread is stuck in socket.Recieve(). This causes a SocketException to be thrown in the listening thread, which can be caught, handled and cleanly closed. However, my issue occurs when I can't determine the NativeErrorCode of the SocketException to know why it's closing.
I don't want to hide errors by catching all SocketExceptions, just the NativeErrorCode 10004 error. NativeErrorCode is not accessiable in the SocketException class, however I can see it in IntelliSense, any ideas?
private void ListenThread()
{
//Listens for a recieved packet, first thing reads the 'int' 4 bytes at the start describing length
//Then reads in that length and deserialises a message out of it
try
{
byte[] lengthBuffer = new byte[4];
while (socket.Receive(lengthBuffer, 4, SocketFlags.None) == 4)
{
int msgLength = BitConverter.ToInt32(lengthBuffer, 0);
if (msgLength > 0)
{
byte[] messageBuffer = new byte[msgLength];
socket.Receive(messageBuffer);
messageBuffer = Prereturn(messageBuffer);
Message msg = DeserialiseMessage(messageBuffer);
receivedQueue.Enqueue(msg);
receivedEvent.Set();
MessagesRecievedCount += 1;
}
}
}
catch (SocketException se)
{
//Need to detect when it's a good reason, and bad, NativeErrorCode does not exist in se
//if(se.NativeErrorCode == 10004)
//{
// }
}
}
instead of se.NativeErrorCode you may use se.SocketErrorCode(System.Net.Sockets.SocketError), it is more clear.
Also, i usually use async sockets. They are built on the event model, so if something arrives to а socket buffer, a callback func will be called
public void ReceiveAsync()
{
socket.BeginReceive(tempBytes, 0, tempBytes.Length, 0, ReadCallback, this);//immediately returns
}
private void ReadCallback(IAsyncResult ar)//is called if something is received in the buffer as well as if other side closed connection - in this case countBytesRead will be 0
{
int countBytesRead = handler.EndReceive(ar);
if (countBytesRead > 0)
{
//read tempBytes buffer
}
}
I followed this link http://msdn.microsoft.com/en-us/library/bew39x2a(v=vs.110).aspx to write this program.
public string ReceiveResponse(Socket client, int bufferSize)
{
// Receive the response from the remote device.
Receive(client, bufferSize);
// Wait until we receive entire response.
receiveDone.WaitOne();
return response;
}
And my receive callback is
public static void ReceiveCallback(IAsyncResult ar)
{
StateObject so = (StateObject)ar.AsyncState;
Socket s = so.WorkSocket;
int read = s.EndReceive(ar);
if (read > 0)
{
so.Sb.Append(Encoding.ASCII.GetString(so.Buffer, 0, read));
s.BeginReceive(so.Buffer, 0, 1024, 0,
new AsyncCallback(ReceiveCallback), so);
}
else
{
if (so.Sb.Length > 1) // Code never reaching this block??
{
//All of the data has been read, so displays it to the console
string strContent;
strContent = so.Sb.ToString();
Console.WriteLine(String.Format("Read {0} byte from socket" +
"data = {1} ", strContent.Length, strContent));
}
receiveDone.Set();
}
}
As commented in my code else block never reaching so my receiveDone manual reset event blocking my unit test forever.
When Microsoft says this is the way to go then why it is not working for me???
I really need it for real time and multi threaded environment. I wasted a lot time on this but couldn't find a proper reason why this is happening? and how I can fix this?
Hi guys i have an issue currently when using a Socket to send data.
I am currently getting a very strange issue when a Client sends to the server, for example "HEART", as a heart beat. afer sending the first time the server starts receiving a whole lot of \0, not always the same amount before the HEART. I am using queues to queue up sending so on slow connections it waits for the current send to be done before it sends the next but for tiny lengths like that i'm a bit confused.
public void Send(string data)
{
if (Connected)
{
SendQueue.Enqueue(data);
if (t.ThreadState == ThreadState.Stopped)
{
t = new Thread(new ThreadStart(SendData));
t.Start();
}
else if (t.ThreadState == ThreadState.Unstarted)
t.Start();
}
}
and the SendData function
private void SendData()
{
if (sending)
return;
sending = true;
while (SendQueue.Count > 0)
{
if (ClientSocket.Connected)
{
byte[] data = Networking.StringToByte((string)SendQueue.Dequeue());
ClientSocket.Send(data);
}
}
sending = false;
}
i don't think it's the sending function because i've debugged it and the byte array always holds the correct info.
the receiving end is even simpler.
public string Receive()
{
string msg = "";
if (Connected)
{
byte[] data = new byte[1024];
while (ClientSocket.Avaliable > 0)
{
ClientSocket.Receive(data);
msg += Networking.ByteToString(data).Trim();
}
}
return msg;
}
If anyone could point out where i'm going wrong or if i've gone at this the entirely wrong way that would be great.
Thanks guys.
I will remind people that it's seemingly random lengths of \0 each 2 seconds (in this example for the HEART message heartbeat)
This piece of code can't be correct:
byte[] data = new byte[1024];
while (ClientSocket.Avaliable > 0)
{
ClientSocket.Receive(data);
msg += Networking.ByteToString(data).Trim();
}
It seems you do not take into account how much data you actually receive. You have to look at the return value from ClientSocket.Receive I don't know what your Networking.ByteToString does but when your code runs Networking.ByteToString , that function cannot know how much data you actually received. It'll probably convert the entire buffer - all 1024 bytes. And that's likely where all your zeroes comes from. It could be that somewhere you're doing a similar thing on the sending side.
You also might need to keep in mind that TCP is stream oriented, not packet oriented. If you do 1 Send call, that can take several Receive calls to read, or 1 Receive call might read the data from many Send calls.