UWP之使用StreamSocket建立聊天室

一、瞭解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。


一個聊天室的基本原理大致就是如此,程序員的魅力大多來自於創造性,太多的知識反而成爲了成長的桎梏。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章