话说这个学期我们有一门课叫“中间件”,老师叫我们做一个基于TCP的聊天程序,主要结构如图
1.所有Client端需要与Server端连接(感觉这句话好白痴,TCP肯定要连接了才能工作)
2.Client端的功能是可以群发和私聊(用过QQ都应该知道什么是群发和私聊吧),但都必须经过Server端中转,也就是实现了类似通讯中间件的功能。
PS:开始写之前我是对网络编程这块完全没有认识的,上网找了几个TCP的程序,都是只能实现群发功能,或者只能实现client与server之间相互发的功能,
还没有哪个是可以实现上面所说的功能的程序的(如果有的请留言给我,我去下一个下来学习一下,O(∩_∩)O谢谢)。
实现方法有好多,用Socket类可以实现,用 TcpClient类和TcpListener类也可以实现,我就选择了后者,因为比较简单。
下面就列一下我用到的技术:
多线程,异步回调,委托,设计模式的观察者模式…………
先让大家看一下客户端和服务器端的界面先吧(本人不会做界面,而且界面上有很多Label是用来检查接收的情况,请大家选择性过滤掉)
server端的
client端的
1.Server端先启动服务,新建一个线程,绑定一个套接字,之后监听
2.Client端点击连接之后,就会与Server端建立连接。
3.每当有一个Client加入Server时,Server都会通知所有的Client更新用户列表(观察者模式)
4.点私聊和用户之后,就可以私聊;点群发,就发个所有用户。
代码解说,先看看解决方案
先说一下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);

public ClientMain() { InitializeComponent(); setlistboxcallback = new SetListBoxCallBack(SetListBox); //注册listbox回调函数 settextboxreceivecallback = new SetTextBoxReceiveCallBack(SetTextBoxReceive); ////注册textbox回调函数 setcomboboxcallback = new SetComboBoxCallBack(SetComboBox); ////注册combobox回调函数 removecomboboxcallback = new RemoveComboBoxItemsCallBack(RemoveComboBoxItems); } //注册combobox删除的回调函数

//连接按钮事件 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("本机终结点:{0}", client.Client.LocalEndPoint)); txt_ReceiveMsg.Invoke(settextboxreceivecallback, "开始与服务器连接"); allDone.WaitOne(); btn_Connect.Enabled = false; } catch { MessageBox.Show("登录服务器失败,请确认服务器是否正常工作!"); } }

//接收回调函数 private void ReceiveCallBack(IAsyncResult iar) { allDone.Set(); try { client = (TcpClient)iar.AsyncState; client.EndConnect(iar); txt_ReceiveMsg.Invoke(settextboxreceivecallback, string.Format("与服务器{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("已经与服务器断开连接!"); this.Close(); } } //读取回调函数 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 { } }

//发送按钮事件 private void btn_Send_Click(object sender, EventArgs e) {//如果群发按钮被选中时 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(); } //发送函数 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 { } } //发送回调函数 private void SendCallBack(IAsyncResult iar) { try { ns.EndWrite(iar); } catch (Exception e) { MessageBox.Show(e.Message); } finally { } }

//listbox回调函数 private void SetListBox(string str) { lb_Users.Items.Add(str); } //txtbox回调函数 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; //开始服务按钮 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); //发送成员列表,这里用了观察者模式 SendList(); //与服务器连接后的不同的客户端的datareadwrite开始异步接收数据 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); }

//读取消息回调函数 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) != "#") //群发为#,没有则为单发 { 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()) { //找到需要发送的对象 DataReadWrite obj = (DataReadWrite)clientlist[i]; //增加发送源的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 { } } //发送消息 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}发送:{1}", datareadwrite.client.Client.RemoteEndPoint, str)); } catch (Exception e) { lst_ServerList.Items.Add(e.Message); //发送失败时,清除发送不成功的IP地址 listBox1.Invoke(removelistboxcallback, datareadwrite); comboBox1.Invoke(removecomboboxcallback, datareadwrite); //SendList(); } finally { } } //发送回调 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 { } } //停止服务按钮 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("服务器于" + DateTime.Now.ToString() + "停止运行."); }

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类,这个类是实现将一个client的属性和功能集成到一个类中,主要是负责处理数据的,包括一个tcpclient对象,
一个网络流对象,2个负责读写的字节数组

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]; }
在构造函数里得到连接上服务器的句柄,包含服务器的IP地址信息,从client对象上得到流对象,流对象就是用来在网络上进行流的读写。
以下是实现的截图
尝试了群发和私聊
由于是新手,所以里面用词方面可能有些不准确,而且程序这是初成品,还有很多方面的BUG。例如退出时会发生异常,当有人离开时,列表不会更新。
希望大家看了之后给点意见和建议,谢谢大家撒。有空多留言,谢谢
参考书籍:《visual c#网络编程技术与实践》和《c#网络应用高级编程》
ps:漏了源码,现在上传qqsocket.rar
来源:http://www.cnblogs.com/cookies9/archive/2010/06/02/1750244.html