1. 程式人生 > >C#:簡單的Socket非同步通訊功能(客戶端)

C#:簡單的Socket非同步通訊功能(客戶端)

上一篇(http://www.rexcao.net/archives/159)講了服務端使用Socket傳送訊息的方法,這一篇來解決一下客戶端如何接收服務端訊息的問題。

目標

1、非同步接收服務端訊息並顯示

2、傳送自定義訊息給服務端(由於上一篇做的是Windows服務,沒有介面,這個需要另行處理才可以顯示客戶端的訊息)

思路

1、非同步從一個已連線的Socket物件中獲取訊息

2、在按鈕的點選事件中向服務端傳送訊息

注意事項

1、因為使用的是非同步通訊,回撥方法是由系統執行的,這個牽扯到跨程序呼叫資源的問題

2、一旦一個Socket連線斷開後,再次建立連線需要建立一個新的Socket物件來與服務端建立連線。

程式碼

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net.Sockets;


namespace FormClient
{
    public partial class Form1 : Form
    {
        private delegate void FlushClient(string msg);//使用委託解決跨執行緒呼叫問題
        private delegate void DelegateControls(bool val, string msg);
        Socket socket;
        static string host = "127.0.0.1";
        static int port = 4530;
        static byte[] buffer = new byte[1024];
        public Form1()
        {
            InitializeComponent();
        }


        private void Form1_Load(object sender, EventArgs e)
        {
            this.ConServer();
        }


        private void btnRetry_Click(object sender, EventArgs e)
        {
            this.ConServer();
        }
        /// <summary>
        /// 向textbox寫入資訊並使其滾動至底部
        /// 支援跨執行緒呼叫(使用委託實現,參考:http://bbs.csdn.net/topics/280001358 )
        /// </summary>
        /// <param name="msg">要顯示的訊息文字</param>
        private void WriteScroll(string msg)
        {
            if (this.InvokeRequired)
            {
                FlushClient fc = new FlushClient(WriteScroll);
                object[] ps = new object[] { msg };
                this.Invoke(fc, ps);
                return;
            }
            else
            {
                msg = msg + "\r\n";
                textBox1.Text += msg;
                textBox1.Focus();
                textBox1.Select(textBox1.TextLength, 0);
                textBox1.ScrollToCaret();
            }
        }
        /// <summary>
        /// 連線伺服器
        /// </summary>
        private void ConServer()
        {
            this.WriteScroll("Trying to connect the server...");
            try
            {
                this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                this.socket.Connect(host, port);
                this.socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), this.socket);
                ControlsSwitch(false, "");
            }
            catch (SocketException ex)
            {
                this.ControlsSwitch(true, ex.Message);
            }
            catch (Exception ex)
            {
                this.WriteScroll(ex.Message);
            }
        }
        /// <summary>
        /// 接收來自伺服器的訊息
        /// </summary>
        /// <param name="ar"></param>
        public void ReceiveMessage(IAsyncResult ar)
        {
            try
            {
                var socket = ar.AsyncState as Socket;


                var length = socket.EndReceive(ar);
                //讀取出來訊息內容
                var message = Encoding.Unicode.GetString(buffer, 0, length);
                //顯示訊息
                this.WriteScroll("From host:" + message);


                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket);
            }
            catch (SocketException ex)
            {
                ControlsSwitch(true, ex.Message);
            }
            catch (InvalidOperationException ex)
            {
                ControlsSwitch(true, ex.Message);
            }


        }




        private void ControlsSwitch(bool val, string msg)
        {
            if (this.InvokeRequired)
            {
                DelegateControls dc = new DelegateControls(ControlsSwitch);
                object[] ps = new object[] { val, msg };
                this.Invoke(dc, ps);
            }
            else 
            {
                btnRetry.Visible = val;
                lblStatus.Text = msg;
            }
        }


        /// <summary>
        /// 任何時候想要WriteScroll時,都呼叫這個方法,而不是直接呼叫WriteScroll
        /// 這個方法可以根據你的呼叫是否跨執行緒而進行相應的處理
        /// </summary>
        /// <param name="msg"></param>
        public void InDirectOutput(string msg)
        {
            if (this.InvokeRequired)//根據這個來判斷是否跨執行緒
            {
                //是跨執行緒,則用Invoke的方式,在建立的執行緒內執行
                FlushClient fc = new FlushClient(WriteScroll);
                //這裡初始化引數並傳入Invoke函式。
                object[] ps = new object[] { msg };
                this.Invoke(fc, ps);
                return;
            }
            else 
            {
                //不是跨執行緒,直接訪問
                WriteScroll(msg);
            }
        }


        private void btnSend_Click(object sender, EventArgs e)
        {
            var outputBuffer = Encoding.Unicode.GetBytes(txtSend.Text);
            this.socket.BeginSend(outputBuffer, 0, outputBuffer.Length, SocketFlags.None, null, null);
        }
    }
}

最終結果

test