話說這個學(xué)期我們有一門課叫“中間件”,老師叫我們做一個基于TCP的聊天程序,,主要結(jié)構(gòu)如圖
1.所有Client端需要與Server端連接(感覺這句話好白癡,,TCP肯定要連接了才能工作)
2.Client端的功能是可以群發(fā)和私聊(用過QQ都應(yīng)該知道什么是群發(fā)和私聊吧),但都必須經(jīng)過Server端中轉(zhuǎn),,也就是實現(xiàn)了類似通訊中間件的功能,。
PS:開始寫之前我是對網(wǎng)絡(luò)編程這塊完全沒有認(rèn)識的,上網(wǎng)找了幾個TCP的程序,,都是只能實現(xiàn)群發(fā)功能,,或者只能實現(xiàn)client與server之間相互發(fā)的功能,
還沒有哪個是可以實現(xiàn)上面所說的功能的程序的(如果有的請留言給我,,我去下一個下來學(xué)習(xí)一下,,O(∩_∩)O謝謝)。
實現(xiàn)方法有好多,,用Socket類可以實現(xiàn),,用 TcpClient類和TcpListener類也可以實現(xiàn),我就選擇了后者,,因為比較簡單,。
下面就列一下我用到的技術(shù):
多線程,異步回調(diào),,委托,,設(shè)計模式的觀察者模式…………
先讓大家看一下客戶端和服務(wù)器端的界面先吧(本人不會做界面,而且界面上有很多Label是用來檢查接收的情況,,請大家選擇性過濾掉)
server端的
client端的
1.Server端先啟動服務(wù),新建一個線程,,綁定一個套接字,之后監(jiān)聽
2.Client端點擊連接之后,就會與Server端建立連接,。
3.每當(dāng)有一個Client加入Server時,,Server都會通知所有的Client更新用戶列表(觀察者模式)
4.點私聊和用戶之后,就可以私聊;點群發(fā),,就發(fā)個所有用戶,。
代碼解說,先看看解決方案
先說一下client端
聲明變量
System.Collections.ArrayList clientlist = new System.Collections.ArrayList(); private bool isExit = false; private delegate void SetListBoxCallBack(string str); private SetListBoxCallBack setlistboxcallback; private delegate void SetTextBoxReceiveCallBack(string str); private SetTextBoxReceiveCallBack settextboxreceivecallback; private delegate void SetComboBoxCallBack(string str); private SetComboBoxCallBack setcomboboxcallback; private delegate void RemoveComboBoxItemsCallBack(DataReadWrite datareadwrite); private RemoveComboBoxItemsCallBack removecomboboxcallback; private TcpClient client; private NetworkStream ns; private ManualResetEvent allDone = new ManualResetEvent(false);
構(gòu)造函數(shù)
public ClientMain() { InitializeComponent(); setlistboxcallback = new SetListBoxCallBack(SetListBox); //注冊listbox回調(diào)函數(shù) settextboxreceivecallback = new SetTextBoxReceiveCallBack(SetTextBoxReceive); ////注冊textbox回調(diào)函數(shù) setcomboboxcallback = new SetComboBoxCallBack(SetComboBox); ////注冊combobox回調(diào)函數(shù) removecomboboxcallback = new RemoveComboBoxItemsCallBack(RemoveComboBoxItems); } //注冊combobox刪除的回調(diào)函數(shù)
連接按鈕
//連接按鈕事件 private void btn_Connect_Click(object sender, EventArgs e) { client = new TcpClient(AddressFamily.InterNetwork); IPAddress serverip = IPAddress.Parse(txt_Server.Text); //采用的是異步方式 AsyncCallback receiveCallBack = new AsyncCallback(ReceiveCallBack); allDone.Reset();
try {//開始連接 client.BeginConnect(serverip, Convert.ToInt32(txt_Port.Text),receiveCallBack,client); txt_ReceiveMsg.Invoke(settextboxreceivecallback, string.Format("本機終結(jié)點:{0}", client.Client.LocalEndPoint)); txt_ReceiveMsg.Invoke(settextboxreceivecallback, "開始與服務(wù)器連接"); allDone.WaitOne(); btn_Connect.Enabled = false; } catch { MessageBox.Show("登錄服務(wù)器失敗,,請確認(rèn)服務(wù)器是否正常工作,!"); } }
接收和回調(diào)
//接收回調(diào)函數(shù) private void ReceiveCallBack(IAsyncResult iar) { allDone.Set(); try { client = (TcpClient)iar.AsyncState; client.EndConnect(iar); txt_ReceiveMsg.Invoke(settextboxreceivecallback, string.Format("與服務(wù)器{0}連接成功", client.Client.RemoteEndPoint)); ns = client.GetStream(); DataRead dataRead = new DataRead(ns, client.ReceiveBufferSize); ns.BeginRead(dataRead.msg, 0, dataRead.msg.Length, ReadCallBack, dataRead); } catch { MessageBox.Show("已經(jīng)與服務(wù)器斷開連接!"); this.Close(); }
}
//讀取回調(diào)函數(shù) private void ReadCallBack(IAsyncResult iar) { try { DataRead dataread = (DataRead)iar.AsyncState; int recv = dataread.ns.EndRead(iar); lbluserlist.Text = Encoding.Unicode.GetString(dataread.msg, 0, recv); string userlist1 = lbluserlist.Text; if (userlist1.StartsWith("#")) { lb_Users.Items.Clear(); string[] abc = userlist1.Split(new char[] { '#' }); for (int i = 1; i < abc.Length ; i++) lb_Users.Items.Add(abc[i]); } else txt_ReceiveMsg.Invoke(settextboxreceivecallback, Encoding.Unicode.GetString(dataread.msg, 0, recv)); if (isExit == false) { dataread = new DataRead(ns, client.ReceiveBufferSize); ns.BeginRead(dataread.msg, 0, dataread.msg.Length, ReadCallBack, dataread); } } catch (Exception e) { MessageBox.Show(e.Message); } finally { } }
發(fā)送功能
//發(fā)送按鈕事件 private void btn_Send_Click(object sender, EventArgs e) { //如果群發(fā)按鈕被選中時 if (rbteam.Checked == true) { //lb_Users.SelectedItems.Clear(); SendString(txt_SendMsg.Text); } //如果私聊按鈕被選中時 else if (rbself.Checked == true) { if (lb_Users.SelectedItem != null) SendString("#" + lb_Users.SelectedItem.ToString().Trim() + "#" + txt_SendMsg.Text); //label10.Text = "#" + lb_Users.SelectedItem.ToString().Trim() + "#" + txt_SendMsg.Text; else MessageBox.Show("你還沒有選取要私聊的對象"); } //label9.Text = "對大家說:" + txt_SendMsg.Text; txt_SendMsg.Clear(); }
//發(fā)送函數(shù) private void SendString(string str) { try { byte[] bytesdata = Encoding.Unicode.GetBytes(str + "\r\n"); ns.BeginWrite(bytesdata, 0, bytesdata.Length, new AsyncCallback(SendCallBack), ns); ns.Flush(); } catch (Exception e) { MessageBox.Show(e.Message); } finally { } }
//發(fā)送回調(diào)函數(shù) private void SendCallBack(IAsyncResult iar) { try { ns.EndWrite(iar); } catch (Exception e) { MessageBox.Show(e.Message); } finally { } }
回調(diào)函數(shù)的實現(xiàn)
//listbox回調(diào)函數(shù) private void SetListBox(string str) { lb_Users.Items.Add(str); }
//txtbox回調(diào)函數(shù) private void SetTextBoxReceive(string str) { txt_ReceiveMsg.AppendText(str+"\r\n"); }
在看看Server端
開始階段
public ServerMain() { InitializeComponent(); setlistboxcallback = new SetListBoxCallBack(SetLbListBox); setlistboxcallback2 = new SetListBoxCallBack(SetListBox); removelistboxcallback = new RemoveListBoxCallBack(RemoveListBoxItems); setcomboboxcallback = new SetComboBoxCallBack(SetComboBox); removecomboboxcallback = new RemoveComboBoxItemsCallBack(RemoveComboBoxItems); }
private bool isExit = false; System.Collections.ArrayList clientlist = new System.Collections.ArrayList(); TcpListener listener; private delegate void SetListBoxCallBack(string str); private SetListBoxCallBack setlistboxcallback; private SetListBoxCallBack setlistboxcallback2; private delegate void RemoveListBoxCallBack(DataReadWrite datareadwrite); private RemoveListBoxCallBack removelistboxcallback; private ManualResetEvent allDone = new ManualResetEvent(false); private delegate void SetComboBoxCallBack(string str); private SetComboBoxCallBack setcomboboxcallback; private delegate void RemoveComboBoxItemsCallBack(DataReadWrite datareadwrite); private RemoveComboBoxItemsCallBack removecomboboxcallback;
//開始服務(wù)按鈕 private void btn_Start_Click(object sender, EventArgs e) { //新建線程接受connect Thread myThread = new Thread(new ThreadStart(AcceptConnection)); myThread.Start(); btn_Start.Enabled = false; btn_End.Enabled=true; }
//線程AcceptConnection private void AcceptConnection() { IPAddress[] ip = Dns.GetHostAddresses(Dns.GetHostName()); //IPAddress ipp = IPAddress.Parse("192.168.76.103"); listener = new TcpListener(ip[0], Convert.ToInt32(txt_ServerPort.Text)); listener.Start(); while (isExit == false) { try { allDone.Reset(); AsyncCallback callback = new AsyncCallback(AcceptTcpClientCallBack); lst_ServerList.Invoke(setlistboxcallback, "開始等待連接"); listener.BeginAcceptTcpClient(callback, listener); allDone.WaitOne(); } catch (Exception e) { MessageBox.Show(e.Message); break; } finally { } } }
private void AcceptTcpClientCallBack(IAsyncResult iar) { try { allDone.Set(); TcpListener mylistener = (TcpListener)iar.AsyncState; TcpClient client = mylistener.EndAcceptTcpClient(iar); lst_ServerList.Invoke(setlistboxcallback, "已接收客戶連接" + client.Client.RemoteEndPoint); listBox1.Invoke(setlistboxcallback2, client.Client.RemoteEndPoint.ToString()); comboBox1.Invoke(setcomboboxcallback, client.Client.RemoteEndPoint.ToString()); DataReadWrite datareadwrite = new DataReadWrite(client); clientlist.Add(datareadwrite); //發(fā)送成員列表,,這里用了觀察者模式 SendList(); //與服務(wù)器連接后的不同的客戶端的datareadwrite開始異步接收數(shù)據(jù) datareadwrite.ns.BeginRead(datareadwrite.read, 0, datareadwrite.read.Length, ReadCallBack, datareadwrite); } catch (Exception e) { lst_ServerList.Invoke(setlistboxcallback, e.Message); //MessageBox.Show(e.Message); return; } finally { } }
private void SendList() { string userlist = ""; for (int i = 0; i < clientlist.Count; i++) { userlist = userlist + "#" + ((DataReadWrite)clientlist[i]).client.Client.RemoteEndPoint.ToString(); label14.Text = userlist; } foreach (DataReadWrite drw in clientlist) SendString(drw, userlist); }
運行階段
//讀取消息回調(diào)函數(shù) private void ReadCallBack(IAsyncResult iar) { DataReadWrite datareadwrite = (DataReadWrite)iar.AsyncState; try { int recv = datareadwrite.ns.EndRead(iar); string aa = Encoding.Unicode.GetString(datareadwrite.read, 0, recv); //string bb = ""; stringaa.Text = aa; if (isExit == false) { if (aa.Substring(0, 1) != "#") //群發(fā)為#,,沒有則為單發(fā) { aa = aa.Insert(0, datareadwrite.client.Client.RemoteEndPoint.ToString() + "對大家說:"); //stringbb.Text = aa.Substring(1, aa.Length-1); foreach (DataReadWrite drw in clientlist) SendString(drw, aa); } else //私聊時截取ip地址,收到的信息為“#192.168.76.102:3315#消息” { int c=aa.LastIndexOf("#"); //截取ip地址的位置index string ipaddress = aa.Substring(1, c - 1); //截取ip地址 for (int i = 0; i <= listBox1.Items.Count - 1; i++) { if (ipaddress == listBox1.Items[i].ToString()) { //找到需要發(fā)送的對象 DataReadWrite obj = (DataReadWrite)clientlist[i]; //增加發(fā)送源的IP地址和端口號 aa = aa.Insert(c + 1, datareadwrite.client.Client.RemoteEndPoint.ToString() + "跟你說:"); SendString(obj, aa.Substring(c+1)); break; //MessageBox.Show(aa.Substring(c, aa.Length - 2)); } } } datareadwrite.ns.BeginRead(datareadwrite.read, 0, datareadwrite.read.Length, ReadCallBack, datareadwrite); } } catch (Exception e) { lst_ServerList.Invoke(setlistboxcallback, e.Message); listBox1.Invoke(removelistboxcallback, datareadwrite); comboBox1.Invoke(removecomboboxcallback, datareadwrite); //SendList(); } finally { } }
//發(fā)送消息 private void SendString(DataReadWrite datareadwrite, string str) { try { datareadwrite.write = Encoding.Unicode.GetBytes(str + "\r\n"); datareadwrite.ns.BeginWrite(datareadwrite.write, 0, datareadwrite.write.Length, new AsyncCallback(SendCallBack), datareadwrite); datareadwrite.ns.Flush(); lst_ServerList.Invoke(setlistboxcallback, string.Format("向{0}發(fā)送:{1}", datareadwrite.client.Client.RemoteEndPoint, str)); } catch (Exception e) { lst_ServerList.Items.Add(e.Message); //發(fā)送失敗時,,清除發(fā)送不成功的IP地址 listBox1.Invoke(removelistboxcallback, datareadwrite); comboBox1.Invoke(removecomboboxcallback, datareadwrite); //SendList(); } finally { } }
//發(fā)送回調(diào) private void SendCallBack(IAsyncResult iar) { DataReadWrite datareadwrite = (DataReadWrite)iar.AsyncState; try { datareadwrite.ns.EndRead(iar); } catch (Exception e) { lst_ServerList.Invoke(setlistboxcallback, e.Message); listBox1.Invoke(removelistboxcallback, datareadwrite); comboBox1.Invoke(removecomboboxcallback, datareadwrite); } finally { } }
//停止服務(wù)按鈕 private void btn_End_Click(object sender, EventArgs e) { isExit = true; allDone.Set();
btn_Start.Enabled = true; btn_End.Enabled = false;
lst_ServerList.Items.Add("服務(wù)器于" + DateTime.Now.ToString() + "停止運行."); }
回調(diào)函數(shù)實現(xiàn)
private void RemoveListBoxItems(DataReadWrite datareadwrite) { int index = clientlist.IndexOf(datareadwrite); listBox1.Items.RemoveAt(index); }
private void SetListBox(string str) { listBox1.Items.Add(str); }
private void SetLbListBox(string str) { lst_ServerList.Items.Add(str); lst_ServerList.SelectedIndex = listBox1.Items.Count - 1; lst_ServerList.ClearSelected(); }
private void RemoveComboBoxItems(DataReadWrite datareadwrite) { int index = clientlist.IndexOf(datareadwrite); comboBox1.Items.RemoveAt(index); }
private void SetComboBox(object obj) { comboBox1.Items.Add(obj); }
再介紹一下DataReadWrite類,,這個類是實現(xiàn)將一個client的屬性和功能集成到一個類中,主要是負(fù)責(zé)處理數(shù)據(jù)的,,包括一個tcpclient對象,,
一個網(wǎng)絡(luò)流對象,2個負(fù)責(zé)讀寫的字節(jié)數(shù)組
DataReadWrite
public class DataReadWrite { public TcpClient client; public NetworkStream ns; public byte[] read; public byte[] write; public DataReadWrite(TcpClient client) { this.client = client; ns = client.GetStream(); read = new byte[client.ReceiveBufferSize]; write = new byte[client.SendBufferSize]; }
public void InitReadArray() { read = new byte[client.ReceiveBufferSize]; }
public void InitWriteArray() { write = new byte[client.SendBufferSize]; }
在構(gòu)造函數(shù)里得到連接上服務(wù)器的句柄,,包含服務(wù)器的IP地址信息,,從client對象上得到流對象,流對象就是用來在網(wǎng)絡(luò)上進行流的讀寫,。
以下是實現(xiàn)的截圖
嘗試了群發(fā)和私聊
由于是新手,,所以里面用詞方面可能有些不準(zhǔn)確,而且程序這是初成品,,還有很多方面的BUG,。例如退出時會發(fā)生異常,當(dāng)有人離開時,,列表不會更新,。
希望大家看了之后給點意見和建議,謝謝大家撒,。有空多留言,,謝謝
參考書籍:《visual c#網(wǎng)絡(luò)編程技術(shù)與實踐》和《c#網(wǎng)絡(luò)應(yīng)用高級編程》
|