基於socket開發網絡調試助手


 

 1.什么是Socket?

在計算機領域socket被翻譯為套接字,它是計算機之間進行通信的一種方式,通過socket這種約定,一台計算機可以向另外一台計算機發送數據和接收數據。

2.Socket的本質?

Socket本質是編程接口(API),對TCP/IP的封裝,TCP/IP也要提供可供程序員做網絡開發所用的接口,這就是socket編程接口。

3.socket的作用?

可以實現不同虛擬機或者是計算機之間的通信。

4.socket的典型應用?

(1)socket的典型應用之一 就是web服務器和瀏覽器,瀏覽器獲取用戶輸入的URL,向服務器發起請求,服務器分析接收到的URL,將對應的網頁內容返回給瀏覽器,瀏覽器再經過解析和渲染,就將文字、

圖片、視頻等元素呈現給用戶。

(2)QQ或者微信等聊天工具也是socket的應用之一,本地的QQ或者微信程序就是客戶端,登錄過程就是連接服務器的過程,聊天過程其實就是socket的發送和接收過程。

socket主要包括以下幾種接口:

 

 socket位於應用層和傳輸層之間,把socket比作門,門外是郵局,你要送信就要通過門,把信從門送出去到郵局,然后由郵局幫你送達目標的門,目的地主任再打開門,從門取出來郵局送過來的信,上述比喻

中,郵局就是傳輸層(及更下面的層),而門內就是應用。

socket的編程方式?

socket,一切皆文件,都可以用"打開open------>讀寫read/write------->close"模式來操作。socket就是該模式的一個實現,socket即是一個特殊的文件,一些socket函數就是對其進行的操作(讀/寫IO,打開,關閉)

因此socket提供了類似於連接(Connect),關閉(Close),發送,接收等方法調用。

數據的傳輸方式:STREAM和DREAM

(1)STREAM表示面向連接的數據傳輸方式,數據可以准確無誤的到達另一台計算機,如果損壞或丟失,可以重新發送,但是效率較慢。

(2)DREAM表示無連接的數據傳輸方式,計算機只管傳輸數據,不做數據校驗,DREAM所作的校驗工作少,所以效率比STREAM高。

QQ視頻聊天就是使用DREAM傳輸數據,因為首先要保證通信效率,盡可能減小延遲,而數據的准確性是次要的,即使丟失很小的一部分數據,視頻和音頻也可以正常解析,最多出現噪點和雜音,不會對通

信質量有質的影響。

接下來,我們將使用socket來開發網絡調試助手。

服務器端程序的編寫:

第一步,創建一個用於通信的socket套接字

第二步,給已經創建的套接字綁定一個端口號,這個一般通過設置網絡套接口地址和調用Bind()函數來實現。

第三步,調用Listen()函數使套接字成為一個監聽的套接字。

第四步,調用accept()函數來接收客戶端的連接,這時候就可以和客戶端通信了。

第五步,處理客戶端的連接請求。

第六步,終止連接。

界面搭建:

