Socket是一種通信TCP/IP的通訊介面,也就是HTTP的抽象層,就是Socket在Http之上,Socket也就是發動機。實際上,傳輸層的TCP是基於網路層的IP協議的,而應用層的HTTP協議又是基於傳輸層的TCP協議的,而Socket本身不算是協議,就像上面所說,它只是提供了一個針對TCP或 ...
Socket是一種通信TCP/IP的通訊介面,也就是HTTP的抽象層,就是Socket在Http之上,Socket也就是發動機。實際上,傳輸層的TCP是基於網路層的IP協議的,而應用層的HTTP協議又是基於傳輸層的TCP協議的,而Socket本身不算是協議,就像上面所說,它只是提供了一個針對TCP或者UDP編程的介面。在C#中可以非常方便的使用Socket進行數據傳輸。
Socket對象是C#使用它的重要對象在Socket的構造函數中,我們可以設置它的地址,Socket的類,支持的協議等等,其定義如下:
public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType);
我們想要使用Socket,那麼就必須創建Socket的對象,創建這個對象,就必須需要IPEndPoint對象來綁定到套接詞字中,有如下定義
// 創建負責監聽的套接字,註意其中的參數; socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 獲得文本框中的IP對象; IPAddress address = IPAddress.Parse(textBox1.Text.Trim()); // 創建包含ip和埠號的網路節點對象; IPEndPoint endPoint = new IPEndPoint(address, int.Parse(textBox2.Text.Trim()));
然後再通過Socket的Bind來進行綁定。
socketWatch.Bind(endPoint);
因為我們時刻會被內網中的其他ip和埠進行連接,那麼我們就需要創建線程來進行觀察,有如下定義
// 設置監聽隊列的長度; socketWatch.Listen(10); // 創建負責監聽的線程; threadWatch = new Thread(WatchConnecting); threadWatch.IsBackground = true; threadWatch.Start(); ShowMsg("伺服器啟動監聽成功!");
其檢測方法如下,其中就是不斷的去檢測客戶端的連接請求,通過Accept()方法可以獲取一個套接字,然後通過Socket對象的RemoteEndPoint()可以獲取一個IP。
void WatchConnecting() { while (true) // 持續不斷的監聽客戶端的連接請求; { // 開始監聽客戶端連接請求,Accept方法會阻斷當前的線程; Socket sokConnection = socketWatch.Accept(); // 一旦監聽到一個客戶端的請求,就返回一個與該客戶端通信的 套接字; // 想列表控制項中添加客戶端的IP信息; Online.Items.Add(sokConnection.RemoteEndPoint.ToString()); // 將與客戶端連接的 套接字 對象添加到集合中; dict.Add(sokConnection.RemoteEndPoint.ToString(), sokConnection); ShowMsg("客戶端連接成功!"); Thread thr = new Thread(RecMsg); thr.IsBackground = true; thr.Start(sokConnection); dictThread.Add(sokConnection.RemoteEndPoint.ToString(), thr); // 將新建的線程 添加 到線程的集合中去。 } }
最後我們開啟一個線程去執行RecMsg方法,然後我們不停的去監聽客戶端給我們的數據發送。
void RecMsg(object sokConnectionparn) { Socket sokClient = sokConnectionparn as Socket; while (true) { // 定義一個2M的緩存區; byte[] arrMsgRec = new byte[1024 * 1024 * 2]; // 將接受到的數據存入到輸入 arrMsgRec中; int length = -1; try { length = sokClient.Receive(arrMsgRec); // 接收數據,並返回數據的長度; } catch (SocketException se) { ShowMsg("異常:" + se.Message); // 從 通信套接字 集合中刪除被中斷連接的通信套接字; dict.Remove(sokClient.RemoteEndPoint.ToString()); // 從通信線程集合中刪除被中斷連接的通信線程對象; dictThread.Remove(sokClient.RemoteEndPoint.ToString()); // 從列表中移除被中斷的連接IP Online.Items.Remove(sokClient.RemoteEndPoint.ToString()); break; } catch (Exception e) { ShowMsg("異常:" + e.Message); // 從 通信套接字 集合中刪除被中斷連接的通信套接字; dict.Remove(sokClient.RemoteEndPoint.ToString()); // 從通信線程集合中刪除被中斷連接的通信線程對象; dictThread.Remove(sokClient.RemoteEndPoint.ToString()); // 從列表中移除被中斷的連接IP Online.Items.Remove(sokClient.RemoteEndPoint.ToString()); break; } if (arrMsgRec[0] == 0) // 表示接收到的是數據; { string strMsg = System.Text.Encoding.UTF8.GetString(arrMsgRec, 1, length - 1);// 將接受到的位元組數據轉化成字元串; ShowMsg(strMsg); } if (arrMsgRec[0] == 1) // 表示接收到的是文件; { SaveFileDialog sfd = new SaveFileDialog(); if (sfd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK) {// 在上邊的 sfd.ShowDialog() 的括弧裡邊一定要加上 this 否則就不會彈出 另存為 的對話框,而彈出的是本類的其他視窗,,這個一定要註意!!!【解釋:加了this的sfd.ShowDialog(this),“另存為”視窗的指針才能被SaveFileDialog的對象調用,若不加thisSaveFileDialog 的對象調用的是本類的其他視窗了,當然不彈出“另存為”視窗。】 string fileSavePath = sfd.FileName;// 獲得文件保存的路徑; // 創建文件流,然後根據路徑創建文件; using (FileStream fs = new FileStream(fileSavePath, FileMode.Create)) { fs.Write(arrMsgRec, 1, length - 1); ShowMsg("文件保存成功:" + fileSavePath); } } } } }
我們在方法中獲得了一個Object類型的對象,將這個Object對象轉換成了Socket,然後我們通過Socket的方法Receive()方法接收返回的數據,其中裡面有它的屬性,可以獲取ip還有一些數據等等。伺服器向客戶端發送數據也是非常簡單。通過Send方法就可以了,如以下定義:
string strMsg = "伺服器" + "\r\n" + " -->" + richTextBox1.Text.Trim() + "\r\n"; byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); // 將要發送的字元串轉換成Utf-8位元組數組; byte[] arrSendMsg = new byte[arrMsg.Length + 1]; arrSendMsg[0] = 0; // 表示發送的是消息數據 Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length); string strKey = ""; strKey = Online.Text.Trim(); if (string.IsNullOrEmpty(strKey)) // 判斷是不是選擇了發送的對象; { MessageBox.Show("請選擇你要發送的好友!!!"); } else { dict[strKey].Send(arrSendMsg);// 解決了 sokConnection是局部變數,不能再本函數中引用的問題; ShowMsg(strMsg); richTextBox1.Clear(); }
最後需要註意的是,如果你的文件較大,有的時候這個緩衝區達不到你的文件位元組那麼大,那麼就會截斷,所以與的時候,先將文件轉換為Byte是正確的做法。只要在客戶端進行逆轉就可以了!