Socket網絡編程筆記(C#代碼)

目錄

一、Socket概述

二、UDP收發 

原理:

特點:

UDP發:

UDP收 :

運行結果:

三、UDP綜合實例

運行結果:​

四、TCP Socket

原理:

客戶端:

服務端:

實驗結果:

五、TCP Socket多線程應用

六、Socket與HTTP

七、總結


 

期末考試考到了Socket概念的簡答題。之前做項目自己也用到過。剛好現在空閒整理一下心得筆記。我會從網絡編程的概念,優缺點和實際應用來總結。

這篇博客也參考了我們學校的教材和其他的博客。

一、Socket概述

百度百科:

網絡上的兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一端稱爲一個socket。

建立網絡通信連接至少要一對端口號(socket)。socket本質是編程接口(API),對TCP/IP的封裝,TCP/IP也要提供可供程序員做網絡開發所用的接口,這就是Socket編程接口;HTTP是轎車,提供了封裝或者顯示數據的具體形式;Socket是發動機,提供了網絡通信的能力。

Socket就是套接字,它是引用網絡連接的特殊的文件描述符,由3個基本要素組成:AddressFamily(網絡類型)、 SocketType(數據傳輸類型)及ProtocolType(採用的網絡協議)。

二、UDP收發 

原理:

UDP是無連接協議,只需要知道對方的IP和Port就能進行數據的傳輸,其中IP負責定位主機Port負責定位應用。在C#中UDP網絡通信中並未使用socket類,但是他使用的UDPClient類和Socket原理類似。

特點:

  • 雖然UDP協議無法保證數據可靠性,但因爲它是基於無連接的協議,能夠消除生成連接的系統延遲,所以速度比TCP更快;
  • UDP既支持一對一連接,也支持一對多連接,所以,可以使用廣播的方式多地址發送,而TCP僅支持一對一通信;
  • UDPTCP的報頭比是820,所以相對TCPUDP消耗更少的網絡帶寬;
  • UDP傳輸的數據有消息邊界,而TCP傳輸的數據沒有消息邊界。

UDP發:

namespace UDPSend
{
    public partial class Form1 : Form
    {
        //定義一個UDPClient類型的字段
        UdpClient udpClient;
        public Form1()
        {
            //創建一個未與指定地址或端口綁定的UDPClient實例
            udpClient = new UdpClient();
            InitializeComponent();
        }

        //發送數據
        private void button1_Click(object sender, EventArgs e)
        {
            //臨時存儲textBox1中的數據
            string temp = this.textBox1.Text;
            //將textBox1中的數據(文本)轉化爲字節編碼以便發送
            byte[] bData = System.Text.Encoding.UTF8.GetBytes(temp);
            //向本機的13579端口發送數據(方法1)
            udpClient.Send(bData, bData.Length, Dns.GetHostName(), 13579);
            //向本機的13579端口發送數據(方法2)
            //利用方法2,可向其他計算機端口方式數據
            //udpClient.Connect(IPAddress.Parse("127.0.0.1"), 13579);
            //udpClient.Send(bData, bData.Length);
        }
    }
}

UDP收 :

namespace UDPReceive
{
    public partial class Form1 : Form
    {
        //定義一個UDPClient類型的字段
        UdpClient udpClient;
        //定義一個線程 
        Thread thread;

        public Form1()
        {
            //屏蔽異常以便跨線程訪問控件 
            CheckForIllegalCrossThreadCalls = false;
            InitializeComponent();
            //創建一個與指定端口綁定的UDPClient
            //實例,此端口須與發送方端口相同 
            udpClient = new UdpClient(13579);
        }

        //監聽並接收數據
        private void listen()
        {
            //定義一個終結點,因爲此前創建的UDPClient實例已與指定端口綁定,
            //所以,此處的IP地址和端口可任意設置或不設置
            IPEndPoint iep = null;
            while (true)
            {
                //獲得發送方的數據包並轉換爲指定字符類型。
                //ref關鍵字使參數按引用傳遞,當控制權傳給回調用方法時,
                //在方法中對參數所做的任何更改都將反映在該變量中
                string sData = System.Text.Encoding.UTF8.GetString(udpClient.Receive(ref iep));
                //將接收到的數據添加到listBox1的條目中
                this.listBox1.Items.Add(sData);
            }
        }

        //啓動數據接收
        private void button1_Click(object sender, EventArgs e)
        {
            //創建一個線程以監聽並接收數據 
            thread = new Thread(new ThreadStart(listen));
            //設置爲後臺線程,以便關閉窗體時終止線程 
            thread.IsBackground = true;
            thread.Start();
        }

        //關閉窗體時終止線程
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            //終止線程
            if (thread != null) thread.Abort();
        }
    }
}