啟動服務器的程序編寫:

   #region 開啟服務
        /// <summary>
        /// 開啟服務
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_StartService_Click(object sender, EventArgs e)
        {
            // 第一步:調用socket()函數創建一個用於通信的套接字,這里使用基於Tcp的方式
            socketSever = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // 第二步:給已經創建的套接字綁定一個端口號,這一般通過設置網絡套接口地址和調用bind()函數來實現。

            IPEndPoint ipe = new IPEndPoint(IPAddress.Parse(this.txt_IP.Text), int.Parse(this.txt_Port.Text));

            try
            {
                socketSever.Bind(ipe);
            }
            catch (Exception ex)
            {
                //寫入日志

                AddLog(2, "服務器開啟失敗:" + ex.Message);
                return;
            }

            // 第三步:調用listen()函數使套接字成為一個監聽套接字。這個10表示的是存放在緩沖池的連接數(類似於數據庫連接池),並不是最大的連接的客戶端數量
//當接收到客戶端連接,就會從緩沖區中拿掉一個,緩沖區就會空出一個位置,如果把監聽客戶端的線程注釋掉,那么會發現,緩沖區的位置移植無法釋放,
socketSever.Listen(10); //創建一個監聽的線程 Task.Run(new Action(() => { CheckListening(); })); AddLog(0, "服務器開啟成功"); this.btn_StartService.Enabled = false; } #endregion

監聽線程

  #region 監聽線程

        /// <summary>
        /// 檢查監聽的線程方法體
        /// </summary>
        private void CheckListening()
        {
            while (true)
            {
                // 第四步:調用accept()函數來接受客戶端的連接,這是就可以和客戶端通信了,Accept是一個阻塞式的,當有客戶端連接上服務器的時候,就會繼續向下執行。
                Socket socketClient = socketSever.Accept();

                string client = socketClient.RemoteEndPoint.ToString();

                AddLog(0, client + "上線了");
                //添加該客戶端到字典中
                CurrentClientlist.Add(client, socketClient);

                UpdateOnline(client, true);

                Task.Run(new Action(() =>
                {
                    ReceiveMessage(socketClient);
                }));
            }
        }


        #endregion

接收來自客戶端的消息

   #region 多線程接收數據
        /// <summary>
        /// 接收客戶端數據的方法
        /// </summary>
        /// <param name="socketClient"></param>
        private void ReceiveMessage(Socket socketClient)
        {
            while (true)
            {
                // 創建一個10M的緩沖區

                byte[] buffer = new byte[1024 * 1024 * 10];

                int length = -1;

                string client = socketClient.RemoteEndPoint.ToString();

                // 第五步:處理客戶端的連接請求。
                try
                {
//Receive也是一個阻塞式的方法,當接收到消息后,將會繼續執行。接收到的數據將會放到緩沖區buffer中
//如果客戶端斷線,一個將會進入try catch,另外一個會進入接收的length為0 length
= socketClient.Receive(buffer); } catch (Exception) {
//客戶端下線 UpdateOnline(client,
false); AddLog(0, client + "下線了");
//從字典中移除 CurrentClientlist.Remove(client);
break; } if (length > 0) { string msg = string.Empty; MessageType type = (MessageType)buffer[0]; //客戶端發送消息時,首個字節表示的是發送的數據的類型,實際真正的數據長度是length-1,根據首字節來判斷接收數據的類型 switch (type) {
//接收的是ASCLL
case MessageType.ASCII: msg = Encoding.ASCII.GetString(buffer, 1, length - 1); AddLog(0, client + "" + msg); break;
//接收UTF8
case MessageType.UTF8: msg = Encoding.UTF8.GetString(buffer, 1, length - 1); AddLog(0, client + "" + msg); break;
//接收十六進制
case MessageType.Hex: msg = HexGetString(buffer, 1, length - 1); AddLog(0, client + "" + msg); break;
//接收文件
case MessageType.File: Invoke(new Action(() => { SaveFileDialog sfd = new SaveFileDialog(); sfd.Filter = "txt files(*.txt)|*.txt|xls files(*.xls)|*.xls|xlsx files(*.xlsx)|*.xlsx|All files(*.*)|*.*"; if (sfd.ShowDialog() == DialogResult.OK) { string fileSavePath = sfd.FileName; using (FileStream fs = new FileStream(fileSavePath, FileMode.Create)) {
//將緩沖區的數據寫入 fs.Write(buffer,
1, length - 1); } AddLog(0, "文件成功保存至" + fileSavePath); } })); break;
//接收JSON
case MessageType.JSON: Invoke(new Action(() => { string res = Encoding.Default.GetString(buffer, 1, length); List<Student> StuList = JSONHelper.JSONToEntity<List<Student>>(res); new FrmJSON(StuList).Show(); AddLog(0, "接收JSON數據:" + res); })); break; default: break; } }
//length<=0,也視為客戶端下線
else { UpdateOnline(client, false); AddLog(0, client + "下線了"); CurrentClientlist.Remove(client); break; } } } #endregion

字節數組轉16進制字符串方法

 #region 16進制字符串處理
        /// <summary>
        /// 將字節數組轉換為16進制字符串
        /// </summary>
        /// <param name="buffer">字節數組</param>
        /// <param name="start">起始位置</param>
        /// <param name="length">截取的數組長度</param>
        /// <returns></returns>
        private string HexGetString(byte[] buffer, int start, int length)
        {
            string Result = string.Empty;

            if (buffer != null && buffer.Length >= start + length)
            {
                //截取字節數組

                byte[] res = new byte[length];

                Array.Copy(buffer, start, res, 0, length);

                string Hex = Encoding.Default.GetString(res, 0, res.Length);

                // 01   03 0 40 0A
                if (Hex.Contains(" "))
                {
                    string[] str = Regex.Split(Hex, "\\s+", RegexOptions.IgnoreCase);

                    foreach (var item in str)
                    {
                        Result += "0x" + item + " ";
                    }
                }
                else
                {
                    Result += "0x" + Hex;
                }

            }
            else
            {
                Result = "Error";
            }
            return Result;
        }


        #endregion

更新在線列表方法,當客戶端上線/下線的時候,服務器端的列表可以更新。

 #region 在線列表更新
        /// <summary>
        /// 在線列表更新
        /// </summary>
        /// <param name="client">客戶端ip</param>
        /// <param name="operate">從列表中移除或者從列表中增加</param>
        private void UpdateOnline(string client, bool operate)
        {
            //是否是跨線程訪問
            if (!this.lst_Online.InvokeRequired)
            {
                if (operate)
                {
                    this.lst_Online.Items.Add(client);
                }
                else
                {
                    foreach (string item in this.lst_Online.Items)
                    {
                        if (item == client)
                        {
                            this.lst_Online.Items.Remove(item);
                            break;
                        }
                    }
                }
            }
            else
            {
                Invoke(new Action(() =>
                {
                    if (operate)
                    {
                        this.lst_Online.Items.Add(client);
                    }
                    else
                    {
                        foreach (string item in this.lst_Online.Items)
                        {
                            if (item == client)
                            {
                                this.lst_Online.Items.Remove(item);
                                break;
                            }
                        }
                    }
                }));
            }
        }

        #endregion

顯示日志方法

        /// <summary>
        /// 顯示日志的方法
        /// </summary>
        /// <param name="index">0,1,2分別表示顯示日志信息的種類:正常,警告,錯誤</param>
        /// <param name="info">日志信息</param>
        private void AddLog(int index, string info)
        {
            if (!this.lst_Rcv.InvokeRequired)
            {
                ListViewItem lst = new ListViewItem("   " + CurrentTime, index);

                lst.SubItems.Add(info);

                lst_Rcv.Items.Insert(lst_Rcv.Items.Count, lst);

            }
            else
            {
                Invoke(new Action(() =>
                {
                    ListViewItem lst = new ListViewItem("   " + CurrentTime, index);

                    lst.SubItems.Add(info);

                    lst_Rcv.Items.Insert(lst_Rcv.Items.Count, lst);
                }));
            }
        }

接收數據類型的枚舉

public enum MessageType
    {
        ASCII,
        UTF8,
        Hex,
        File,
        JSON
    }

服務器發送指定格式的消息數據給指定的客戶端和群發客戶端,這里首先要將發送的數據轉換為你要發送的那種數據格式

      

        //創建字典集合,鍵是ClientIp,值是SocketClient
        private Dictionary<string, Socket> CurrentClientlist = new Dictionary<string, Socket>();

        #region 發送ASCII

        private void btn_SendASCII_Click(object sender, EventArgs e)
        {
            //選擇要發送的數據的客戶端
            if (this.lst_Online.SelectedItems.Count > 0)
            {
                AddLog(0, "發送內容:" + this.txt_Send.Text.Trim());

                byte[] send = Encoding.ASCII.GetBytes(this.txt_Send.Text.Trim());

                //創建最終發送的數組
                byte[] sendMsg = new byte[send.Length + 1];

                //整體拷貝數組,把send數組,從索引為1開始,拷貝send.Length個長度的字節數組給sendMsg

Array.Copy(send, 0, sendMsg, 1, send.Length); //給首字節賦值,為了方便客戶端識別該數據是什么類型的數據 sendMsg[0] = (byte)MessageType.ASCII; foreach (var item in this.lst_Online.SelectedItems) { //獲取Socket對象 string client = item.ToString(); CurrentClientlist[client]?.Send(sendMsg); } this.txt_Send.Clear(); } else { MessageBox.Show("請選擇你要發送的客戶端對象!", "發送消息"); } } #endregion
        #region 發送Hex
        private void btn_SendHex_Click(object sender, EventArgs e)
        {
            if (this.lst_Online.SelectedItems.Count > 0)
            {
                AddLog(0, "發送內容:" + this.txt_Send.Text.Trim());
                //默認的是Hex byte[] send = Encoding.Default.GetBytes(this.txt_Send.Text.Trim());

                //創建最終發送的數組
                byte[] sendMsg = new byte[send.Length + 1];

                //整體拷貝數組,把send數組,從索引為1開始,拷貝send.Length個長度的字節數組給sendMsg
                Array.Copy(send, 0, sendMsg, 1, send.Length);
                //給首字節賦值
                sendMsg[0] = (byte)MessageType.Hex;

                foreach (var item in this.lst_Online.SelectedItems)
                {
                    //獲取Socket對象
                    string client = item.ToString();

                    CurrentClientlist[client]?.Send(sendMsg);
                }

                this.txt_Send.Clear();
            }
            else
            {
                MessageBox.Show("請選擇你要發送的客戶端對象!", "發送消息");
            }
        }

        #endregion
       #region 發送UTF8
        private void btn_SendUTF8_Click(object sender, EventArgs e)
        {
            if (this.lst_Online.SelectedItems.Count > 0)
            {
                AddLog(0, "發送內容:" + this.txt_Send.Text.Trim());
                
                byte[] send = Encoding.UTF8.GetBytes(this.txt_Send.Text.Trim());

                //創建最終發送的數組
                byte[] sendMsg = new byte[send.Length + 1];

                //整體拷貝數組
                Array.Copy(send, 0, sendMsg, 1, send.Length);

                //給首字節賦值

                sendMsg[0] = (byte)MessageType.UTF8;

                foreach (var item in this.lst_Online.SelectedItems)
                {
                    //獲取Socket對象
                    string client = item.ToString();

                    CurrentClientlist[client]?.Send(sendMsg);
                }

                this.txt_Send.Clear();
            }
            else
            {
                MessageBox.Show("請選擇你要發送的客戶端對象!", "發送消息");
            }
        }

        #endregion
       #region 發送文件
        private void btn_SendFile_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(this.txt_File.Text))
            {
                MessageBox.Show("請先選擇你要發送的文件路徑", "發送文件");
                return;
            }
            else
            {
                if (this.lst_Online.SelectedItems.Count > 0)
                {
                    //分兩次發送:第一次發送文件名,第二次發送文件內容
                    using (FileStream fs = new FileStream(this.txt_File.Text, FileMode.Open))
                    {
                        //第一次發送文件名稱

                        //獲取文件名稱
                        string filename = Path.GetFileName(this.txt_File.Text);
                        //獲取后綴名
                        string fileExtension = Path.GetExtension(this.txt_File.Text);

                        string strMsg = "發送文件:" + filename + "." + fileExtension;

                        byte[] send1 = Encoding.UTF8.GetBytes(strMsg);

                        byte[] send1Msg = new byte[send1.Length + 1];

                        Array.Copy(send1, 0, send1Msg, 1, send1.Length);

                        send1Msg[0] = (byte)MessageType.UTF8;

                        foreach (var item in this.lst_Online.SelectedItems)
                        {
                            //獲取Socket對象
                            string client = item.ToString();

                            CurrentClientlist[client]?.Send(send1Msg);
                        }


                        //第二次發送文件內容

                        byte[] send2 = new byte[1024 * 1024 * 10];

                        //有效長度
                        int length = fs.Read(send2, 0, send2.Length);

                        byte[] send2Msg = new byte[length + 1];

                        Array.Copy(send2, 0, send2Msg, 1, length);

                        send2Msg[0] = (byte)MessageType.File;

                        foreach (var item in this.lst_Online.SelectedItems)
                        {
                            //獲取Socket對象
                            string client = item.ToString();

                            CurrentClientlist[client]?.Send(send2Msg);
                        }

                        this.txt_File.Clear();

                        AddLog(0, strMsg);
                    }
                }
                else
                {
                    MessageBox.Show("請選擇你要發送的客戶端對象!", "發送消息");
                }
            }
        }

        #endregion
        #region 選擇文件
        private void btn_SelectFile_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();

            //設置默認的路徑
            ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                this.txt_File.Text = ofd.FileName;

                AddLog(0, "選擇文件:" + this.txt_File.Text);
            }
        }

        #endregion
  #region 發送JSON
        private void btn_SendJSON_Click(object sender, EventArgs e)
        {
            if (this.lst_Online.SelectedItems.Count > 0)
            {
                //創建集合
                List<Student> stuList = new List<Student>()
            {
                new Student(){ StudentID=10001,StudentName="小明",ClassName="軟件一班"},
                new Student(){ StudentID=10002,StudentName="小紅",ClassName="軟件二班"},
                new Student(){ StudentID=10003,StudentName="小花",ClassName="軟件三班"},
            };

                //將對象轉換為JSON字符串
                string str = JSONHelper.EntityToJSON(stuList);

                byte[] send = Encoding.Default.GetBytes(str);

                byte[] sendMsg = new byte[send.Length + 1];

                Array.Copy(send, 0, sendMsg, 1, send.Length);

                sendMsg[0] = (byte)MessageType.JSON;

                foreach (var item in this.lst_Online.SelectedItems)
                {
                    //獲取Socket對象
                    string client = item.ToString();

                    CurrentClientlist[client]?.Send(sendMsg);
                }

            }
            else
            {
                MessageBox.Show("請選擇你要發送的客戶端對象!", "發送消息");
            }
        }

        #endregion

