通过模拟请求或序列化来发送不同的消息
思路:要发送不同的消息(文字,图片,文件,震动等等),我想可不可以对不同类型的消息作一个标识呢,在哪里标志呢?
因为在传输的时候服务器与客户端只以字节流的方式来完成数据的传输,那么我们就可以在不同的数据数里加一个标志。
图为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结合起来,其实核心的就是这些了,如果想尽善尽美的话,还要考虑的问题还有很多。还是那句话,说起来简单,做起来难。
源码已上传,下载地址:https://files.cnblogs.com/inline/MyChat.zip