UWP开发之StreamSocket聊天室(二)
来源:互联网 发布:php base64编码 编辑:程序博客网 时间:2024/05/16 18:42
本节主要知识点:
1.StreamSocket
2.StreamSocketListener
3.Json序列化和反序列化(Json.Net 第三方库)
4.DataWriter
5.DataReader
这篇博客我们接着上次的说,今天来实现SocketBusiness项目里面的代码
SocketBusiness 主要用来处理StreamSocket客户端和服务端的逻辑的,先看下UML图大致了解下类关系:
SocketBusiness中有四个类:
SocketFactory :用来根据条件生成 客户端/服务端 ,简单工厂
SocketBase : Socket客户端/服务端的抽象类 ,包含 客户端/服务端 通用的一些方法
ClientSocket:客户端Socket类,继承与SocketBase
ServerSocket:服务端Socket类,继承与SocketBase
有人或许有点疑惑为什么要这样设计,其实是我偷懒了,为什么这么说?因为前不久刚做过一个小项目是客户端和服务端同体的App,我直接把SocketBusiness直接拿过来用了,什么是客户端和服务端同体的App,就是说任何一台机器的App都有可能为服务端或者客户端,为了达到代码的复用性,所以就变成上面这种类关系了,由SocketBase提供抽象方法(启动服务端监听/连接服务端、关闭服务端监听/关闭客户端连接)以及统一的发送消息处理消息的逻辑,而像连接服务端/启动监听这种操作交由具体的客户端类和服务端类去实现。
而在我们的聊天室Demo中其实没必要这样做,我们完全可以将客户端ClientSocket的代码放到客户端项目中、ServerSocket的代码放到服务端项目中
但懒得改了,这样做虽然体现不出来什么好处,但绝对也没啥坏处。
那我们来看看代码,注释写的很详细的我就不解释了
SocketBase
无论是服务端还是客户端,如果想要发送数据到远程目标计算机,都需要先拿到对方的Socket对象的OutputStream流,然后使用DataWriter对象的各种Write方法写入数据,最后通过调用DataWriter对象的StoreAsync异步发送数据。
DataWriter支持写入很多种数据,具体如下:
不过我还是建议发送数据时,将数据的Model进行json或者xml序列化为string,然后再将数据string转为byte[] 作为最终数据在网络中传输。
ServerSocket
先贴下完整代码:
服务端启动监听的步骤:
主要代码片段:
当有客户端Socket请求连接服务端Socket时,就会触发StreamSocketListener监听器的ConnectionReceived事件,在事件中,我们通过事件StreamSocketListenerConnectionReceivedEventArgs参数可以拿到客户端的socket对象
然后使用客户端的Socket.InputStream 来初始化 DataReader对象,使用DataReader来循环读取数据流即可。
作为聊天室的服务端我们除了要处理每个客户端的数据外,我们在接受到每个客户端的消息时还要把该消息转发到其他客户端,所以在接受到数据后要坐下客户端群转发数据。
主要代码片段:
ClientSocket
客户端代码和服务端代码类似,唯一不同的就是需要使用StreamSocket对象的ConnectAsync方法来连接服务端计算机,连接成功后的操作和服务端的操作就一样了,也是使用DataReader对象来读取目标计算机发送来的数据
Ok,今天就到这吧,下篇来写服务端UI和界面逻辑的实现。
本文出自:53078485群大咖Aran
1.StreamSocket
2.StreamSocketListener
3.Json序列化和反序列化(Json.Net 第三方库)
4.DataWriter
5.DataReader
这篇博客我们接着上次的说,今天来实现SocketBusiness项目里面的代码
SocketBusiness 主要用来处理StreamSocket客户端和服务端的逻辑的,先看下UML图大致了解下类关系:
SocketBusiness中有四个类:
SocketFactory :用来根据条件生成 客户端/服务端 ,简单工厂
SocketBase : Socket客户端/服务端的抽象类 ,包含 客户端/服务端 通用的一些方法
ClientSocket:客户端Socket类,继承与SocketBase
ServerSocket:服务端Socket类,继承与SocketBase
有人或许有点疑惑为什么要这样设计,其实是我偷懒了,为什么这么说?因为前不久刚做过一个小项目是客户端和服务端同体的App,我直接把SocketBusiness直接拿过来用了,什么是客户端和服务端同体的App,就是说任何一台机器的App都有可能为服务端或者客户端,为了达到代码的复用性,所以就变成上面这种类关系了,由SocketBase提供抽象方法(启动服务端监听/连接服务端、关闭服务端监听/关闭客户端连接)以及统一的发送消息处理消息的逻辑,而像连接服务端/启动监听这种操作交由具体的客户端类和服务端类去实现。
而在我们的聊天室Demo中其实没必要这样做,我们完全可以将客户端ClientSocket的代码放到客户端项目中、ServerSocket的代码放到服务端项目中
但懒得改了,这样做虽然体现不出来什么好处,但绝对也没啥坏处。
那我们来看看代码,注释写的很详细的我就不解释了
SocketBase
namespace SocketBusiness{ /// <summary> /// Socket客户端/服务端 工厂 /// </summary> public class SocketFactory { public static SocketBase CreatInkSocket(bool isServer, string ip, string serviceName) { return isServer ? (SocketBase) new ServerSocket(ip, serviceName) : new ClientSocket(ip, serviceName); } } public abstract class SocketBase { /// <summary> /// 客户端列表 /// </summary> protected List<StreamSocket> ClientSockets; /// <summary> /// 用于连接或监听的ip地址 /// </summary> protected string IpAddress; /// <summary> /// 标识是否为服务端 /// </summary> protected bool IsServer; /// <summary> /// 新消息到达通知 /// </summary> public Action<MessageModel> MsgReceivedAction; /// <summary> /// 服务端启动监听/客户端启动连接 失败时的通知 /// </summary> public Action<Exception> OnStartFailed; /// <summary> /// 服务端启动监听/客户端启动连接 成功时的通知 /// </summary> public Action OnStartSuccess; /// <summary> /// 连接或监听的端口号 /// </summary> protected string RemoteServiceName; /// <summary> /// 客户端Socket对象 /// </summary> protected StreamSocket Socket; /// <summary> /// 是否在监听端口/是否和服务端在保持着连接 /// </summary> public bool Working; /// <summary> /// 客户端/服务端 流写入器 /// </summary> protected DataWriter Writer; /// <summary> /// 客户端连接到服务器 / 服务端启动监听 /// </summary> /// <returns></returns> public abstract Task Start(); /// <summary> /// 客户端断开连接 / 服务端停止监听 /// </summary> public abstract void Dispose(); /// <summary> /// 发送消息 /// </summary> /// <param name="msg">消息对象</param> /// <param name="client">客户端Client对象</param> /// <returns></returns> public async Task SendMsg(MessageModel msg, StreamSocket client = null) { if (msg != null) { await SendData(client, JsonConvert.SerializeObject(msg)); } } protected async Task SendData(StreamSocket client, string data) { try { if (!Working) return; if (string.IsNullOrEmpty(data)) return; if (!IsServer) { if (Writer == null) { Writer = new DataWriter(Socket.OutputStream); } await WriterData(data); } else if (IsServer) { foreach (var clientSocket in ClientSockets.Where(s => s != client)) { try { Writer = new DataWriter(clientSocket.OutputStream); await WriterData(data); //分离流 防止OutputStream对象被释放 Writer.DetachStream(); } catch (Exception) { // } } } } catch (Exception exception) { if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) { throw; } Debug.WriteLine("Send failed with error: " + exception.Message); } } private async Task WriterData(string data) { //转成 byte[] 发送 var bytes = Encoding.UTF8.GetBytes(data); //先写入数据的长度 Writer.WriteInt32(bytes.Length); //写入数据 Writer.WriteBytes(bytes); await Writer.StoreAsync(); Debug.WriteLine("Data sent successfully."); } }}
SocketBase.cs中的代码主要是定义服务端/客户端的抽象方法以及实现发送数据的逻辑
无论是服务端还是客户端,如果想要发送数据到远程目标计算机,都需要先拿到对方的Socket对象的OutputStream流,然后使用DataWriter对象的各种Write方法写入数据,最后通过调用DataWriter对象的StoreAsync异步发送数据。
DataWriter支持写入很多种数据,具体如下:
// 将布尔值写入输出流。public void WriteBoolean(System.Boolean value); // 将指定缓冲区的内容写入输出流。public void WriteBuffer(IBuffer buffer); // 从缓冲区的指定字节写入输出流。public void WriteBuffer(IBuffer buffer, System.UInt32 start, System.UInt32 count); // 将字节值写入输出流。public void WriteByte(System.Byte value); // 将一个字节值数组写入到输出流。public void WriteBytes(System.Byte[] value); // 将DateTime写入到输出流。public void WriteDateTime(DateTimeOffset value); // 将浮点值写入输出流。public void WriteDouble(System.Double value); // 将 GUID 值写入输出流。public void WriteGuid(Guid value); // 将 16 位整数值写入输出流。public void WriteInt16(System.Int16 value); // 将 32 位整数值写入输出流。public void WriteInt32(System.Int32 value); // 将 64 位整数值写入输出流。public void WriteInt64(System.Int64 value); // 将浮点值写入输出流。public void WriteSingle(System.Single value); // 将字符串值写入输出流。public System.UInt32 WriteString(System.String value); // 将TimeSpan写入输出流。public void WriteTimeSpan(TimeSpan value); // 将 16位无符号整数值写入输出流。public void WriteUInt16(System.UInt16 value); // 将 32 位无符号整数值写入输出流。public void WriteUInt32(System.UInt32 value); // 将 64 位无符号整数值写入输出流。public void WriteUInt64(System.UInt64 value);
不过我还是建议发送数据时,将数据的Model进行json或者xml序列化为string,然后再将数据string转为byte[] 作为最终数据在网络中传输。
ServerSocket
先贴下完整代码:
public class ServerSocket : SocketBase{ /// <summary> /// Socket监听器 /// </summary> protected StreamSocketListener Listener; /// <summary> /// 构造函数 /// </summary> /// <param name="ip">ip</param> /// <param name="remoteServiceName">端口号</param> public ServerSocket(string ip, string remoteServiceName) { IpAddress = ip; RemoteServiceName = remoteServiceName; } /// <summary> /// 启动监听 /// </summary> /// <returns></returns> public override async Task Start() { try { if (Working) return; IsServer = true; ClientSockets = new List<StreamSocket>(); Listener = new StreamSocketListener() { Control = { KeepAlive = false } }; Listener.ConnectionReceived += OnConnection; //新连接接入时的事件 await Listener.BindEndpointAsync(new HostName(IpAddress), RemoteServiceName); Working = true; OnStartSuccess?.Invoke(); } catch (Exception exc) { OnStartFailed?.Invoke(exc); } } private async void OnConnection(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) { Writer = null; //获取新接入的Socket的InputStream 来读取远程目标发来的数据 var reader = new DataReader(args.Socket.InputStream); //添加一个StreamSocket客户端到 客户端列表 ClientSockets.Add(args.Socket); try { while (Working) { //等待数据进来 var sizeFieldCount = await reader.LoadAsync(sizeof(uint)); if (sizeFieldCount != sizeof(uint)) { reader.DetachStream(); reader.Dispose(); //主动断开连接 ClientSockets?.Remove(args.Socket); return; } var stringLength = reader.ReadUInt32(); //先获取数据的长度 var actualStringLength = await reader.LoadAsync(stringLength); if (stringLength != actualStringLength) { //数据接收中断开连接 reader.DetachStream(); reader.Dispose(); ClientSockets?.Remove(args.Socket); return; } var dataArray = new byte[actualStringLength]; //根据数据长度获取数据 reader.ReadBytes(dataArray); //转为json数据字符串 var dataJson = Encoding.UTF8.GetString(dataArray); //反序列化数据为对象 var data = JsonConvert.DeserializeObject<MessageModel>(dataJson); //给所有客户端发送数据 await SendMsg(data, args.Socket); //触发新消息到达Action MsgReceivedAction?.Invoke(data); } } catch (Exception exception) { if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) { // } Debug.WriteLine(string.Format("Received data: \"{0}\"", "Read stream failed with error: " + exception.Message)); reader.DetachStream(); reader.Dispose(); ClientSockets?.Remove(args.Socket); } } public async override void Dispose() { Working = false; //给所有客户端发送断开服务的消息 await SendMsg(new MessageModel { MessageType = MessageType.Disconnect }); foreach (var clientSocket in ClientSockets) { clientSocket.Dispose(); } ClientSockets.Clear(); ClientSockets = null; Listener.ConnectionReceived -= OnConnection; Listener?.CancelIOAsync(); Listener.Dispose(); Listener = null; }}
服务端启动监听的步骤:
- 创建StreamSocketListener监听对象
- 订阅SteamSocketListener对象的ConnectionReceived事件
- 调用SteamSocketListener对象的BindEndpointAsync方法来启动Socket监听
主要代码片段:
Listener = new StreamSocketListener(){ Control = { KeepAlive = false }};Listener.ConnectionReceived += OnConnection; //新连接接入时的事件await Listener.BindEndpointAsync(new HostName(IpAddress), RemoteServiceName);
当有客户端Socket请求连接服务端Socket时,就会触发StreamSocketListener监听器的ConnectionReceived事件,在事件中,我们通过事件StreamSocketListenerConnectionReceivedEventArgs参数可以拿到客户端的socket对象
然后使用客户端的Socket.InputStream 来初始化 DataReader对象,使用DataReader来循环读取数据流即可。
作为聊天室的服务端我们除了要处理每个客户端的数据外,我们在接受到每个客户端的消息时还要把该消息转发到其他客户端,所以在接受到数据后要坐下客户端群转发数据。
主要代码片段:
var dataArray = new byte[actualStringLength];//根据数据长度获取数据reader.ReadBytes(dataArray);//转为json数据字符串var dataJson = Encoding.UTF8.GetString(dataArray);//反序列化数据为对象var data = JsonConvert.DeserializeObject<MessageModel>(dataJson);//给所有客户端发送数据await SendMsg(data, args.Socket);//触发新消息到达ActionMsgReceivedAction?.Invoke(data);
ClientSocket
public class ClientSocket : SocketBase{ private HostName _hostName; /// <summary> /// 构造函数 /// </summary> /// <param name="ip">目标ip</param> /// <param name="remoteServiceName">端口号</param> public ClientSocket(string ip, string remoteServiceName) { IsServer = false; IpAddress = ip; RemoteServiceName = remoteServiceName; } /// <summary> /// 开始连接到服务端 /// </summary> /// <returns></returns> public override async Task Start() { try { if (Working) return; _hostName = new HostName(IpAddress); //初始化StreamSocket对象 Socket = new StreamSocket(); Socket.Control.KeepAlive = false; Debug.WriteLine(("Connecting to: " + _hostName.DisplayName)); //开始连接目标计算机 await Socket.ConnectAsync(_hostName, RemoteServiceName); OnStartSuccess?.Invoke(); Debug.WriteLine("Connected"); Working = true; await Task.Run(async () => { //创建一个读取器 来读取服务端发送来的数据 var reader = new DataReader(Socket.InputStream); try { while (Working) { var sizeFieldCount = await reader.LoadAsync(sizeof(uint)); if (sizeFieldCount != sizeof(uint)) { //主动断开连接 reader.DetachStream(); OnStartFailed?.Invoke(new Exception("断开连接")); Dispose(); return; } var stringLength = reader.ReadUInt32(); var actualStringLength = await reader.LoadAsync(stringLength); if (stringLength != actualStringLength) { //数据接收中断开连接 reader.DetachStream(); OnStartFailed?.Invoke(new Exception("断开连接")); Dispose(); return; } //接受数据 var dataArray = new byte[actualStringLength]; reader.ReadBytes(dataArray); //转为json字符串 var dataJson = Encoding.UTF8.GetString(dataArray); //反序列化为数据对象 var data = JsonConvert.DeserializeObject<MessageModel>(dataJson); //新消息到达通知 MsgReceivedAction?.Invoke(data); } } catch (Exception exception) { if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) { } Debug.WriteLine(string.Format("Received data: \"{0}\"", "Read stream failed with error: " + exception.Message)); reader.DetachStream(); OnStartFailed?.Invoke(exception); Dispose(); } }); } catch (Exception exception) { if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) { } Debug.WriteLine(string.Format("Received data: \"{0}\"", "Read stream failed with error: " + exception.Message)); OnStartFailed?.Invoke(exception); Dispose(); } } public override void Dispose() { Working = false; Writer = null; Socket?.Dispose(); Socket = null; }}
客户端代码和服务端代码类似,唯一不同的就是需要使用StreamSocket对象的ConnectAsync方法来连接服务端计算机,连接成功后的操作和服务端的操作就一样了,也是使用DataReader对象来读取目标计算机发送来的数据
Ok,今天就到这吧,下篇来写服务端UI和界面逻辑的实现。
本文出自:53078485群大咖Aran
0 0
- UWP开发之StreamSocket聊天室(二)
- UWP开发之StreamSocket聊天室 (一)
- UWP开发之StreamSocket聊天室(三)
- UWP开发之StreamSocket聊天室(四)
- UWP开发之StreamSocket聊天室(五)
- UWP之使用StreamSocket建立聊天室
- UWP之C++/CX开发
- uwp开发之 设置储存
- UWP开发入门系列笔记之(一):UWP初览
- Android开发之聊天室
- Win10的UWP开发之Hello World
- 【Win10】UAP/UWP/通用 开发之 SplitView
- UWP开发大坑之----路由事件
- UWP开发入门系列笔记之(零):UWP的前世今生
- Win10的UWP之标题栏的返回键(二)
- java网络编程之聊天室客户端(二)
- java socket编程之聊天室(二)
- Win10开发之UWP控件的隐藏空间
- javaScript的on方法,动态触发事件
- C++判断域名是否合法
- Activity启动过程详解
- iOS9 HTTP 不能正常使用的解决办法
- composer新理解
- UWP开发之StreamSocket聊天室(二)
- struct tm 与 time_t
- SpringMVC的几种返回方式
- @SuppressWarnings("resource")
- 模板_线段树
- spring mvc 返回json的配置
- 一个libvirt/qemu创建虚拟机错误的解决办法
- android源码学习之源码编译并nexus s真机刷机
- 转-局部搜索的形象描述