标签:
1.前言
这是本系列的第三篇文章,上一篇我们讲到实现了客户端对客户端的一对一的普通聊天,本篇文章实现好友间发送抖屏与收发各种类型文件,我对不同的消息进行了封装,以及对服务器解析消息进行了重构。向往常一样我会把聊天服务器部署到广域网服务器上,到时候大家就可以可以在源码里面打开客户端与我聊天啦!(这只是一个初级版功能简单不支持离线消息,所以聊天的前提是我在线(用户名为张三的就是我,Q我吧)……),也可以自己打开两个客户端测试一下,服务端的部署文档放在我的源码根目录下。
2.本篇实现功能
1. 客户端之间抖屏消息的发送与接收。
2.客户端之间文件的发送与接收。
3.具体实现
(1)消息类型的枚举
消息类型
1 public enum MsgTypeEnum 2 { 3 // 登录消息 4 Login =0, 5 //一般消息 6 String =1, 7 //抖动屏幕 8 Shake=2, 9 //文件 10 File=3 11 }
(2)消息类
通过对不同消息的进行各种自定义的协议;(登录消息只在连接服务器时使用,所以没有写入消息类)
a.一般消息的自定义协议(“|”这个符号为本系统解析标志)
如果我们假设当前用户为张三,向好友李四发送一句“你好”。通过socket发送到服务器的格式为“1张三|李四|你好”,然后通过服务器的解析发送给李四客户端,(可能你会有这样的疑问,为什么消息要经过服务器解析才转发,为什么不客户端直接发消息给客户端,这样更省事,其实如果部署与广域网,客户端通过socket是不能直接连接好友客户端的,必须通过部署在广域网的服务器来保证他们之间通信)
b.抖屏的自定义协议(“|”这个符号为本系统解析标志)
如果我们假设当前用户为张三,向好友李四发送一个抖屏消息,通过socket发送到服务器的格式为“2张三|李四|”,然后通过服务器的解析发送给李四客户端,李四客户端调用自己的抖动方法。
c.文件的自定义协议(“|”这个符号为本系统解析标志)
如果我们假设当前用户为张三,向好友李四发送一个txt文件,通过socket发送到服务器的格式为“3张三|李四|.txt|文件流”,然后通过服务器的解析发送给李四客户端,李四客户端确定是否保存文件(这里只是实现直接读取的文件,只适应于小文件(读取到内存中默认5M),大文件要用到断点续传等技术,这里不做研究)。
/// <summary>
/// 消息类
/// </summary>
public class MyMessage
{
MsgTypeEnum msgType;
string filePath;
string strMsg;
/// <summary>
/// 普通文本消息
/// </summary>
/// <param name="msgString">消息</param>
public MyMessage(string msgString)
{
this.msgType = MsgTypeEnum.String;
this.strMsg = msgString;
}
/// <summary>
/// 消息类型
/// </summary>
/// <param name="msgType"></param>
public MyMessage(MsgTypeEnum msgType)
{
this.msgType = msgType;
}
/// <summary>
/// 文件类型
/// </summary>
/// <param name="filePath"></param>
/// <param name="msgType"></param>
public MyMessage(string filePath, MsgTypeEnum msgType)
{
this.msgType = MsgTypeEnum.File;
this.filePath = filePath;
}
/// <summary>
/// 根据消息类型返回消息byte数组
/// </summary>
/// <returns></returns>
public byte[] ToByteData()
{
//消息数组
byte[] arrMsg = null;
switch (msgType)
{
case MsgTypeEnum.String:
{
//1.将消息字符串转成 字节数组
arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg);
break;
}
case MsgTypeEnum.File:
{
string fileType = Path.GetExtension(filePath) + "|"; //提示对方接收文件的类型
byte[] buffer0 = Encoding.UTF8.GetBytes(fileType);
var list = new List<byte>();
list.AddRange(buffer0);
//这里直接读取的文件,只适应于小文件(读取到内存中),大文件要用到断点续传等技术
list.AddRange(File.ReadAllBytes(filePath));
byte[] newbuffer = list.ToArray();
arrMsg= newbuffer;
break;
}
case MsgTypeEnum.Shake:
{
arrMsg = new byte[0];
break;
}
}
return arrMsg;
}
/// <summary>
/// 标记消息类型+发信人+收信人(最终消息)
/// </summary>
/// <param name="oldArr"></param>
/// <param name="fromClient"></param>
/// <param name="toClient"></param>
/// <returns></returns>
public byte[] MakeNewArrWithFlag(byte[] oldArr,string fromClient,string toClient)
{
//消息格式: (类型标识符) 发信人| 收信人 +具体信息
string str = fromClient + "|" + toClient + "|";
byte[] buffer0 = Encoding.UTF8.GetBytes(str);
var list = new List<byte>();
list.AddRange(buffer0);
list.AddRange(oldArr);
byte[] newbuffer = list.ToArray();
byte[] newArr = new byte[newbuffer.Length + 1];
//1.将当前对象的类型转成标识数值存入新数组的第一个元素
newArr[0] = (byte)msgType;
//2.将 老数组的 数据 复制到 新数组中(从新数组第二个位置开始存放)
newbuffer.CopyTo(newArr, 1);
//3.返回 带标识位的 新消息数组
return newArr;
}
}
(3)消息的发送
以文件发送为例
a.选择文件
1 private void btnSelFile_Click(object sender, EventArgs e) 2 { 3 using (OpenFileDialog ofd = new OpenFileDialog()) 4 { 5 ofd.Multiselect = false; 6 ofd.Title = "选择要发送的文件"; 7 if (ofd.ShowDialog() == DialogResult.OK) 8 { 9 filePath = ofd.FileName; 10 txtMsg.Text = filePath; 11 } 12 } 13 }
b.发送文件
1 private void btnSendFile_Click(object sender, EventArgs e) 2 { 3 if (!IsSelectFriend()||filePath=="") 4 { 5 MessageBox.Show("选择好友与文件"); 6 return; 7 } 8 friend = lbFriends.SelectedItem.ToString(); 9 try 10 { 11 //界面显示消息 12 string newstr = "我" + ":" + friend + "\r\n" + "\"发送了一个文件\"" + " ____[" + DateTime.Now + 13 "]" + "\r\n" + "\r\n"; 14 ShowSmsg(newstr); 15 SendFile(); 16 filePath = ""; 17 } 18 catch 19 { 20 MessageBox.Show("与服务器连接失败"); 21 } 22 } 23 24 private void SendFile() 25 { 26 //消息类创建-发往服务器的消息 格式为 (发送者|接收者|信息) 27 MyMessage msg = new MyMessage(filePath,MsgTypeEnum.File); 28 //消息转字节 29 byte[] buffer = msg.ToByteData(); 30 //添加标识符+发送者|接收者| 31 byte[] sendbuffer = msg.MakeNewArrWithFlag(buffer, userName, friend); 32 clientSocket.Send(sendbuffer); 33 txtMsg.Text = ""; 34 }
(4)消息的接收
以抖屏消息接收为例
a.判断消息类型
在监听服务socket的接收线程方法中对新消息标志位解析
1 public void ReceMsg() 2 { 3 while (true) 4 { 5 try 6 { 7 var buffer = new byte[1024*1024*5]; 8 9 int dateLength = clientSocket.Receive(buffer); //接收服务端发送过来的数据 10 //一般消息 11 if (buffer[0] == (byte) MsgTypeEnum.String) 12 { 13 //不解析第一位标识符 14 HandleStr(buffer, dateLength); 15 } 16 //抖屏消息 17 else if (buffer[0] == (byte) MsgTypeEnum.Shake) 18 { 19 HandleShake(buffer, dateLength); 20 } 21 //文件消息 22 else if (buffer[0] == (byte) MsgTypeEnum.File) 23 { 24 HandleFile(buffer, dateLength); 25 } 26 else 27 { 28 MessageBox.Show("接收文件解析失败"); 29 } 30 } 31 catch 32 { 33 } 34 } 35 } 36 37 public void HandleShake(byte[] buffer, int dateLength) 38 { 39 //不解析第一位标识符 40 string ReceiveMsg = Encoding.UTF8.GetString(buffer, 1, dateLength - 1); 41 string[] msgTxt = ReceiveMsg.Split(‘|‘); 42 string newstr = " " + msgTxt[0] + ":我" + "\r\n" + " " + "\"发送了一个抖屏\"" + " ____[" + 43 DateTime.Now + "]" + "\r\n" + "\r\n"; 44 //把接收到消息显示在文本框中。 45 ShowSmsg(newstr); 46 //震动 47 ShakeBody(); 48 }
b.解析抖屏效果
1 Random randObj = new Random(); 2 3 private void ShakeBody() 4 { 5 //1.获取窗体当前坐标 6 Point oldPoint = this.Location; 7 //2.循环改变 窗体 坐标 8 for (int i = 0; i < 15; i++) 9 { 10 //3.设置新的随机坐标 11 this.Location = new Point(oldPoint.X + randObj.Next(30), oldPoint.Y + randObj.Next(30)); 12 System.Threading.Thread.Sleep(50); 13 //4.还原坐标 14 this.Location = oldPoint; 15 System.Threading.Thread.Sleep(50); 16 } 17 }
(5)消息演示
a.抖屏效果
b.文档收发效果
(6)总结
本次实现了客户端对客户端的抖屏与收发各种类型文件。实现思路大体为:客户端1使用自定义的协议将不同类型消息发送到服务器再由服务器解析转发给客户端2。好了聊天的基本原理到这里已经差不多讲完了。前面这些只是用户的模拟,下一篇我们加入SQLServer数据库实现登录与好友的添加等功能。最后你可以打开源码的客户端,登录张三以外的客户端给我发消息,我这边登录的是张三的账户,或者打开两个客户端自己聊天(不需要运行服务端,默认是我的服务器IP,理论上有网就可以聊天),赶快试一下吧!!!
这个系列未完,待续。。。。。。。。。。。。。。。。。。。。。,期待您的关注
源码地址:http://pan.baidu.com/s/1nvs3JmT
标签:
原文地址:http://www.cnblogs.com/Leo_wl/p/5470490.html