C# TCP實現多個客戶端與服務端 資料 與 檔案的傳輸
阿新 • • 發佈:2020-10-12
下面是我用C#寫的 一個簡單的TCP通訊,主要的功能有:
(1) 多個客戶端與伺服器間的資料交流
(2)可以實現群發的功能
(3)客戶端與服務端可以進行檔案的傳輸
主要用到的知識: TCP裡的 socket 、、、 多執行緒 Thread 、、、
下面的是介面:
服務端程式碼:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq;7 using System.Text; 8 using System.Windows.Forms; 9 using System.Net.Sockets; 10 using System.Net; // IP,IPAddress, IPEndPoint,埠等; 11 using System.Threading; 12 using System.IO; 13 14 namespace _11111 15 { 16 public partial class frm_server : Form 17 { 18 public frm_server()19 { 20 InitializeComponent(); 21 TextBox.CheckForIllegalCrossThreadCalls = false; 22 } 23 24 Thread threadWatch = null; // 負責監聽客戶端連線請求的 執行緒; 25 Socket socketWatch = null; 26 27 Dictionary<string, Socket> dict = new Dictionary<string, Socket>(); 28 Dictionary<string, Thread> dictThread = new Dictionary<string, Thread>(); 29 30 private void btnBeginListen_Click(object sender, EventArgs e) 31 { 32 // 建立負責監聽的套接字,注意其中的引數; 33 socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 34 // 獲得文字框中的IP物件; 35 IPAddress address = IPAddress.Parse(txtIp.Text.Trim()); 36 // 建立包含ip和埠號的網路節點物件; 37 IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim())); 38 try 39 { 40 // 將負責監聽的套接字繫結到唯一的ip和埠上; 41 socketWatch.Bind(endPoint); 42 } 43 catch (SocketException se) 44 { 45 MessageBox.Show("異常:"+se.Message); 46 return; 47 } 48 // 設定監聽佇列的長度; 49 socketWatch.Listen(10); 50 // 建立負責監聽的執行緒; 51 threadWatch = new Thread(WatchConnecting); 52 threadWatch.IsBackground = true; 53 threadWatch.Start(); 54 ShowMsg("伺服器啟動監聽成功!"); 55 //} 56 } 57 58 /// <summary> 59 /// 監聽客戶端請求的方法; 60 /// </summary> 61 void WatchConnecting() 62 { 63 while (true) // 持續不斷的監聽客戶端的連線請求; 64 { 65 // 開始監聽客戶端連線請求,Accept方法會阻斷當前的執行緒; 66 Socket sokConnection = socketWatch.Accept(); // 一旦監聽到一個客戶端的請求,就返回一個與該客戶端通訊的 套接字; 67 // 想列表控制元件中新增客戶端的IP資訊; 68 lbOnline.Items.Add(sokConnection.RemoteEndPoint.ToString()); 69 // 將與客戶端連線的 套接字 物件新增到集合中; 70 dict.Add(sokConnection.RemoteEndPoint.ToString(), sokConnection); 71 ShowMsg("客戶端連線成功!"); 72 Thread thr = new Thread(RecMsg); 73 thr.IsBackground = true; 74 thr.Start(sokConnection); 75 dictThread.Add(sokConnection.RemoteEndPoint.ToString(), thr); // 將新建的執行緒 新增 到執行緒的集合中去。 76 } 77 } 78 79 void RecMsg(object sokConnectionparn) 80 { 81 Socket sokClient = sokConnectionparn as Socket; 82 while (true) 83 { 84 // 定義一個2M的快取區; 85 byte[] arrMsgRec = new byte[1024 * 1024 * 2]; 86 // 將接受到的資料存入到輸入 arrMsgRec中; 87 int length = -1; 88 try 89 { 90 length = sokClient.Receive(arrMsgRec); // 接收資料,並返回資料的長度; 91 } 92 catch (SocketException se) 93 { 94 ShowMsg("異常:" + se.Message); 95 // 從 通訊套接字 集合中刪除被中斷連線的通訊套接字; 96 dict.Remove(sokClient.RemoteEndPoint.ToString()); 97 // 從通訊執行緒集合中刪除被中斷連線的通訊執行緒物件; 98 dictThread.Remove(sokClient.RemoteEndPoint.ToString()); 99 // 從列表中移除被中斷的連線IP 100 lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString()); 101 break; 102 } 103 catch (Exception e) 104 { 105 ShowMsg("異常:" + e.Message); 106 // 從 通訊套接字 集合中刪除被中斷連線的通訊套接字; 107 dict.Remove(sokClient.RemoteEndPoint.ToString()); 108 // 從通訊執行緒集合中刪除被中斷連線的通訊執行緒物件; 109 dictThread.Remove(sokClient.RemoteEndPoint.ToString()); 110 // 從列表中移除被中斷的連線IP 111 lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString()); 112 break; 113 } 114 if (arrMsgRec[0] == 0) // 表示接收到的是資料; 115 { 116 string strMsg = System.Text.Encoding.UTF8.GetString(arrMsgRec,1, length-1);// 將接受到的位元組資料轉化成字串; 117 ShowMsg(strMsg); 118 } 119 if (arrMsgRec[0] == 1) // 表示接收到的是檔案; 120 { 121 SaveFileDialog sfd = new SaveFileDialog(); 122 123 if (sfd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK) 124 {// 在上邊的 sfd.ShowDialog() 的括號裡邊一定要加上 this 否則就不會彈出 另存為 的對話方塊,而彈出的是本類的其他視窗,,這個一定要注意!!!【解釋:加了this的sfd.ShowDialog(this),“另存為”視窗的指標才能被SaveFileDialog的物件呼叫,若不加thisSaveFileDialog 的物件呼叫的是本類的其他視窗了,當然不彈出“另存為”視窗。】 125 126 string fileSavePath = sfd.FileName;// 獲得檔案儲存的路徑; 127 // 建立檔案流,然後根據路徑建立檔案; 128 using (FileStream fs = new FileStream(fileSavePath, FileMode.Create)) 129 { 130 fs.Write(arrMsgRec, 1, length - 1); 131 ShowMsg("檔案儲存成功:" + fileSavePath); 132 } 133 } 134 } 135 } 136 } 137 138 void ShowMsg(string str) 139 { 140 txtMsg.AppendText(str + "\r\n"); 141 } 142 143 // 傳送訊息 144 private void btnSend_Click(object sender, EventArgs e) 145 { 146 string strMsg = "伺服器" + "\r\n" + " -->" + txtMsgSend.Text.Trim() + "\r\n"; 147 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); // 將要傳送的字串轉換成Utf-8位元組陣列; 148 byte[] arrSendMsg=new byte[arrMsg.Length+1]; 149 arrSendMsg[0] = 0; // 表示傳送的是訊息資料 150 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length); 151 string strKey = ""; 152 strKey = lbOnline.Text.Trim(); 153 if (string.IsNullOrEmpty(strKey)) // 判斷是不是選擇了傳送的物件; 154 { 155 MessageBox.Show("請選擇你要傳送的好友!!!"); 156 } 157 else 158 { 159 dict[strKey].Send(arrSendMsg);// 解決了 sokConnection是區域性變數,不能再本函式中引用的問題; 160 ShowMsg(strMsg); 161 txtMsgSend.Clear(); 162 } 163 } 164 165 /// <summary> 166 /// 群發訊息 167 /// </summary> 168 /// <param name="sender"></param> 169 /// <param name="e">訊息</param> 170 private void btnSendToAll_Click(object sender, EventArgs e) 171 { 172 string strMsg = "伺服器" + "\r\n" + " -->" + txtMsgSend.Text.Trim() + "\r\n"; 173 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); // 將要傳送的字串轉換成Utf-8位元組陣列;
174 }
1 byte[] arrSendMsg = new byte[arrMsg.Length + 1]; // 上次寫的時候把這一段給弄掉了,實在是抱歉哈~ 用來標識傳送是資料而不是檔案,如果沒有這一段的客戶端就接收不到訊息了~~~ 2 arrSendMsg[0] = 0; // 表示傳送的是訊息資料 3 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length);
1 foreach (Socket s in dict.Values) 2 { 3 s.Send(arrMsg); 4 } 5 ShowMsg(strMsg); 6 txtMsgSend.Clear(); 7 ShowMsg(" 群發完畢~~~"); 8 } 9 10 // 選擇要傳送的檔案 11 private void btnSelectFile_Click_1(object sender, EventArgs e) 12 { 13 OpenFileDialog ofd = new OpenFileDialog(); 14 ofd.InitialDirectory = "D:\\"; 15 if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) 16 { 17 txtSelectFile.Text = ofd.FileName; 18 } 19 } 20 21 // 檔案的傳送 22 private void btnSendFile_Click_1(object sender, EventArgs e) 23 { 24 if (string.IsNullOrEmpty(txtSelectFile.Text)) 25 { 26 MessageBox.Show("請選擇你要傳送的檔案!!!"); 27 } 28 else 29 { 30 // 用檔案流開啟使用者要傳送的檔案; 31 using (FileStream fs = new FileStream(txtSelectFile.Text, FileMode.Open)) 32 { 33 string fileName=System.IO.Path.GetFileName(txtSelectFile.Text); 34 string fileExtension=System.IO.Path.GetExtension(txtSelectFile.Text); 35 string strMsg = "我給你傳送的檔案為: "+fileName+fileExtension+"\r\n"; 36 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); // 將要傳送的字串轉換成Utf-8位元組陣列; 37 byte[] arrSendMsg = new byte[arrMsg.Length + 1]; 38 arrSendMsg[0] = 0; // 表示傳送的是訊息資料 39 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length); 40 bool fff = true; 41 string strKey = ""; 42 strKey = lbOnline.Text.Trim(); 43 if (string.IsNullOrEmpty(strKey)) // 判斷是不是選擇了傳送的物件; 44 { 45 MessageBox.Show("請選擇你要傳送的好友!!!"); 46 } 47 else 48 { 49 dict[strKey].Send(arrSendMsg);// 解決了 sokConnection是區域性變數,不能再本函式中引用的問題; 50 byte[] arrFile = new byte[1024 * 1024 * 2]; 51 int length = fs.Read(arrFile, 0, arrFile.Length); // 將檔案中的資料讀到arrFile陣列中; 52 byte[] arrFileSend = new byte[length + 1]; 53 arrFileSend[0] = 1; // 用來表示傳送的是檔案資料; 54 Buffer.BlockCopy(arrFile, 0, arrFileSend, 1, length); 55 // 還有一個 CopyTo的方法,但是在這裡不適合; 當然還可以用for迴圈自己轉化; 56 // sockClient.Send(arrFileSend);// 傳送資料到服務端; 57 dict[strKey].Send(arrFileSend);// 解決了 sokConnection是區域性變數,不能再本函式中引用的問題; 58 txtSelectFile.Clear(); 59 } 60 } 61 } 62 txtSelectFile.Clear(); 63 } 64 65 } 66 }
客戶端程式碼:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 10 using System.Net; 11 using System.Net.Sockets; 12 using System.Threading; 13 using System.IO; 14 15 namespace _2222222 16 { 17 public partial class frmClient : Form 18 { 19 public frmClient() 20 { 21 InitializeComponent(); 22 TextBox.CheckForIllegalCrossThreadCalls = false; 23 } 24 25 Thread threadClient = null; // 建立用於接收服務端訊息的 執行緒; 26 Socket sockClient = null; 27 private void btnConnect_Click(object sender, EventArgs e) 28 { 29 IPAddress ip = IPAddress.Parse(txtIp.Text.Trim()); 30 IPEndPoint endPoint=new IPEndPoint (ip,int.Parse(txtPort.Text.Trim())); 31 sockClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 32 try 33 { 34 ShowMsg("與伺服器連線中……"); 35 sockClient.Connect(endPoint); 36 37 } 38 catch (SocketException se) 39 { 40 MessageBox.Show(se.Message); 41 return; 42 //this.Close(); 43 } 44 ShowMsg("與伺服器連線成功!!!"); 45 threadClient = new Thread(RecMsg); 46 threadClient.IsBackground = true; 47 threadClient.Start(); 48 49 } 50 51 void RecMsg() 52 { 53 while (true) 54 { 55 // 定義一個2M的快取區; 56 byte[] arrMsgRec = new byte[1024 * 1024 * 2]; 57 // 將接受到的資料存入到輸入 arrMsgRec中; 58 int length = -1; 59 try 60 { 61 length = sockClient.Receive(arrMsgRec); // 接收資料,並返回資料的長度; 62 } 63 catch (SocketException se) 64 { 65 ShowMsg("異常;" + se.Message); 66 return; 67 } 68 catch (Exception e) 69 { 70 ShowMsg("異常:"+e.Message); 71 return; 72 } 73 if (arrMsgRec[0] == 0) // 表示接收到的是訊息資料; 74 { 75 string strMsg = System.Text.Encoding.UTF8.GetString(arrMsgRec, 1, length-1);// 將接受到的位元組資料轉化成字串; 76 ShowMsg(strMsg); 77 } 78 if (arrMsgRec[0] == 1) // 表示接收到的是檔案資料; 79 { 80 81 try 82 { 83 SaveFileDialog sfd = new SaveFileDialog(); 84 85 if (sfd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK) 86 {// 在上邊的 sfd.ShowDialog() 的括號裡邊一定要加上 this 否則就不會彈出 另存為 的對話方塊,而彈出的是本類的其他視窗,,這個一定要注意!!!【解釋:加了this的sfd.ShowDialog(this),“另存為”視窗的指標才能被SaveFileDialog的物件呼叫,若不加thisSaveFileDialog 的物件呼叫的是本類的其他視窗了,當然不彈出“另存為”視窗。】 87 88 string fileSavePath = sfd.FileName;// 獲得檔案儲存的路徑; 89 // 建立檔案流,然後根據路徑建立檔案; 90 using (FileStream fs = new FileStream(fileSavePath, FileMode.Create)) 91 { 92 fs.Write(arrMsgRec, 1, length - 1); 93 ShowMsg("檔案儲存成功:" + fileSavePath); 94 } 95 } 96 } 97 catch (Exception aaa) 98 { 99 MessageBox.Show(aaa.Message); 100 } 101 } 102 } 103 } 104 void ShowMsg(string str) 105 { 106 txtMsg.AppendText(str + "\r\n"); 107 } 108 109 // 傳送訊息; 110 private void btnSendMsg_Click(object sender, EventArgs e) 111 { 112 string strMsg = txtName.Text.Trim()+"\r\n"+" -->"+ txtSendMsg.Text.Trim()+ "\r\n"; 113 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); 114 byte[] arrSendMsg = new byte[arrMsg.Length + 1]; 115 arrSendMsg[0] = 0; // 用來表示傳送的是訊息資料 116 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length); 117 sockClient.Send(arrSendMsg); // 傳送訊息; 118 ShowMsg(strMsg); 119 txtSendMsg.Clear(); 120 } 121 122 // 選擇要傳送的檔案; 123 private void btnSelectFile_Click(object sender, EventArgs e) 124 { 125 OpenFileDialog ofd = new OpenFileDialog(); 126 ofd.InitialDirectory = "D:\\"; 127 if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) 128 { 129 txtSelectFile.Text = ofd.FileName; 130 } 131 } 132 133 //向伺服器端傳送檔案 134 private void btnSendFile_Click(object sender, EventArgs e) 135 { 136 if (string.IsNullOrEmpty(txtSelectFile.Text)) 137 { 138 MessageBox.Show("請選擇要傳送的檔案!!!"); 139 } 140 else 141 { 142 // 用檔案流開啟使用者要傳送的檔案; 143 using (FileStream fs = new FileStream(txtSelectFile.Text, FileMode.Open)) 144 { 145 //在傳送檔案以前先給好友傳送這個檔案的名字+副檔名,方便後面的儲存操作; 146 string fileName = System.IO.Path.GetFileName(txtSelectFile.Text); 147 string fileExtension = System.IO.Path.GetExtension(txtSelectFile.Text); 148 string strMsg = "我給你傳送的檔案為: " + fileName + "\r\n"; 149 byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); 150 byte[] arrSendMsg = new byte[arrMsg.Length + 1]; 151 arrSendMsg[0] = 0; // 用來表示傳送的是訊息資料 152 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length); 153 sockClient.Send(arrSendMsg); // 傳送訊息; 154 155 byte[] arrFile = new byte[1024 * 1024 * 2]; 156 int length = fs.Read(arrFile, 0, arrFile.Length); // 將檔案中的資料讀到arrFile陣列中; 157 byte[] arrFileSend = new byte[length + 1]; 158 arrFileSend[0] = 1; // 用來表示傳送的是檔案資料; 159 Buffer.BlockCopy(arrFile, 0, arrFileSend, 1, length); 160 // 還有一個 CopyTo的方法,但是在這裡不適合; 當然還可以用for迴圈自己轉化; 161 sockClient.Send(arrFileSend);// 傳送資料到服務端; 162 txtSelectFile.Clear(); 163 } 164 } 165 } 166 } 167 }
原文出自:https://blog.csdn.net/chwei_cson/article/details/7737766