一、瞭解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。一個聊天室的基本原理大致就是如此,程序員的魅力大多來自於創造性,太多的知識反而成爲了成長的桎梏。