C#學(xué)習(xí)教程之Socket的簡(jiǎn)單使用
前言
在開(kāi)始介紹socket前先補(bǔ)充補(bǔ)充基礎(chǔ)知識(shí),在此基礎(chǔ)上理解網(wǎng)絡(luò)通信才會(huì)順理成章,當(dāng)然有基礎(chǔ)的可以跳過(guò)去了。都是廢話(huà),進(jìn)入正題。
TCP/IP:Transmission Control Protocol/Internet Protocol,傳輸控制協(xié)議/因特網(wǎng)互聯(lián)協(xié)議,又名網(wǎng)絡(luò)通訊協(xié)議。簡(jiǎn)單來(lái)說(shuō):TCP控制傳輸數(shù)據(jù),負(fù)責(zé)發(fā)現(xiàn)傳輸?shù)膯?wèn)題,一旦有問(wèn)題就發(fā)出信號(hào),要求重新傳輸,直到所有數(shù)據(jù)安全正確地傳輸?shù)侥康牡?,而IP是負(fù)責(zé)給因特網(wǎng)中的每一臺(tái)電腦定義一個(gè)地址,以便傳輸。從協(xié)議分層模型方面來(lái)講:TCP/IP由:網(wǎng)絡(luò)接口層(鏈路層)、網(wǎng)絡(luò)層、傳輸層、應(yīng)用層。它和OSI的七層結(jié)構(gòu)以及對(duì)于協(xié)議族不同,下圖簡(jiǎn)單表示:



