微风IM 采用开源的networkcomms2.3.1c#通信框架
源码 数据库 (数据库和以前一样 没有变动)
前面有好几位朋友说希望微风IM能够支持图片的发送,于是学习了图片发送的一些相关知识,在微风IMV3.1中实现了图片的发送。
如果客户端之间P2P通道已经打通,则直接在客户端之间发送,不经过服务器。
如果P2P通道没有打通,则通过服务器转发。
效果图:
下面简单的介绍一下相关知识
在TCP 通信中,Image类本身不支持序列化。
我们想要发送的话,需要做一些变通。简单的讲,就是在序列化之前把Image类转化为二进制数据,序列化完成后,解析的时候再把二进制数据解析成Image类。
我们使用的是开源的 protobuf.net序列化器。
对IMage图片类进行包装
using System; using System.Collections.Generic; using System.Text; using ProtoBuf; using System.Drawing; using System.IO; using ProtoBuf; namespace SimpleIM.Business { [ProtoContract] public class ImageWrapper { /// <summary> /// 把Image对象存储为私有的字节数组 /// </summary> [ProtoMember(1)] private byte[] _imageData; /// <summary> /// 图片名称 /// </summary> [ProtoMember(2)] public string ImageName { get; set; } /// <summary> /// 图片对象 /// </summary> public Image Image { get; set; } /// <summary> /// 私有的无参数构造函数 反序列化时需要使用 /// </summary> private ImageWrapper() { } /// <summary> /// 创建一个新的 ImageWrapper类 /// </summary> /// <param name="imageName"></param> /// <param name="image"></param> public ImageWrapper(string imageName, Image image) { this.ImageName = imageName; this.Image = image; } /// <summary> ///序列化之前,把图片转化为二进制数据 /// </summary> [ProtoBeforeSerialization] private void Serialize() { if (Image != null) { //We need to decide how to convert our image to its raw binary form here using (MemoryStream inputStream = new MemoryStream()) { //For basic image types the features are part of the .net framework Image.Save(inputStream, Image.RawFormat); //If we wanted to include additional data processing here //such as compression, encryption etc we can still use the features provided by NetworkComms.Net //e.g. see DPSManager.GetDataProcessor<LZMACompressor>() //Store the binary image data as bytes[] _imageData = inputStream.ToArray(); } } } /// <summary> /// 反序列化时,把二进制数据转化为图片对象 /// </summary> [ProtoAfterDeserialization] private void Deserialize() { MemoryStream ms = new MemoryStream(_imageData); //If we added custom data processes we have the perform the reverse operations here before //trying to recreate the image object //e.g. DPSManager.GetDataProcessor<LZMACompressor>() Image = Image.FromStream(ms); _imageData = null; } } }
修改一下用于传输聊天信息的契约类,使之包含相关的图片
using System; using System.Collections.Generic; using System.Text; using ProtoBuf; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.ComponentModel; namespace SimpleIM.Business { /// <summary> /// 此契约类存放聊天对话消息 /// </summary> [ProtoContract] public class NewChatContract { //用户ID [ProtoMember(1)] public string UserID { get; set; } //用户名 [ProtoMember(2)] public string UserName { get; set; } //目标用户ID [ProtoMember(3)] public string DestUserID { get; set; } //目标用户名 [ProtoMember(4)] public string DestUserName { get; set; } //聊天的内容,主要是文本消息 [ProtoMember(5)] public string Content { get; set; } //发送的时间 [ProtoMember(6)] public DateTime SendTime { get; set; } [ProtoMember(7)] public IList<ImageWrapper> ImageList { get; set; } //下面这段代码主要是为了防止列表为空,如果列表为空,不加入下面这段代码,序列化会有问题 [DefaultValue(false), ProtoMember(8)] private bool IsEmptyList { get { return ImageList != null && ImageList.Count == 0; } set { if (value) { ImageList = new List<ImageWrapper>(); } } } public NewChatContract() { } public NewChatContract(string userID, string userName, string destUserID, string destUserName, string content, IList<ImageWrapper> imageList, DateTime sendTime) { this.UserID = userID; this.UserName = userName; this.DestUserID = destUserID; this.DestUserName = destUserName; this.Content = content; this.ImageList = imageList; this.SendTime = sendTime; } } }
修改ChatControl控件,添加2个按钮
并添加相关代码,具体大家可以看一下源码
需要说明的是,聊天控件使用的是RichTextBox的扩展控件,本来想是否能够提取出RichTextBox中的内容,即包含所有的文字信息和图片信息,然后以发送二进制数据的形式发送,
但是对这方面不太了解,查了好些文章,也没有完后,对这方面比较了解的朋友请指点一下。
后来使用了一个变通的方法,即把文本内容,和图片文件分开发送。
在ChatControl控件中增加了一个字典类变量
public Dictionary<string, Image> imageDict = new Dictionary<string, Image>();
当用户插入图片时,会把图片添加到此字典中。
当用户发送聊天信息时,会提取字典中的图片列表进行发送,并清空图片字典。
点击发送聊天信息时的相关代码:
/ /控件中的图片字典 public Dictionary<string, Image> imageDict = null; //图片包装类的列表 IList<ImageWrapper> imageWrapperList = new List<ImageWrapper>(); private void chatControl1_BeginToSend(string content) { this.chatControl1.ShowMessage(Common.UserName, DateTime.Now, content, true); imageDict = this.chatControl1.imageDict; //把控件中的图片字典,添加到图片包装类列表中 foreach (KeyValuePair<string, Image> kv in imageDict) { ImageWrapper newWrapper = new ImageWrapper(kv.Key, kv.Value); imageWrapperList.Add(newWrapper); } //清除控件中图片字典的内容 this.chatControl1.ClearImageDic(); //从客户端 Common中获取相应连接 Connection p2pConnection = Common.GetUserConn(this.friendID); if (p2pConnection != null) { NewChatContract chatContract = new NewChatContract(); chatContract.UserID = Common.UserID; chatContract.UserName = Common.UserName; chatContract.DestUserID = this.friendID; chatContract.DestUserName = this.friendID; chatContract.Content = content; chatContract.SendTime = DateTime.Now; chatContract.ImageList = imageWrapperList; p2pConnection.SendObject("ClientChatMessage", chatContract); this.chatControl1.Focus(); LogInfo.LogMessage("通过p2p通道发送消息,当前用户ID为"+Common.UserID+"当前Tcp连接端口号"+p2pConnection.ConnectionInfo.LocalEndPoint.Port.ToString (), "P2PINFO"); } else { NewChatContract chatContract = new NewChatContract(); chatContract.UserID = Common.UserID; chatContract.UserName = Common.UserName; chatContract.DestUserID = this.friendID; chatContract.DestUserName = this.friendID; chatContract.Content = content; chatContract.SendTime = DateTime.Now; chatContract.ImageList = imageWrapperList; Common.TcpConn.SendObject("ChatMessage", chatContract); this.chatControl1.Focus(); LogInfo.LogMessage("服务器转发消息", "P2PINFO"); } }
图片的传送是经常用的功能,在其他的程序中也经常用到,稍微改动一下,可以实现客户端拍照片,然后传送到服务器上保存等类似功能。