JsonHelper類

    public class JSONHelper
    {
        /// <summary>
        /// 實體對象轉換成JSON字符串
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="x"></param>
        /// <returns></returns>
        public static string EntityToJSON<T>(T x)
        {
            string result = string.Empty;

            try
            {
                result = JsonConvert.SerializeObject(x);
            }
            catch (Exception)
            {
                result = string.Empty;
            }
            return result;

        }

        /// <summary>
        /// JSON字符串轉換成實體類
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="json"></param>
        /// <returns></returns>
        public static T JSONToEntity<T>(string json)
        {
            T t = default(T);
            try
            {
                t = (T)JsonConvert.DeserializeObject(json, typeof(T));
            }
            catch (Exception)
            {
                t = default(T);
            }

            return t;              
        }

    }

客戶端程序編寫步驟:

第一步,調用socket()函數創建一個用於通信的套接字。

第二步,通過設置套接字地址結構,說明客戶端與之通信的服務器的Ip地址和端口號。

第三步,調用connect()函數來建立與服務器的連接。

第四步,調用讀寫函數發送或者接收數據。

第五步,終止連接。

 界面搭建:

 

 連接服務器,同時客戶端要判斷服務器是否斷開連接

       #region 連接服務器
        private void btn_Connect_Click(object sender, EventArgs e)
        {
            AddLog(0, "與服務器連接中");

            // 第一步:調用socket()函數創建一個用於通信的套接字。
            socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // 第二步:通過設置套接字地址結構,說明客戶端與之通信的服務器的IP地址和端口號。
            IPEndPoint ipe = new IPEndPoint(IPAddress.Parse(this.txt_IP.Text.Trim()), int.Parse(this.txt_Port.Text.Trim()));

            // 第三步:調用connect()函數來建立與服務器的連接。

            try
            {
                socketClient.Connect(ipe);
            }
            catch (Exception ex)
            {
                AddLog(2, "連接服務器失敗:" + ex.Message);
                return;
            }

            //創建一個監聽來自服務器消息的線程

            Task.Run(new Action(() =>
            {
                CheckReceiveMsg();
            }));

            AddLog(0, "成功連接至服務器");

            this.btn_Connect.Enabled = false;
        }

        #endregion
       #region 多線程接收來自服務器的數據

        private void CheckReceiveMsg()
        {
            while (true)
            {
                // 創建一個緩沖區

                byte[] buffer = new byte[1024 * 1024 * 10];

                int length = -1;

                //  第四步:調用讀寫函數發送或者接收數據。
                try
                {
                    length = socketClient.Receive(buffer);
                }
                catch (Exception)
                {
                    // 服務器斷線,可以添加服務器斷線的日志
                    break;
                }

                if (length > 0)
                {
                    string msg = string.Empty;

                    MessageType type = (MessageType)buffer[0];

                    switch (type)
                    {
                        case MessageType.ASCII:
                           //來自服務器的消息內容
                            msg = Encoding.ASCII.GetString(buffer, 1, length - 1);

                            AddLog(0,  "服務器:" + msg);

                            break;
                        case MessageType.UTF8:

                            msg = Encoding.UTF8.GetString(buffer, 1, length - 1);

                            AddLog(0,  "服務器:" + msg);

                            break;
                        case MessageType.Hex:

                            msg = HexGetString(buffer, 1, length - 1);

                            AddLog(0,  "服務器:" + msg);

                            break;
                        case MessageType.File:

                            Invoke(new Action(() =>
                            {
                                SaveFileDialog sfd = new SaveFileDialog();

                                sfd.Filter = "txt files(*.txt)|*.txt|xls files(*.xls)|*.xls|xlsx files(*.xlsx)|*.xlsx|All files(*.*)|*.*";

                                if (sfd.ShowDialog() == DialogResult.OK)
                                {
                                    string fileSavePath = sfd.FileName;

                                    using (FileStream fs = new FileStream(fileSavePath, FileMode.Create))
                                    {
                                        fs.Write(buffer, 1, length - 1);
                                    }

                                    AddLog(0, "文件成功保存至" + fileSavePath);
                                }
                            }));

                            break;
                        case MessageType.JSON:

                            Invoke(new Action(() =>
                            {
                                string res = Encoding.Default.GetString(buffer, 1, length);

                                List<Student> StuList = JSONHelper.JSONToEntity<List<Student>>(res);

                                new FrmJSON(StuList).Show();

                                AddLog(0, "接收JSON數據:" + res);

                            }));

                            break;
                        default:
                            break;
                    }

                }
               
                else
                {
                    // 如果length<=0,說明服務器斷線了,可以添加服務器斷線的日志
                    break;
                }
            }
        }

        #endregion

