diff --git a/Interact/AttrNames.cs b/Interact/AttrNames.cs new file mode 100644 index 0000000..0c7e985 --- /dev/null +++ b/Interact/AttrNames.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Interact +{ + //Json数据的字段访问字符串 + public static class AttrNames + { + //基本信息 + public const string Token = "Token"; + public const string Operation = "Operation"; + + //处理结果信息 + public const string Error = "Error"; + + //获取用户信息 + public const string User = "User"; + public const string NickName = "NickName"; + public const string Motto = "Motto"; + public const string UGroup = "UGroup"; + public const string Photo = "Photo"; + public const string Total = "Total"; + public const string Count = "Count"; + + //消息 + public const string When = "When"; + public const string From = "From"; + public const string To = "To"; + } +} diff --git a/Interact/Client-Fields.cs b/Interact/Client-Fields.cs new file mode 100644 index 0000000..e3aaaf5 --- /dev/null +++ b/Interact/Client-Fields.cs @@ -0,0 +1,37 @@ +using System; +using System.Net.Sockets; +using System.Collections.Concurrent; +using System.Threading; +using System.Collections.Generic; + +//定义StormClient私有字段 + +namespace Interact +{ + public partial class StormClient + { + //TCP客户端 + private static TcpClient tcpClient; + //消息发送队列 + private static BlockingCollection sendQueue; + //数据读取线程 + private static Thread readLoopThread; + //数据发送线程 + private static Thread sendLoopThread; + //某些请求需要持续的等待服务器返回数据包,然后将所有数据包处理后提供给用户, + // 此字段用于暂存这些未接收完全的数据包。它的key是对应请求的Token字串。 + private static Dictionary workingDictionary; + + //属性控制字段,任何地方都不应该修改它们 + //发送消息时是否要求服务器返回处理结果。由DoesSendMessageReturn属性控制,不要直接修改这个字段 + private static volatile bool doesSendMessageReturnResult = false; + //当前连接状态。由Status属性控制,不要直接修改这个字段 + private static volatile ClientStatus clientStatus = ClientStatus.Uninitialized; + + //线程安全-锁 + //更改连接状态锁 + private static object statusLocker = new object(); + //"发送消息时是否要求服务器返回处理结果"Falg锁 + private static object doesSendMessageReturnLocker = new object(); + } +} diff --git a/Interact/Client-Handle.cs b/Interact/Client-Handle.cs new file mode 100644 index 0000000..4d66c96 --- /dev/null +++ b/Interact/Client-Handle.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Drawing; +using System.IO; + +//StormClient对数据的处理相关内容 + +namespace Interact +{ + public partial class StormClient + { + //处理连接中断异常。 + private static void HandleConnectionBroken(Exception ex) + { + lock (statusLocker) + { + if (Status != ClientStatus.Running) + return; + statusLocker = ClientStatus.Stopped; + tcpClient.Close(); + OnDisconnect?.Invoke(ex); + } + } + + #region 处理服务器数据 + //根据包头信息处理数据 + private static void HandleAll(byte[] head, byte[] data) + { + string headStr = Encoding.UTF8.GetString(head); + JObject jsonObj = (JObject)JsonConvert.DeserializeObject(headStr); + switch (jsonObj[AttrNames.Operation].ToString()) + { + case Operations.Panic: + HandlePanic(GetResultHead(jsonObj)); + break; + case Operations.Login: + HandleLoginResult(jsonObj, data); + break; + case Operations.TransMessage: + HandleMessage(jsonObj, data); + break; + case Operations.SendMessage: + HandleSendMessageResult(GetResultHead(jsonObj)); + break; + case Operations.Offline: + HandlePanic(GetResultHead(jsonObj)); + break; + case Operations.UpdateUserInfo: + HandleUpdateUserInfoDone(GetResultHead(jsonObj)); + break; + case Operations.GetUsers: + HandleGetUsersPack(jsonObj, data); + break; + default: + break; + } + } + //提取基本结果包头结构 + private static ResultHead GetResultHead(JObject head) + { + ResultHead resultHead = new ResultHead + { + Token = head[AttrNames.Token].ToString(), + Operation = head[AttrNames.Operation].ToString(), + }; + if (head[AttrNames.Error] != null) + resultHead.Error = head[AttrNames.Error].ToString(); + return resultHead; + } + + //处理服务器异常断开连接消息 + private static void HandlePanic(ResultHead head) + { + SetStatus(ClientStatus.Stopped); + OnPanic?.Invoke(head); + } + + //处理登录反馈消息 + private static void HandleLoginResult(JObject head, byte[] data) + { + //提取结果包头 + ResultHead resultHead = GetResultHead(head); + //发起登录完成事件 + if (head[AttrNames.Error].ToString() != "") + { + OnLoginDone?.Invoke(resultHead, null); + return; + } + User user = new User + { + Name = head[AttrNames.User].ToString(), + NickName = head[AttrNames.NickName].ToString(), + Motto = head[AttrNames.Motto].ToString(), + Group = (UserGroup)Enum.Parse(typeof(UserGroup), head[AttrNames.UGroup].ToString()), + Photo = User.DefaultPhoto + }; + if (int.Parse(head[AttrNames.Photo].ToString()) > 0) + { + MemoryStream ms = new MemoryStream(data); + user.Photo = Image.FromStream(ms); + } //用户头像数据 + OnLoginDone?.Invoke(resultHead, user); + } + + //收到消息 + private static void HandleMessage(JObject head, byte[] data) + { + //提取结果包头 + ResultHead resultHead = GetResultHead(head); + Message message = new Message() + { + When = DateTime.Parse(head[AttrNames.When].ToString()), + From = User.FromUserName(head[AttrNames.From].ToString()), + To = User.Me, + Text = Encoding.UTF8.GetString(data) + }; + OnMessage?.Invoke(message); + } + + //发送消息执行结果 + private static void HandleSendMessageResult(ResultHead head) + { + OnSendMessageDone?.Invoke(head); + } + + //处理掉线、强制登出 + private static void HandleOffiline(ResultHead head) + { + SetStatus(ClientStatus.Stopped); + OnOffline?.Invoke(head); + } + + //处理更改信息结果 + private static void HandleUpdateUserInfoDone(ResultHead head) + { + OnUpdateUserInfoDone?.Invoke(head); + } + + //处理获取用户列表的不连续返回包。当所有包接收完毕后返回给客户 + private static void HandleGetUsersPack(JObject head, byte[] data) + { + ResultHead result = GetResultHead(head); + if (head[AttrNames.Error].ToString() != string.Empty) + { + OnGetUserListDone?.Invoke(result, new User[0]); + return; + } //获取失败 + else if (int.Parse(head[AttrNames.Count].ToString()) <= 0) + { + User[] us; + if (workingDictionary.ContainsKey(result.Token)) + us = workingDictionary[result.Token] as User[]; + else + us = new User[0]; + User.Users = us; + OnGetUserListDone?.Invoke(result, us); + return; + } //获取完成 + //获取用户数据 + User user = new User + { + Name = head[AttrNames.User].ToString(), + NickName = head[AttrNames.NickName].ToString(), + Motto = head[AttrNames.Motto].ToString(), + Group = (UserGroup)Enum.Parse(typeof(UserGroup), head[AttrNames.UGroup].ToString()), + Photo = User.DefaultPhoto + }; //基础数据 + if (int.Parse(head[AttrNames.Photo].ToString()) > 0) + { + MemoryStream ms = new MemoryStream(data); + user.Photo = Image.FromStream(ms); + } //头像数据 + //加入临时用户列表缓存 + User[] users; + int total = int.Parse(head[AttrNames.Total].ToString()); //总用户数 + int count = int.Parse(head[AttrNames.Count].ToString()); //已接收用户数 + if (count > total) + return; + if (workingDictionary.ContainsKey(head[AttrNames.Token].ToString())) + users = workingDictionary[result.Token] as User[]; + else + users = new User[total]; + users[count - 1] = user; + workingDictionary[result.Token] = users; + } + #endregion + + } + +} diff --git a/Interact/Client-Read.cs b/Interact/Client-Read.cs new file mode 100644 index 0000000..79c2cf8 --- /dev/null +++ b/Interact/Client-Read.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +using System.Net.Sockets; + +//StormClient读取数据相关内容 +namespace Interact +{ + public partial class StormClient + { + // 数据读取线程,从tcp连接读取数据 + private static void ReadLoop() + { + NetworkStream stream = StormClient.tcpClient.GetStream(); + try + { + while (StormClient.Status == ClientStatus.Running) + { + byte[] head = ReadDataWithLength(stream); + byte[] data = ReadDataWithLength(stream); + HandleAll(head, data); + } + } + catch (System.IO.IOException ex) + { + HandleConnectionBroken(ex); + } + catch (ObjectDisposedException ex) + { + HandleConnectionBroken(ex); + } + } + // 从网络流读取数据 + private static byte[] ReadDataWithLength(NetworkStream stream) + { + byte[] lenBuffer = new byte[4]; //数据长度 + //获取数据长度 + stream.Read(lenBuffer, 0, 4); + int length = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(lenBuffer, 0)); + if (length <= 0) + return null; + //读取数据 + byte[] data = new byte[length]; + stream.Read(data, 0, length); + return data; + } + } +} diff --git a/Interact/Client-Send.cs b/Interact/Client-Send.cs new file mode 100644 index 0000000..589655f --- /dev/null +++ b/Interact/Client-Send.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Newtonsoft.Json; +using System.Net; +using System.Net.Sockets; + +//StormClient发送数据相关内容 +namespace Interact +{ + public partial class StormClient + { + // 消息写入线程,向tcp连接写入数据 + private static void SendLoop() + { + Packet packet = null; + NetworkStream stream = tcpClient.GetStream(); + //json序列化时忽略值为null的项 + JsonSerializerSettings setting = new JsonSerializerSettings(); + setting.NullValueHandling = NullValueHandling.Ignore; + try + { + //阻塞式的从发送队列中取出数据 + foreach (Packet i in sendQueue.GetConsumingEnumerable()) + { + packet = i; //为了在代码块外引用i + //结束循环 + if (StormClient.Status != ClientStatus.Running) + break; + //向tcp连接写数据 + byte[] lenBuffer; //数据长度缓存 + byte[] headData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(packet.Head, setting)); + //写包头 + lenBuffer = BitConverter.GetBytes((UInt32)IPAddress.HostToNetworkOrder(headData.Length)); + stream.Write(lenBuffer, 0, 4); + stream.Write(headData, 0, headData.Length); + //写数据 + int length = 0; + if (packet.Data != null) + length = packet.Data.Length; + lenBuffer = BitConverter.GetBytes((UInt32)IPAddress.HostToNetworkOrder(length)); + stream.Write(lenBuffer, 0, 4); + if (length > 0) + stream.Write(packet.Data, 0, packet.Data.Length); + //发送成功回调 + ResultHead result = new ResultHead + { + Token = packet.Head.Token, + Operation = packet.Head.Operation, + Error = "" + }; + packet.CallBack?.Invoke(result); + } + } + catch (System.IO.IOException ex) + { + HandleConnectionBroken(ex); + } + catch (ObjectDisposedException ex) + { + HandleConnectionBroken(ex); + } + finally + { + List packets = new List(sendQueue.Count + 1); + if (packet != null) + packets.Add(packet); + while (sendQueue.TryTake(out packet)) + { + if (packet != null) + packets.Add(packet); + } + foreach (Packet p in packets) + { + //发送失败回调 + ResultHead result = new ResultHead + { + Token = packet.Head.Token, + Operation = packet.Head.Operation, + Error = "Client has stopped running." + }; + packet.CallBack?.Invoke(result); + } + } + } + //发送数据 + private static bool Send(Packet packet) + { + if (sendLoopThread == null || !sendLoopThread.IsAlive) + return false; + //加入数据发送队列等待 + sendQueue.Add(packet); + return true; + } + } +} diff --git a/Interact/Client.cs b/Interact/Client.cs new file mode 100644 index 0000000..c0560c5 --- /dev/null +++ b/Interact/Client.cs @@ -0,0 +1,260 @@ +using System; +using System.Text; +using System.Net.Sockets; +using System.Threading; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; + +namespace Interact +{ + //连接中断 + public delegate void ConnectionBrokenHandler(Exception ex); + //通用请求结果处理 + public delegate void ResultHandler(ResultHead head); + //消息到达事件处理 + public delegate void MessageHandler(Message msg); + //登录反馈到达 + public delegate void LoginDoneHandler(ResultHead head, User user); + //获取用户列表结果处理 + public delegate void GetUserListDoneHandler(ResultHead head, User[] users); + + /// + /// 与服务器进行数据通信,并向外部提供一系列通信接口 + /// + public static partial class StormClient + { + /// + /// 连接中断事件。注意,此事件并不保证连接中断时总是触发,例如主动关闭连接、登出操作 + /// + public static event ConnectionBrokenHandler OnDisconnect; + /// + /// 服务器异常消息处理事件 + /// + public static event ResultHandler OnPanic; + /// + /// 服务器强制注销登陆事件 + /// + public static event ResultHandler OnOffline; + /// + /// 消息到达事件 + /// + public static event MessageHandler OnMessage; + /// + /// 发送消息处理完成反馈事件(仅在DoesSendMessageReturn为true时有效) + /// + public static event ResultHandler OnSendMessageDone; + /// + /// 更新用户信息结果处理事件 + /// + public static event ResultHandler OnUpdateUserInfoDone; + /// + /// 登录结果处理事件 + /// + public static event LoginDoneHandler OnLoginDone; + /// + /// 获取用户列表结果处理事件 + /// + public static event GetUserListDoneHandler OnGetUserListDone; + + //当前连接状态 + public static ClientStatus Status + { + get { lock (statusLocker) { return clientStatus; } } + } + //发送消息时是否要求服务器返回处理结果 + public static bool DoesSendMessageReturn + { + get { lock (doesSendMessageReturnLocker) { return doesSendMessageReturnResult; } } + set { lock (doesSendMessageReturnLocker) { doesSendMessageReturnResult = value; } } + } + + + static StormClient() + { + tcpClient = new TcpClient(); + sendQueue = new BlockingCollection(); + workingDictionary = new Dictionary(); + SetStatus(ClientStatus.Uninitialized); + } + + //设置当前连接状态 + internal static void SetStatus(ClientStatus value) + { + lock (statusLocker) { clientStatus = value; } + } + + /// + /// 初始化客户端,连接服务器。如果成功则启动数据接收和发送线程。 + /// 注意,当网络不稳定时此方法可能会阻塞。 + /// + /// 如果已连接到服务器返回true,否则返回false。 + public static bool Initialize() + { + if (StormClient.tcpClient.Connected) + return true; + try + { + StormClient.tcpClient.Connect(Interact.Properties.Resources.RemoteServerAddr, int.Parse(Interact.Properties.Resources.RemoteServerPort)); + } + catch (Exception) + { + return false; + } + workingDictionary?.Clear(); //清空临时记录 + if (sendLoopThread != null) + sendLoopThread.Abort(); + if (readLoopThread != null) + readLoopThread.Abort(); + //启动数据收发线程 + sendLoopThread = new Thread(new ThreadStart(SendLoop)); + readLoopThread = new Thread(new ThreadStart(ReadLoop)); + sendLoopThread.IsBackground = readLoopThread.IsBackground = true; + SetStatus(ClientStatus.Running); + sendLoopThread.Start(); + readLoopThread.Start(); + return true; + } + + /// + /// 登录。此操作为异步操作,服务器返回结果后产生OnLoginDone事件。 + /// + /// 用户名 + /// 密码 + /// 数据发送完毕回调函数。注意,结果处理回调请设置OnLoginDone事件 + /// 成功将请求加入发送队列返回true,否则返回false。 + public static bool QueueLogin(string user, string password, Action callback = null) + { + JsonLogInfoHead logInfo = new JsonLogInfoHead() + { + Token = Guid.NewGuid().ToString(), + Operation = Operations.Login, + User = user, + Pwd = password + }; + Packet packet = new Packet + { + Head = logInfo, + Data = null, + CallBack = callback + }; + return Send(packet); + } + + /// + /// 请求发送一条消息。异步,此方法将请求放入请求队列后返回。 + /// + /// 要发送的消息文本 + /// 消息接收者 + /// 数据发送完毕时回调函数。 + /// 成功将请求加入发送队列返回true,否则返回false。 + public static bool QueueSendMessage(Message message, Action callback = null) + { + JsonSendMessageHead jsonObj = new JsonSendMessageHead() + { + Token = "", + Operation = Operations.SendMessage, + To = message.To.Name, + NeedResult = DoesSendMessageReturn ? "1" : "0" + }; + Packet packet = new Packet + { + Head = jsonObj, + Data = Encoding.UTF8.GetBytes(message.Text), + CallBack = callback + }; + return Send(packet); + } + + /// + /// 请求登出。异步,此方法将请求放入请求队列后返回。 + /// + /// 数据发送完毕时回调函数。 + /// 成功将请求加入发送队列返回true,否则返回false。 + public static bool QueueLogout(Action callback = null) + { + BaseHead jsonObj = new BaseHead() + { + Token = "", + Operation = Operations.Logout, + }; + Packet packet = new Packet + { + Head = jsonObj, + Data = null, + CallBack = callback + }; + bool success = Send(packet); + SetStatus(ClientStatus.Stopped); + return success; + } + + /// + /// 请求用户列表。异步,此方法将请求放入请求队列后返回。 + /// + /// 数据发送完毕时回调函数。 + /// 成功将请求加入发送队列返回true,否则返回false。 + public static bool QueueGetUserList(Action callback = null) + { + BaseHead jsonObj = new BaseHead() + { + Token = "", + Operation = Operations.GetUsers, + }; + Packet packet = new Packet + { + Head = jsonObj, + Data = null, + CallBack = callback + }; + return Send(packet); + } + + /// + /// 请求更新当前登录用户信息。异步,此方法将请求放入请求队列后返回。 + /// + /// 新的用户信息,如果某项值为null,则不改变该项。 + /// 数据发送完毕时回调函数。 + /// 成功将请求加入发送队列返回true,否则返回false。 + public static bool QueueUpdateUserInfo(UserInfo userInfo, Action callback = null) + { + byte[] photoData = null; //头像数据 + //将头像数据转换到字符数组 + if (userInfo.Photo != null) + { + MemoryStream memStream = new MemoryStream(); + userInfo.Photo.Save(memStream, userInfo.Photo.RawFormat); + photoData = new byte[memStream.Length]; + memStream.Seek(0, SeekOrigin.Begin); + memStream.Read(photoData, 0, photoData.Length); + } + //构建数据包 + JsonUserInfoHead jsonObj = new JsonUserInfoHead() + { + Token = Guid.NewGuid().ToString(), + Operation = Operations.UpdateUserInfo, + NickName = userInfo.NickName, + Password = userInfo.Password, + Motto = userInfo.Motto, + Photo = userInfo.Photo == null ? 0 : photoData.Length + }; + Packet packet = new Packet + { + Head = jsonObj, + Data = photoData, + CallBack = callback + }; + return Send(packet); + } + + /// + /// 客户端连接状态 + /// + public enum ClientStatus + { + Uninitialized, //未启动 + Running, //运行中 + Stopped //已停止 + } + } +} diff --git a/Interact/Exception.cs b/Interact/Exception.cs new file mode 100644 index 0000000..6634055 --- /dev/null +++ b/Interact/Exception.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Interact +{ + public class ConnectionBrokenException : Exception + { + public ConnectionBrokenException(string message) : base(message) { } + } +} diff --git a/Interact/Interact.csproj b/Interact/Interact.csproj new file mode 100644 index 0000000..7b2a3cb --- /dev/null +++ b/Interact/Interact.csproj @@ -0,0 +1,71 @@ + + + + + Debug + AnyCPU + {CEF054AF-95B7-4B88-934E-02705FEEA554} + Library + Properties + Interact + Interact + v4.0 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + .\Newtonsoft.Json.dll + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + \ No newline at end of file diff --git a/Interact/JsonObject.cs b/Interact/JsonObject.cs new file mode 100644 index 0000000..d70a7c1 --- /dev/null +++ b/Interact/JsonObject.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +//定义Json序列化与反序列化时使用的数据结构,仅内部使用 + +namespace Interact +{ + //登录信息包头 + internal class JsonLogInfoHead : BaseHead + { + public string User; + public string Pwd; + } + + //发送消息包头 + internal class JsonSendMessageHead : BaseHead + { + public string To; //消息接收者用户名 + public string NeedResult = "0"; //是否要求服务器返回处理结果。否传“0” + } + + //更新用户信息包头 + internal class JsonUserInfoHead : BaseHead + { + public string NickName = null; + public string Password = null; + public string Motto = null; + public int Photo = 0; //新头像大小 + } +} diff --git a/Interact/Message.cs b/Interact/Message.cs new file mode 100644 index 0000000..26df5b0 --- /dev/null +++ b/Interact/Message.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Interact +{ + //定义一则用户接收到的消息 + public class Message + { + public Message() { } + public Message(string text, User Receiver) + { + When = DateTime.Now; + From = User.Me; + To = Receiver; + Text = text; + } + + /*保留 + public const UInt32 MaxHeadLength = 4096; //最大包头长度 + public const UInt32 MaxMessageLength = 0x6400000; //最大数据长度 + */ + public DateTime When; //服务器接收时间 + public User From; //消息发送者 + public User To; //消息接收者 + public string Text; //消息内容 + + //将消息保存到数据库(保留) + protected bool Save() { return false; } + } + + //从服务器接收的原始数据封包 + internal class Packet + { + public BaseHead Head; //包头 + public byte[] Data; //包内容 + public Action CallBack; //数据发送完成的回调函数。注意,此时仅保证数据已发送, + //服务器可能并没有处理完成请求 + } + //允许请求的操作列表。具体定义见设计文档 + public static class Operations + { + public const string Panic = "Panic"; + public const string TransMessage = "TransMessage"; + public const string SendMessage = "SendMessage"; + public const string Login = "Login2"; + public const string Logout = "Logout"; + public const string Offline = "Offline"; + public const string UpdateUserInfo = "UpdateUserInfo"; + public const string GetUsers = "GetUsers"; + } + + #region 定义数据交互用到的数据结构(可能作为事件或回调函数的参数) + //包头基本结构 + public class BaseHead + { + //请求ID,由发送方定义的随机字符串。如果接受方有返回数据应包含相同的Token + public string Token; + //操作类型,决定数据的其他内容 + public string Operation; + } + // 接受方返回的执行结果反馈包头,包含错误文本 + public class ResultHead : BaseHead + { + //错误文本,为空则执行成功 + public string Error; + } + //获取单一用户信息结果包头 + #endregion +} diff --git a/Interact/Newtonsoft.Json.dll b/Interact/Newtonsoft.Json.dll new file mode 100644 index 0000000..5b3a1eb Binary files /dev/null and b/Interact/Newtonsoft.Json.dll differ diff --git a/Interact/Properties/AssemblyInfo.cs b/Interact/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..49a4d20 --- /dev/null +++ b/Interact/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Interact")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Interact")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("cef054af-95b7-4b88-934e-02705feea554")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Interact/Properties/Resources.Designer.cs b/Interact/Properties/Resources.Designer.cs new file mode 100644 index 0000000..66f8f97 --- /dev/null +++ b/Interact/Properties/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Interact.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Interact.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to mattuy.top. + /// + internal static string RemoteServerAddr { + get { + return ResourceManager.GetString("RemoteServerAddr", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 3727. + /// + internal static string RemoteServerPort { + get { + return ResourceManager.GetString("RemoteServerPort", resourceCulture); + } + } + } +} diff --git a/Interact/Properties/Resources.resx b/Interact/Properties/Resources.resx new file mode 100644 index 0000000..f7b9790 --- /dev/null +++ b/Interact/Properties/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + mattuy.top + + + 3727 + + \ No newline at end of file diff --git a/Interact/User.cs b/Interact/User.cs new file mode 100644 index 0000000..5976cc1 --- /dev/null +++ b/Interact/User.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Drawing; + +namespace Interact +{ + //用户 + public class User + { + //根据用户名匹配用户。如果用户不存在,则返回一个仅用户名有效的实例 + internal static User FromUserName(string userName) + { + if(User.Users != null) + { + foreach (User user in User.Users) + { + if (user.Name == userName) + return user; + } + } + User unidentifiedUser = new User + { + Name = userName, + NickName = "", + Motto = "", + Group = UserGroup.User, + Photo = User.DefaultPhoto + }; + return unidentifiedUser; + } + internal static User[] Users; //用户列表。仅Interact内部访问 + + public static User Me; //当前用户 + public static Image DefaultPhoto; //默认头像 + + public string Name; //用户名 + public string NickName; //昵称 + public string Motto; //个性签名 + public UserGroup Group; //用户组 + public Image Photo; //头像 + } + + //更新用户信息时新的用户信息。如果项值为null则不更改相应的信息 + public class UserInfo + { + public string Password = null; //新密码 + public string NickName = null; //新昵称 + public string Motto = null; //新签名 + public Image Photo = null; //新头像 + } + + //用户类型 + public enum UserGroup + { + User, //普通用户 + Vip, //会员 + Admin, //管理员 + Group //群组 + } +} diff --git a/StormChat.sln b/StormChat.sln new file mode 100644 index 0000000..a6d4cca --- /dev/null +++ b/StormChat.sln @@ -0,0 +1,51 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2041 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interact", "Interact\Interact.csproj", "{CEF054AF-95B7-4B88-934E-02705FEEA554}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StormChatWPF", "StormChatWPF\StormChatWPF.csproj", "{CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CEF054AF-95B7-4B88-934E-02705FEEA554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CEF054AF-95B7-4B88-934E-02705FEEA554}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CEF054AF-95B7-4B88-934E-02705FEEA554}.Debug|x64.ActiveCfg = Debug|Any CPU + {CEF054AF-95B7-4B88-934E-02705FEEA554}.Debug|x64.Build.0 = Debug|Any CPU + {CEF054AF-95B7-4B88-934E-02705FEEA554}.Debug|x86.ActiveCfg = Debug|Any CPU + {CEF054AF-95B7-4B88-934E-02705FEEA554}.Debug|x86.Build.0 = Debug|Any CPU + {CEF054AF-95B7-4B88-934E-02705FEEA554}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CEF054AF-95B7-4B88-934E-02705FEEA554}.Release|Any CPU.Build.0 = Release|Any CPU + {CEF054AF-95B7-4B88-934E-02705FEEA554}.Release|x64.ActiveCfg = Release|Any CPU + {CEF054AF-95B7-4B88-934E-02705FEEA554}.Release|x64.Build.0 = Release|Any CPU + {CEF054AF-95B7-4B88-934E-02705FEEA554}.Release|x86.ActiveCfg = Release|Any CPU + {CEF054AF-95B7-4B88-934E-02705FEEA554}.Release|x86.Build.0 = Release|Any CPU + {CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Debug|x64.ActiveCfg = Debug|Any CPU + {CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Debug|x64.Build.0 = Debug|Any CPU + {CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Debug|x86.ActiveCfg = Debug|Any CPU + {CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Debug|x86.Build.0 = Debug|Any CPU + {CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Release|Any CPU.Build.0 = Release|Any CPU + {CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Release|x64.ActiveCfg = Release|Any CPU + {CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Release|x64.Build.0 = Release|Any CPU + {CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Release|x86.ActiveCfg = Release|Any CPU + {CF4D6BE0-A862-401F-A3A6-4FDAC32F5B70}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {16287CCA-1001-41B2-B0BA-101618B1DCFB} + EndGlobalSection +EndGlobal diff --git a/StormChatWPF/App.config b/StormChatWPF/App.config new file mode 100644 index 0000000..74ade9d --- /dev/null +++ b/StormChatWPF/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/StormChatWPF/App.xaml b/StormChatWPF/App.xaml new file mode 100644 index 0000000..1fc1979 --- /dev/null +++ b/StormChatWPF/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/StormChatWPF/App.xaml.cs b/StormChatWPF/App.xaml.cs new file mode 100644 index 0000000..55f30e6 --- /dev/null +++ b/StormChatWPF/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace StormChatWPF +{ + /// + /// App.xaml 的交互逻辑 + /// + public partial class App : Application + { + } +} diff --git a/StormChatWPF/Chat.cs b/StormChatWPF/Chat.cs new file mode 100644 index 0000000..0e54842 --- /dev/null +++ b/StormChatWPF/Chat.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Interact; +using System.Drawing; +using System.Windows; + +namespace StormChatWPF +{ + class Chat + { + public static List ContactsList;//联系人集合 + public static Chat chat=new Chat(); + private Chat() + { + StormClient.OnLoginDone += OnHaveLogin; + StormClient.OnGetUserListDone += OnGetContactsList; + StormClient.OnMessage +=OnMessage; + } + internal static User CurrentContact { get; set; }//设置当前联系人对象 + /// + /// 获取联系人列表(完成后打开主窗体) + /// + private void OnGetContactsList(ResultHead head, User[] users) + { + if (head.Error != "") + { + MessageBox.Show("无法获取联系人列表"); + return; + } + App.Current.Dispatcher.Invoke( + (Action)delegate () + { + ContactsList = new List(users); + MainWindow mainWindow = new MainWindow(); + mainWindow.Show(); + LogWindow.Instence.Close(); + }); + } + private void OnHaveLogin(ResultHead head, User user) + { + if (head.Error == "") + { + User.Me = user; + StormClient.QueueGetUserList(); + } + else + { + MessageBox.Show("请核对账号密码!"); + } + }//登录完成 + private void OnMessage(Message message) + { + if (MainWindow.Instence != null) + { + MainWindow.Instence.ShowMessage(message); + } + }//接受到新消息 + + internal void SendMessage(string text,User target) + { + Message msg = new Message(text,target); + Action f = delegate (ResultHead head) + { + MainWindow.Instence.ShowMessage(msg); + }; + StormClient.QueueSendMessage(msg,f); + }//发送消息 + internal static void Log(string user, string password) + { + if (StormClient.Initialize()) + { + if (!StormClient.QueueLogin(user, password)) + { + MessageBox.Show("登录失败"); + } + } + else + { + MessageBox.Show("连接服务器失败!"); + } + }//登录 + + } +} diff --git a/StormChatWPF/LogWindow.xaml b/StormChatWPF/LogWindow.xaml new file mode 100644 index 0000000..86d517f --- /dev/null +++ b/StormChatWPF/LogWindow.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + +