效果展示
①客戶端發送消息給服務器
②服務器發送消息給指定客戶端
③服務器群發消息給客戶端
③服務器發送文件給客戶端
④服務器給客戶端發送震動指令
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(); } }
需要代碼的關注私聊哦!
后續會陸續更新其他資料,喜歡請關注哦!