客服端發送消息給服務器,這里就只發送ASCLL,其余的數據格式和服務器端類似,有興趣的可以下載源碼查看。

       #region 發送ASCII
        private void btn_SendASCII_Click(object sender, EventArgs e)
        {
            AddLog(0, "發送內容:" + this.txt_Send.Text.Trim());

            byte[] send = Encoding.ASCII.GetBytes(this.txt_Send.Text.Trim());

            //創建最終發送的數組
            byte[] sendMsg = new byte[send.Length + 1];

            //整體拷貝數組
            Array.Copy(send, 0, sendMsg, 1, send.Length);

            //給首字節賦值

            sendMsg[0] = (byte)MessageType.ASCII;

            socketClient?.Send(sendMsg);

            this.txt_Send.Clear();
        }

        #endregion

客戶端下線時,需要斷開與服務器的連接

       #region 窗體關閉
        private void FrmTCPClient_FormClosing(object sender, FormClosingEventArgs e)
        {
            //客戶端下線的時候,關閉客戶端套接字
            socketClient?.Close();
        }

        #endregion

 斷開服務器

      #region 斷開服務器

        private void btn_DisConn_Click(object sender, EventArgs e)
        {
            socketClient?.Close();
        }

        #endregion

 

源碼下載鏈接:https://pan.baidu.com/s/1wIXLC0AlGyM1VoYeYOKMpg

提取碼:8qjn

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

,把send數組,從索引為1開始,拷貝send.Length個長度的字節數組給sendMsg


免責聲明!

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



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