注:第一張圖:TCP/IP的四層結(jié)構(gòu)對(duì)應(yīng)OSI七層結(jié)構(gòu)。
第三張圖:TCP/IP協(xié)議族在OSI七層中的位置及對(duì)應(yīng)的功能。
第二張圖:TCP/IP協(xié)議模塊關(guān)系圖。
現(xiàn)階段socket通信使用TCP、UDP協(xié)議,相對(duì)應(yīng)UDP來(lái)說(shuō),TCP則是比較安全穩(wěn)定的協(xié)議了。
Socket是一種通信TCP/IP的通訊接口,也就是HTTP的抽象層,就是Socket在Http之上,Socket也就是發(fā)動(dòng)機(jī)。實(shí)際上,傳輸層的TCP是基于網(wǎng)絡(luò)層的IP協(xié)議的,而應(yīng)用層的HTTP協(xié)議又是基于傳輸層的TCP協(xié)議的,而Socket本身不算是協(xié)議,就像上面所說(shuō),它只是提供了一個(gè)針對(duì)TCP或者UDP編程的接口。
在C#中可以非常方便的使用Socket進(jìn)行數(shù)據(jù)傳輸。
Socket對(duì)象是C#使用它的重要對(duì)象在Socket的構(gòu)造函數(shù)中,我們可以設(shè)置它的地址,Socket的類(lèi),支持的協(xié)議等等,其定義如下:
public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType);
我們想要使用Socket,那么就必須創(chuàng)建Socket的對(duì)象,創(chuàng)建這個(gè)對(duì)象,就必須需要IPEndPoint對(duì)象來(lái)綁定到套接詞字中,有如下定義
// 創(chuàng)建負(fù)責(zé)監(jiān)聽(tīng)的套接字,注意其中的參數(shù); socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 獲得文本框中的IP對(duì)象; IPAddress address = IPAddress.Parse(textBox1.Text.Trim()); // 創(chuàng)建包含ip和端口號(hào)的網(wǎng)絡(luò)節(jié)點(diǎn)對(duì)象; IPEndPoint endPoint = new IPEndPoint(address, int.Parse(textBox2.Text.Trim()));
然后再通過(guò)Socket的Bind來(lái)進(jìn)行綁定。
socketWatch.Bind(endPoint);
因?yàn)槲覀儠r(shí)刻會(huì)被內(nèi)網(wǎng)中的其他ip和端口進(jìn)行連接,那么我們就需要?jiǎng)?chuàng)建線(xiàn)程來(lái)進(jìn)行觀察,有如下定義
// 設(shè)置監(jiān)聽(tīng)隊(duì)列的長(zhǎng)度;
socketWatch.Listen(10);
// 創(chuàng)建負(fù)責(zé)監(jiān)聽(tīng)的線(xiàn)程;
threadWatch = new Thread(WatchConnecting);
threadWatch.IsBackground = true;
threadWatch.Start();
ShowMsg("服務(wù)器啟動(dòng)監(jiān)聽(tīng)成功!");
其檢測(cè)方法如下,其中就是不斷的去檢測(cè)客戶(hù)端的連接請(qǐng)求,通過(guò)Accept()方法可以獲取一個(gè)套接字,然后通過(guò)Socket對(duì)象的RemoteEndPoint()可以獲取一個(gè)IP。
void WatchConnecting()
{
while (true) // 持續(xù)不斷的監(jiān)聽(tīng)客戶(hù)端的連接請(qǐng)求;
{
// 開(kāi)始監(jiān)聽(tīng)客戶(hù)端連接請(qǐng)求,Accept方法會(huì)阻斷當(dāng)前的線(xiàn)程;
Socket sokConnection = socketWatch.Accept(); // 一旦監(jiān)聽(tīng)到一個(gè)客戶(hù)端的請(qǐng)求,就返回一個(gè)與該客戶(hù)端通信的 套接字;
// 想列表控件中添加客戶(hù)端的IP信息;
Online.Items.Add(sokConnection.RemoteEndPoint.ToString());
// 將與客戶(hù)端連接的 套接字 對(duì)象添加到集合中;
dict.Add(sokConnection.RemoteEndPoint.ToString(), sokConnection);
ShowMsg("客戶(hù)端連接成功!");
Thread thr = new Thread(RecMsg);
thr.IsBackground = true;
thr.Start(sokConnection);
dictThread.Add(sokConnection.RemoteEndPoint.ToString(), thr); // 將新建的線(xiàn)程 添加 到線(xiàn)程的集合中去。
}
}
最后我們開(kāi)啟一個(gè)線(xiàn)程去執(zhí)行RecMsg方法,然后我們不停的去監(jiān)聽(tīng)客戶(hù)端給我們的數(shù)據(jù)發(fā)送。
void RecMsg(object sokConnectionparn)
{
Socket sokClient = sokConnectionparn as Socket;
while (true)
{
// 定義一個(gè)2M的緩存區(qū);
byte[] arrMsgRec = new byte[1024 * 1024 * 2];
// 將接受到的數(shù)據(jù)存入到輸入 arrMsgRec中;
int length = -1;
try
{
length = sokClient.Receive(arrMsgRec); // 接收數(shù)據(jù),并返回?cái)?shù)據(jù)的長(zhǎng)度;
}
catch (SocketException se)
{
ShowMsg("異常:" + se.Message);
// 從 通信套接字 集合中刪除被中斷連接的通信套接字;
dict.Remove(sokClient.RemoteEndPoint.ToString());
// 從通信線(xiàn)程集合中刪除被中斷連接的通信線(xiàn)程對(duì)象;
dictThread.Remove(sokClient.RemoteEndPoint.ToString());
// 從列表中移除被中斷的連接IP
Online.Items.Remove(sokClient.RemoteEndPoint.ToString());
break;
}
catch (Exception e)
{
ShowMsg("異常:" + e.Message);
// 從 通信套接字 集合中刪除被中斷連接的通信套接字;
dict.Remove(sokClient.RemoteEndPoint.ToString());
// 從通信線(xiàn)程集合中刪除被中斷連接的通信線(xiàn)程對(duì)象;
dictThread.Remove(sokClient.RemoteEndPoint.ToString());
// 從列表中移除被中斷的連接IP
Online.Items.Remove(sokClient.RemoteEndPoint.ToString());
break;
}
if (arrMsgRec[0] == 0) // 表示接收到的是數(shù)據(jù);
{
string strMsg = System.Text.Encoding.UTF8.GetString(arrMsgRec, 1, length - 1);// 將接受到的字節(jié)數(shù)據(jù)轉(zhuǎn)化成字符串;
ShowMsg(strMsg);
}
if (arrMsgRec[0] == 1) // 表示接收到的是文件;
{
SaveFileDialog sfd = new SaveFileDialog();
if (sfd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
{// 在上邊的 sfd.ShowDialog() 的括號(hào)里邊一定要加上 this 否則就不會(huì)彈出 另存為 的對(duì)話(huà)框,而彈出的是本類(lèi)的其他窗口,,這個(gè)一定要注意?。?!【解釋?zhuān)杭恿藅his的sfd.ShowDialog(this),“另存為”窗口的指針才能被SaveFileDialog的對(duì)象調(diào)用,若不加thisSaveFileDialog 的對(duì)象調(diào)用的是本類(lèi)的其他窗口了,當(dāng)然不彈出“另存為”窗口?!?
string fileSavePath = sfd.FileName;// 獲得文件保存的路徑;
// 創(chuàng)建文件流,然后根據(jù)路徑創(chuàng)建文件;
using (FileStream fs = new FileStream(fileSavePath, FileMode.Create))
{
fs.Write(arrMsgRec, 1, length - 1);
ShowMsg("文件保存成功:" + fileSavePath);
}
}
}
}
}
我們?cè)诜椒ㄖ蝎@得了一個(gè)Object類(lèi)型的對(duì)象,將這個(gè)Object對(duì)象轉(zhuǎn)換成了Socket,然后我們通過(guò)Socket的方法Receive()方法接收返回的數(shù)據(jù),其中里面有它的屬性,可以獲取ip還有一些數(shù)據(jù)等等。服務(wù)器向客戶(hù)端發(fā)送數(shù)據(jù)也是非常簡(jiǎn)單。通過(guò)Send方法就可以了,如以下定義:
string strMsg = "服務(wù)器" + "\r\n" + " -->" + richTextBox1.Text.Trim() + "\r\n";
byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); // 將要發(fā)送的字符串轉(zhuǎn)換成Utf-8字節(jié)數(shù)組;
byte[] arrSendMsg = new byte[arrMsg.Length + 1];
arrSendMsg[0] = 0; // 表示發(fā)送的是消息數(shù)據(jù)
Buffer.BlockCopy(arrMsg, 0, arrSendMsg, 1, arrMsg.Length);
string strKey = "";
strKey = Online.Text.Trim();
if (string.IsNullOrEmpty(strKey)) // 判斷是不是選擇了發(fā)送的對(duì)象;
{
MessageBox.Show("請(qǐng)選擇你要發(fā)送的好友?。?!");
}
else
{
dict[strKey].Send(arrSendMsg);// 解決了 sokConnection是局部變量,不能再本函數(shù)中引用的問(wèn)題;
ShowMsg(strMsg);
richTextBox1.Clear();
}
最后需要注意的是,如果你的文件較大,有的時(shí)候這個(gè)緩沖區(qū)達(dá)不到你的文件字節(jié)那么大,那么就會(huì)截?cái)啵耘c的時(shí)候,先將文件轉(zhuǎn)換為Byte是正確的做法。只要在客戶(hù)端進(jìn)行逆轉(zhuǎn)就可以了!
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
C#導(dǎo)出GridView數(shù)據(jù)到Excel文件類(lèi)實(shí)例
這篇文章主要介紹了C#導(dǎo)出GridView數(shù)據(jù)到Excel文件類(lèi),實(shí)例分析了C#使用GridView及Excel的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03
C#操作SQLite數(shù)據(jù)庫(kù)方法小結(jié)
這篇文章介紹了C#操作SQLite數(shù)據(jù)庫(kù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06
C#實(shí)現(xiàn)窗體抖動(dòng)的兩種方法
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)窗體抖動(dòng)的兩種方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11
C#中將xml文件反序列化為實(shí)例時(shí)采用基類(lèi)還是派生類(lèi)的知識(shí)點(diǎn)討論
在本篇文章里小編給大家整理的是關(guān)于C#中將xml文件反序列化為實(shí)例時(shí)采用基類(lèi)還是派生類(lèi)的知識(shí)點(diǎn)討論,有需要的朋友們學(xué)習(xí)下。2019-11-11
C#讀取多條數(shù)據(jù)記錄導(dǎo)出到Word之圖片輸出改造
這篇文章主要為大家詳細(xì)介紹了C#讀取多條數(shù)據(jù)記錄并導(dǎo)出到Word標(biāo)簽?zāi)0逯械膱D片輸出問(wèn)題,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2024-11-11
登錄驗(yàn)證全局控制的幾種方式總結(jié)(session)
在登陸驗(yàn)證或者其他需要用到session全局變量的時(shí)候,歸結(jié)起來(lái),主要有以下三種較方便的實(shí)現(xiàn)方式。(其中個(gè)人較喜歡使用第一種實(shí)現(xiàn)方法)2014-01-01
DevExpress實(shí)現(xiàn)TreeList向上遞歸獲取公共父節(jié)點(diǎn)的方法
這篇文章主要介紹了DevExpress實(shí)現(xiàn)TreeList向上遞歸獲取公共父節(jié)點(diǎn)的方法,需要的朋友可以參考下2014-08-08
C#多線(xiàn)程同步不同實(shí)現(xiàn)方式小結(jié)
本文主要介紹了C#多線(xiàn)程同步不同實(shí)現(xiàn)方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-01-01
C#模板方法模式(Template Method Pattern)實(shí)例教程
這篇文章主要介紹了C#模板方法模式(Template Method Pattern),以實(shí)例形式講述了C#抽象類(lèi)模板方法的用法,具有很高的實(shí)用價(jià)值,需要的朋友可以參考下2014-09-09

