首先不多說,最終實現界面如下,可以通過點擊啟動服務,開啟TCP服務器:
開啟TCP服務器之后,可以通過點擊客戶端,打開一個獨立的TCP客戶端,打開客戶端之后,輸入正確的IP地址和端口號,可以進行連接服務器,這里可以同時開啟多個客戶端:
每個客戶端連接成功后,服務器的列表中會多出一個EndPoint,連接成功后,服務器和客戶端之間就可以自由通話了,可以發送消息,也可以發送文件。
其實這就是QQ等即時通信工具的雛形,如果兩個客戶端之間需要通信,就通過服務器進行中轉。
由於服務器代碼量較大,下面上傳一下客戶端的代碼,僅供參考:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.IO; 7 using System.Linq; 8 using System.Net; 9 using System.Net.Sockets; 10 using System.Text; 11 using System.Threading; 12 using System.Threading.Tasks; 13 using System.Windows.Forms; 14 15 namespace MyTCPServer 16 { 17 delegate void FileSaveDelegate(byte[] bt,int length); 18 19 public partial class FrmClient : Form 20 { 21 public FrmClient() 22 { 23 InitializeComponent(); 24 25 //委托對象綁定方法 26 MyShowMsg += ShowMsg; 27 28 MyFileSave += FileSave; 29 } 30 31 //運行標志位 32 private bool IsRun = true; 33 34 //創建連接服務器的Socket 35 Socket sockClient = null; 36 37 //創建接收服務器消息的線程 38 Thread thrClient = null; 39 40 //創建委托對象 41 ShowMsgDelegate MyShowMsg; 42 43 FileSaveDelegate MyFileSave; 44 45 private void btnConnect_Click(object sender, EventArgs e) 46 { 47 //獲取IP對象 48 IPAddress address = IPAddress.Parse(this.txtIp.Text.Trim()); 49 50 //根據IP對象和端口號創建網絡節點對象 51 IPEndPoint endPoint = new IPEndPoint(address, int.Parse(this.txtPort.Text.Trim())); 52 53 //創建負責連接的套接字,注意其中參數:[IPV4地址,字節流,TCP協議] 54 sockClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 55 56 try 57 { 58 Invoke(MyShowMsg, "與服務器連接中......"); 59 sockClient.Connect(endPoint); 60 } 61 catch (Exception ex) 62 { 63 MessageBox.Show("建立連接失敗:" + ex.Message, "建立連接"); 64 return; 65 } 66 67 Invoke(MyShowMsg, "與服務器連接成功!"); 68 69 thrClient = new Thread(ReceiveMsg); 70 thrClient.IsBackground = true; 71 thrClient.Start(); 72 73 this.btnConnect.Enabled = false; 74 } 75 76 private void FileSave(byte[] arrMsgRec, int length) 77 { 78 try 79 { 80 SaveFileDialog sfd = new SaveFileDialog(); 81 82 if (sfd.ShowDialog(this) == DialogResult.OK) 83 { 84 85 string fileSavePath = sfd.FileName;// 獲得文件保存的路徑; 86 // 創建文件流,然后根據路徑創建文件; 87 using (FileStream fs = new FileStream(fileSavePath, FileMode.Create)) 88 { 89 fs.Write(arrMsgRec, 1, length - 1); 90 Invoke(MyShowMsg, "文件保存成功:" + fileSavePath); 91 } 92 } 93 } 94 catch (Exception ex) 95 { 96 MessageBox.Show(ex.Message); 97 } 98 99 } 100 101 private void ReceiveMsg() 102 { 103 while(IsRun) 104 { 105 // 定義一個2M的緩存區 106 byte[] arrMsgRec = new byte[1024 * 1024 * 2]; 107 108 // 將接受到的數據存入到輸入 arrMsgRec中 109 int length = -1; 110 try 111 { 112 length = sockClient.Receive(arrMsgRec); // 接收數據,並返回數據的長度; 113 } 114 catch (SocketException) 115 { 116 return; 117 } 118 catch (Exception e) 119 { 120 Invoke(MyShowMsg, "連接斷開:" + e.Message); 121 return; 122 } 123 if (arrMsgRec[0] == 0) // 表示接收到的是消息數據; 124 { 125 string strMsg = System.Text.Encoding.UTF8.GetString(arrMsgRec, 1, length - 1);// 將接受到的字節數據轉化成字符串; 126 Invoke(MyShowMsg, strMsg); 127 } 128 if (arrMsgRec[0] == 1) // 表示接收到的是文件數據; 129 { 130 Invoke(MyFileSave, arrMsgRec, length); 131 } 132 } 133 } 134 135 private void ShowMsg(string str) 136 { 137 txtMsg?.AppendText(str + Environment.NewLine); 138 } 139 140 private void btnSend_Click(object sender, EventArgs e) 141 { 142 string strMsg = txt_Name.Text.Trim() + Environment.NewLine + " -->" + txtMsgSend.Text.Trim() + Environment.NewLine; 143 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); 144 byte[] arrSendMsg = new byte[arrMsg.Length + 1]; 145 arrSendMsg[0] = 0; // 用來表示發送的是消息數據 146 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length); 147 sockClient.Send(arrSendMsg); // 發送消息; 148 Invoke(MyShowMsg, strMsg); 149 txtMsgSend.Clear(); 150 } 151 152 private void btnSendFile_Click(object sender, EventArgs e) 153 { 154 if (string.IsNullOrEmpty(txtSelectFile.Text)) 155 { 156 MessageBox.Show("請選擇要發送的文件!!!"); 157 } 158 else 159 { 160 // 用文件流打開用戶要發送的文件; 161 using (FileStream fs = new FileStream(txtSelectFile.Text, FileMode.Open)) 162 { 163 //在發送文件以前先給好友發送這個文件的名字+擴展名,方便后面的保存操作; 164 string fileName = System.IO.Path.GetFileName(txtSelectFile.Text); 165 string fileExtension = System.IO.Path.GetExtension(txtSelectFile.Text); 166 string strMsg = "發送的文件為: " + fileName + Environment.NewLine; 167 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); 168 byte[] arrSendMsg = new byte[arrMsg.Length + 1]; 169 arrSendMsg[0] = 0; // 用來表示發送的是消息數據 170 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length); 171 sockClient.Send(arrSendMsg); // 發送消息; 172 173 byte[] arrFile = new byte[1024 * 1024 * 2]; 174 int length = fs.Read(arrFile, 0, arrFile.Length); // 將文件中的數據讀到arrFile數組中; 175 byte[] arrFileSend = new byte[length + 1]; 176 arrFileSend[0] = 1; // 用來表示發送的是文件數據; 177 Buffer.BlockCopy(arrFile, 0, arrFileSend, 1, length); 178 // 還有一個 CopyTo的方法,但是在這里不適合; 當然還可以用for循環自己轉化; 179 sockClient.Send(arrFileSend);// 發送數據到服務端; 180 txtSelectFile.Clear(); 181 } 182 } 183 } 184 185 private void btnSelectFile_Click(object sender, EventArgs e) 186 { 187 OpenFileDialog ofd = new OpenFileDialog(); 188 ofd.InitialDirectory = "D:\\"; 189 if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) 190 { 191 txtSelectFile.Text = ofd.FileName; 192 } 193 } 194 195 private void FrmClient_FormClosing(object sender, FormClosingEventArgs e) 196 { 197 IsRun = false; 198 sockClient?.Close(); 199 } 200 } 201 }
如果大家還有什么不明白的地方,可以關注一下微信公眾號:dotNet工控上位機