一、了解Socket编程
Socket就是在两个端口之间建立管道连接来传输数据。
二、SocketStream使用流程
由于聊天室基于C/S模型,所以需要Client客户端与Server模型,在两个UWP之间是不能进行Socket通信的,所以测试的时候可以写在同一个UWP内。
建立TCP的连接。
客户端:
public async Task Start()
{
try
{
if (_isWorking == true) return;
HostName _hostname = new HostName(_IPAddress);
_streamSocket = new StreamSocket();
_streamSocket.Control.KeepAlive = false;
await _streamSocket.ConnectAsync(_hostname, _port);
_isWorking = true;
await Task.Run(async () =>
{
var reader = new DataReader(_streamSocket.InputStream);
reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
try
{
while (_isWorking)
{
await reader.LoadAsync(sizeof(uint));
uint len = reader.ReadUInt32();
await reader.LoadAsync(len);
string msg = reader.ReadString(reader.UnconsumedBufferLength);
}
}
catch (Exception ex)
{
}
});
}
catch (Exception ex)
{
}
}
开始建立连接,其中各个变量名即为类名。
在使用中await _client.Start()即可建立连接,注意ip和remoteServiceName的赋值;
发送消息:
public async Task SendMsg(string data)
{
if (!_isWorking)
return;
if (_streamSocket == null)
return;
if(_writer == null)
_writer = new DataWriter(_streamSocket.OutputStream);
MessageModel msg = new MessageModel
{
_message = data,
_sendTime = DateTime.Now,
_messageType = MessageType.TextMessage,
horizontal = Windows.UI.Xaml.HorizontalAlignment.Left
};
await WriteData(JsonConvert.SerializeObject(msg));
}
private async Task WriteData(string data)
{
var bytes = Encoding.UTF8.GetBytes(data);
_writer.WriteInt32(bytes.Length);
_writer.WriteBytes(bytes);
await _writer.StoreAsync();
}
发送消息主要是借助DataWriter在已经建立的socket连接的StreamSocket的OutputStream中写入数据。其中MessageModel为发送消息的模型,自定义类型,转换为json字符串发送出去,在接收端再重新反序列化。
核心代码:
await _streamSocket.ConnectAsync(_hostname, _port);
await _writer.StoreAsync();
注意:接收数据也可以放在Client中,对StreamSocket的InputStream进行读操作即可,但由于聊天室基于Peer to Peer模型,没有核心服务器,所以每个节点都需要建立多个客户端给不同的服务端发送数据,一个服务端负责接收数据。
服务端:
public async Task Start()
{
try
{
if (_isWorking == true) return;
ClientSockets = new List<StreamSocket>();
_streamSocketListener = new StreamSocketListener() { Control = { KeepAlive = false } };
_streamSocketListener.ConnectionReceived += OnConnectedReceived;
await _streamSocketListener.BindEndpointAsync(new HostName(_IPAddress), _port);
_isWorking = true;
//SuccessInvoke
}
catch
{
//Fail.Invoke
StopServer();
}
}
private async void OnConnectedReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
{
_dataWriter = null;
//读取数据
var reader = new DataReader(args.Socket.InputStream);
//增加到socket列表中
ClientSockets.Add(args.Socket);
try
{
//维持数据流
while (_isWorking == true)
{
await reader.LoadAsync(sizeof(uint));
//第一个数字保存长度
uint len = reader.ReadUInt32();
var actualStringLength = await reader.LoadAsync(len);
var dataArray = new byte[actualStringLength];
reader.ReadBytes(dataArray);
var dataJson = Encoding.UTF8.GetString(dataArray);
var data = JsonConvert.DeserializeObject<MessageModel>(dataJson);
Messenger.Default.Send(data, "MsgReceivedAction");
//MsgRcv.Invoke(data, new EventArgs() );
//MsgRcv.Invoke(new Action() => _RcvMessages?.Add(new MessageModel() { _message = data, horizontal = Windows.UI.Xaml.HorizontalAlignment.Left }));
//Thread loop = new Thread();
//_RcvMessages?.Add(new MessageModel() { _message = data, horizontal = Windows.UI.Xaml.HorizontalAlignment.Left }));
//var data = JsonConvert.DeserializeObject<MessageModel>(msg);
//sendMsg(args.Socket, data);
//MsgReceivedAction?.Invoke(data);
}
}
catch (Exception)
{
//
}
}
服务端就不需要发送数据的部分了,值得注意的是OnConnectedReceived是接受新的连接的时候进行的操作,在内部用lambda表达式建立一个async异步执行的函数,while(1)执行,当接受到数据的时候用mvvmlight框架(NuGet包管理器内搜索)进行数据显示,在不同的page间进行事件绑定,在主页面注册一个函数,进行事件处理:
Messenger.Default.Register<MessageModel>(this, "MsgReceivedAction", MsgReceivedAction);
private void MsgReceivedAction(MessageModel obj)
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
Messages.Add(obj);
CurrentChat._message.Add(obj);
//CurrentChat._message.Add();
});
}
其中Messages是一个ObservableCollection<MessageModel>类型的对象,用于在ListView中显示Message.
由于ObservableCollection需要重新绘制UI,所以只用简单的方法,比如上文中注释的所有方法都是行不通的,如简单地传送进去ObservableCollection对象Add,或者Action.Invoke等,需要借助DispatcherHelper或者系统的Dispatch来实现想要的效果。
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
这个语句可以实现条目控件可以
充满ListViewItem。
一个聊天室的基本原理大致就是如此,程序员的魅力大多来自于创造性,太多的知识反而成为了成长的桎梏。