1. 程式人生 > 實用技巧 >C# TCP實現多個客戶端與服務端 資料 與 檔案的傳輸

C# TCP實現多個客戶端與服務端 資料 與 檔案的傳輸

下面是我用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