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  
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;    }}


 服务端启动监听的步骤:
  1.     创建StreamSocketListener监听对象
  2.     订阅SteamSocketListener对象的ConnectionReceived事件
  3.     调用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
原创粉丝点击