通過模擬請求或序列化來發送不同的消息
思路:要發送不同的消息(文字,圖片,文件,震動等等),我想可不可以對不同類型的消息作一個標識呢,在哪里標志呢?
因為在傳輸的時候服務器與客戶端只以字節流的方式來完成數據的傳輸,那么我們就可以在不同的數據數里加一個標志。
圖為httpwatch捕獲的http報文
這種思想來源自HTTP請求與響應報文,模擬HTTP請求與響應,我自己規定字節流里第一個字節{0:文字,1:文件,2:震動}。
但是這個時候有一個壞處,如果標識后期又要加一個功能,比如要求用戶可能控制不同的文字大小,不同的文字與顏色。那樣標志就越來越多了,傳輸的報文也越來越難封裝和解析了(中間多了定義標識,合並與拆封字符串的過程)。
這個時候我們就會用面象對象的思想來想這個問題了。能不能把一個對象用來傳輸呢?因為此時的網絡是以字節數組的形式在不同電腦中傳輸的,那怎么樣把一個對象轉換成一個字節數組在不同電腦中來進行發送呢?
這個時候我們就應該想到了序列化。
圖為我的文件結構
由於我的服務器與客戶端在不同的程序集,所以我就定義一個common的類庫在不同程序集中來進行傳輸。
MyCommon類:
[Serializable] public class MyCommon { //定義標志 private int flag; public int Flag { get { return flag; } set { flag = value; } } //傳輸的字節數組 private byte[] buffer; public byte[] Buffer { get { return buffer; } set { buffer = value; } } }
服務器端:
using System.Runtime.Serialization.Formatters.Binary; namespace MyChatServer { public partial class Form1 : Form { public Form1() { InitializeComponent(); //不檢測跨線程操作的合法性 Control.CheckForIllegalCrossThreadCalls = false; } //當多用戶連接到同一個服務器的時候,記錄通信點與對應通信的socket,以字典的鍵值對來保存,方便調用,這樣服務器就可以選擇的來給不同的用戶發送消息了 Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>(); Dictionary<string, Thread> dicThread = new Dictionary<string, Thread>(); private void btnStart_Click_1(object sender, EventArgs e) { //ip地址 IPAddress ip = IPAddress.Parse(txtServer.Text); //當前計算機上任何可用的ip地址 //IPAddress ip = IPAddress.Any; IPEndPoint point = new IPEndPoint(ip, int.Parse(txtPort.Text)); //創建負責監聽的socket Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { //綁定ip和端口號 socket.Bind(point); //監聽 socket.Listen(10); ShowMsg("開始監聽"); } catch (Exception ex) { ShowMsg(ex.Message); return; } Thread th = new Thread(Listen); th.IsBackground = true; th.Start(socket); } int count = 0; void Listen(object o) { Socket socket = o as Socket; while (count<100) { try { count++; //創建一個負責通信用的socket 阻塞窗體的運行 Socket connSocket = socket.Accept(); string s = connSocket.RemoteEndPoint.ToString(); ShowMsg(s + ":連接成功"); //記錄通信用的socket dicSocket.Add(s, connSocket); cboUsers.Items.Add(s); //接收消息 Thread th = new Thread(RecMsg); th.IsBackground = true; th.Start(connSocket); dicThread.Add(s, th); } catch (Exception ex) { ShowMsg(ex.Message); break; } } } void RecMsg(object o) { Socket connSocket = o as Socket; while (true) { try { //接收客戶端發送過來的消息 byte[] buffer = new byte[1024 * 1024]; //num 接收到的實際有效的字節個數 int num = connSocket.Receive(buffer); string s = Encoding.UTF8.GetString(buffer, 0, num); ShowMsg(connSocket.RemoteEndPoint.ToString() + ":" + s); } catch (Exception ex) { ShowMsg(ex.Message); break; } } } void ShowMsg(string msg) { txtLog.AppendText(msg + "\r\n"); } //服務器發送消息 private void btnSend_Click(object sender, EventArgs e) { //獲取文本框內容 轉化成字節數組 byte[] buffer = Encoding.UTF8.GetBytes(txtMsg.Text); try { //List<byte> list = new List<byte>(); ////標志 0 文字 //list.Add(0); //list.AddRange(buffer); Common.MyCommon my = new Common.MyCommon(); my.Flag = 0; my.Buffer = buffer; //客戶端的通信用的socket //獲取當前選中的客戶端的ip地址 string s = cboUsers.Text; dicSocket[s].Send(Serialize(my)); } catch (Exception ex) { ShowMsg(ex.Message); } } private void Form1_Load(object sender, EventArgs e) { } //選擇文件 private void btnSelect_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { txtPath.Text = ofd.FileName; } } //發送文件 private void btnSendFile_Click(object sender, EventArgs e) { string key = cboUsers.Text; using (FileStream fs = new FileStream(txtPath.Text,FileMode.Open)) { byte[] buffer = new byte[fs.Length]; fs.Read(buffer,0,buffer.Length); //List<byte> list = new List<byte>(); ////標志 1 文件 //list.Add(1); //list.AddRange(buffer); Common.MyCommon my = new Common.MyCommon(); my.Flag = 1; my.Buffer = buffer; dicSocket[key].Send(Serialize(my)); } } //震動 private void btnZD_Click(object sender, EventArgs e) { string key = cboUsers.Text; //byte[] buffer = new byte[1]; ////標志 震動 //buffer[0] = 2; Common.MyCommon my = new Common.MyCommon(); my.Flag = 2; dicSocket[key].Send(Serialize(my)); } //序列化,我想把這個對象轉換成一個數組,那就傳進去對象給我返回數組吧 byte[] Serialize(Common.MyCommon my) { //先把這個對象轉換成一個流,再把這個流轉換成一個字節數組,這個流只是一個中轉流,用內存流就可以了 using (MemoryStream stream = new MemoryStream()) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(stream,my); //把序列化好的流轉換成數組 byte[] buffer = stream.ToArray(); //返回出字節數組 return buffer; } } } }
客戶端:
using System.Runtime.Serialization.Formatters.Binary; namespace MyChatClient { public partial class Form1 : Form { public Form1() { InitializeComponent(); Control.CheckForIllegalCrossThreadCalls = false; } Socket socket; private void btnStart_Click(object sender, EventArgs e) { //服務器的ip地址與端口 IPAddress ip = IPAddress.Parse(txtServer.Text); IPEndPoint point = new IPEndPoint(ip,int.Parse(txtPort.Text)); socket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { //連接服務器 socket.Connect(point); ShowMsg("連接成功"); //接收服務器發送的消息 Thread th = new Thread(RecMsg); th.IsBackground = true; th.Start(); } catch (Exception ex) { ShowMsg(ex.Message); } } //接收服務器發送過來的消息 void RecMsg() { while (true) { try { byte[] buffer = new byte[1024 * 1024]; int num = socket.Receive(buffer); Common.MyCommon my = Deserialize(buffer, num); //當是文字 if(my.Flag == 0) { string s = Encoding.UTF8.GetString(my.Buffer, 0,my.Buffer.Length); ShowMsg(s); } //接收文件 else if (my.Flag == 1) { //提示是否保存文件 DialogResult dr = MessageBox.Show("是否保存文件?","提示",MessageBoxButtons.YesNo); if (dr == System.Windows.Forms.DialogResult.Yes) { SaveFileDialog sfd = new SaveFileDialog(); //保存文件 win7下使用的時候注意,必須加this if (sfd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK) { using (FileStream fs = new FileStream(sfd.FileName,FileMode.Create)) { fs.Write(my.Buffer,0,my.Buffer.Length); } } } } else if (my.Flag == 2) { //窗口置於頂層 this.TopMost = true; //震動 ZD(); } } catch (Exception ex) { ShowMsg(ex.Message); break; } } } void ShowMsg(string msg) { txtLog.AppendText(msg + "\r\n"); } //發送消息 private void btnSend_Click(object sender, EventArgs e) { if (socket != null) { try { byte[] buffer = Encoding.UTF8.GetBytes(txtMsg.Text); socket.Send(buffer); } catch (Exception ex) { ShowMsg(ex.Message); } } } private void Form1_Load(object sender, EventArgs e) { } //震動 string dir = "top"; int count = 10; void ZD() { for (int i = 0; i < count; i++) { if (dir == "top") { dir = "bottom"; this.Location = new Point(this.Location.X - 5, this.Location.Y - 5); } else { dir = "top"; this.Location = new Point(this.Location.X + 5, this.Location.Y + 5); } System.Threading.Thread.Sleep(50); } } //反序列化,把一個字節數組轉換成一個對象, Common.MyCommon Deserialize(byte[] buffer,int num) { using (MemoryStream ms = new MemoryStream(buffer, 0, num)) { BinaryFormatter bf = new BinaryFormatter(); Common.MyCommon my = bf.Deserialize(ms) as Common.MyCommon; return my; } } } }
成功了,來看看結果吧
這只是一個實驗,客戶端對客戶端只是服務器的轉發,如果想做成像QQ那樣的,可以與ADO.NET結合起來,其實核心的就是這些了,如果想盡善盡美的話,還要考慮的問題還有很多。還是那句話,說起來簡單,做起來難。
源碼已上傳,下載地址:http://files.cnblogs.com/inline/MyChat.zip