運行結果:

 

 

三、UDP綜合實例

把收發程序結合在一起就是一個基於UDP協議的聊天程序了。(基本功能,若有需求自行完善)

namespace UDPApp
{
    public partial class Form1 : Form
    {
        #region var
        //定義一個UDPClient_send類型的字段
        UdpClient udpClient;
        //定義一個UDPClient_receive類型的字段
        UdpClient udpClient_receive;
        //定義一個線程 
        Thread thread;
        #endregion
        public Form1()
        {
            //創建一個未與指定地址或端口綁定的UDPClient實例
            udpClient = new UdpClient();
            InitializeComponent();
            //屏蔽異常以便跨線程訪問控件 
            CheckForIllegalCrossThreadCalls = false;
            
        }
        //發送數據send
        private void button1_Click(object sender, EventArgs e)
        {
            //臨時存儲textBox1中的數據
            string temp = this.textBox1.Text;
            //將textBox1中的數據(文本)轉化爲字節編碼以便發送
            byte[] bData = System.Text.Encoding.UTF8.GetBytes(temp);
            //向本機的13579端口發送數據(方法1)
            udpClient.Send(bData, bData.Length, Dns.GetHostName(), Convert.ToUInt16(this.textsend.Text));
            //向本機的13579端口發送數據(方法2)
            //利用方法2,可向其他計算機端口方式數據
            //udpClient.Connect(IPAddress.Parse("127.0.0.1"), 13579);
            //udpClient.Send(bData, bData.Length);
            this.textBox1.Text = null;
        }

        #region receive
        private void btnStart_Click(object sender, EventArgs e)
        {
            //創建一個與指定端口綁定的UDPClient
            //實例,此端口須與發送方端口相同 
            udpClient_receive = new UdpClient(Convert.ToUInt16(this.textreceive.Text));
            //創建一個線程以監聽並接收數據 
            thread = new Thread(new ThreadStart(listen));
            //設置爲後臺線程,以便關閉窗體時終止線程 
            thread.IsBackground = true;
            thread.Start();
        }
        //監聽並接收數據
        private void listen()
        {
           // DateTime currentTime = new DateTime();
            //定義一個終結點,因爲此前創建的UDPClient實例已與指定端口綁定,
            //所以,此處的IP地址和端口可任意設置或不設置
            IPEndPoint iep = null;
            while (true)
            {
                //獲得發送方的數據包並轉換爲指定字符類型。
                //ref關鍵字使參數按引用傳遞,當控制權傳給回調用方法時,
                //在方法中對參數所做的任何更改都將反映在該變量中
                string sData = System.Text.Encoding.UTF8.GetString(udpClient_receive.Receive(ref iep));
                //將接收到的數據添加到listBox1的條目中--回車問題
                string[] ssData = sData.Split('\n');
                for (int i = 0; i < ssData.Length; i++)
                {
                    this.listBox1.Items.Add(DateTime.Now.ToString() + " 消息: " + ssData[i]);
                }
               // this.listBox1.Items.Add(DateTime.Now.ToString()+" 消息: "+sData);
            }
        }
        #endregion

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            //終止線程
            if (thread != null) thread.Abort();
        }

       
    }
}

UDP的接收方要一直等待監聽是否有數據發送到監聽的端口上。所以開一個線程去循環執行listen()方法。

這裏需要注意一下端口的問題,程序中我把端口固定了,再啓動一個程序是會出現端口占用的問題,所以我們要選擇不同的端口來監聽UDP。

運行結果:

四、TCP Socket

原理:

TCP基於C/S是面向連接的可靠傳輸,TCP是一種面向連接的協議,即利用TCP傳輸數據的時候,首先必須先用低級通信協議IP在計算機之間建立連接(也就是所謂的握手,然後纔可以傳輸數據),不同於UDP,TCP Socket分爲Socket和ServerSocket對應着client和server,下面我來用代碼實現一個簡單的TCP通訊功能:


客戶端:

namespace ChatClient
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;//禁用此異常
        }

        #region 變量
        //客戶機與服務器之間的連接狀態
        public bool bConnected = false;
        //偵聽線程
        public Thread tAcceptMsg = null;
        //用於Socket通信的IP地址和端口
        public IPEndPoint IPP = null;
        //Socket通信
        public Socket socket = null;
        //網絡訪問的基礎數據流
        public NetworkStream nStream = null;
        //創建讀取器
        public TextReader tReader = null;
        //創建編寫器
        public TextWriter wReader = null;
        #endregion

        //顯示信息
        public void AcceptMessage()
        {
            string sTemp; //臨時存儲讀取的字符串
            while (bConnected)
            {
                try
                {
                    //連續從當前流中讀取字符串直至結束
                    sTemp = tReader.ReadLine();
                    if (sTemp.Length != 0)
                    {
                        //richTextBox2_KeyPress()和AcceptMessage()
                        //都將向richTextBox1寫字符,可能訪問有衝突,
                        //所以,需要多線程互斥
                        lock (this)
                        {
                            richTextBox1.Text = "服務器:" + sTemp + "\n" + richTextBox1.Text;
                        }
                    }
                }
                catch
                {
                    MessageBox.Show("無法與服務器通信。");
                }
            }
            //禁止當前Socket上的發送與接收
            socket.Shutdown(SocketShutdown.Both);
            //關閉Socket,並釋放所有關聯的資源
            socket.Close();
        }

        //創建與服務器的連接,偵聽並顯示聊天信息
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                IPP = new IPEndPoint(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socket.Connect(IPP);
                if (socket.Connected)
                {
                    nStream = new NetworkStream(socket);
                    tReader = new StreamReader(nStream);
                    wReader = new StreamWriter(nStream);
                    tAcceptMsg = new Thread(new ThreadStart(this.AcceptMessage));
                    tAcceptMsg.Start();
                    bConnected = true;
                    button1.Enabled = false;
                    MessageBox.Show("與服務器成功建立連接,可以通信。");
                }
            }
            catch
            {
                MessageBox.Show("無法與服務器通信。");
            }
        }

        //發送信息
        private void richTextBox2_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)13)//按下的是回車鍵
            {
                if (bConnected)
                {
                    try
                    {
                        //richTextBox2_KeyPress()和AcceptMessage()
                        //都將向richTextBox1寫字符,可能訪問有衝突,
                        //所以,需要多線程互斥
                        lock (this)
                        {
                            richTextBox1.Text = "客戶機:" + richTextBox2.Text + richTextBox1.Text;
                            //客戶機聊天信息寫入網絡流,以便服務器接收
                            wReader.WriteLine(richTextBox2.Text);
                            //清理當前緩衝區數據,使所有緩衝數據寫入基礎設備
                            wReader.Flush();
                            //發送成功後,清空輸入框並聚集之
                            richTextBox2.Text = "";
                            richTextBox2.Focus();
                        }
                    }
                    catch
                    {
                        MessageBox.Show("與服務器連接斷開。");
                    }
                }
                else
                {
                    MessageBox.Show("未與服務器建立連接,不能通信。");
                }
            }
        }

        //關閉窗體時斷開socket連接,並終止線程(否則,VS調試程序將仍處於運行狀態)
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
                socket.Close();
                tAcceptMsg.Abort();
            }
            catch
            { }
        }
    }
}

