上一篇學習日記C#網絡編程之--TCP協議(一)中以服務端接受客戶端的請求鏈接結尾
既然服務端已經與客戶端創建了鏈接,那麼溝統統道已經打通,載滿數據的小火車就能夠彼此傳送和接收了。如今讓咱們來看看數據的傳送與接收html
先把服務端與客戶端的鏈接代碼敲出來編程
服務端 IPAddress ip = new IPAddress(new byte[] { 127, 1, 1, 1 }); TcpListener server = new TcpListener(ip, 8005); server.Start();//服務端啓動偵聽
TcpClient client = server.AcceptTcpClient();//接受發起鏈接對象的同步方法
Console.WriteLine("收到客戶端鏈接請求")//若是沒有客戶端請求鏈接,這句話是沒法Print out的
客戶端 IPAddress ip=IPAddress.Parse("127.1.1.1"); TcpClient client=new TcpClient(); client.Connect(ip,8005);//8005端口號,必須與服務端給定的端口號一致,不然天堂無門
先看看服務端的特殊標記的那句代碼小程序
AcceptTcpClient() 這個方法是一個同步方法,在沒有接受到鏈接請求的時候,位於它下面的代碼是不會被執行的,也就是線程阻塞在這裏,進行不下去了,想出城沒有城防長官的批覆是不能的,嘿嘿...數組
鏈接後,客戶端要發送數據給服務端,先貼代碼再說緩存
NetworkStream dataStream=client.GetStream(); string msg="服務端親啓!"; byte[] buffer=Encoding.default.getBytes(msg); stream.write(buffer,0,buffer.length);
//這段代碼呈接上面那段客戶端代碼
NetworkStream 在網絡中進行傳輸的數據流,也就是說傳輸數據必須寫入此流中,纔可以互通有無。
首先客戶端先獲取用於發送信息的流,而後將要發送的信息存入byte[] 數組中(數據必須是byte[] 纔可以寫入流中),最後就是寫入傳輸的數據流,發送安全
聰明的你想必已經知道如何在服務端獲取數據了
既然客戶端費力的把數據包裝發給服務端了,那麼服務端天然要把包裝拆了,獲得數據,上代碼:網絡
NetworkStream dataStream=client.GetStream(); byte[] buffer=new byte[8192]; int dataSize=dataStream.Read(buffer,0,8192); Console.write(Encoding.default.GetString(buffer,0,dataSize)); //這段代碼呈接上面那段服務端代碼
代碼一寫,我以爲再說多餘了,不過還要在說一兩句,嘿嘿
Read() 方法須要三個參數,1,存儲數據的緩存空間。2,寫入數據的起始點就是從存儲空間的什麼位置開始寫入數據。3,就是存儲空間的大小。返回寫入數據的大小值
Encoding.default.GetString() 參數解析
1,存儲數據的緩存空間。2,從什麼位置開始接收數據。3,接收多少數據多線程
以上只是再簡單不過的數據發送,並且只是客戶端發給服務端,只能發一條信息而已,那若是想彼此互發,而且想發多少條信息均可以,怎麼辦呢異步
首先基於以上的代碼,編寫一個WPF的小程序post
下圖分別是客戶端和服務端
界面很簡單,要實現的功能就是客戶端與服務端互發信息。
感受仍是直接上代碼吧
服務端的所有代碼以下:
public delegate void showData(string msg);//委託,防止跨線程的訪問控件,引發的安全異常 private const int bufferSize = 8000;//緩存空間 private TcpClient client; private TcpListener server; /// <summary> /// 結構體:Ip、端口 /// </summary> struct IpAndPort { public string Ip; public string Port; } /// <summary> /// 開始偵聽 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnStart_Click(object sender, RoutedEventArgs e) { if (txtIP.Text.Trim() == string.Empty) { return; } if (txtPort.Text.Trim() == string.Empty) { return; } Thread thread = new Thread(reciveAndListener); //若是線程綁定的方法帶有參數的話,那麼這個參數的類型必須是object類型,因此講ip,和端口號 寫成一個結構體進行傳遞 IpAndPort ipHePort = new IpAndPort(); ipHePort.Ip = txtIP.Text; ipHePort.Port = txtPort.Text; thread.Start((object)ipHePort); } /// <summary> /// 發送信息給客戶端 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, RoutedEventArgs e) { if (txtSendMsg.Text.Trim() != string.Empty) { NetworkStream sendStream = client.GetStream();//得到用於數據傳輸的流 byte[] buffer = Encoding.Default.GetBytes(txtSendMsg.Text.Trim());//將數據存進緩存中 sendStream.Write(buffer,0,buffer.Length);//最終寫入流中 txtSendMsg.Text = string.Empty; } } /// <summary> /// 偵聽客戶端的鏈接並接收客戶端發送的信息 /// </summary> /// <param name="ipAndPort">服務端Ip、偵聽端口</param> private void reciveAndListener(object ipAndPort) { IpAndPort ipHePort = (IpAndPort)ipAndPort; IPAddress ip = IPAddress.Parse(ipHePort.Ip); server = new TcpListener(ip, int.Parse(ipHePort.Port)); server.Start();//啓動監聽 rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText), "服務端開啓偵聽....\n"); // btnStart.IsEnabled = false; //獲取鏈接的客戶端對象 client = server.AcceptTcpClient(); rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText),"有客戶端請求鏈接,鏈接已創建!");//AcceptTcpClient 是同步方法,會阻塞進程,獲得鏈接對象後纔會執行這一步 //得到流 NetworkStream reciveStream = client.GetStream(); #region 循環監聽客戶端發來的信息 do { byte[] buffer = new byte[bufferSize]; int msgSize; try { lock (reciveStream) { msgSize = reciveStream.Read(buffer, 0, bufferSize); } if (msgSize == 0) return; string msg = Encoding.Default.GetString(buffer, 0, bufferSize); rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText), "\n客戶端曰:" + Encoding.Default.GetString(buffer, 0, msgSize)); } catch { rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText), "\n 出現異常:鏈接被迫關閉" ); break; } } while (true); #endregion }
客戶端代碼:
TcpClient client; private const int bufferSize = 8000; NetworkStream sendStream; public delegate void showData(string msg); private void btnConnect_Click(object sender, RoutedEventArgs e) { if (txtIP.Text.Trim() == string.Empty) { return; } if (txtPort.Text.Trim() == string.Empty) { return; } IPAddress ip = IPAddress.Parse(txtIP.Text); client = new TcpClient(); client.Connect(ip, int.Parse(txtPort.Text)); rtbtxtShowData.AppendText("開始鏈接服務端....\n"); rtbtxtShowData.AppendText("已經鏈接服務端\n"); //獲取用於發送數據的傳輸流 sendStream = client.GetStream(); Thread thread = new Thread(ListenerServer); thread.Start(); } private void btnSend_Click(object sender, RoutedEventArgs e) { if (client != null) { //要發送的信息 if (txtSendMsg.Text.Trim() == string.Empty) return; string msg = txtSendMsg.Text.Trim(); //將信息存入緩存中 byte[] buffer = Encoding.Default.GetBytes(msg); //lock (sendStream) //{ sendStream.Write(buffer, 0, buffer.Length); //} rtbtxtShowData.AppendText("發送給服務端的數據:" + msg + "\n"); txtSendMsg.Text = string.Empty; } } private void ListenerServer() { do { try { int readSize; byte[] buffer = new byte[bufferSize]; lock (sendStream) { readSize = sendStream.Read(buffer, 0, bufferSize); } if (readSize == 0) return; rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText), "服務端曰:" + Encoding.Default.GetString(buffer, 0, readSize)+"\n"); } catch { rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText), "報錯"); } //將緩存中的數據寫入傳輸流 } while (true); }
其中用到了,多線程處理還有委託,由於以上咱們用到的不論是Connect,仍是AcceptTcpClient方法 都是同步方法,會阻塞進程,致使窗口沒法自由移動
rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText), "服務端開啓偵聽....\n");
上面這句代碼或許有些人不解,我也花了一些時間才懂這樣寫的
其實因爲在WPF中不容許跨線程訪問,訪問了會拋異常,可是在WPF中的窗口控件都有一個Dispatcher(調度器)屬性,容許訪問控件的線程;既然不容許直接訪問,就告訴控件咱們要幹什麼就行了。
因此在多線程中使用控件的Dispatcher屬性,這樣就不是跨線程訪問了,而後咱們在看看Invoke方法
經過上面的標示,看的出須要一個委託類型的方法,因此就將RichTextBox 的賦值方法AppendText 綁定到一個委託showData上。
下面是一段引用,看了或許能更明白點
WPF的UI線程都交給一個叫作調度器的類了。 WPF 應用程序啓動時具備兩個線程:一個用於處理呈現,另外一個用於管理 UI。 呈現線程實際上隱藏在後臺運行,而 UI 線程則接收輸入、處理事件、繪製屏幕以及運行應用程序代碼。UI 線程在一個名爲 Dispatcher 的對象中將工做項進行排隊。 Dispatcher 根據優先級選擇工做項,並運行每個工做項直到完成。Dispatcher 類提供兩種註冊工做項的方法:Invoke 和 BeginInvoke。 這兩個方法都會安排執行一個委託。Invoke 是同步調用,即它直到 UI 線程實際執行完該委託時才返回。BeginInvoke 是異步調用,於是將當即返回。------引用自WPF筆記12: 線程處理模型
執行以上程序的效果圖:
Ok,至此客戶端與服務端的數據傳遞就大功告成了,這只是一個很簡單的操做,若是有多個客戶端呢?要求異步通訊,怎麼辦?不急,慢慢來,不積跬步無以致千里
若是有什麼錯的,但願指正。