3. 服务器端程序的代码编写。
对于服务器端,主要的作用是监听客户端的连接请求并确认其请求。程序一开始便打开一个StartListening()线程。
private void StartListening(){ listener = new TcpListener(listenport); listener.Start(); while (true) { try { Socket s = listener.AcceptSocket(); clientsocket = s; clientservice = new Thread(new ThreadStart(ServiceClient)); clientservice.Start(); } catch(Exception e) { Console.WriteLine(e.ToString() ); } }}
该线程是一直处于运行状态的。当服务器端接收到一个来自客户端的连接请求后,它就打开一个ServiceClient()线程来服务客户端。当一个连接被建立后,每个客户端就被赋予一个属于它自己的套接字。同时,一个Client类的对象被建立。该对象包含了客户端的一些相关信息,该信息被保存在一个数组列表中。
Client类如下:
using System;using System.Threading;namespace ChatServer{using System.Net.Sockets;using System.Net;////// Client 的摘要说明。///public class Client{private Thread clthread;private EndPoint endpoint;private string name;private Socket sock;public Client(string _name, EndPoint _endpoint, Thread _thread, Socket _sock){// TODO: 在此处添加构造函数逻辑clthread = _thread;endpoint = _endpoint;name = _name;sock = _sock;}public override string ToString(){return endpoint.ToString()+ " : " + name;}public Thread CLThread{get{return clthread;}set{clthread = value;}}public EndPoint Host{get{return endpoint;}set{endpoint = value;}}public string Name{get{return name;}set{name = value;}}public Socket Sock{get{return sock;}set{sock = value;}}}}
程序的主体部分应是ServiceClient()函数。该函数是一个独立的线程,其主要部分是一个while循环。在循环体内,程序处理各种客户端命令。服务器端接收来自客户端的以ASCII码给出的字符串,其中包含了一个|形式的分隔符。字符串中|以前的部分就是具体的命令,包括CONN、CHAT、PRIV、GONE四种类型。CONN命令建立一个新的客户端连接,将现有的用户列表发送给新用户并告知其他用户有一个新用户加入。CHAT命令将新的信息发送给所有用户。PRIV命令将悄悄话发送给某个用户。GONE命令从用户列表中除去一个已离开的用户并告知其他的用户某某已经离开了。同时,GONE命令可以设置布尔型的变量keepalive为false从而结束与客户端连接的线程。ServiceClient()函数如下:
private void ServiceClient(){Socket client = clientsocket;bool keepalive = true;while (keepalive){Byte[] buffer = new Byte[1024];client.Receive(buffer);string clientcommand = System.Text.Encoding.ASCII.GetString(buffer);string[] tokens = clientcommand.Split(new Char[]{'|'});Console.WriteLine(clientcommand);if (tokens[0] == "CONN"){for(int n=0; n{Client cl = (Client)clients[n];SendToClient(cl, "JOIN|" + tokens[1]);}EndPoint ep = client.RemoteEndPoint;Client c = new Client(tokens[1], ep, clientservice, client);clients.Add(c);string message = "LIST|" + GetChatterList() +"rn";SendToClient(c, message);lbClients.Items.Add(c);}if (tokens[0] == "CHAT"){for(int n=0; n{Client cl = (Client)clients[n];SendToClient(cl, clientcommand);}}if (tokens[0] == "PRIV"){string destclient = tokens[3];for(int n=0; n{Client cl = (Client)clients[n];if(cl.Name.CompareTo(tokens[3]) == 0)SendToClient(cl, clientcommand);if(cl.Name.CompareTo(tokens[1]) == 0)SendToClient(cl, clientcommand);}}if (tokens[0] == "GONE"){int remove = 0;bool found = false;int c = clients.Count;for(int n=0; n{Client cl = (Client)clients[n];SendToClient(cl, clientcommand);if(cl.Name.CompareTo(tokens[1]) == 0){remove = n;found = true;lbClients.Items.Remove(cl);}}if(found)clients.RemoveAt(remove);client.Close();keepalive = false;}}}
这样,服务器端程序就基本完成了。(其他略次要的代码可以参见源代码中的Form1.cs文件)程序运行图示如下:
客户端程序:
1. 打开VS.net,新建一个C#的模板为Windows 应用程序的项目,不妨命名为ChatClient。
2. 布置界面。往界面上添加一个ListBox控件(用于显示用户列表),一个RichTextBox控件(用于显示聊天消息以及系统消息),一个TextBox控件(用于发送消息),一个CheckBox控件(确定是否为悄悄话),一个StatusBar控件以及四个Button控件(分别为连接、断开连接、开始记录、发送)。各个控件的属性设置可以参见源代码中的具体设置,这里从略。界面设计好后的图象如下:
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)3. 客户端程序的代码编写。
当客户端试图和服务器端进行连接时,一个连接必须建立而且得向服务器端进行注册。 EstablishConnection()函数运用一个TcpClient来和服务器端取得连接,同时创建一个NetworkStream来发送消息。还有,端口号和服务器端的是保持一致的,均为5555。EstablishConnection()函数如下:
private void EstablishConnection(){statusBar1.Text = "正在连接到服务器";try{clientsocket = new TcpClient(serveraddress,serverport);ns = clientsocket.GetStream();sr = new StreamReader(ns);connected = true;}catch (Exception){MessageBox.Show("不能连接到服务器!","错误",MessageBoxButtons.OK, MessageBoxIcon.Exclamation);statusBar1.Text = "已断开连接";}}
在和服务器端连接成功后,程序就用RegisterWithServer()函数向服务器端发送一个CONN命令。该命令先是发送该用户的名称,然后从服务器端获得其他所有用户的列表,所有用户列表是在ListBox控件中显示的。该函数如下:
private void RegisterWithServer(){try{string command = "CONN|" + ChatOut.Text;Byte[] outbytes = System.Text.Encoding.ASCII.GetBytes(command.ToCharArray());ns.Write(outbytes,0,outbytes.Length);string serverresponse = sr.ReadLine();serverresponse.Trim();string[] tokens = serverresponse.Split(new Char[]{'|'});if(tokens[0] == "LIST"){statusBar1.Text = "已连接";btnDisconnect.Enabled = true;}for(int n=1; nlbChatters.Items.Add(tokens[n].Trim(new char[]{'r','n'}));this.Text = clientname + ":已连接到服务器";}catch (Exception){MessageBox.Show("注册时发生错误!","错误",MessageBoxButtons.OK, MessageBoxIcon.Exclamation);}}
在此之后,当然就是用户之间的聊天了,由ReceiveChat()函数来完成。该函数是一个独立的线程,它处理所有用户获得的消息和用户发送的消息。它主要处理了CHAT、PRIV、JOIN、GONE、QUIT等命令,处理的方法和服务器端的类似。具体函数实现如下:
private void ReceiveChat(){bool keepalive = true;while (keepalive){try{Byte[] buffer = new Byte[2048];ns.Read(buffer,0,buffer.Length);string chatter = System.Text.Encoding.ASCII.GetString(buffer);string[] tokens = chatter.Split(new Char[]{'|'});if (tokens[0] == "CHAT"){rtbChatIn.AppendText(tokens[1]);if(logging)logwriter.WriteLine(tokens[1]);}if (tokens[0] == "PRIV"){rtbChatIn.AppendText("Private from ");rtbChatIn.AppendText(tokens[1].Trim() );rtbChatIn.AppendText(tokens[2] + "rn");if(logging){logwriter.Write("Private from ");logwriter.Write(tokens[1].Trim() );logwriter.WriteLine(tokens[2] + "rn");}}if (tokens[0] == "JOIN"){rtbChatIn.AppendText(tokens[1].Trim() );rtbChatIn.AppendText(" has joined the Chatrn");if(logging){logwriter.WriteLine(tokens[1]+" has joined the Chat");}string newguy = tokens[1].Trim(new char[]{'r','n'});lbChatters.Items.Add(newguy);}if (tokens[0] == "GONE"){rtbChatIn.AppendText(tokens[1].Trim() );rtbChatIn.AppendText(" has left the Chatrn");if(logging){logwriter.WriteLine(tokens[1]+" has left the Chat");}lbChatters.Items.Remove(tokens[1].Trim(new char[]{'r','n'}));}if (tokens[0] == "QUIT"){ns.Close();clientsocket.Close();keepalive = false;statusBar1.Text = "服务器端已停止";connected= false;btnSend.Enabled = false;btnDisconnect.Enabled = false;}}catch(Exception){}}}