本节主要知识点:
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);
//触发新消息到达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