首先創建一個Socket和InetSocketAddress ,然後通過Socket的connect()方法進行連接,連接成功後可以獲取到輸出流,通過該輸出流就可以向服務端傳輸數據。

服務端:

namespace ChatServer
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;//禁用此異常
        }

        #region 變量
        //客戶機與服務器之間的連接狀態
        private bool bConnected = false;
        //偵聽線程
        private Thread tAcceptMsg = null;
        //用於Socket通信的IP地址和端口
        private IPEndPoint IPP = null;
        //Socket通信
        private Socket socket = null;
        private Socket clientSocket = null;   //此處可參考,使用之前看是否分配內存
        //網絡訪問的基礎數據流
        private NetworkStream nStream = null;
        //創建讀取器
        private TextReader tReader = null;
        //創建編寫器
        private TextWriter wReader = null;
        #endregion

        //顯示信息----無連接關閉 .Accept()方法報錯?
        public void AcceptMessage()
        {
            //接受客戶機的連接請求
            clientSocket = socket.Accept();  //此處等待

            if (clientSocket != null)
            {
                bConnected = true;
                this.label1.Text = "與客戶 " + clientSocket.RemoteEndPoint.ToString() + " 成功建立連接。";
            }

            nStream = new NetworkStream(clientSocket);
            //讀字節流
            tReader = new StreamReader(nStream);
            //寫字節流
            wReader = new StreamWriter(nStream);

            string sTemp; //臨時存儲讀取的字符串
            while (bConnected)
            {
                try
                {
                    //連續從當前流中讀取字符串直至結束
                    sTemp = tReader.ReadLine();
                    if (sTemp.Length != 0)
                    {
                        //richTextBox2_KeyPress()和AcceptMessage()
                        //都將向richTextBox1寫字符,可能訪問有衝突,
                        //所以,需要多線程互斥
                        lock (this)
                        {
                            richTextBox1.Text = "客戶機:" + sTemp + "\n" + richTextBox1.Text;
                        }
                    }
                }
                catch
                {
                    tAcceptMsg.Abort();
                    MessageBox.Show("無法與客戶機通信。");
                }
            }
            //禁止當前Socket上的發送與接收
            clientSocket.Shutdown(SocketShutdown.Both);
            //關閉Socket,並釋放所有關聯的資源
            clientSocket.Close();
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
        }

        //啓動偵聽並顯示聊天信息--New Socket()
        private void button1_Click(object sender, EventArgs e)
        {
            //服務器偵聽端口可預先指定(此處使用了最大端口值)
            //Any表示服務器應偵聽所有網絡接口上的客戶活動
            IPP = new IPEndPoint(IPAddress.Any, 45678);
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Bind(IPP);//關聯(綁定)節點
            socket.Listen(0);//0表示連接數量不限

            //創建偵聽線程
            tAcceptMsg = new Thread(new ThreadStart(this.AcceptMessage));
            tAcceptMsg.Start();
            button1.Enabled = false;
        }

        //發送信息
        private void richTextBox2_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)13)//按下的是回車鍵
            {
                if (bConnected)
                {
                    try
                    {
                        //richTextBox2_KeyPress()和AcceptMessage()
                        //都將向richTextBox1寫字符,可能訪問有衝突,
                        //所以,需要多線程互斥
                        lock (this)
                        {
                            richTextBox1.Text = "服務器:" + richTextBox2.Text + richTextBox1.Text;
                            //客戶機聊天信息寫入網絡流,以便服務器接收
                            wReader.WriteLine(richTextBox2.Text);
                            //清理當前緩衝區數據,使所有緩衝數據寫入基礎設備
                            wReader.Flush();
                            //發送成功後,清空輸入框並聚集之
                            richTextBox2.Text = "";
                            richTextBox2.Focus();
                        }
                    }
                    catch
                    {
                        MessageBox.Show("無法與客戶機通信!");
                    }
                }
                else
                {
                    MessageBox.Show("未與客戶機建立連接,不能通信。");
                }
            }
        }

        //關閉窗體時斷開socket連接,並終止線程(否則,VS調試程序將仍處於運行狀態)
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
                socket.Close();
                tAcceptMsg.Abort();
            }
            catch
            { }
        }

    }
}

首先創建一個服務端Socket並明確端口號,通過accept()方法獲取到鏈接過來的客戶端Socket,從客戶端Socket中獲取輸入流,最後由輸入流讀取客戶端傳輸來的數據。

實驗結果:

五、TCP Socket多線程應用

TCP相比於UDP有一個缺點就是僅支持一對一連接,不可以一對多通信。但是通過多線程的技術手段,我們還是可以實現一對多通信的效果。

//僅需要修改AcceptMessage()方法即可
        public void AcceptMessage()
        {
            //接受客戶機的連接請求
            clientSocket = socket.Accept();  //此處等待
            new Thread(() => {
            if (clientSocket != null)
            {
                bConnected = true;
                this.label1.Text = "與客戶 " + clientSocket.RemoteEndPoint.ToString() + " 成功建立連接。";
            }

            nStream = new NetworkStream(clientSocket);
            //讀字節流
            tReader = new StreamReader(nStream);
            //寫字節流
            wReader = new StreamWriter(nStream);

            string sTemp; //臨時存儲讀取的字符串
            while (bConnected)
            {
                try
                {
                    //連續從當前流中讀取字符串直至結束
                    sTemp = tReader.ReadLine();
                    if (sTemp.Length != 0)
                    {
                        //richTextBox2_KeyPress()和AcceptMessage()
                        //都將向richTextBox1寫字符,可能訪問有衝突,
                        //所以,需要多線程互斥
                        lock (this)
                        {
                            richTextBox1.Text = "客戶機:" + sTemp + "\n" + richTextBox1.Text;
                        }
                    }
                }
                catch
                {
                    tAcceptMsg.Abort();
                    MessageBox.Show("無法與客戶機通信。");
                }
            }
            //禁止當前Socket上的發送與接收
            clientSocket.Shutdown(SocketShutdown.Both);
            //關閉Socket,並釋放所有關聯的資源
            clientSocket.Close();
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
            }
        }

我這個程序還存在很多的BUG,需要學習的可自行完善。大概的思路就是這樣的。是應該新建一個Socket的而不是處理數據的線程。Server回覆給指定Socket的問題。。。。。。

六、Socket與HTTP

 在計算機網絡中這兩者根本不是一個概念。Socket是一個封裝的API,基於TCP協議進行數據通信。而HTTP是超文本傳輸協議,是基於TCP的應用層協議。所以我們還可以通過Socket編程來實現以下HTTP協議的GET請求做一個測試。

這裏用ONENET服務器做測試,向ONENET服務器發送一下內容:

POST /devices/25900768/datapoints HTTP/1.1
api-key: WXs6QMIDeT1FSX1JwfVIFTRAh=o=
Host:api.heclouds.com
Connection:close
Content-Length:59

{"datastreams":[{"id":"111","datapoints":[{"value":50}]}]}

就可以在ONENET平臺上增加一個數據點了

七、總結

網絡編程也許大家可以做出來。但是如果大家不瞭解這些網絡協議還是希望大家學習一下。雖然對我們做網絡編程沒有多大影響,但是對於整個程序的深入理解會有很好的效果。

最後把所有程序打包給大家:https://mp.csdn.net/postedit?not_checkout=1

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