1. 程式人生 > >C#網路程式設計 tcpclient (阻塞非同步)

C#網路程式設計 tcpclient (阻塞非同步)

C# 高效的語言,既然選擇了它來寫網路伺服器,就應該使用最簡單的方式。

MS提供了SOCKET和TCPCLIENT兩種方式給我們使用,TCPCLIENT封裝了socket,似乎應該用tcpclient來搞。。。

關於阻塞:

該概念出現的場景是伺服器端,在接受客戶端連線的場景。我們在伺服器端用TcpListener開啟一個監聽,等待客戶端的連線,需要使用AcceptTcpClient()方法。

這個AcceptTcpClient()方法一旦使用,整個執行緒就停住(阻塞)了。  直到有客戶端連線上來後,AcceptTcpClient()方法才返回客戶端的連線。

關於非同步:

該概念出現在網路連線已經建立好後的場景,伺服器程式或客戶端程式,從連線的資料流裡面讀取或寫入資料,如果資料流中有資料才讀取,程式程式碼跟著執行就是同步,

或者不管有無資料,都去試做讀資料流中的資料,而使用回撥函式來處理這攤子事情,就是非同步。

基於TCPCLIENT,下面使用簡單的程式碼來實現一個阻塞,非同步的網路伺服器。

伺服器開始,使用一個執行緒來處理監聽操作:

            Thread lsThread = new Thread(new ThreadStart(server_listen));
            lsThread.IsBackground = true;
            lsThread.Start();
        //////////////////
        /////網路監聽////
        /////////////////
        private void server_listen()
        {
            listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 3000);
            listener.Start();

            while (true)
            {
                Thread.Sleep(1000);
                while (listener.Pending())  //跳開阻塞,用這個在關閉伺服器程式時不會出現異常
                {
                    tcpuser one = new tcpuser();
                    one.tcp = listener.AcceptTcpClient();
                    
                    
                    Thread subThread = new Thread(new ParameterizedThreadStart(f1)); //執行緒
                    subThread.Start(one);
                    

                    
                  
                    gv.tcpuserlist.Add(one);  

                }
            }
        }

關於listener.Pending,這個方法可以測試listener中有沒有客戶端連線上來,如果有,再去接收客戶端的連線。

這樣的寫法,就可以避免程式被直接使用AcceptTcpClient()方法阻塞住。


        //多客戶端的處理執行緒
        private void f1(object xxx)
        {
            tcpuser tp = (tcpuser)xxx;
            NetworkStream stream = tp.tcp.GetStream();            

            while (true)
            {

                var begint = DateTime.Now;
                rwl.EnterReadLock(); //無論花費多長時間,執行緒都會一直等到獲得鎖為止
                

                try
                {
                        //對話邏輯處理 
                        client_business(tp);
                        
                        //網路寫                        
                        if (!String.IsNullOrWhiteSpace(tp.txtout))
                        {
                            tp.writebuffer = Encoding.UTF8.GetBytes(tp.txtout + "."); //加一次訊息結束符號.
                            if (tp.tcp != null && tp.tcp.Connected)
                            {
                                if (stream.CanWrite) { stream.BeginWrite(tp.writebuffer, 0, tp.writebuffer.Length, new AsyncCallback(WriteCallBack), tp); }
                            }
                            else { tp.islogon = 0; rwl.ExitReadLock(); break; }
                        }

                        
                        //網路讀
                        if (tp.tcp != null && tp.tcp.Connected) { stream.BeginRead(tp.readbuffer, 0, tp.readbuffer.Length, new AsyncCallback(ReadCallBack), tp); }
                        else { tp.islogon = 0; rwl.ExitReadLock(); break; }
                     

                }
                catch { listBox2.Items.Insert(0, "對話模組 ERROR"); tp.islogon = 0; rwl.ExitReadLock(); break; }



                Thread.Sleep(100);                
                rwl.ExitReadLock();
                listBox1.Items.Insert(0, tp.playername + " client:" + Convert.ToInt32(DateTime.Now.Subtract(begint).TotalMilliseconds).ToString());             
                
            }

        }

上面的程式碼使用BeginRead來讀取流中的資料,BeginRead方法是一個回撥函式,WIN系統會處理函式中的程式碼,並且它自己結束時候,又呼叫了自己,就形成了一個迴圈,可以不停的處理資料。

下面是BeginRead函式的程式碼內容:

        //網路讀子函式
        public void ReadCallBack(IAsyncResult ar)
        {
            try
            {
                tcpuser one = (tcpuser)ar.AsyncState;
                if (one.tcp != null && one.tcp.Connected)
                {
                    NetworkStream stream = (NetworkStream)one.tcp.GetStream();
                    int numberOfBytesRead = stream.EndRead(ar);
                    one.txtin = Encoding.UTF8.GetString(one.readbuffer, 0, numberOfBytesRead);
                 
                    if (!String.IsNullOrWhiteSpace(one.txtin))
                    { one.txtlonglong = one.txtlonglong + one.txtin;  one.txtin = ""; } //每次收到的,都放longlong

                }
            }
            catch { listBox2.Items.Insert(0, "ReadCallBack ERROR"); }
        }


        //網路寫子函式
        public void WriteCallBack(IAsyncResult ar)
        {
            try
            {
                tcpuser one = (tcpuser)ar.AsyncState;
                if (one.tcp != null && one.tcp.Connected)
                {
                    NetworkStream stream = (NetworkStream)one.tcp.GetStream();
                    stream.EndWrite(ar);
                    one.txtout = "";
                }
            }
            catch { listBox2.Items.Insert(0, "WriteCallBack ERROR");  }
        }