C# Socket 網絡編程


效果展示

  ①客戶端發送消息給服務器

    

  ②服務器發送消息給指定客戶端

    

  ③服務器群發消息給客戶端

     

   ③服務器發送文件給客戶端

   ④服務器給客戶端發送震動指令

1、什么是Socket網絡編程

  兩台計算機相互通信靠的就是Socket,類似兩個人要通信靠電話,也就是說Socket就是電腦間(程序間)的電話機
  Socket英文原意是孔、插座,作為進程通信機制,取后一種意思,通常也稱套接字,用於描述IP地址和端口。IP地址指向某平台服務器,端口用於連接到某一個應用程序。

    

  Socket在通信過程中所處位置(作用)理解:

    

    釋義:男生要到女生宿舍找女朋友出去玩,不能直接進入女生宿舍去找,要經過宿管大媽,由宿管大媽打電話告知你的女朋友,電話打通后你們之間就能通話了。

    這里的宿管大媽就是負責監聽的Socket,如果有男生(客戶端發送請求)來了就創建負責通信的Socket(電話機),從而使該男生(客戶端)與對應女生(服務器

    某個應用程序)可以進行通信了。

 2、Socket支持的協議(TCP協議、UDP協議)

  協議:類似與兩個人打電話有一個默認協議就是都說普通話,如果張三是四川人李四是上海人他們都說家鄉話(當地方言),可能他們都聽不懂對方的話,在網絡中常用的協議有:TCP協議、UDP協議

  TCP/IP協議(Transmission Control Protocol/Internet Protocol):傳輸控制協議/網間協議,是一個工業標准的協議集,它是為廣域網設計的。

  TCP協議:屬於TCP/IP協議中的一種,相對於UDP協議安全穩定,在TCP傳輸過程中要經過3次握手,傳輸效率相對低。

    

     釋義:客戶端像服務器發送消息(你有空嗎?),服務器回復(我有空),客戶端再發給服務器(我曉得你有空了),經過了3次握手,客戶端和服務器才開始通信。

  UDP協議(User Data Protocol):用戶數據協議,與TCPx相對應的協議。他也屬於TCP/IP協議的一種,它只管發,不管對方有沒有接收,相對於TCP協議快速、效率

  高、不穩定,容易發生數據丟失。客戶端直接發給服務器發送消息,不管服務器是否有空接收數據等,都發給服務器。類似於電報、電台廣播。

  協議的選擇?

    兩種傳輸協議比較,無論好壞各有各自的優點。

    當數據傳輸的性能必須讓位於數據傳輸的完整性、可控性和可靠性時,TCP協議(安全穩定)是當然的選擇。

    當強調傳輸性能而不是傳輸完整性時,如:音頻和多媒體應用,UDP協議是最好的選擇。也就是說視頻傳輸的時候用UDP協議(快速效率高),因為視頻聊天時更希

    望看到對方流暢不卡,但是清晰度可能低一點。

  2、端口分類

  公認端口:也稱為常用端口,端口號為0~1023,它們緊密的綁定一些特殊服務。通常這些端口的通信明確了某種服務協議,不可再重新定義它的作用對象。如80端口

  (http通信)、23端口(Telnet服務)。

  注冊端口端口號為1024~49151,它們松散的綁定一些服務,也有許多服務綁定了這些端口,這些端口同樣也用於其他目的,且多數沒有明確定義對象,不同程序可以

  根據需求自己定義,常用於大企業。這些端口對網絡的安全十分重要,所以對於服務器一般來說要關閉這些端口。

  動態端口/私有端口端口號為49152~65535,理論上不應該把常用服務分配在這些端口上,但實際上有較為特殊的程序,特別是木馬就非常喜歡這些端口,因為這些端口

  常常不會引起人們注意,容易隱藏。

  3、Socket通信的基本流程及如何創建Socke

  ①、Socket通信的基本流程

    

   ②創建服務器界面

    創建一個解決方案名稱Socket網絡編程,在其中創建Windows 窗體應用程序(.NET Framework)名稱SocketChatServer,窗體改名為FrmSocketServer。

    Ip:本地Ip地址

    Port:端口號(動態端口/私有端口

    UserIp:客戶端的Ip地址

    

     服務器代碼

public partial class FrmSocketServer : Form
    {
        public FrmSocketServer()
        {
            InitializeComponent();

            // 取消捕獲對錯誤線程的調用(解決跨域線程的問題)
            //Control.CheckForIllegalCrossThreadCalls = false;
        }

        //定義一個鍵值對集合用於存儲客戶端的Ip、端口及負責通信的Socket
        Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();

        List<Socket> clientCommunicationSocketList = new List<Socket>();

        private void StartMonitor_Click(object sender, EventArgs e)
        {
            // 創建負責監聽的Socket
            // 第一個參數AddressFamily設置網絡尋址協議,InterNetwork表示IPV4
            // 第二個參數SocketType設置數據傳輸方式(Socket類型),這個要根據第三個參數來設置,Stream此類型的 Socket 與單個對方主機進行通信,並且在通信開始之前需要遠程主機連接
            // 第三個參數為UDP協議時,第二個參數就要為Dgram(報文)
            Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // 綁定監聽的Ip和Port
            IPAddress ip = IPAddress.Parse(textIp.Text);
            int port = Convert.ToInt32(textPort.Text);
            IPEndPoint endPoint = new IPEndPoint(ip, port);
            socketWatch.Bind(endPoint);

            ShowMsg("監聽成功");
            StartMonitor.Enabled = false;

            // 設置監聽隊列數量(如果監聽太多就要使用分流)
            socketWatch.Listen(10);

            // 創建一個線程去執行這個方法
            Thread thread = new Thread(Listen);

            // 設置后台線程
            thread.IsBackground = true;

            // 標記這個線程准備就緒了,可以隨時被執行,具體什么時候執行這個線程由CPU決定
            thread.Start(socketWatch);
        }

        Socket socketCommunication;

        /// <summary>
        /// 讓線程去執行負責通信的Socket
        /// </summary>
        /// <param name="obj"></param>
        private void Listen(object obj)
        {
            Socket socketWatch = obj as Socket;

            // 不斷地監聽客戶端有沒有發送請求過來
            while (true)
            {
                // 創建負責監聽的Socket
                socketCommunication = socketWatch.Accept();
                dicSocket.Add(socketCommunication.RemoteEndPoint.ToString(), socketCommunication);
                clientCommunicationSocketList.Add(socketCommunication);
                ShowMsg(socketCommunication.RemoteEndPoint.ToString() + "連接成功");

                // 解決跨線程的訪問
                if (cboUser.InvokeRequired)
                {
                    cboUser.Invoke(new Action(() =>
                    {
                        cboUser.Items.Add(socketCommunication.RemoteEndPoint.ToString());
                    }), null);
                }
                else
                {
                    cboUser.Items.Add(socketCommunication.RemoteEndPoint.ToString());
                }

                // 創建一個線程去執行接收客戶端發送過來消息這個方法
                Thread thread = new Thread(Receive);

                // 設置后台線程
                thread.IsBackground = true;

                // 標記這個線程准備就緒了,可以隨時被執行,具體什么時候執行這個線程由CPU決定
                thread.Start(socketCommunication);
            }
        }

        /// <summary>
        /// 接收客戶端發送過來消息
        /// </summary>
        /// <param name="obj"></param>
        private void Receive(object obj)
        {
            Socket socketWatch = obj as Socket;

            // 定義接收數據的大小
            byte[] buffer = new byte[1024 * 1024 * 2];

            while (true)
            {
                try
                {
                    // r 表示實際接收數據的字節數
                    int r = socketWatch.Receive(buffer);

                    // 解決(關閉窗口后 r=0)
                    if (r == 0)
                    {
                        // break;
                        socketWatch.Shutdown(SocketShutdown.Both);
                        socketWatch.Close();
                        return;
                    }
                    // 字節數據轉字符串
                    string str = Encoding.UTF8.GetString(buffer, 0, r);
                    // 顯示消息格式:客戶端Ip端口,消息
                    ShowMsg(socketWatch.RemoteEndPoint.ToString() + ":" + str);
                }
                catch
                {
                }
            }
        }

        private void ShowMsg(string str)
        {
            // 解決跨線程的訪問
            if (textReceiveMsg.InvokeRequired)
            {
                textReceiveMsg.Invoke(new Action<string>(s =>
                {
                    textReceiveMsg.AppendText(s + "\r\n");
                }), str);
            }
            else
            {
                //textReceiveMsg.Text = str + "\r\n" + textReceiveMsg.Text;
                textReceiveMsg.AppendText(str + "\r\n");
            }
        }

        /// <summary>
        /// 發送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SendMsg_Click(object sender, EventArgs e)
        {
            string str = textSendMsg.Text.Trim();

            // 字符串轉字節數組
            byte[] buffer = Encoding.UTF8.GetBytes(str);

            #region 第一種思路
            byte[] newBuffer = new byte[buffer.Length + 1];
            newBuffer[0] = 0;
            Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);
            #endregion

            #region 第二種思路
            //List<byte> list = new List<byte>();
            //list.Add(0);
            //list.AddRange(buffer);
            //byte[] newBuffer = list.ToArray();
            #endregion

            string ipPort = cboUser.SelectedItem.ToString();

            // 只能給最后一個客戶端發送消息
            //socketCommunication.Send(buffer);

            // 給指定客戶端發送消息
            dicSocket[ipPort].Send(newBuffer);

            textSendMsg.Clear();
        }

        /// <summary>
        /// 群發消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BulkMsg_Click(object sender, EventArgs e)
        {
            foreach (var socket in clientCommunicationSocketList)
            {
                if (socket.Connected)
                {
                    string str = textSendMsg.Text.Trim();

                    // 字符串轉字節數組
                    byte[] buffer = Encoding.UTF8.GetBytes(str);
                    
                    
                    #region 第一種思路
                    //byte[] newBuffer = new byte[buffer.Length + 1];
                    //newBuffer[0] = 0;
                    //Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);
                    #endregion

                    #region 第二種思路
                    List<byte> list = new List<byte>();
                    list.Add(0);
                    list.AddRange(buffer);
                    byte[] newBuffer = list.ToArray();
                    #endregion

                    socket.Send(newBuffer);
                }
            }
            textSendMsg.Clear();
        }

        /// <summary>
        /// 選擇文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SelectFile_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();

            // 初始目錄
            ofd.InitialDirectory = @"C:\Users\DHR\Desktop";
            ofd.Title = "請選擇要發送的文件";
            ofd.Filter = "所有文件|*.*|txt文本文檔|*.txt";
            ofd.ShowDialog();

            textPath.Text = ofd.FileName;
        }

        /// <summary>
        /// 發送文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SendFile_Click(object sender, EventArgs e)
        {
            #region 第一種思路,使用文件流方式讀取
            string path = textPath.Text;
            using (FileStream fsRead = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                // 定義發送文件的大小
                byte[] buffer = new byte[1024 * 1024 * 3];
                // 實際讀取到的字節數(文件存儲到buffer數組中)
                int r = fsRead.Read(buffer, 0, buffer.Length);
                List<byte> list = new List<byte>();
                list.Add(1);
                list.AddRange(buffer);
                byte[] newBuffer = list.ToArray();

                string ipPort = cboUser.SelectedItem.ToString();

                // 為什么要加后面3個參數(newBuffer這里是3M+1byte 實際文件可能不會超過3M)
                dicSocket[ipPort].Send(newBuffer, 0, r + 1, SocketFlags.None);
            }
            #endregion

            #region 第二種思路,使用File.ReadAllBytes讀取
            //using (OpenFileDialog ofd = new OpenFileDialog())
            //{
            //    if (ofd.ShowDialog() != DialogResult.OK)
            //    {
            //        return;
            //    }
            //    // 把文件寫入到字節數組
            //    byte[] buffer = File.ReadAllBytes(ofd.FileName);
            //    byte[] newBuffer = new byte[buffer.Length + 1];
            //    newBuffer[0] = 1;
            //    Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);

            //    string ipPort = cboUser.SelectedItem.ToString();

            //    dicSocket[ipPort].Send(newBuffer, SocketFlags.None);
            //}
            #endregion
        }

        private void Shock_Click(object sender, EventArgs e)
        {
            byte[] buffer = new byte[1];
            buffer[0] = 2;
            string ipPort = cboUser.SelectedItem.ToString();

            dicSocket[ipPort].Send(buffer);
        }
    }

  ③創建客戶端界面

    

     客戶端代碼

    public partial class FrmSocketClinet : Form
    {
        public FrmSocketClinet()
        {
            InitializeComponent();

            // 取消捕獲對錯誤線程的調用(解決跨域線程的問題)
            //Control.CheckForIllegalCrossThreadCalls = false;
        }

        Socket socketCommunication;

        private void StartConnect_Click(object sender, EventArgs e)
        {
            // 創建負責通信的Socket
            // 第一個參數AddressFamily設置網絡尋址協議,InterNetwork表示IPV4
            // 第二個參數SocketType設置數據傳輸方式(Socket類型),這個要根據第三個參數來設置,Stream此類型的 Socket 與單個對方主機進行通信,並且在通信開始之前需要遠程主機連接
            // 第三個參數為UDP協議時,第二個參數就要為Dgram(報文)
            socketCommunication = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // 連接到服務器
            IPAddress ip = IPAddress.Parse(textIp.Text);
            int port = Convert.ToInt32(textPort.Text);
            IPEndPoint endPoint = new IPEndPoint(ip, port);
            socketCommunication.Connect(endPoint);

            ShowMsg("連接成功");
            StartConnect.Enabled = false;

            // 創建一個線程去執行接收服務器發送過來消息這個方法
            Thread thread = new Thread(Receive);

            // 設置后台線程
            thread.IsBackground = true;

            // 標記這個線程准備就緒了,可以隨時被執行,具體什么時候執行這個線程由CPU決定
            thread.Start();
        }

        /// <summary>
        /// 接收服務器發送過來消息
        /// </summary>
        /// <param name="obj"></param>
        private void Receive()
        {
            // 定義接收數據的大小
            byte[] buffer = new byte[1024 * 1024 * 2];

            while (true)
            {
                try
                {
                    // r 表示實際接收數據的字節數
                    int r = socketCommunication.Receive(buffer);

                    // 解決(服務器關閉 r=0)
                    if (r == 0)
                    {
                        // break;
                        socketCommunication.Shutdown(SocketShutdown.Both);
                        socketCommunication.Close();
                        return;
                    }
                    int n = buffer[0];

                    // 文本d
                    if (n == 0)
                    {
                        // 字節數據轉字符串
                        //string str = Encoding.UTF8.GetString(buffer, 0, r);
                        string str = Encoding.UTF8.GetString(buffer, 1, r - 1);

                        // 顯示消息格式:客戶端Ip端口,消息
                        ShowMsg(socketCommunication.RemoteEndPoint.ToString() + ":" + str);
                    }
                    // 文件
                    else if (n == 1)
                    {
                        #region 第一種思路,使用文件流寫
                        SaveFileDialog sfd = new SaveFileDialog();
                        sfd.InitialDirectory = @"C:\Users\DHR\Desktop";
                        sfd.Title = "請選擇要保存文件的位置";
                        sfd.Filter = "所有文件|*.*|txt文本文檔|*.txt";

                        // 解決跨線程的訪問
                        if (this.InvokeRequired)
                        {
                            this.Invoke(new Action(() =>
                            {
                                sfd.ShowDialog(this);
                            }), null);
                        }
                        else
                        {
                            sfd.ShowDialog(this);
                        }

                        string path = sfd.FileName;
                        using (FileStream fsWrite = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))
                        {
                            fsWrite.Write(buffer, 1, r - 1);
                        }
                        MessageBox.Show("保存成功");
                        #endregion

                        #region 第二種思路 使用File.ReadAllBytes寫
                        //using (SaveFileDialog sfd = new SaveFileDialog())
                        //{
                        //    sfd.InitialDirectory = @"C:\Users\DHR\Desktop";
                        //    sfd.Title = "請選擇要保存文件的位置";
                        //    sfd.Filter = "所有文件|*.*|txt文本文檔|*.txt";
                        //    sfd.DefaultExt = "txt";

                        //    // 解決跨線程的訪問
                        //    if (this.InvokeRequired)
                        //    {
                        //        this.Invoke(new Action(() =>
                        //        {
                        //            if (sfd.ShowDialog(this) != DialogResult.OK)
                        //            {
                        //                return;
                        //            }
                        //        }), null);
                        //    }
                        //    else
                        //    {
                        //        if (sfd.ShowDialog(this) != DialogResult.OK)
                        //        {
                        //            return;
                        //        }
                        //    }

                        //    byte[] newBuffer = new byte[r - 1];
                        //    Buffer.BlockCopy(buffer, 1, newBuffer, 0, r - 1);
                        //    File.WriteAllBytes(sfd.FileName, newBuffer);
                        //    MessageBox.Show("保存成功");
                        //}
                        #endregion

                    }
                    // 震動
                    else if (n == 2)
                    {
                        Shock();
                    }
                }
                catch (Exception e)
                {

                }
            }
        }

        /// <summary>
        ///震動方法
        /// </summary>
        private void Shock()
        {
            // 記錄窗體的初始位置
            Point p = new Point(this.Location.X, this.Location.Y);
           
            // 初始化Random對象
            Random r = new Random();

            // 解決跨線程的訪問
            if (this.InvokeRequired)
            {
                this.Invoke(new Action(() =>
                {
                    for (int i = 0; i < 3000; i++)
                    {
                        this.Location = new Point(p.X + r.Next(-2, 2), p.Y + r.Next(-2, 2));

                    }
                }), null);
            }
            else
            {
                for (int i = 0; i < 3000; i++)
                {
                    this.Location = new Point(p.X + r.Next(-10, 10), p.Y + r.Next(-10, 10));
                }
            }

        }

        private void ShowMsg(string str)
        {
            // 解決跨線程的訪問
            if (textReceiveMsg.InvokeRequired)
            {
                textReceiveMsg.Invoke(new Action<string>(s =>
                {
                    textReceiveMsg.AppendText(s + "\r\n");
                }), str);
            }
            else
            {
                //textReceiveMsg.Text = str + "\r\n" + textReceiveMsg.Text;
                textReceiveMsg.AppendText(str + "\r\n");
            }
        }


        /// <summary>
        /// 發送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SendMsg_Click(object sender, EventArgs e)
        {
            string str = textSendMsg.Text.Trim();

            // 字符串轉字節數組
            byte[] buffer = Encoding.UTF8.GetBytes(str);
            socketCommunication.Send(buffer);

            textSendMsg.Clear();
        }
    }

  需要代碼的關注私聊哦!

  后續會陸續更新其他資料,喜歡請關注哦!

  我的博客:https://www.cnblogs.com/duhaoran/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM