閱讀962 返回首頁    go 阿裏雲 go 技術社區[雲棲]


C# 網絡編程之使用Socket類Send、Receive方法的同步通訊

      經過幾天學習,終於解決了再C#網絡編程中使用Socket類Send和Receive方法開發的客戶端和服務端的同步通訊程序;實現了又客戶端想服務器發送消息的界麵程序.主要使用的方法是:
      1.Socket套接字編程的知識,通過IPAddress定義一個IP地址,IPEndPoint定義一個主機,Socket實例套接字對象sock和線程Thread的的成員變量;
      2.再調用方法bind綁定端口、listen監聽端口、accept接受連接請求、connect請求連接來連接客戶端和服務器;
      3.建立連接後通過Send和Receive方法通過線程循環接受連接請求中發送的消息,實現通信並顯示在相應的控件中;
      4.最後調用socket的close和shutdown方法關閉套接字,停止連接監聽.
      下麵是程序運行後的結果:
      (服務端接受客戶端發送的消息:這是一個單方的通信,但實現雙方的方法相同,因為服務端的"歡飲使用本服務器"也反饋顯示在了客戶端)

      (客戶端)

      下麵是本程序的源代碼:

      服務端

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;
using System.Net.Sockets;
using System.Threading;

namespace tbServer
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //添加私有成員
        private IPAddress myIP = IPAddress.Parse("127.0.0.1");   //定義IP對象
        private IPEndPoint MyServer;                             //定義主機
        private Socket sock;                                     //套接字對象實例
        private bool sign = true;                                //控製循環
        private Thread thread;                                   //創建控製線程
        private Socket socklin;                                  //臨時套接字,接受客戶端連接請求

        //雙擊"開始監聽"按鈕添加Click事件
        private void button1_Click(object sender, EventArgs e)
        {
            try 
            {
                myIP = IPAddress.Parse(textBox1.Text);           //字符串轉換為IP
            }
            catch 
            {
                MessageBox.Show("你輸入的IP地址格式錯誤!");
            }

            try 
            {
                //定義主機
                MyServer = new IPEndPoint(myIP, Int32.Parse(textBox2.Text));
                //構造套接字
                sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //綁定端口
                sock.Bind(MyServer);
                //開始監聽
                sock.Listen(10);
                //狀態欄信息添加 textBox3替代statusStrip1(不會用)
                textBox3.Text = "主機" + textBox1.Text + " 端口" + textBox2.Text + " 開始監聽";
                //構造線程
                thread = new Thread(new ThreadStart(targett)); //targett自定義函數:接受客戶端連接請求
                //啟動線程用於接受連接和接受數據
                thread.Start();
            }
            catch(Exception msg) {
                textBox3.Text = msg.Message;
            }
        }

        //targett():自定義函數,該方法循環開始接受客戶端的連接請求
        private void targett()
        {
            socklin = sock.Accept();   //接受連接請求
            sign = true;               //循環標誌變量true

            //連接
            if (socklin.Connected)
            {
                textBox3.Text = "與客戶端連接";

                //信息反饋給客戶端Client 
                Byte[] byteNum = new Byte[64];                     //構造字節數組
                byteNum = System.Text.Encoding.BigEndianUnicode.GetBytes("歡飲使用本服務器".ToCharArray());
                socklin.Send(byteNum,byteNum.Length,0);            //發送數據

                //sign為true 循環接受數據
                while (sign)
                {
                    Byte[] byteNum2 = new Byte[128];
                    socklin.Receive(byteNum2,byteNum2.Length,0);   //接受數據
                    string str = System.Text.Encoding.BigEndianUnicode.GetString(byteNum2);
                    richTextBox1.AppendText(str+"\r\n");           //顯示字符串
                   
                    //獲取richTextBox1行數
                    int length = richTextBox1.Lines.Length;        
                    //如果客戶端發送倒數第二行的字符串為"@@@" 斷開連接
                    if(richTextBox1.Lines[length-2]=="@@@")
                    {
                        textBox3.Text = "與客戶端斷開連接";
                        //關閉套接字實例(both表示發送和接受關閉)
                        socklin.Shutdown(System.Net.Sockets.SocketShutdown.Both); 
                        socklin.Close();
                        sign = false;                //設為false退出循環
                    }
                }
            }
        }

        //雙擊"停止監聽"按鈕添加Click事件
        private void button2_Click(object sender, EventArgs e)
        {
            try 
            {
                sign = false;
                sock.Close();
                textBox3.Text = "主機" + textBox1.Text + "端口" + textBox2.Text + "監聽停止";
            }
            catch 
            {
                MessageBox.Show("監聽尚未開始,關閉無效!");
            }

        }

        //載入Form是設置非安全訪問,防止線程無效操作
        private void Form1_Load(object sender, EventArgs e)
        {
            //非安全線程訪問,不檢查線程是否安全
            Control.CheckForIllegalCrossThreadCalls = false;    
        }
    }
}

      客戶端

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;
using System.Net.Sockets;
using System.Threading;

