UWP开发之StreamSocket聊天室(二)

本节主要知识点:
    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);
//触发新消息到达Action
MsgReceivedAction?.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
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值