Basic Client/Server Chat Application In C#


SUBMITTED BY: menamagice

DATE: Aug. 9, 2017, 1:54 a.m.

FORMAT: ANTLR With C# Target

SIZE: 19.7 kB

HITS: 207

  1. Welcome to the Basic Client/Server Chat Application in C#. In this tutorial I will provide the basics for a simple chat application in C# utilizing TCPClient, StreamReader, and the StreamWriter Classes in the .Net Framework.
  2. In this application you have 3 components, the server (a class file), the communication component (a class file) and the client application. We will look at all 3 of these components individually, and how the can combine to create your basic chat application. The first component, the chat server, is where the messages are sent back and forth between the client and the server. Before writing any methods you need to add the following references to your class.
  3. 1
  4. using System.IO;
  5. 2
  6. using System.Net;
  7. 3
  8. using System;
  9. 4
  10. using System.Threading;
  11. 5
  12. using Chat = System.Net;
  13. 6
  14. using System.Collections;
  15. I know some of you are going to look at the 5th reference and ask questions regarding Chat = System.Net. When adding references in C# you are allowed to add aliases to your references, thus allowing you to have multiple uses of the same Namespace at the same time, acting as 2 different objects.
  16. NOTE: To use Aliases for the Namespace reference it has to be in conjunction with the Using Statement.
  17. The first thing we do in our Server class is create 3 global variables, 2 are Hashtable variables, and the third is a TCPListener variable, which is used to listen for connections from TCP Clients.
  18. 1
  19. System.Net.Sockets.TcpListener chatServer;
  20. 2
  21. public static Hashtable nickName;
  22. 3
  23. public static Hashtable nickNameByConnect;
  24. These three variables will be used throughout our ChatServer.cs class file. Next, is the Public ChatServer() method, this is where we start the chat server and connect. We will then use our TCPListener object to check if there are any pending connection requests. If there are pending requests we then create a new connection, let the user know they're connected, then create our DoCommunication Object.
  25. We'll get to the DoCommunication object later in this tutorial. Here is the code for this method
  26. 01
  27. public ChatServer()
  28. 02
  29. {
  30. 03
  31. //create our nickname and nickname by connection variables
  32. 04
  33. nickName = new Hashtable(100);
  34. 05
  35. nickNameByConnect = new Hashtable(100);
  36. 06
  37. //create our TCPListener object
  38. 07
  39. chatServer = new System.Net.Sockets.TcpListener(4296);
  40. 08
  41. //check to see if the server is running
  42. 09
  43. //while (true) do the commands
  44. 10
  45. while (true)
  46. 11
  47. {
  48. 12
  49. //start the chat server
  50. 13
  51. chatServer.Start();
  52. 14
  53. //check if there are any pending connection requests
  54. 15
  55. if (chatServer.Pending())
  56. 16
  57. {
  58. 17
  59. //if there are pending requests create a new connection
  60. 18
  61. Chat.Sockets.TcpClient chatConnection = chatServer.AcceptTcpClient();
  62. 19
  63. //display a message letting the user know they're connected
  64. 20
  65. Console.WriteLine("You are now connected");
  66. 21
  67. //create a new DoCommunicate Object
  68. 22
  69. DoCommunicate comm = new DoCommunicate(chatConnection);
  70. 23
  71. }
  72. 24
  73. }
  74. 25
  75. }
  76. Next, since this is a basic chat application, we need a method for sending our messages to all that are connected. Here we create a StreamWriter object, used to write our messages to the chat window, a TcpClient Array, to hold all the TcpClients for all connected users, then we copy the users nickname to the chat server window. After that we create a loop, looping through all the TcpClients, we check if the message eing sent is empty or that index of our TcpClient array is empty. From there we send our message to the chat window, and flush to make sure the buffer is empty.
  77. In your Catch, of our Try...Catch block, is where we handle the Exception that is caused when a user leaves or disconnects. We display a message letting the users know that that person has disconnected, we remove that nickname from the list, then dispose of that users TcpClient instance. Here is the code for this method
  78. 01
  79. public static void SendMsgToAll(string nick, string msg)
  80. 02
  81. {
  82. 03
  83. //create a StreamWriter Object
  84. 04
  85. StreamWriter writer;
  86. 05
  87. ArrayList ToRemove = new ArrayList(0);
  88. 06
  89. //create a new TCPClient Array
  90. 07
  91. Chat.Sockets.TcpClient[] tcpClient = new Chat.Sockets.TcpClient[ChatServer.nickName.Count];
  92. 08
  93. //copy the users nickname to the CHatServer values
  94. 09
  95. ChatServer.nickName.Values.CopyTo(tcpClient, 0);
  96. 10
  97. //loop through and write any messages to the window
  98. 11
  99. for (int cnt = 0; cnt < tcpClient.Length; cnt++)
  100. 12
  101. {
  102. 13
  103. try
  104. 14
  105. {
  106. 15
  107. //check if the message is empty, of the particular
  108. 16
  109. //index of out array is null, if it is then continue
  110. 17
  111. if (msg.Trim() == "" || tcpClient[cnt] == null)
  112. 18
  113. continue;
  114. 19
  115. //Use the GetStream method to get the current memory
  116. 20
  117. //stream for this index of our TCPClient array
  118. 21
  119. writer = new StreamWriter(tcpClient[cnt].GetStream());
  120. 22
  121. //white our message to the window
  122. 23
  123. writer.WriteLine(nick + ": " + msg);
  124. 24
  125. //make sure all bytes are written
  126. 25
  127. writer.Flush();
  128. 26
  129. //dispose of the writer object until needed again
  130. 27
  131. writer = null;
  132. 28
  133. }
  134. 29
  135. //here we catch an exception that happens
  136. 30
  137. //when the user leaves the chatroow
  138. 31
  139. catch (Exception e44)
  140. 32
  141. {
  142. 33
  143. e44 = e44;
  144. 34
  145. string str = (string)ChatServer.nickNameByConnect[tcpClient[cnt]];
  146. 35
  147. //send the message that the user has left
  148. 36
  149. ChatServer.SendSysMsg("** " + str + " ** Has Left The Room.");
  150. 37
  151. //remove the nickname from the list
  152. 38
  153. ChatServer.nickName.Remove(str);
  154. 39
  155. //remove that index of the array, thus freeing it up
  156. 40
  157. //for another user
  158. 41
  159. ChatServer.nickNameByConnect.Remove(tcpClient[cnt]);
  160. 42
  161. }
  162. 43
  163. }
  164. 44
  165. }
  166. The next method we introduce is a way to send a system message, this method is almost identical to the SendMsgToAll method, except here we dont dispose of the TcpClient instance, since the message is being sent by the system, not a user.
  167. 01
  168. public static void SendSystemMessage(string msg)
  169. 02
  170. {
  171. 03
  172. //create our StreamWriter object
  173. 04
  174. StreamWriter writer;
  175. 05
  176. ArrayList ToRemove = new ArrayList(0);
  177. 06
  178. //create our TcpClient array
  179. 07
  180. Chat.Sockets.TcpClient[] tcpClient = new Chat.Sockets.TcpClient[ChatServer.nickName.Count];
  181. 08
  182. //copy the nickname value to the chat servers list
  183. 09
  184. ChatServer.nickName.Values.CopyTo(tcpClient, 0);
  185. 10
  186. //loop through and write any messages to the window
  187. 11
  188. for (int i = 0; i < tcpClient.Length; i++)
  189. 12
  190. {
  191. 13
  192. try
  193. 14
  194. {
  195. 15
  196. //check if the message is empty, of the particular
  197. 16
  198. //index of out array is null, if it is then continue
  199. 17
  200. if (msg.Trim() == "" || tcpClient[i] == null)
  201. 18
  202. continue;
  203. 19
  204. //Use the GetStream method to get the current memory
  205. 20
  206. //stream for this index of our TCPClient array
  207. 21
  208. writer = new StreamWriter(tcpClient[i].GetStream());
  209. 22
  210. //send our message
  211. 23
  212. writer.WriteLine(msg);
  213. 24
  214. //make sure the buffer is empty
  215. 25
  216. writer.Flush();
  217. 26
  218. //dispose of our writer
  219. 27
  220. writer = null;
  221. 28
  222. }
  223. 29
  224. catch (Exception e44)
  225. 30
  226. {
  227. 31
  228. e44 = e44;
  229. 32
  230. ChatServer.nickName.Remove(ChatServer.nickNameByConnect[tcpClient[i]]);
  231. 33
  232. ChatServer.nickNameByConnect.Remove(tcpClient[i]);
  233. 34
  234. }
  235. 35
  236. }
  237. 36
  238. }
  239. Believe it or not, thats the entirety of the ChatServer Class, simple isnt it. Working with Tcp objects can be fun, as you can do so much with them. In this simple application you could add the functionality to send files back and forth between users, and more. That may be the end of the ChatServer Class, but its not the end of creating our application.
  240. The next component to look at is the DoCommunicate Class. This is the component that does the work for our server. For a chat application to work efficiently, and work as people expect a chat application to work, it needs to be a multi-threaded application. Meaning each user is running in their own thread, which allows for the messages to be sent and received in real time. Multi threading gives the illusion that multiple activities are happening at the same time.
  241. The main purpose of multi threading is to improve performance. With each user in the chat application operating on their own thread, users don't have to wait for one user to be finished to send their message, they're able to send them simultaneously. C# has some powerful items in the System.Threading Namespace, which is used for, you guessed it, running multiple threads and synchronizing them.
  242. For our DoCommunicate.cs class file we need the following references
  243. 1
  244. using System.IO;
  245. 2
  246. using System.Net;
  247. 3
  248. using System;
  249. 4
  250. using System.Threading;
  251. 5
  252. using Chat = System.Net;
  253. 6
  254. using System.Collections;
  255. 7
  256. using PC;
  257. Once again we add an alias to an instance of the System.Net Namespace reference, this prevents namespace collisions in our class. Like the ChatServer class, the first thing we do in our class is create some global variables, 4 of them:
  258. A TCPClient object
  259. A StreamReader object
  260. A StreamWriter object
  261. And a string object
  262. In this method is where the new Thread is created and started, allowing this user to react in real time in the application.
  263. 1
  264. public DoCommunicate(System.Net.Sockets.TcpClient tcpClient)
  265. 2
  266. {
  267. 3
  268. //create our TcpClient
  269. 4
  270. client = tcpClient;
  271. 5
  272. //create a new thread
  273. 6
  274. Thread chatThread = new Thread(new ThreadStart(startChat));
  275. 7
  276. //start the new thread
  277. 8
  278. chatThread.Start();
  279. 9
  280. }
  281. Notice when we create our new Thread we pass it a method called startChat. We'll get to this method momentarily, but first we need to do a couple things that startChat relies on. Once the thread is created and
  282. started, we need to get the nickname the user wishes to use. For this we use the GetNick method we created. Here we simply ask the user what their nickname is, then return that value to the startChat method.
  283. 1
  284. private string GetNick()
  285. 2
  286. {
  287. 3
  288. //ask the user what nickname they want to use
  289. 4
  290. writer.WriteLine("What is your nickname? ");
  291. 5
  292. //ensure the buffer is empty
  293. 6
  294. writer.Flush();
  295. 7
  296. //return the value the user provided
  297. 8
  298. return reader.ReadLine();
  299. 9
  300. }
  301. Now lets look at the aforementioned startChat method. Here we create our StreamReader and StreamWriter objects and set the global string variable nickName to the value returned from the GetNick method. Next thing we do is check to ensure that the nickname provided by the user doesn't already exist, if it does we prompt them for a nickname until we find one thats not already in use.
  302. Once they provide a valid nickname we add their nickname to the server, preventing another user from using it, then we send a system message letting the other users know there is a new user. From there we create a new Thread, which calls the runChat method. Lets first look at the startChat method
  303. 01
  304. private void startChat()
  305. 02
  306. {
  307. 03
  308. //create our StreamReader object to read the current stream
  309. 04
  310. reader = new System.IO.StreamReader(client.GetStream());
  311. 05
  312. //create our StreamWriter objec to write to the current stream
  313. 06
  314. writer = new System.IO.StreamWriter(client.GetStream());
  315. 07
  316. writer.WriteLine("Welcome to PCChat!");
  317. 08
  318. //retrieve the users nickname they provided
  319. 09
  320. nickName = GetNick();
  321. 10
  322. //check is the nickname is already in session
  323. 11
  324. //prompt the user until they provide a nickname not in use
  325. 12
  326. while (PC.ChatServer.nickName.Contains(nickName))
  327. 13
  328. {
  329. 14
  330. //since the nickname is in use we display that message,
  331. 15
  332. //then prompt them again for a nickname
  333. 16
  334. writer.WriteLine("ERROR - Nickname already exists! Please try a new one");
  335. 17
  336. nickName = GetNick();
  337. 18
  338. }
  339. 19
  340. //add their nickname to the chat server
  341. 20
  342. PC.ChatServer.nickName.Add(nickName, client);
  343. 21
  344. PC.ChatServer.nickNameByConnect.Add(client, nickName);
  345. 22
  346. //send a system message letting the other user
  347. 23
  348. //know that a new user has joined the chat
  349. 24
  350. PC.ChatServer.SendSystemMessage("** " + nickName + " ** Has joined the room");
  351. 25
  352. writer.WriteLine("Now Talking.....\r\n-------------------------------");
  353. 26
  354. //ensure the buffer is empty
  355. 27
  356. writer.Flush();
  357. 28
  358. //create a new thread for this user
  359. 29
  360. Thread chatThread = new Thread(new ThreadStart(runChat));
  361. 30
  362. //start the thread
  363. 31
  364. chatThread.Start();
  365. 32
  366. }
  367. The last method in our DoCommunicate.cs Class is the runChat method called by the new thread in startChat. This is simply for reading the current stream and sending our messages to the chat window.
  368. 01
  369. private void runChat()
  370. 02
  371. //use a try...catch to catch any exceptions
  372. 03
  373. {
  374. 04
  375. try
  376. 05
  377. {
  378. 06
  379. //set out line variable to an empty string
  380. 07
  381. string line = "";
  382. 08
  383. while (true)
  384. 09
  385. {
  386. 10
  387. //read the curent line
  388. 11
  389. line = reader.ReadLine();
  390. 12
  391. //send our message
  392. 13
  393. PC.ChatServer.SendMsgToAll(nickName, line);
  394. 14
  395. }
  396. 15
  397. }
  398. 16
  399. catch (Exception e44)
  400. 17
  401. {
  402. 18
  403. Console.WriteLine(e44);
  404. 19
  405. }
  406. 20
  407. }
  408. That is the end of our DoCommunicate class. So far you have seen how to create a chat server, a class to handle the work of the chat application. You have learned about TcpClients, TcpListeners, StreamReaders, StreamWriters, and Threads. We discussed the purpose of a multi threaded application, and how to create one, and you have learned about adding an alias to your reference to prevent namespace collision in your application.
  409. Now that we have our chat server completely defined, we need a client application to chat with. In this application I have a single form, ChatClient, but I did this a little differently. I didn't add any controls via drag and drop, I added them at runtime, personally I wouldn't recommend this for new programmers.
  410. First thing i our client application is a Windows API call, the reference we need is the ExitProcess function. That looks like this
  411. 1
  412. [DllImport("kernel32.dll")]
  413. 2
  414. private static extern void ExitProcess(int a);
  415. In void Main is where I create a new form, add my controls, set the properties of the window, WindowState, Text, and my TcpClient and call the Connect method of the System.Net.Sockets.TcpClient Class.
  416. With the Connect method you provide the IP address, or host name, along with the port number to connect to, then it connects you to that information. Since this is a basic application, that information is hard coded into the application, with a real application you would have an area to give the user the option to specify which chat server they wish to connect to. Aside from the Main method we have three more methods:
  417. ChatClient_Closing: This handles what needs to be done once the user closes the application. This all happens as the form is closing.
  418. key_up: This is what sends our message to the chat window. Since I do it on the key up event, they will see what you're typing as you trype. For an actual application this functionality would be added to a Send button, or when the user hits Enter.
  419. Run: This is the running of the chat application, reading the current stream and appending it to the current contents of the chat window, and placing the cursor at the end of the text already in the textbox you're typing your message into
  420. How I'm appending the text to the current contents of the chat window is by using the AppendText Method of the TextBox Class.
  421. First lets look at the code for the Closing Event of the form.
  422. 1
  423. private static void ChatClient_Closing(object s, CancelEventArgs e)
  424. 2
  425. {
  426. 3
  427. e.Cancel = false;
  428. 4
  429. //exit the application
  430. 5
  431. Application.Exit();
  432. 6
  433. //call the ExitProcess API
  434. 7
  435. ExitProcess(0);
  436. 8
  437. }
  438. When the form closes, it calls the Application.Exit Method, then the call to the ExitProcess Function.
  439. Next we have the code for the Control.KeyUp Event, which is what sends our messages to the chat window. In this method, we create a StreamWriter for writing to the current stream. To do this we call the GetStream Method of the System.Net.Sockets.TcpClient class. GetStream retrieves the current NetworkStream, used for sending and receiving messages across a network.
  440. 01
  441. private static void key_up(object s, KeyEventArgs e)
  442. 02
  443. {
  444. 03
  445. //create our textbox value variable
  446. 04
  447. TextBox txtChat = (TextBox)s;
  448. 05
  449. //check to make sure the length of the text
  450. 06
  451. //in the TextBox is greater than 1 (meaning it has text in it)
  452. 07
  453. if (txtChat.Lines.Length > 1)
  454. 08
  455. {
  456. 09
  457. //create a StreamWriter based on the current NetworkStream
  458. 10
  459. StreamWriter writer = new StreamWriter(tcpClient.GetStream());
  460. 11
  461. //write our message
  462. 12
  463. writer.WriteLine(txtChat.Text);
  464. 13
  465. //ensure the buffer is empty
  466. 14
  467. writer.Flush();
  468. 15
  469. //clear the textbox for our next message
  470. 16
  471. txtChat.Text = "";
  472. 17
  473. txtChat.Lines = null;
  474. 18
  475. }
  476. 19
  477. }
  478. Next we have the code for our run method. This creates a StreamReader Object, using GetStream to retrieve the current NetworkStream, this will be used for reading the messages in the stream. We then append the value in the current stream, line by line, to the chat window.
  479. view sourceprint?
  480. 01
  481. private static void run()
  482. 02
  483. {
  484. 03
  485. //create our StreamReader Object, based on the current NetworkStream
  486. 04
  487. StreamReader reader = new StreamReader(tcpClient.GetStream());
  488. 05
  489. while (true)
  490. 06
  491. {
  492. 07
  493. //call DoEvents so other processes can process
  494. 08
  495. //simultaneously
  496. 09
  497. Application.DoEvents();
  498. 10
  499. //create a TextBox reference
  500. 11
  501. TextBox txtChat = (TextBox)client.Controls[0];
  502. 12
  503. //append the current value in the
  504. 13
  505. //current NetworkStream to the chat window
  506. 14
  507. txtChat.AppendText(reader.ReadLine() + "\r\n");
  508. 15
  509. //place the cursor at the end of the
  510. 16
  511. //text in the textbox for typing our messages
  512. 17
  513. txtChat.Selectionstart = txtChat.Text.Length;
  514. 18
  515. }
  516. 19
  517. }
  518. That is the end of the tutorial Basic Client/Server Chat Application in C#. I am enclosing all three files with this tutorial. They are under the Public GNU License which means you can modify the code to suit your needs, but you need to provide a reference to the original creator of the code. Also, you are not allowed to remove the license header at the beginning of all the files in this solution.
  519. I hope you enjoyed this tutorial, and found it useful. I will next write a tutorial for an advanced client/server chat application, to show what can be done with the techniques we learned in this tutorial.
  520. Thank you so much for reading :)
  521. NOTE: You're going to want to take the ChatServer Class and possibly make an application out of that as well. I have it as a class file as I'm using a different implementation of the server.

comments powered by Disqus