Can anyone help me with Windows service in C#?
I used topshelf to create windows service. I have issue with run two methods together. When I run app via in console, it works. But when I try to run in windows service, it's don't work together like I need.
First I start communication with OPC DA server and second I want start TCP Listerner
public void Start()
{
try
{
T1 = new Thread(new ThreadStart(DoOpc));
T1.Start();
T2 = new Thread(new ThreadStart(TcpConn));
T2.Start();
}
catch (Exception e)
{
Program.LogEntry("Error", e.ToString(), "Fatal");
}
}
Methods:
public static void TcpConn()
{
try
{
Program.LogEntry("TCP Comm", "Theard start...");
Int32 port = Int32.Parse(ConfigurationManager.AppSettings["TCP_port"]);
IPAddress localAddr = IPAddress.Parse(ConfigurationManager.AppSettings["TCP_IP"]);
TcpServer = new TcpListener(localAddr, port);
TcpServer.Start();
const int bytesize = 2048 * 2048; // Constant, not going to change later
Byte[] bytes = new Byte[bytesize];
String data = null;
while (true)
{
TcpClient client = TcpServer.AcceptTcpClient();
IPAddress TcpIPClient = ((IPEndPoint)(client.Client.RemoteEndPoint)).Address;
NetworkStream stream = client.GetStream();
data = null;
Program.LogEntry("TCP Comm", "Connected! IP: " + TcpIPClient);
int i;
if (StopTcp == true)
{
Program.LogEntry("Service status", "Stoping TCP server...");
client.Close();
client.GetStream().Close();
TcpServer.Stop();
break;
}
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
{
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
Program.LogEntry("TCP Comm", "Received: " + data);
string output = ProccessData(data);
byte[] msg = System.Text.Encoding.ASCII.GetBytes(output);
Program.LogEntry("TCP Comm", "Output: " + output);
stream.Write(msg, 0, msg.Length);
}
stream.Close();
client.Close();
}
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e.ToString());
Program.LogEntry("TCP Comm", "SocketException: " + e.ToString(), "Fatal");
}
finally
{
Console.WriteLine("TCP: bye bye");
Program.LogEntry("TCP Comm", "Bye Bye", "Warning");
TcpServer.Stop();
System.Environment.Exit(6);
}
}
OPC
private static void DoOpc()
{
try
{
URL url = new URL(ConfigurationManager.AppSettings["OPC_Server"]);
OpcServer = new Opc.Da.Server(new OpcCom.Factory(), url);
OpcServer.Connect();
if (OpcServer.IsConnected == true)
{
Program.LogEntry("OPC Client", "Connected !");
}
else
{
Program.LogEntry("OPC Client", "NOT Connected!", "Fatal");
}
OpcServer.ServerShutdown += new Opc.ServerShutdownEventHandler(OnServerShutdown);
// Evidencia groupy/channelu ------Create group
Opc.Da.SubscriptionState state = new Opc.Da.SubscriptionState();
state.ClientHandle = 0;
//state.Name = "Device1";
state.UpdateRate = 500;
state.Active = true; // true;
Opc.Da.Subscription group = (Opc.Da.Subscription)OpcServer.CreateSubscription(state);
// Callback on Data change
group.DataChanged += new Opc.Da.DataChangedEventHandler(OnDataChange);
Opc.Da.Item NewItem;
List<Opc.Da.Item> NewItems = new List<Opc.Da.Item>();
foreach (var item in ActiveItems.Where(x => x.Active == true))
{
NewItem = new Opc.Da.Item();
NewItem.ClientHandle = 0;
NewItem.ItemName = item.Device + "." + item.Tag; //int4 = 32 bit
NewItems.Add(NewItem);
}
Item[] Items = NewItems.ToArray();
ItemResult[] ir = group.AddItems(Items);
SubscriptionState activatestate = group.GetState();
activatestate.Active = true;
group.ModifyState((int)Opc.Da.StateMask.Active, activatestate);
TcpConn();
}
catch (Exception e)
{
Program.LogEntry("Error with OPC connection", e.ToString(), "Fatal");
}
}
I have a socket thread that blocks on read. I set the receivetimeout on the socket to 30 seconds; if no byte is read, the read goes into IO exception(inner exception: error code: 10060). I end the process and close the streams and set the thread to null. For some reason, I can't seem to close the socket connection. I am not sure where the error is. Below is my code:
public class SampleClientSocket
{
private Socket cltSocket = null;
private volatile bool readyToRecv = false;
NetworkStream ns;
BinaryReader br;
BinaryWriter wr;
private string _caller = "SampleSocketClient";
Thread workerThread;
static void Main(string[] args)
{
BeginWork();
}
public void BeginWork()
{
try
{
IPEndPoint ipe = new IPEndPoint("127.0.0.1", 5000);
cltSocket = new Socket(ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
cltSocket.Connect(ipe);
cltSocket.ReceiveTimeout = 30000;
cltSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
workerThread = new Thread(doWork_01);
workerThread.Start();
readyToRecv = true;
}
catch (Exception f)
{
Console.WriteLine(this._caller + f.StackTrace);
}
}
private void doWork_01()
{
try
{
this.ns = new NetworkStream(this.cltSocket);
this.br = new BinaryReader(this.ns);
this.wr = new BinaryWriter(this.ns);
readyToRecv = true;
this._lastSocketRead = toUnixTime(DateTime.Now);
}
catch
{
Console.WriteLine(this._caller + "ERROR OPENING STREAMS.");
readyToRecv = false;
}
while (this.readyToRecv)
{
bool _etxEncountered = false;
MemoryStream currentMessage = new MemoryStream();
byte buf = (byte)0;
while (!_etxEncountered)
{
try
{
buf = this.br.ReadByte();
}
catch (EndOfStreamException)
{
Console.WriteLine(this._caller + "ERROR: END OF STREAM");
readyToRecv = false;
_etxEncountered = true;
break;
}
catch (ObjectDisposedException)
{
Console.WriteLine(this._caller + "ERROR: Stream is closed!");
readyToRecv = false;
_etxEncountered = true;
break;
}
catch (IOException i)
{
Console.WriteLine(this._caller + "ERRORSTACK: IOException!" + i.StackTrace);
//if the ReeceiveTimeout is reached an IOException will be raised...
/// with an InnerException of type SocketException an ErrorCode 10060
var socketExpert = i.InnerException as SocketException;
if (socketExpert.ErrorCode != 10060)
{
Console.WriteLine(this._caller + "ERROR: IOException!" + socketExpert.ErrorCode);
readyToRecv = false;
_etxEncountered = true;
}
else
{
Console.WriteLine(this._caller + "Read: IOException for timeout! NOT DISCONNECTING");
readyToRecv = false;
_etxEncountered = true;
break;
}
}
catch (Exception f)
{
Console.WriteLine(this._caller + "Unknown exception" + f.StackTrace);
var socketExpert = f.InnerException as SocketException;
Console.WriteLine(this._caller + "Error code number: " + socketExpert.ErrorCode);
readyToRecv = false;
_etxEncountered = true;
}
if (buf.Equals(0x03) == true)
{
_etxEncountered = true;
break;
}
else if (buf.Equals(0x02) == false)
{
try
{
currentMessage.WriteByte(buf);
}
catch (Exception)
{
}
}
}
try { Thread.Sleep(10); }
catch { }
} // end while
EndWork();
}
public void EndWork()
{
try
{
readyToRecv = false;
//close streams
this.br.BaseStream.Flush();
this.br.Close();
this.br.Dispose();
this.wr.Flush();
this.wr.Close();
this.wr.Dispose();
this.ns.Flush();
this.ns.Close();
this.ns.Dispose();
this.cltSocket.Shutdown(SocketShutdown.Both);
//this.cltSocket.Disconnect(true);
this.cltSocket.Close();
this.cltSocket.Dispose();
this.cltSocket = null;
Console.WriteLine(this._caller + "GOODBYE...");
try
{
this.workerThread.Join(1000);
if (this.workerThread.IsAlive)
{
this.workerThread.Abort();
}
}
catch { }
this.workerThread = null;
}
catch
{
this.readyToRecv = false;
this.workerThread = null;
}
return;
}
}
Any help is appreciated.
There seem to be numerous problems with this code.
I think the reason for the problems is that you are trying to join the worker thread on the worker thread. doWork_01 is run on a worker thread, and therefore EndWork will also run on the worker thread. this would be equivalent to calling Thread.CurrentThread.Join(), and will just deadlock. This deadlock will probably keep your process alive and might cause issues like not releasing the socket.
I see little reason for trying to use multiple threads here. The original thread just starts the worker and does nothing else. I would recommend removing all threading and just use local variables for the socket, and use using to ensure it is disposed.
To properly exit the process, use Environment.Exit(0);, Application.Exit or just return from Main.
I had a thread that wasn't hitting the Socket.Shutdown and socket.close. Thank you jdweng for pointing this to me.
I'm trying to listen multi Serial port by automatically creating a Serial Port object and assigning an event to it. The mapping function checks whether the received data is correct with TestMachine and returns true/false. However, the problem occurs when I try to put the code into a for or if loop, the first serial port receives the correct data (Mapping function return true) then the second port not received data, This only happens when one of 2 ports received the right result(Mapping function return true).
code below without If(true).
SerialPort _serialPortA = new SerialPort(portA.ToString());
_serialPortA.BaudRate = machineA.BaudRate;
_serialPortA.Parity = machineA.Parity;
_serialPortA.StopBits = machineA.StopBits;
_serialPortA.DataBits = machineA.DataBits;
_serialPortA.Handshake = machineA.Handshake;
_serialPortA.DataReceived += new SerialDataReceivedEventHandler((sender2, e2) => DataReceivedHandler(sender2, e2, machineA));
try
{
if (!_serialPortA.IsOpen)
{
_serialPortA.Open();
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
SerialPort _serialPortB = new SerialPort(portB.ToString());
_serialPortB.BaudRate = machineB.BaudRate;
_serialPortB.Parity = machineB.Parity;
_serialPortB.StopBits = machineB.StopBits;
_serialPortB.DataBits = machineB.DataBits;
_serialPortB.Handshake = machineB.Handshake;
_serialPortB.DataReceived += new SerialDataReceivedEventHandler((sender3, e3) => DataReceivedHandler(sender3, e3, machineB));
try
{
if (!_serialPortB.IsOpen)
{
_serialPortB.Open();
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Code below not working. The different is only put code into if(true){...} block.
if (true) {
SerialPort _serialPortA = new SerialPort(portA.ToString());
_serialPortA.BaudRate = machineA.BaudRate;
_serialPortA.Parity = machineA.Parity;
_serialPortA.StopBits = machineA.StopBits;
_serialPortA.DataBits = machineA.DataBits;
_serialPortA.Handshake = machineA.Handshake;
_serialPortA.DataReceived += new SerialDataReceivedEventHandler((sender2, e2) => DataReceivedHandler(sender2, e2, machineA));
try
{
if (!_serialPortA.IsOpen)
{
_serialPortA.Open();
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
if (true) {
SerialPort _serialPortB = new SerialPort(portB.ToString());
_serialPortB.BaudRate = machineB.BaudRate;
_serialPortB.Parity = machineB.Parity;
_serialPortB.StopBits = machineB.StopBits;
_serialPortB.DataBits = machineB.DataBits;
_serialPortB.Handshake = machineB.Handshake;
_serialPort10.DataReceived += new SerialDataReceivedEventHandler((sender3, e3) => DataReceivedHandler(sender3, e3, machineB));
try
{
if (!_serialPortB.IsOpen)
{
_serialPortB.Open();
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
this is DataReceivedHandler function
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e, AnalysisMachine m)
{
//Thread.Sleep(100);
string res = ((SerialPort)sender).ReadExisting();
res = res.Replace("\r", "");
try
{
if (m.Mapping(res, m.Name))
{
m.PostData();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
There's a typo in your second if(true):
_serialPortB.Handshake = machineB.Handshake;
_serialPort10.DataReceived += new SerialDataReceivedEventHandler((sender3, e3) => DataReceivedHandler(sender3, e3, machineB));
So you're not hooking the event to _serialPortB but to a different port instead.
I'm rather new to tcp/ip so I hope my code's not an embarrassment. I have a tcp listener/client with 2 problems.
No keep alive ping so I won't know when to close the connection
If the connection is dropped, it won't smoothly reconnect
I am unsure how to implement these functions into my current codebase and am asking for best practices/ideas. Previous answers I've found all seem to be using either old information or different classes.
The listener's only required to connect to one client so that's fine.
TCPListener:
public async Task Start() {
tcpListener = new TcpListener(_iPAddress, _listeningPort);
tcpListener.Start(1);
//Debug.Log("Listening on port " + _listeningPort);
try {
tcpClient = await tcpListener.AcceptTcpClientAsync();
clientConnected = true;
//Debug.Log("Got connection request from " + tcpClient.Client.RemoteEndPoint.ToString());
} catch (Exception exp) {
Debug.Log(exp.ToString());
}
}
public async void GetData(Action<Ball> callback) {
Ball ball;
try {
using (var networkStream = tcpClient.GetStream())
using (var reader = new StreamReader(networkStream))
using (var writer = new StreamWriter(networkStream)) {
writer.AutoFlush = true;
while (gameInPlay) {
string dataFromClient = await reader.ReadLineAsync();
ball = Newtonsoft.Json.JsonConvert.DeserializeObject<Ball>(dataFromClient);
ball = BallConfiguration.AttachBallNumber(ball);
if (registeredBalls.Any(regEpc => regEpc == ball.Epc)) {
callback(ball);
}
}
}
} catch (NullReferenceException e) {
Debug.Log(e);
} finally {
tcpListener.Stop();
clientConnected = false;
}
}
TCPClient:
static void Main(string[] args) {
try {
while (true) {
if (!connectedToServer) {
ConnectClient(IPAddress.Loopback, 11000);
}
Thread.Sleep(3000);
}
} catch (Exception e) {
Console.WriteLine("Exception : {0}", e.Message);
Console.ReadLine();
} finally {
if (tcpClient != null) {
tcpClient.Close();
}
rfidReader.Stop();
rfidReader.Disconnect();
}
}
private async static void ConnectClient(IPAddress ip, int port) {
if (tcpClient != null) {
try {
Console.WriteLine("Trying to connect to Server...");
await tcpClient.ConnectAsync(ip, port);
connectedToServer = true;
Console.WriteLine("Connected to Server");
} catch (SocketException) {
Console.WriteLine("Could not connect to server");
} catch (ObjectDisposedException e) {
Console.WriteLine(e);
}
}
}
can someone please help me out with this... I've been struggling all day.
So I'm trying to learn Async sockets which is something that's been giving me trouble.
The issue is basically the way I'm updating the ListBox with people who have joined the chat room's names:
Basically what I'm doing is having each client send "!!addlist [nickname]" when they join the server.
It's not ideal as it doesn't check for duplicates etc. but now I just want to know why it won't work.
Whenever somebody adds a name they haven't seen before, they will also send "!!addlist [nick]"
In this way, every time someone joins, the lists should be updated for everyone.
The issue seems to be that all the clients start communicating at the same time and it interferes with the buffer.
I've tried using a separate buffer for every client so that's not the issue.
I've tried using lock() but that doesn't seem to be working either.
Essentially what happens is the buffers seem to truncate; where there is data from two different people in the same buffer.
Please just tell me what I'm doing wrong with the buffers or on the client side:
Note that the async socket is using Send instead of BeginSend.
I've tried both methods and they run into the same issue... so it's probably client side?
public partial class Login : Form
{
private ChatWindow cw;
private Socket serverSocket;
private List<Socket> socketList;
private byte[] buffer;
private bool isHost;
private bool isClosing;
public void startListening()
{
try
{
this.isHost = true; //We're hosting this server
cw.callingForm = this; //Give ChatForm the login form (this) [that acts as the server]
cw.Show(); //Show ChatForm
cw.isHost = true; //Tell ChatForm it is the host (for display purposes)
this.Hide(); //And hide the login form
serverSocket.Bind(new IPEndPoint(IPAddress.Any, int.Parse(portBox.Text))); //Bind to our local address
serverSocket.Listen(1); //And start listening
serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null); //When someone connects, begin the async callback
cw.connectTo("127.0.0.1", int.Parse(portBox.Text), nicknameBox.Text); //And have ChatForm connect to the server
}
catch (Exception) { /*MessageBox.Show("Error:\n\n" + e.ToString());*/ } //Let us know if we ran into any errors
}
public void AcceptCallback(IAsyncResult AR)
{
try
{
Socket s = serverSocket.EndAccept(AR); //When someone connects, accept the new socket
socketList.Add(s); //Add it to our list of clients
s.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), s); //Begin the async receive method using our buffer
serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null); //And start accepting new connections
}
catch (Exception) {}
}
public void ReceiveCallback(IAsyncResult AR) //When a message from a client is received
{
try
{
if (isClosing)
return;
Socket s = (Socket)AR.AsyncState; //Get the socket from our IAsyncResult
int received = s.EndReceive(AR); //Read the number of bytes received (*need to add locking code here*)
byte[] dbuf = new byte[received]; //Create a temporary buffer to store just what was received so we don't have extra data
Array.Copy(buffer, dbuf, received); //Copy the received data from our buffer to our temporary buffer
foreach (Socket client in socketList) //For each client that is connected
{
try
{
if (client != (Socket)AR.AsyncState) //If this isn't the same client that just sent a message (*client handles displaying these*)
client.Send(dbuf); //Send the message to the client
}
catch (Exception) { }
} //Start receiving new data again
s.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), s);
}
catch (Exception) { /*cw.output("\n\nError:\n\n" + e.ToString());*/ }
}
public void SendCallback(IAsyncResult AR)
{
try
{
Socket s = (Socket)AR.AsyncState;
s.EndSend(AR);
}
catch (Exception) { /*cw.output("\n\nError:\n\n" + e.ToString());*/ }
}
Here is the client side:
public void getData()
{
try
{
byte[] buf = new byte[1024];
string message = "";
while(isConnected)
{
Array.Clear(buf, 0, buf.Length);
message = "";
clientSocket.Receive(buf, buf.Length, SocketFlags.None);
message = Encoding.ASCII.GetString(buf);
if (message.StartsWith("!!addlist"))
{
message = message.Replace("!!addlist", "");
string userNick = message.Trim();
if (!namesBox.Items.Contains(userNick))
{
addNick(userNick.Trim());
}
continue;
}
else if (message.StartsWith("!!removelist"))
{
message = message.Replace("!!removelist", "");
string userNick = message.Trim();
removeNick(userNick);
output("Someone left the room: " + userNick);
continue;
}
else if (!namesBox.Items.Contains(message.Substring(0, message.IndexOf(":"))))
{
addNick(message.Substring(0, message.IndexOf(":")).Trim()); //So they at least get added when they send a message
}
output(message);
}
}
catch (Exception)
{
output("\n\nConnection to the server lost.");
isConnected = false;
}
}
Here is my addNick function that seems to fix some things?
public void addNick(string n)
{
if (n.Contains(" ")) //No Spaces... such a headache
return;
if (n.Contains(":"))
return;
bool shouldAdd = true;
n = n.Trim();
for (int x = namesBox.Items.Count - 1; x >= 0; --x)
if (namesBox.Items[x].ToString().Contains(n))
shouldAdd = false;
if (shouldAdd)
{
namesBox.Items.Add(n);
output("Someone new joined the room: " + n);
sendRaw("!!addlist " + nickName);
}
}
I think the issue is that some of the packets are being skipped?
Maybe there's too much code in the client after Receive before it gets called again?
Should I create a separate thread for each message so that receive runs constantly? (Dumb)
Should I have my client use Async receives and sends as well?
I have a feeling that is the answer ^
With all of the checks I do, I managed to clean up the duplicate name issue... but i regularly receive messages with spaces and partial messages from other clients it seems.
Okay so, after messing with this for a long time, I have it relatively stable.
For starters, I added the following state object:
public class StateObject
{
public Socket workSocket = null;
public const int BufferSize = 1024;
public byte[] buffer = new byte[BufferSize];
public StringBuilder sb = new StringBuilder();
public bool newConnection = true;
}
This makes it easy to keep track of each connection and gives each connection its own buffer.
The second thing I did was look for a new line in each message.
I wasn't looking for this in the original code and I believe this was the root of most issues.
I also gave the responsibility of dealing with username management to the server; something that I should have done from the start obviously.
Here is the current server code:
This code is in no way perfect and I'm continuously finding new errors the more I try to break it. I'm going to keep messing with it for awhile but at the moment, it seems to work decently.
public partial class Login : Form
{
private ChatWindow cw;
private Socket serverSocket;
private List<Socket> socketList;
private byte[] buffer;
private bool isHost;
private bool isClosing;
private ListBox usernames;
public Login()
{
InitializeComponent();
}
private void Login_Load(object sender, EventArgs e)
{
ipLabel.Text = getLocalIP();
cw = new ChatWindow();
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socketList = new List<Socket>();
buffer = new byte[1024];
isClosing = false;
usernames = new ListBox();
}
public string getLocalIP()
{
return Dns.GetHostEntry(Dns.GetHostName()).AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToString();
}
private void joinButton_Click(object sender, EventArgs e)
{
try
{
int tryPort = 0;
this.isHost = false;
cw.callingForm = this;
if (ipBox.Text == "" || portBox.Text == "" || nicknameBox.Text == "" || !int.TryParse(portBox.Text.ToString(), out tryPort))
{
MessageBox.Show("You must enter an IP Address, Port, and Nickname to connect to a server.", "Missing Info");
return;
}
this.Hide();
cw.Show();
cw.connectTo(ipBox.Text, int.Parse(portBox.Text), nicknameBox.Text);
}
catch(Exception otheree) {
MessageBox.Show("Error:\n\n" + otheree.ToString(),"Error connecting...");
cw.Hide();
this.Show();
}
}
private void hostButton_Click(object sender, EventArgs e)
{
int tryPort = 0;
if (portBox.Text == "" || nicknameBox.Text == "" || !int.TryParse(portBox.Text.ToString(), out tryPort)) {
MessageBox.Show("You must enter a Port and Nickname to host a server.", "Missing Info");
return;
}
startListening();
}
public void startListening()
{
try
{
this.isHost = true; //We're hosting this server
cw.callingForm = this; //Give ChatForm the login form (this) [that acts as the server]
cw.Show(); //Show ChatForm
cw.isHost = true; //Tell ChatForm it is the host (for display purposes)
this.Hide(); //And hide the login form
serverSocket.Bind(new IPEndPoint(IPAddress.Any, int.Parse(portBox.Text))); //Bind to our local address
serverSocket.Listen(1); //And start listening
serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null); //When someone connects, begin the async callback
cw.connectTo("127.0.0.1", int.Parse(portBox.Text), nicknameBox.Text); //And have ChatForm connect to the server
}
catch (Exception) {}
}
public void AcceptCallback(IAsyncResult AR)
{
try
{
StateObject state = new StateObject();
state.workSocket = serverSocket.EndAccept(AR);
socketList.Add(state.workSocket);
state.workSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
}
catch (Exception) {}
}
public void ReceiveCallback(IAsyncResult AR)
{
try
{
if (isClosing)
return;
StateObject state = (StateObject)AR.AsyncState;
Socket s = state.workSocket;
String content = "";
int received = s.EndReceive(AR);
if(received > 0)
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, received));
content = state.sb.ToString();
if (content.IndexOf(Environment.NewLine) > -1) //If we've received the end of the message
{
if (content.StartsWith("!!addlist") && state.newConnection)
{
state.newConnection = false;
content = content.Replace("!!addlist", "");
string userNick = content.Trim();
if (isHost && userNick.StartsWith("!"))
userNick = userNick.Replace("!", "");
userNick = userNick.Trim();
if (userNick.StartsWith("!") || userNick == string.Empty || usernames.Items.IndexOf(userNick) > -1)
{
//Invalid Username :c get dropped
s.Send(Encoding.ASCII.GetBytes("Invalid Username/In Use - Sorry :("));
s.Shutdown(SocketShutdown.Both);
s.Disconnect(false);
s.Close();
socketList.Remove(s);
return;
}
usernames.Items.Add(userNick);
foreach (string name in usernames.Items)
{
if (name.IndexOf(userNick) < 0)
{
s.Send(Encoding.ASCII.GetBytes("!!addlist " + name + "\r\n"));
Thread.Sleep(10); //such a hack... ugh it annoys me that this works
}
}
foreach (Socket client in socketList)
{
try
{
if (client != s)
client.Send(Encoding.ASCII.GetBytes("!!addlist " + userNick + "\r\n"));
}
catch (Exception) { }
}
}
else if (content.StartsWith("!!removelist") && !state.newConnection)
{
content = content.Replace("!!removelist", "");
string userNick = content.Trim();
usernames.Items.Remove(userNick);
foreach (Socket client in socketList)
{
try
{
if (client != s)
client.Send(Encoding.ASCII.GetBytes("!!removelist " + userNick + "\r\n"));
}
catch (Exception) { }
}
}
else if (state.newConnection) //if they don't give their name and try to send data, just drop.
{
s.Shutdown(SocketShutdown.Both);
s.Disconnect(false);
s.Close();
socketList.Remove(s);
return;
}
else
{
foreach (Socket client in socketList)
{
try
{
if (client != s)
client.Send(System.Text.Encoding.ASCII.GetBytes(content));
}
catch (Exception) { }
}
}
}
Array.Clear(state.buffer, 0, StateObject.BufferSize);
state.sb.Clear();
s.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
}
catch (Exception) {
socketList.Remove(((StateObject)AR.AsyncState).workSocket);
}
}
public void SendCallback(IAsyncResult AR)
{
try
{
StateObject state = (StateObject)AR.AsyncState;
state.workSocket.EndSend(AR);
}
catch (Exception) {}
}
private void Login_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
this.isClosing = true;
if (this.isHost)
{
foreach (Socket c in socketList)
{
if (c.Connected)
{
c.Close();
}
}
serverSocket.Shutdown(SocketShutdown.Both);
serverSocket.Close();
serverSocket = null;
serverSocket.Dispose();
}
socketList.Clear();
}
catch (Exception) { }
finally
{
Application.Exit();
}
}
}
public class StateObject
{
public Socket workSocket = null;
public const int BufferSize = 1024;
public byte[] buffer = new byte[BufferSize];
public StringBuilder sb = new StringBuilder();
public bool newConnection = true;
}
The client code (work in progress):
public partial class ChatWindow : Form
{
private Socket clientSocket;
private Thread chatThread;
private string ipAddress;
private int port;
private bool isConnected;
private string nickName;
public bool isHost;
public Login callingForm;
private static object conLock = new object();
public ChatWindow()
{
InitializeComponent();
isConnected = false;
isHost = false;
}
public string getIP() {
return Dns.GetHostEntry(Dns.GetHostName()).AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToString();
}
public void displayError(string err)
{
output(Environment.NewLine + Environment.NewLine + err + Environment.NewLine);
}
public void op(string s)
{
try
{
lock (conLock)
{
chatBox.Text += s;
}
}
catch (Exception) { }
}
public void connectTo(string ip, int p, string n) {
try
{
this.Text = "Trying to connect to " + ip + ":" + p + "...";
this.ipAddress = ip;
this.port = p;
this.nickName = n;
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
if (!isHost)
{
op("Connecting to " + ipAddress + ":" + port + "...");
}
else
{
output("Listening on " + getIP() + ":" + port + "...");
}
clientSocket.Connect(ipAddress, port);
isConnected = true;
if (!isHost)
{
this.Text = "Connected to " + ipAddress + ":" + port + " - Nickname: " + nickName;
output("Connected!");
}
else
{
this.Text = "Hosting on " + getIP() + ":" + port + " - Nickname: " + nickName;
}
chatThread = new Thread(new ThreadStart(getData));
chatThread.Start();
nickName = nickName.Replace(" ", "");
nickName = nickName.Replace(":", "");
if(nickName.StartsWith("!"))
nickName = nickName.Replace("!", "");
namesBox.Items.Add(nickName);
sendRaw("!!addlist " + nickName);
}
catch (ThreadAbortException)
{
//do nothing; probably closing chat window
}
catch (Exception e)
{
if (!isConnected)
{
this.Hide();
callingForm.Show();
clearText();
MessageBox.Show("Error:\n\n" + e.ToString(), "Error connecting to remote host");
}
}
}
public void removeNick(string n)
{
if (namesBox.Items.Count <= 0)
return;
for (int x = namesBox.Items.Count - 1; x >= 0; --x)
if (namesBox.Items[x].ToString().Contains(n))
namesBox.Items.RemoveAt(x);
}
public void clearText()
{
try
{
lock (conLock)
{
chatBox.Text = "";
}
}
catch (Exception) { }
}
public void addNick(string n)
{
if (n.Contains(" ")) //No Spaces... such a headache
return;
if (n.Contains(":"))
return;
bool shouldAdd = true;
n = n.Trim();
for (int x = namesBox.Items.Count - 1; x >= 0; --x)
if (namesBox.Items[x].ToString().Contains(n))
shouldAdd = false;
if (shouldAdd)
{
namesBox.Items.Add(n);
output("Someone new joined the room: " + n);
//sendRaw("!!addlist " + nickName);
}
}
public void addNickNoMessage(string n)
{
if (n.Contains(" ")) //No Spaces... such a headache
return;
if (n.Contains(":"))
return;
bool shouldAdd = true;
n = n.Trim();
for (int x = namesBox.Items.Count - 1; x >= 0; --x)
if (namesBox.Items[x].ToString().Contains(n))
shouldAdd = false;
if (shouldAdd)
{
namesBox.Items.Add(n);
//sendRaw("!!addlist " + nickName);
}
}
public void getData()
{
try
{
byte[] buf = new byte[1024];
string message = "";
while(isConnected)
{
Array.Clear(buf, 0, buf.Length);
message = "";
int gotData = clientSocket.Receive(buf, buf.Length, SocketFlags.None);
if (gotData == 0)
throw new Exception("I swear, this was working before but isn't anymore...");
message = Encoding.ASCII.GetString(buf);
if (message.StartsWith("!!addlist"))
{
message = message.Replace("!!addlist", "");
string userNick = message.Trim();
if(!namesBox.Items.Contains(userNick))
{
addNick(userNick);
}
continue;
}
else if (message.StartsWith("!!removelist"))
{
message = message.Replace("!!removelist", "");
string userNick = message.Trim();
removeNick(userNick);
output("Someone left the room: " + userNick);
continue;
}
output(message);
}
}
catch (Exception)
{
isConnected = false;
output(Environment.NewLine + "Connection to the server lost.");
}
}
public void output(string s)
{
try
{
lock (conLock)
{
chatBox.Text += s + Environment.NewLine;
}
}
catch (Exception) { }
}
private void ChatWindow_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
if(isConnected)
sendRaw("!!removelist " + nickName);
isConnected = false;
clientSocket.Shutdown(SocketShutdown.Receive);
if (chatThread.IsAlive)
chatThread.Abort();
callingForm.Close();
}
catch (Exception) { }
}
private void sendButton_Click(object sender, EventArgs e)
{
if(isConnected)
send(sendBox.Text);
}
private void sendBox_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
if (isConnected)
{
if (sendBox.Text != "")
{
send(sendBox.Text);
sendBox.SelectAll();
e.SuppressKeyPress = true;
e.Handled = true;
}
}
}
}
private void send(string t) {
try
{
byte[] data = System.Text.Encoding.ASCII.GetBytes(nickName + ": " + t + "\r\n");
clientSocket.Send(data);
output(nickName + ": " + t);
}
catch (Exception e)
{
displayError(e.ToString());
}
}
private void sendRaw(string t)
{
try
{
byte[] data = System.Text.Encoding.ASCII.GetBytes(t + "\r\n");
clientSocket.Send(data);
}
catch (Exception e)
{
displayError(e.ToString());
}
}
private void chatBox_TextChanged(object sender, EventArgs e)
{
chatBox.SelectionStart = chatBox.Text.Length;
chatBox.ScrollToCaret();
}
private void sendBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
e.SuppressKeyPress = true;
}
}
To do:
Add invokes, more delegates, do some more QA and find out what breaks it.
Also, I believe there's still the possibility of packet loss due to the client addlist functions being in the read loop. I believe this is why the "crappy hack" using Thread.Sleep(10) in the server callback for name population is an issue.
I think it might be better to either pass the command off to another thread while continuing to read or have the client tell the server it's ready for another name.
Otherwise, there might be some data loss during name updates.
The other thing is that, as was said in the comments above, delegates should be used when updating the UI objects (chatbox and listbox). I wrote the code for these but ultimately removed it because there was no noticeable change and I wanted to keep it simple.
I do still use an object lock when outputting text to the chatbox, but there's no noticeable difference there.
The code should be added as not using delegates is potentially problematic, but I literally caught the chat box in an infinite loop of updates without issue.
I tried breaking it with telnet and was successful so I added a newConnection property to the StateObject to ensure that each client can only send "!!addlist" once.
There are, of course, other ways to abuse the server by creating a client that joins and leaves repeatedly, so ultimately I will probably end up passing the !!removelist handling to the server instead of leaving it up to the client.