namespace tbClient
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //添加私有成員
        private IPAddress myIP = IPAddress.Parse("127.0.0.1");   //定義IP對象
        private IPEndPoint MyServer;                             //定義主機
        private Socket sock;                                     //套接字對象實例
        private Thread thread;                                   //創建控製線程
       
        //雙擊"請求連接"按鈕添加Click事件
        private void button1_Click(object sender, EventArgs e)
        {
            try 
            {
                myIP = IPAddress.Parse(textBox1.Text);           //字符串轉換為IP
            }
            catch 
            {
                MessageBox.Show("你輸入的IP地址格式錯誤!");
            }

            try 
            {
                //構造主機
                MyServer = new IPEndPoint(myIP, Int32.Parse(textBox2.Text));
                //構造套接字
                sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //請求連接
                sock.Connect(MyServer);
                //構造線程
                thread = new Thread(new ThreadStart(targett)); //targett自定義函數:接受客戶端連接請求
                //啟動線程用於接受連接和接受數據
                thread.Start();
                //輸出信息
                textBox4.Text = "與主機" + textBox1.Text + " 端口" + textBox2.Text + " 連接成功";
            }
            catch(Exception msg) 
            {
                MessageBox.Show(msg.Message);
            }
        }

        //targett():自定義函數,該方法循環開始接受客戶端的連接請求
        private void targett()
        {
            //構造字節數組
            Byte[] byteNum = new Byte[64];  
            //接受數據       
            sock.Receive(byteNum, byteNum.Length, 0);          
            //將字符數組轉換為字符串
            string str = System.Text.Encoding.BigEndianUnicode.GetString(byteNum);
            textBox3.Text = str;
        }

        //雙擊"發送消息"按鈕添加Click事件
        private void button2_Click(object sender, EventArgs e)
        {
            //構造字節數組
            Byte[] byteNum = new Byte[64];  
            //發送內容
            string send = richTextBox1.Text + "\r\n";
            //將字符串轉換為字節數組
            byteNum = System.Text.Encoding.BigEndianUnicode.GetBytes(send.ToCharArray());
            //發送數據
            sock.Send(byteNum,byteNum.Length,0);
            //構造線程
            Thread threadSend = new Thread(new ThreadStart(targett));
            //啟動線程接受數據
            threadSend.Start();
        }

        //雙擊"關閉連接"按鈕添加Click事件
        private void button3_Click(object sender, EventArgs e)
        {
            Byte[] byteNum = new Byte[64];
            string send = "@@@" + "\r\n";
            byteNum = System.Text.Encoding.BigEndianUnicode.GetBytes(send.ToCharArray());
            sock.Send(byteNum, byteNum.Length, 0);   //將"@@@"發送給服務器

            try 
            {
                sock.Close();
                textBox4.Text="主機" + textBox1.Text + "端口" + textBox2.Text + "斷開連接";
            }
            catch 
            {
                MessageBox.Show("連接尚未建立,斷開無效!");
            }
        }

        //載入Form是設置非安全訪問,防止線程無效操作
        private void Form1_Load(object sender, EventArgs e)
        {
            //非安全線程訪問,不檢查線程是否安全
            Control.CheckForIllegalCrossThreadCalls = false;   
        }
    }
}

      該程序中我遇到的幾個主要問題及解決方法如下:

      1.程序初期總是很卡,出現多次未響應情況?
      因為socket的Accept()函數是阻塞模式,它的執行會造成程序的阻塞,應該把它放置到線程中執行,否則會阻塞當前線程,出現卡死狀態不響應消息,後續代碼也不會執行,所以需要把accept放到創建的線程thread中,放入targett()函數中的“socklin = sock.accept()”即可實現;

      2.在定義的socket對象實例中sock與socklin(臨時接受客戶端連接請求)中混淆?
      socklin = sock.accept,它就是客戶端發送連接的請求,因此在判斷連接時是if(socklin.Connected),同時使用socklin的send和receive方法發送和接受數據;

      3.總是出現“線程間操作無效:從不是創建控件的線程訪問它”的錯誤?
      因為windows窗體控件不是線程安全的,如果幾個線程操作某一控件的狀態,可能會使該控件的狀態不一致,出現爭用或死鎖狀態.我采用的解決方法是添加Form的載入load事件,在load時將CheckForIllegalCrossThreadCalls 屬性的值設置為 false .這樣進行非安全線程訪問時,運行環境就不去檢驗它是否是線程安全的.這是來自與該博客,詳細情況見:https://blog.csdn.net/wangchao0605/article/details/5010864

      總結:
      最後經過一星期的學習與查閱資料,還是把這個程序弄出來了,也學到了很多東西,同時感謝上麵的博主和一些書籍.希望這篇文章對大家有用,有錯或不足之處見諒!
      (BY:Eastmount 2013-7-22  https://blog.csdn.net/eastmount/)

最後更新:2017-04-03 16:48:39

  上一篇:go 3.3.2 PCI設備對不可Cache的存儲器空間進行DMA讀寫
  下一篇:go Shell編程基礎