This is my 1st C# project so I may be doing something obviously improper in the code below.
I am using .NET, WinForms (I think), and this is a desktop application until I get the bugs out.
UpdateGui() uses Invoke((MethodInvoker)delegate to update various GUI controls based on received serial data and
sends a GetStatus() command out the serial port 4 times a second.
Thread Read() reads the response from serial port whenever it arrives which should be near immediate.
SerialPortFixer is a SerialPort IOException Workaround in C# I found at
http://zachsaw.blogspot.com/2010/07/serialport-ioexception-workaround-in-c.html.
After one or both threads die I'll see something like
The thread 0x1288 has exited with code 0 (0x0).
in the debug code output.
Why do UpdateGui() and/or Read() eventually die?
public partial class UpdateStatus : Form
{
private readonly byte[] Command = new byte[32];
private readonly byte[] Status = new byte[32];
readonly Thread readThread;
private static readonly Mutex commandMutex = new Mutex();
private static readonly Mutex statusMutex = new Mutex();
...
public UpdateStatus()
{
InitializeComponent();
SerialPortFixer.Execute("COM2");
if (serialPort1.IsOpen)
{
serialPort1.Close();
}
try
{
serialPort1.Open();
}
catch (Exception e)
{
labelWarning.Text = LOST_COMMUNICATIONS + e;
labelStatus.Text = LOST_COMMUNICATIONS + e;
labelWarning.Visible = true;
}
readThread = new Thread(Read);
readThread.Start();
new Timer(UpdateGui, null, 0, 250);
}
static void ProcessStatus(byte[] status)
{
Status.State = (State) status[4];
Status.Speed = status[6]; // MSB
Status.Speed *= 256;
Status.Speed += status[5];
var Speed = Status.Speed/GEAR_RATIO;
Status.Speed = (int) Speed;
...
}
public void Read()
{
while (serialPort1 != null)
{
try
{
serialPort1.Read(Status, 0, 1);
if (Status[0] != StartCharacter[0]) continue;
serialPort1.Read(Status, 1, 1);
if (Status[1] != StartCharacter[1]) continue;
serialPort1.Read(Status, 2, 1);
if (Status[2] != (int)Command.GetStatus) continue;
serialPort1.Read(Status, 3, 1);
...
statusMutex.WaitOne();
ProcessStatus(Status);
Status.update = true;
statusMutex.ReleaseMutex();
}
catch (Exception e)
{
Console.WriteLine(#"ERROR! Read() " + e);
}
}
}
public void GetStatus()
{
const int parameterLength = 0; // For GetStatus
statusMutex.WaitOne();
Status.update = false;
statusMutex.ReleaseMutex();
commandMutex.WaitOne();
if (!SendCommand(Command.GetStatus, parameterLength))
{
Console.WriteLine(#"ERROR! SendCommand(GetStatus)");
}
commandMutex.ReleaseMutex();
}
private void UpdateGui(object x)
{
try
{
Invoke((MethodInvoker)delegate
{
Text = DateTime.Now.ToLongTimeString();
statusMutex.WaitOne();
if (Status.update)
{
if (Status.Speed > progressBarSpeed.Maximum)
{
Status.Speed = progressBarSpeed.Maximum;
}
progressBarSpeed.Value = Status.Speed;
labelSpeed.Text = Status.Speed + RPM;
...
}
else
{
labelWarning.Text = LOST_COMMUNICATIONS;
labelStatus.Text = LOST_COMMUNICATIONS;
labelWarning.Visible = true;
}
statusMutex.ReleaseMutex();
GetStatus();
});
}
catch (Exception e)
{
Console.WriteLine(#"ERROR! UpdateGui() " + e);
}
}
}
A thread will terminate when there's no more code to execute, or more specifically when the method you specify when you create thread returns.
Maybe serialport1 becomes null?
As for the update timer, there is a special purpose windows forms timer that runs periodically that doesn't require you to use Invoke. It's the right tool for the job
Related
I have to read a file and after processing it's data write result to another file. This process takes too much time so I tried to do read/process/write in a parallel way. Code works well. But there is a problem, when I was testing it for processing very huge file (100GB) after about 40GB program stops working. It do not throw any exception (except 'TimeoutException'). I spend several days and tried to change many things. I know it is not about method or memory. But I am really confused about reason and way to make it works well.
Note: I wanted to post this on Code Review, but Code Review rules says do not post for trouble shooting and else so I posted it here.
The way I use code (It is on backgroundworker)
BackgroundWorker worker = (BackgroundWorker)sender;
ReaderWriterMultiThread readerWriterMultiThread = null;
int bufferSize = 2 * 1024 * 1024;
int readerWriterMultiThreadPartsNumber = 10;
int sizeToReadInThisIteration = 0;
int oldprecentage = 0;
long fileDid = 0;
using (FileStream streamReader = new FileStream(fromAddress, FileMode.Open))
using (BinaryReader binaryReader = new BinaryReader(streamReader))
using (FileStream streamWriter = new FileStream(toAddress, FileMode.Open))
using (BinaryWriter binaryWriter = new BinaryWriter(streamWriter))
{
sizeToReadInThisIteration = bufferSize * readerWriterMultiThreadPartsNumber;
streamWriter.Seek(0, SeekOrigin.Begin);
while (streamWriter.Position < length)
{
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
//change sizeToReadInThisIteration if needs
if (streamWriter.Position + sizeToReadInThisIteration > length)
{ sizeToReadInThisIteration = Convert.ToInt32(length - streamWriter.Position); }
//new it
readerWriterMultiThread = new ReaderWriterMultiThread();
//read/do/write
readerWriterMultiThread.Start(binaryReader, binaryWriter, bufferSize, sizeToReadInThisIteration,
(ref byte[] bytes) => DoNothing(ref bytes));
//report process if needs
fileDid += sizeToReadInThisIteration;
if (((int)(fileDid * 100 / length)) > oldprecentage)
{
oldprecentage = (int)(fileDid * 100 / length);
worker.ReportProgress(oldprecentage);
}
}//while
}//using
DoNothing method is:
public void DoNothing(ref byte[] bufferToCode)
{ }
and ReaderWriterMultiThread class is:( Originally code used threads but I changed it to use tasks.)
public class ReaderWriterMultiThread
{
#region variables
//buffer(contain several part)
List<byte[]> buffer = new List<byte[]>();
//lock objects
private object bufferLockForRead = new object();
private object bufferLockForWrite = new object();
//indexes
int readIndex = 0;
int doReadIndex = 0;
int doWriteIndex = 0;
int writeIndex = 0;
//complete vars
int lastIndex = int.MaxValue;
bool readCompleted = false;
//waiting properties
private bool doIsWaiting = false;
private bool writerIsWaiting = false;
//error properties
private bool anyErrorHappend = false;
private string errorsMessage = string.Empty;
//proc delegate
public delegate void DelegateMethod(ref byte[] bytes);
//proc delegate instance
DelegateMethod delegateM;
//
#endregion variables
//==============================
#region methods
//
public void Start(BinaryReader binaryReader, BinaryWriter binaryWriter, int bufferPartsSize, int size, DelegateMethod delegateMethod)
{
//new delegate
delegateM = new DelegateMethod(delegateMethod);
//for wait all
Task[] tasks = new Task[3];
//run
var parentTask = Task.Factory.StartNew(() =>
{
tasks[0] = Task.Factory.StartNew(() =>
{
Writer(binaryWriter);
});
tasks[1] = Task.Factory.StartNew(() =>
{
Do();
});
tasks[2] = Task.Factory.StartNew(() =>
{
Reader(binaryReader, bufferPartsSize, size);
});
});
//wait
parentTask.Wait();
if (!Task.WaitAll(tasks, 10000))
{ throw new TimeoutException(); }
if (anyErrorHappend)
{ throw new Exception(errorsMessage); }
}
private void AddByReader(byte[] newBytes, bool completed)
{
try
{
lock (bufferLockForRead)
{
//add data to buffer
buffer.Add(newBytes);
//updare readIndex
readIndex++;
//if completed show it
if (completed)
{
readCompleted = true;
lastIndex = buffer.Count;//it uses as <lastIndex (so lastIndex = buffer.Count is ok)
}
//manage happend error
if (anyErrorHappend)
{
readCompleted = true;
lastIndex = doReadIndex + 1;
}
//if do is waiting pulse it
if (doIsWaiting)
{ Monitor.Pulse(bufferLockForRead); }
}
}
catch (Exception ex)
{ Debug.Assert(false, ex.ToString()); }
}
private byte[] GetByDo()
{
try
{
lock (bufferLockForRead)
{
//if data did not read already wait
if (doReadIndex == readIndex)
{
doIsWaiting = true;
Monitor.Wait(bufferLockForRead);
}
//do is not waiting now
doIsWaiting = false;
//in case of emergency
if (doReadIndex > readIndex)
{ return new byte[0]; }
//return
return buffer[doReadIndex++];
}
}
catch (Exception ex)
{
Debug.Assert(false, ex.ToString());
return new byte[0];
}
}
private void AddByDo(byte[] newBytes, string errorMessageFromDO)
{
try
{
lock (bufferLockForWrite)
{
//add data
buffer[doWriteIndex] = newBytes;
//update doWriteIndex
doWriteIndex++;
//error happend in Do
if (errorMessageFromDO.Length > 0)
{
anyErrorHappend = true;
errorsMessage += errorMessageFromDO;
lastIndex = -1;
Monitor.Pulse(bufferLockForWrite);
}
//if reader completed and writer is in wait state pulse it
if (readCompleted && writerIsWaiting)
{
Monitor.Pulse(bufferLockForWrite);
}
}
}
catch (Exception ex)
{ Debug.Assert(false, ex.ToString()); }
}
private byte[] GetByWriter()
{
try
{
lock (bufferLockForWrite)
{
//if data did not proccessed wait
if (writeIndex == doWriteIndex)
{
writerIsWaiting = true;
Monitor.Wait(bufferLockForWrite);
}
//writer is not waithing
writerIsWaiting = false;
//return
return buffer[writeIndex++];
}
}
catch (Exception ex)
{
Debug.Assert(false, ex.ToString());
return new byte[0];
}
}
private void Reader(BinaryReader binaryReader, int bufferPartSize, int sizeToRead)
{
try
{
//vars
bool completed = false;
int readedSize = 0;
byte[] readedBytes = new byte[0];
while (readedSize < sizeToRead && !anyErrorHappend)
{
//change bufferPartSize & completed if needs
if (readedSize + bufferPartSize >= sizeToRead)
{
bufferPartSize = sizeToRead - readedSize;
completed = true;
}
try
{
//read
readedBytes = binaryReader.ReadBytes(bufferPartSize);
}
catch (Exception ex)
{
Debug.Assert(false, ex.ToString());
//error happend
anyErrorHappend = true;
errorsMessage += ex.Message;
//for pulse Do() if it is waiting
byte[] amptyBytesArray = new byte[0];
AddByReader(amptyBytesArray, true);//it is better to do it instead change lastIndex here
break;
}
//add to buffer
AddByReader(readedBytes, completed);
//update readedSize
readedSize += bufferPartSize;
}
}
catch (Exception ex)
{ Debug.Assert(false, ex.ToString()); }
}
private void Writer(BinaryWriter binaryWriter)
{
try
{
//vars
byte[] bytesToWrite = new byte[0];//for put getted data in
for (int i = 0; i < lastIndex; i++)
{
//get data from buffer
bytesToWrite = GetByWriter();
try
{
//write
binaryWriter.Write(bytesToWrite);
}
catch (Exception ex)
{
Debug.Assert(false, ex.ToString());
lastIndex = -1;
anyErrorHappend = true;
errorsMessage = ex.Message;
break;
}
}
}
catch (Exception ex)
{ Debug.Assert(false, ex.ToString()); }
}
private void Do()
{
try
{
//vars
byte[] bytes = new byte[0];//for put readed data/result in
for (int i = 0; i < lastIndex; i++)
{
//get data from buffer
bytes = GetByDo();
try
{
//do
delegateM(ref bytes);
}
catch (Exception ex)
{
Debug.Assert(false, ex.ToString());
//add
AddByDo(new byte[0], "error: " + ex.Message);
break;
}
//add data to buffer
AddByDo(bytes, string.Empty);
}
}
catch (Exception ex)
{ Debug.Assert(false, ex.ToString()); }
}
//
#endregion methods
}
You code is throwing an exception here:
if (!Task.WaitAll(tasks, 10000))
{ throw new TimeoutException(); }
Which means that one of your tasks is taking more than 10 seconds to complete.
This could be because it needs more time. It could also be because one of your tasks is getting stuck waiting to enter a lock. It could also because the Monitor.Wait is getting stuck waiting for a lock.
You can add logging to see where it is getting hung. Additionally if one of your assert statements fail it will prevent a task from completing in time.
As a side note, Disk IO is time consuming and in general trying to parallelize IO operations like this typically aren't going to help because the threads end up stomping all over each other contending for disk access time. The disk ends up needing to spin to numerous positions back and forth to read and write data and you end up with a net effect of actually slowing things down. You might be able to speed things up if you are using RAID or you are reading from one disk and adding the reads to a queue and your write thread is reading from that queue and writing that data to a different disk.
I've got an intra-PC communication server / client set up to send and receive data from one program to another - in this case, a custom server that is listening to text commands and Unity3D.
For the most part, it works, however every once in awhile, it will drop packets, and Unity will not get them without multiple attempts. The packets seem to be sent but lost, as I do see the "Sent message" console log. The following is the code for the server and client:
SERVER:
class TCPGameServer
{
public event EventHandler Error;
public Action<Data> ADelegate;
TcpListener TCPListener;
TcpClient TCPClient;
Client ActiveClient;
NetworkStream networkStream;
StreamWriter returnWriter;
StreamReader streamReader;
Timer SystemTimer = new Timer();
Timer PingTimer = new Timer();
int Port = 8637;
public TCPGameServer()
{
TCPListener = new TcpListener(IPAddress.Loopback, Port);
SystemTimer.Elapsed += StreamTimer_Tick;
SystemTimer.AutoReset = true;
SystemTimer.Interval = 2000;
PingTimer.Elapsed += PingTimer_Tick;
PingTimer.AutoReset = true;
PingTimer.Interval = 30000;
}
public void OpenListener()
{
TCPListener.Start();
TCPListener.BeginAcceptTcpClient(AcceptTCPCallBack, null);
Console.WriteLine("Network Open.");
}
public void GameLogout()
{
SystemTimer.AutoReset = false;
SystemTimer.Stop();
PingTimer.AutoReset = false;
PingTimer.Stop();
ActiveClient = null;
returnWriter.Dispose();
streamReader.Dispose();
Console.WriteLine("The client has logged out successfully.");
}
private void AcceptTCPCallBack(IAsyncResult asyncResult)
{
TCPClient = null;
ActiveClient = null;
returnWriter = null;
streamReader = null;
try
{
TCPClient = TCPListener.EndAcceptTcpClient(asyncResult);
TCPListener.BeginAcceptTcpClient(AcceptTCPCallBack, null);
ActiveClient = new Client(TCPClient);
networkStream = ActiveClient.NetworkStream;
returnWriter = new StreamWriter(TCPClient.GetStream());
streamReader = new StreamReader(TCPClient.GetStream());
Console.WriteLine("Client Connected Successfully.");
Data Packet = new Data();
Packet.cmdCommand = Command.Login;
Packet.strName = "Server";
Packet.strMessage = "LOGGEDIN";
SendMessage(Packet);
SystemTimer.AutoReset = true;
SystemTimer.Enabled = true;
SystemTimer.Start();
Ping();
PingTimer.AutoReset = true;
PingTimer.Enabled = true;
PingTimer.Start();
} catch (Exception ex)
{
OnError(TCPListener, ex);
return;
}
}
private void StreamTimer_Tick(object source, System.Timers.ElapsedEventArgs e)
{
CheckStream();
}
private void PingTimer_Tick(object source, System.Timers.ElapsedEventArgs e)
{
Ping();
}
private void Ping()
{
if (TCPClient.Connected)
{
Data Packet = new Data();
Packet.cmdCommand = Command.Ping;
Packet.strName = "Server";
Packet.strMessage = "PING";
SendMessage(Packet);
}
}
public void CheckStream()
{
try
{
if (TCPClient.Available > 0 || streamReader.Peek() >= 0)
{
string PacketString = streamReader.ReadLine();
Data packet = JsonConvert.DeserializeObject<Data>(PacketString);
switch (packet.cmdCommand)
{
case Command.Logout:
GameLogout();
break;
case Command.Message:
if (ADelegate != null)
{
ADelegate(packet);
}
break;
case Command.Ping:
Console.WriteLine("PONG!");
break;
}
}
} catch (IOException e)
{
Console.WriteLine(e.Message);
} catch (NullReferenceException e)
{
Console.WriteLine(e.Message);
}
}
public void SendMessage(Data packet)
{
if (ActiveClient != null)
{
string packetMessage = JsonConvert.SerializeObject(packet);
returnWriter.WriteLine(packetMessage);
returnWriter.Flush();
}
}
public void OnError(object sender, Exception ex)
{
EventHandler handler = Error;
if (handler != null)
{
ErrorEventArgs e = new ErrorEventArgs(ex);
handler(sender, e);
}
}
public void RegisterActionDelegate(Action<Data> RegisterDelegate)
{
ADelegate += RegisterDelegate;
}
public void UnRegisterActionDelegate(Action<Data> UnregisterDelegate)
{
ADelegate -= UnregisterDelegate;
}
}
CLIENT:
public class TCPNetworkClient
{
public Action<Data> PacketDelegate;
public TcpClient TCPClient;
int Port = 8637;
StreamReader streamReader;
StreamWriter streamWriter;
Timer StreamTimer = new Timer();
public bool LoggedIn = false;
public void Start()
{
if (LoggedIn == false)
{
TCPClient = null;
StreamTimer.AutoReset = true;
StreamTimer.Interval = 2000;
StreamTimer.Elapsed += StreamTimer_Tick;
try
{
TCPClient = new TcpClient("127.0.0.1", Port);
streamReader = new StreamReader(TCPClient.GetStream());
streamWriter = new StreamWriter(TCPClient.GetStream());
StreamTimer.Enabled = true;
StreamTimer.Start();
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
}
private void StreamTimer_Tick(System.Object source, System.Timers.ElapsedEventArgs e)
{
if (TCPClient.Available > 0 || streamReader.Peek() >= 0)
{
string PacketString = streamReader.ReadLine();
Data packet = JsonConvert.DeserializeObject<Data>(PacketString);
PacketDelegate(packet);
}
}
public void Logout()
{
Data Packet = new Data();
Packet.cmdCommand = Command.Logout;
Packet.strMessage = "LOGOUT";
Packet.strName = "Game";
SendMessage(Packet);
if (streamReader != null && streamWriter != null)
{
streamReader.Dispose();
streamWriter.Dispose();
TCPClient.Close();
TCPClient = null;
streamReader = null;
streamWriter = null;
}
StreamTimer.Stop();
}
public void SendMessage(Data packet)
{
string packetMessage = JsonConvert.SerializeObject(packet);
try
{
streamWriter.WriteLine(packetMessage);
streamWriter.Flush();
} catch (Exception e)
{
}
}
public void RegisterActionDelegate(Action<Data> RegisterDelegate)
{
PacketDelegate += RegisterDelegate;
}
public void UnRegisterActionDelegate(Action<Data> UnregisterDelegate)
{
PacketDelegate -= UnregisterDelegate;
}
}
I'm not really sure what's going on, or if there are any more additional checks that I need to add into the system. Note: It's TCP so that "when" this fully works, I can drop the client into other programs that I might write that may not fully rely or use Unity.
new TcpClient("127.0.0.1", Port) is not appropriate for the client. Just use TcpClient(). There is no need to specify IP and port, both of which will end up being wrong.
TCPClient.Available is almost always a bug. You seem to assume that TCP is packet based. You can't test whether a full message is incoming or not. TCP only offers a boundaryless stream of bytes. Therefore, this Available check does not tell you if a whole line is available. Also, there could be multiple lines. The correct way to read is to have a reading loop always running and simply reading lines without checking. Any line that arrives will be processed that way. No need for timers etc.
The server has the same problems.
Issue (2) might have caused the appearance of lost packets somehow. You need to fix this in any case.
I need to wait for the user to input data to the serialport reader and then process the data. However, using this code blocks the UI which is not what I want. Any ideas on how to make sure that data is received or a timeout has occured before continuing?
The reason I use
do
{
Thread.Sleep(1);
} while (...)
is because without it the code return indata before the user has time to change it.
I call ReadFromSerial from the main function and process the data there. If anything goes wrong I want it to return an empty string.
public string ReadFromSerial()
{
try
{
System.IO.Ports.SerialPort Serial1 = new System.IO.Ports.SerialPort("COM1", 9600, System.IO.Ports.Parity.None, 8, System.IO.Ports.StopBits.One);
var MessageBufferRequest = new byte[13] { ... };
int BufferLength = 13;
if (!Serial1.IsOpen)
{
Serial1.Open();
}
Serial1.Write(MessageBufferRequest, 0, BufferLength); //Activates the serialport reader
indata = "";
Stopwatch timer = new Stopwatch();
timer.Start();
Serial1.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
do
{
Thread.Sleep(1);
} while (string.IsNullOrEmpty(indata) && timer.Elapsed.TotalSeconds < 10);
timer.Stop();
if (Serial1.IsOpen)
{
Serial1.Close();
}
return indata;
}
catch (Exception ex)
{
return "";
}
}
private static string indata;
private static void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
try
{
SerialPort sp = (SerialPort)sender;
if (sp.BytesToRead > 0)
{
indata = sp.ReadExisting();
}
}
catch(InvalidOperationException)
{
;
}
}
This is where multi-threading, tasks, async programming and/or event handlers comes in handy. All of them offer something to help you get around stuff like this, depending on the types of objects you're using.
A good starting point in this case would be to run the whole receive loop as a separate thread, then send the received data back to the main thread in some fashion.
Here's the source of a form that does basically what yours does, but either as a Thread or a Task:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// Button: starts Task version
private void button1_Click(object sender, EventArgs e)
{
StartReceiveTask();
}
// Button: starts Thread version
private void button2_Click(object sender, EventArgs e)
{
StartReceiveThread();
}
// Start the Receive loop as a Task
public void StartReceiveTask()
{
System.Threading.Tasks.Task.Run(() => receiveThreadFunc());
}
// Start the Receive loop as a Thread
public void StartReceiveThread()
{
var thd = new System.Threading.Thread(receiveThreadFunc);
thd.Start();
}
// Called when the Receive loop finishes
public void DataReceived(string data)
{
// do something with the data here
}
// The Receive loop, used by both Thread and Task forms.
public void receiveThreadFunc()
{
using (var serial1 = new System.IO.Ports.SerialPort("COM1", 9600, System.IO.Ports.Parity.None, 8, System.IO.Ports.StopBits.One))
{
// open serial port
if (!serial1.IsOpen)
serial1.Open();
// send init command
var initCommand = new byte[13];
serial1.Write(initCommand, 0, initCommand.Length);
// get start time
DateTime start = DateTime.Now;
// buffer for pushing received string data into
StringBuilder indata = new StringBuilder();
// loop until at most 10 seconds have passed
while ((DateTime.Now - start).TotalSeconds < 2)
{
if (serial1.BytesToRead > 0)
{
// allocate a buffer, up to 1K in length, to receive into
int blen = Math.Min(1024, serial1.BytesToRead);
byte[] buffer = new byte[blen];
// read chunks of data until none left
while (serial1.BytesToRead > 0)
{
int rc = serial1.Read(buffer, 0, blen);
// convert data from ASCII format to string and append to input buffer
indata.Append(Encoding.ASCII.GetString(buffer, 0, rc));
}
}
else
System.Threading.Thread.Sleep(25);
// check for EOL
if (indata.Length > 0 && indata.ToString().EndsWith("\r\n"))
break;
}
if (indata.Length > 0)
{
// post data to main thread, via Invoke if necessary:
string data = indata.ToString();
if (this.InvokeRequired)
this.Invoke(new Action(() => { DataReceived(data); }));
else
this.DataReceived(data);
}
}
}
}
I went with solution not to touch what I had already written. Instead I added these methods in my main function.
private void StartReceiveThread()
{
var thd = new System.Threading.Thread(receiveThreadFunc);
thd.Start();
}
private void receiveThreadFunc()
{
string str = Read.ReadFromSerial();
DataReceived(str);
}
private void DataReceived(string data)
{
//Process the data received
}
Ok, so I'm attempting to create a simple Chat application over TCP/IP for a group of friends of mine who play DnD online. Eventually I want to add more features, but for now I just want the chat to work!!
Here is the code I have for the Main Server
class MainServer
{
IPAddress m_address = IPAddress.Parse("127.0.0.1");
Int32 m_port = 5550;
public static Hashtable userNicknames = new Hashtable(50);
public static Hashtable connectionToNick = new Hashtable(50);
public MainServer()
{
TcpListener listener = new TcpListener(m_address, m_port);
Thread listenThread = new Thread(new ParameterizedThreadStart(StartListening));
listenThread.Start(listener);
Console.WriteLine("Listening for incoming connection requests...");
}
private void StartListening(Object listener)
{
TcpListener server = (TcpListener)listener;
ClientCommCenter commC;
server.Start();
while (true)
{
if (server.Pending())
{
TcpClient client = server.AcceptTcpClient();
Console.WriteLine("Client has connected...");
commC = new ClientCommCenter(client);
}
}
}
public static void SendSystemMessage(string msg)
{
StreamWriter writer;
TcpClient[] connectedClients = new TcpClient[MainServer.userNicknames.Count];
MainServer.userNicknames.Values.CopyTo(connectedClients, 0);
for (int ii = 0; ii < connectedClients.Length; ii++)
{
try
{
if (msg.Trim().Equals(String.Empty))
continue;
writer = new StreamWriter(connectedClients[ii].GetStream());
writer.WriteLine("Message from server: " + msg);
writer.Flush();
writer = null;
}
catch (Exception e)
{
MainServer.userNicknames.Remove(MainServer.connectionToNick[connectedClients[ii]]);
MainServer.connectionToNick.Remove(connectedClients[ii]);
}
}
}
public static void SendMessageToAll(string nickname, string msg)
{
StreamWriter writer;
TcpClient[] connectedClients = new TcpClient[MainServer.userNicknames.Count];
MainServer.userNicknames.Values.CopyTo(connectedClients, 0);
for (int ii = 0; ii < connectedClients.Length; ii++)
{
try
{
if (msg.Trim().Equals(String.Empty))
continue;
writer = new StreamWriter(connectedClients[ii].GetStream());
writer.WriteLine(nickname + ": " + msg);
writer.Flush();
writer = null;
}
catch (Exception e)
{
String user = (string)MainServer.connectionToNick[connectedClients[ii]];
SendSystemMessage("ATTENTION: " + user + " has disconnected from chat");
MainServer.userNicknames.Remove(user);
MainServer.connectionToNick.Remove(connectedClients[ii]);
}
}
}
}
Here is the main communication class, used separately by each client
class ClientCommCenter
{
TcpClient m_client;
StreamReader m_reader;
StreamWriter m_writer;
String m_nickname;
public ClientCommCenter(TcpClient client)
{
m_client = client;
Thread chatThread = new Thread(new ThreadStart(StartChat));
chatThread.Start();
}
private String GetNick()
{
m_writer.WriteLine("Enter a nickname to begin.");
m_writer.Flush();
return m_reader.ReadLine();
}
private void StartChat()
{
m_reader = new StreamReader(m_client.GetStream());
m_writer = new StreamWriter(m_client.GetStream());
m_writer.WriteLine("Connected to DnD Chat!!");
m_nickname = GetNick();
while (MainServer.userNicknames.Contains(m_nickname))
{
m_writer.WriteLine("ERROR!!! Username already in use");
m_nickname = GetNick();
}
MainServer.userNicknames.Add(m_nickname, m_client);
MainServer.connectionToNick.Add(m_client, m_nickname);
MainServer.SendSystemMessage("****** " + m_nickname + " ****** has joined the chat!");
m_writer.WriteLine("Now connected....");
m_writer.Flush();
Thread startChatting = new Thread(new ThreadStart(runChat));
startChatting.Start();
}
private void runChat()
{
try
{
String clientMessage = String.Empty;
while(true){
clientMessage = m_reader.ReadLine();
MainServer.SendMessageToAll(m_nickname, clientMessage);
}
}
catch(Exception e)
{
Console.WriteLine(e);
}
}
}
And finally, here is the code for the Client class:
public partial class MainForm : Form
{
[DllImport("kernel32.dll")]
private static extern void ExitProcess(int a);
TcpClient client;
StreamReader m_reader;
StreamWriter m_writer;
public MainForm()
{
InitializeComponent();
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = false;
Application.Exit();
if (m_reader != null)
{
m_reader.Dispose();
}
ExitProcess(0);
}
private void MainForm_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
SendChat();
}
}
private void SendChat()
{
TextBox txtChat = (TextBox)chatEntry;
if (chatEntry.Lines.Length >= 1)
{
m_writer.WriteLine(txtChat.Text);
m_writer.Flush();
chatEntry.Text = String.Empty;
chatEntry.Lines = null;
}
}
private void RunChat()
{
StreamReader reader = new StreamReader(client.GetStream());
while (true)
{
Application.DoEvents();
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker( delegate{
RunChat();
}));
}
if (reader.Peek() > 0)
{
chatDisplay.AppendText(reader.ReadLine() + "\r\n");
chatDisplay.SelectionStart = chatDisplay.Text.Length;
}
}
}
private void toolstripConnectButton_Click(object sender, EventArgs e)
{
client = new TcpClient("127.0.0.1", 5550);
m_writer = new StreamWriter(client.GetStream());
m_reader = new StreamReader(client.GetStream());
Thread chatThread = new Thread(new ThreadStart(RunChat));
chatThread.Start();
while (true)
{
Application.DoEvents();
}
}
private void sendButton_Click(object sender, EventArgs e)
{
SendChat();
}
}
The problem that I am having with the above code is this: I can connect to the running server perfectly fine, and I am correctly prompted by the server that I have connected, and it then prompts me for a nickname.
I type the nickname into the text box and press send. After this occurs however, I stop receiving messages from the server all together. Literally nothing. I can even spam the connect button and it constantly shows up with the same two messages:
"Connected"
"Enter a nickname"
I have been trying to figure this out for close to 5 hours now, and I simply have no idea what is going on. I have a feeling it is something incredibly simple, as the solution is ALWAYS simple.
So, generous people of SO, can you figure out my problem? Why does my streamreader and streamwriter suddenly stop working?!?!?!
Two things:
First, skip the if (reader.Peek() > 0). Just call reader.ReadLine(); this will block until you have a line available. I am not sure why, but even after sending the message, Peek is returning -1, but ReadLine returns a line at that point, fixing the problem. Anyway, spinning around on Application.DoEvents() is not helping matters.
(Similarly, you can skip if (server.Pending())).
Second, your use of Invoke is faulty; you should not be "Invoking" RunChat() because that is the method that repeatedly polls the stream for new data. This means you will run the entire method on the UI thread, which is precisely what you want to avoid. The UI is busy pumping the Windows message queue. You should "Invoke" only the code that modifies the control's properties.
(I suspect that is why you found it necessary to use Application.DoEvents() anyway. You shouldn't need it if you are handling your threading correctly.)
(Also, the first thing you should do is to check InvokeRequired. As your method is now, you're creating a StreamReader that you can never use. There are other places where you do that, but that's off topic.)
Here are two suggestions:
private void RunChat()
{
StreamReader reader = new StreamReader(client.GetStream());
Delegate invoker = new Action<string>(AppendChatText);
while (true)
Invoke(invoker, reader.ReadLine());
}
or, to use the more classic "invoke" pattern:
private void RunChat()
{
StreamReader reader = new StreamReader(client.GetStream());
while (true)
AppendChatText(reader.ReadLine());
}
private void AppendChatText(string text)
{
if (this.InvokeRequired)
{
this.Invoke((Action<string>)AppendChatText, text);
return;
}
chatDisplay.AppendText(text + "\r\n");
chatDisplay.SelectionStart = chatDisplay.Text.Length;
}
The first has the advantage of creating only one Delegate object; the second creates a new one each time.
Finally, this is a very C# 1.2 approach to the problem. A more up-to-date approach would use async/await to avoid creating all those threads (not to mention System.Collections.Generic.Dictionary<,> instead of HashTable).
This is one of my first issues. Whenever I exit out the program, tcpClient.Connect() takes forever to close. I've tried a ton of things, and none of them seem to work.
Take a look at the CreateConnection() thread, if the client isn't connected yet... and I close the program, it takes forever to close. If it IS connected, it closes immediately. I know this can be done with some kind of timeout trick, but i've tried a few and none of them worked.
Please provide a code example if you can.
Also, is there any good tutorial out there for C# on reading/writing the actual bytes with a buffer instead of this version that just does masterServer.writeLine() and masterServer.readline() or are they both just as efficient?
If you see anything else to help me improve this... by all means, go ahead. I'm trying to teach myself how to do this and I have no help, so don't let me go on doing something wrong if you see it!!! Thanks guys!
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;
namespace RemoteClient
{
public partial class Form1 : Form
{
private int MyPort = 56789;
private IPAddress myIp = IPAddress.Parse("210.232.115.79");
private IPAddress serverIp = IPAddress.Parse("72.216.18.77"); // Master Server's IP Address
public static TcpClient masterServer = new TcpClient();
private StreamWriter responseWriter;
private StreamReader commandReader;
private Thread connectionThread;
private Thread commandsThread;
private bool RequestExitConnectionThread { get; set; }
private delegate void AddMessageDelegate(string message, int category);
private delegate void ConnectedDelegate();
private bool isConnected { get; set; }
public Form1()
{
InitializeComponent();
isConnected = false;
}
private void LogMessage(string message, int category)
{
if (category == 1)
{
ListViewItem item = new ListViewItem(message);
item.BackColor = Color.LightGreen;
item.UseItemStyleForSubItems = true;
Log.Items.Add(item).SubItems.Add(DateTime.Now.ToString());
}
if (category == 2)
{
ListViewItem item = new ListViewItem(message);
item.BackColor = Color.Orange;
item.UseItemStyleForSubItems = true;
Log.Items.Add(item).SubItems.Add(DateTime.Now.ToString());
}
if (category == 3)
{
ListViewItem item = new ListViewItem(message);
item.BackColor = Color.Yellow;
item.UseItemStyleForSubItems = true;
Log.Items.Add(item).SubItems.Add(DateTime.Now.ToString());
}
if (category == 0)
{
Log.Items.Add(message).SubItems.Add(DateTime.Now.ToString());
}
}
private void Connected()
{
LogMessage("Found and Accepted Master Server's connection. Waiting for reply...",1);
Status.Text = "Connected!";
Status.ForeColor = Color.Green;
commandsThread = new Thread(new ThreadStart(RecieveCommands));
sendClientInfo();
}
private void exitButton_Click(object sender, EventArgs e)
{
Disconnect();
exitButton.Enabled = false;
exitButton.Text = "Closing...";
if (connectionThread != null)
{
while (connectionThread.IsAlive)
{
Application.DoEvents();
}
}
this.Close();
}
private void Form1_Load(object sender, EventArgs e)
{
Connect();
}
private void Disconnect()
{
RequestExitConnectionThread = true;
if (masterServer != null)
masterServer.Close();
if (connectionThread != null)
connectionThread.Abort();
LogMessage("Closing Client. Please wait while Program threads end.", 2);
}
private void Disconnected()
{
Status.Text = "Disconnected";
Status.ForeColor = Color.Red;
Connect();
}
private void Connect()
{
LogMessage("Attempting to connect to Master Server...", 1);
connectionThread = new Thread(new ThreadStart(CreateConnection));
connectionThread.Start();
}
private void CreateConnection()
{
int i = 1;
bool success = false;
while (!success)
{
try
{
using (masterServer = new TcpClient())
{
IAsyncResult result = masterServer.BeginConnect(serverIp, MyPort, null, null);
success = result.AsyncWaitHandle.WaitOne(1000, false);
}
if (success)
{
BeginInvoke(new ConnectedDelegate(this.Connected), new object[] {});
break;
}
else
{
Thread.Sleep(2000);
BeginInvoke(new AddMessageDelegate(LogMessage), new object[] { "Connection Retry # " + i.ToString() + ". Master Server hasn't been started yet.", 3 });
}
}
catch
{
MessageBox.Show("Error!");
}
i++;
}
}
private void RecieveCommands()
{
MessageBox.Show("Hello!");
commandReader = new StreamReader(masterServer.GetStream());
string CommandResponse = commandReader.ReadLine();
string Command = null;
if (CommandResponse != null)
MessageBox.Show("Recieved Command that was NOT null!");
if (CommandResponse != null)
{
MessageBox.Show("Recieved null response!");
BeginInvoke(new AddMessageDelegate(LogMessage), new object[] { "Disconnected From Master Server. Reason: Recieved Null response.", 1 });
Disconnected();
}
else if (CommandResponse.StartsWith("0"))
{
MessageBox.Show("Recieved 0 as a response!");
Command = CommandResponse.Substring(2).Trim();
isConnected = false;
BeginInvoke(new AddMessageDelegate(LogMessage), new object[] { "Disconnected From Master Server. Reason: " + Command, 1 });
}
else if (CommandResponse.StartsWith("1"))
{
MessageBox.Show("Recieved 1 as a response!");
isConnected = true;
BeginInvoke(new AddMessageDelegate(LogMessage), new object[] { "Connected to Master Server Successfully.", 1 });
}
}
//************************** RESPONSE'S BELOW HERE ************************* \\
private void sendClientInfo()
{
responseWriter = new StreamWriter(masterServer.GetStream());
responseWriter.WriteLine(myIp.ToString());
responseWriter.Flush();
}
}
}
Sorry, after testing it: NO, it does not use an async waithandle, it blocks the process :(
I prefer this solution, which also blocks the process but only by the period you specify, in this case 5 seconds:
using (TcpClient tcp = new TcpClient())
{
IAsyncResult ar = tcp.BeginConnect("127.0.0.1", 80, null, null);
System.Threading.WaitHandle wh = ar.AsyncWaitHandle;
try
{
if (!ar.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5), false))
{
tcp.Close();
throw new TimeoutException();
}
tcp.EndConnect(ar);
}
finally
{
wh.Close();
}
}
From: http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/2281199d-cd28-4b5c-95dc-5a888a6da30d
The following example uses both async connection and async timeout control:
var tcp = new TcpClient();
var ar = tcp.BeginConnect(Ip, Port, null, null);
Task.Factory.StartNew(() =>
{
var wh = ar.AsyncWaitHandle;
try
{
if (!ar.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5), false))
{
// The logic to control when the connection timed out
tcp.Close();
throw new TimeoutException();
}
else
{
// The logic to control when the connection succeed.
tcp.EndConnect(ar);
}
}
finally
{
wh.Close();
}
});
connect with timeout of 2000 ms:
AutoResetEvent connectDone = new AutoResetEvent( false );
TcpClient client = new TcpClient();
client.BeginConnect(
"127.0.0.1", 80,
new AsyncCallback(
delegate( IAsyncResult ar ) {
client.EndConnect( ar );
connectDone.Set();
}
), client
);
if( !connectDone.WaitOne( 2000 ) ) {
Console.WriteLine( "network connection failed!" );
Environment.Exit( 0 );
}
Stream stream = client.GetStream();
Adding a check within your connection process to cancel it if the program is exiting should help.
Try adding this in CreateConnection() inside your while(!success) loop but before your try block:
if(RequestExitConnectionThread)
{
break;
}
Here's an example of an asynchronous BeginConnect() call:
myTcpClient.BeginConnect("localhost", 80, OnConnect, null);
OnConnect function:
public static void OnConnect(IAsyncResult ar)
{
// do your work
}