老溫評(píng)語(yǔ):當(dāng)我 未對(duì)網(wǎng)絡(luò)通訊編程擁有足夠深的認(rèn)識(shí)之前,,總覺(jué)得它非常神秘莫測(cè),,覺(jué)得它遙之不可及,,就像眼巴巴的望著一位美麗的少女而無(wú)法進(jìn)行進(jìn)一步的活動(dòng)!呵呵~見(jiàn)笑了,。之所以道論此篇是想和網(wǎng)絡(luò)上的朋友進(jìn)行一次通訊編程方面的交流,,更重要的是是C#語(yǔ)言!我不會(huì)用那么所謂的“專(zhuān)假”那樣呆板的語(yǔ)言進(jìn)行描述,,我會(huì)用最簡(jiǎn)明最容易理解的意思進(jìn)行簡(jiǎn)述,同時(shí)也想給新人指明一條學(xué)習(xí)C#網(wǎng)絡(luò)編程的捷徑,,讓你們少通幾個(gè)宵,,保養(yǎng)好身體,希望本文能夠給你帶來(lái)拋磚引玉的靈感,,最后麻煩您往死里評(píng)論,,不要給我老溫面子! 閱讀本文之后您可以將它應(yīng)用于:游戲開(kāi)發(fā)中的通訊模塊,、企業(yè)ERP,、企業(yè)客戶(hù)管理系統(tǒng),、網(wǎng)絡(luò)聊天系統(tǒng),、C/S軟件模型等等網(wǎng)絡(luò)編程領(lǐng)域,你要相信C#是很強(qiáng)大的! 先打這兒住,,如果您的頭腦中還沒(méi)有一點(diǎn)點(diǎn)網(wǎng)絡(luò)編程的概念的話請(qǐng)先找“百度先生”請(qǐng)他幫你先學(xué)習(xí)一下!OK,,廢話不多講,繼續(xù)往下看: 要想足夠掌握住網(wǎng)絡(luò)編程,,首先要來(lái)忽悠一下幾個(gè)概念:
什么是TCP/UDP協(xié)議: TCP協(xié)議:是基于連接的協(xié)議,也就是說(shuō),,在正式收發(fā)數(shù)據(jù)前,,必須和對(duì)方建立可靠的連接。一個(gè)TCP連接必須要經(jīng)過(guò)三次“對(duì)話”才能建立起來(lái),,其中的過(guò)程非常復(fù)雜,。“面向連接”就是在正式通信前必須要與對(duì)方建立起連接。比如你給別人打電話,,必須等線路接通了、對(duì)方拿起話筒才能相互通話,。你不用想象的那么復(fù)雜,你就這么認(rèn)為:TCP協(xié)議它是很可靠的,,速度會(huì)有點(diǎn)慢的,兩點(diǎn)或多點(diǎn)之間是有種握手的感覺(jué),,互相信任的,,好像中間有渠道捆在一起的,這樣理解就很容易懂了。 UDP協(xié)議:是與TCP相對(duì)應(yīng)的協(xié)議,。它是面向非連接的協(xié)議,,它不與對(duì)方建立連接,而是直接就把數(shù)據(jù)包發(fā)送過(guò)去,!我們?nèi)绻?/span>TCP比喻成打電話,,那么UDP就是發(fā)傳統(tǒng)的信件,發(fā)過(guò)去再說(shuō),,管它死活,,我只要我自己快捷高效就行,。ping”命令”就是一個(gè)很好的應(yīng)用。 TCP就像你的老婆,,很可靠在家里幫你料理一切家務(wù),而UDP就像你的情人,,讓你著迷讓你的工作變得高效,,讓你的荷爾蒙變的高效,讓你的金錢(qián)消費(fèi)變的高效,,同時(shí)也讓你背負(fù)了意外險(xiǎn)~~呵呵! 什么是SOCKET: 你經(jīng)常聽(tīng)到人們談?wù)撝?/span>socket”,,或許你還不知道它的確切含義。現(xiàn)在讓我告訴你:它是使用標(biāo)準(zhǔn)Unix文件描述符(filedescriptor)和其它程序通訊的方式,。什么?你也許聽(tīng)到一些Unix高手(hacker)這樣說(shuō)過(guò):“呀,,Unix中的一切就是文件,!”那個(gè)家伙也許正在說(shuō)到一個(gè)事實(shí):Unix程序在執(zhí)行任何形式的I/O的時(shí)候,,程序是在讀或者寫(xiě)一個(gè)文件描述符。一個(gè)文件描述符只是一個(gè)和打開(kāi)的文件相關(guān)聯(lián)的整數(shù),。但是(注意后面的話),這個(gè)文件可能是一個(gè)網(wǎng)絡(luò)連接,,FIFO,管道,終端,,磁盤(pán)上的文件或者什么其它的東西,。Unix中所有的東西就是文件,!所以,,你想和Internet上別的程序通訊的時(shí)候,,你將要使用到文件描述符。 就此打住,,我不想再多說(shuō)過(guò)多的SOCKET概念,,說(shuō)白了就是一個(gè)概念,它是一種概念,,系統(tǒng)為了實(shí)現(xiàn)這個(gè)概念提供了一個(gè)API接口給我們使用罷了,,一切交給系統(tǒng)底層處理了。 什么是ISO/OSI參考模型: 什么,?我暈,,ISO/OSI 這是啥,呵呵,。這個(gè)知識(shí)我認(rèn)為有必要了解,,什么全國(guó)計(jì)算機(jī)等級(jí)考試?yán)?,程序員考試?yán)?,軟件設(shè)計(jì)師考試?yán)捕紩?huì)考到這個(gè)知識(shí)點(diǎn),。 有必要看看懂!一幅圖搞定它,,用不著去背這些概念,只要知道一些服務(wù)存在于哪層上面就可以了,。 什么是多線程技術(shù) 相信大家在使用電腦的時(shí)候已經(jīng)接觸過(guò)這個(gè)名詞,所謂的多線程用簡(jiǎn)單話來(lái)說(shuō)就是在同一時(shí)間處理多項(xiàng)任務(wù),。那么如此說(shuō)來(lái)無(wú)論在現(xiàn)實(shí)生活中還是在電腦上都存在著多線程(multithreading)這個(gè)說(shuō)法,因此同樣的是在現(xiàn)實(shí)生活中還是在電腦上通過(guò)多線程將可以有效提供工作效率,。 這里給大家舉個(gè)例子,比如你正在為家人準(zhǔn)備晚餐,,今天的晚餐將會(huì)由下面三道菜:烤雞肉,、土豆泥以及四季豆,。如果打算以“單線程”的方式做這頓晚餐,那么你也許會(huì)首先把烤雞肉做好,,做好烤雞肉之后再做土豆泥然后是四季豆。不幸的是當(dāng)你把最后一道菜四季豆做好之后,,你就會(huì)發(fā)現(xiàn)前面兩道菜烤雞肉和土豆泥早已經(jīng)冷掉了,。 以上的語(yǔ)言我可以這樣概括它:我吃了二個(gè)面包,不感覺(jué)飽,,但是我吃最后個(gè)面包的時(shí)候就會(huì)明顯感覺(jué)到它的存在,! 下面我們繼續(xù)吃最重要部分,最后個(gè)面包,。 我用C#實(shí)現(xiàn)了一個(gè)多客戶(hù)端與服務(wù)端通訊的小工具,,效果圖如下: 涉及到的源碼將開(kāi)放下載! 它由server端和Client端組成,典型的C/S結(jié)構(gòu)例子.這個(gè)例子很簡(jiǎn)單,但是一應(yīng)俱全,首先我們來(lái)看服務(wù)端的代碼部分: 因?yàn)槲覀儠?huì)用到Socket編程和線程技術(shù),所以要引入下面三個(gè)命名空間 using System.Net.Sockets; using System.Net; using System.Threading; 完整的代碼為: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Net.Sockets; using System.Net; using System.Threading; using System.Collections; namespace mytcpserver { public partial class Form1 : Form { Socket s; IPEndPoint ServerIPEP; private Thread th; Socket uc; private ArrayList alSock;// public Form1() { InitializeComponent(); Control.CheckForIllegalCrossThreadCalls = false; } public void Server() { int port = 5656; ServerIPEP = new IPEndPoint(IPAddress.Any, port); s = new Socket(ServerIPEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); s.Bind((EndPoint)ServerIPEP); s.Listen(10); alSock = new ArrayList(); while (true) { try { uc = s.Accept(); alSock.Add(uc); this.textBox1.AppendText(System.Convert.ToString(uc)); byte[] data = new byte[2048]; int rect = uc.Receive(data); byte[] chat = new byte[rect]; Buffer.BlockCopy(data, 0, chat, 0, rect); this.mymessage.AppendText("["+uc.RemoteEndPoint.ToString() +"]"+ System.Text.Encoding.Default.GetString(chat)); } catch (Exception ex) { // MessageBox.Show(ex.Message); } } } private void Form1_Load(object sender, EventArgs e) { } private void button1_Click(object sender, EventArgs e) { //開(kāi)始監(jiān)聽(tīng)部分 this.button1.Enabled = false; this.button2.Enabled = true; th = new Thread(new ThreadStart(Server));//新建一個(gè)用于監(jiān)聽(tīng)的線程 th.Start();//打開(kāi)新線程 } private void button2_Click(object sender, EventArgs e) { //停止監(jiān)聽(tīng)部分 try { this.button2.Enabled = false; this.button1.Enabled = true; s.Close(); th.Abort();//終止線程 } catch (Exception ex) { // MessageBox.Show(ex.Message); } } private void button3_Click(object sender, EventArgs e) { //回送消息部分 if (uc == null) { MessageBox.Show("我說(shuō)你要發(fā)給誰(shuí)啊."); } else { int index = int.Parse(this.textBox2.Text.Trim()); Socket sc = (Socket)alSock[index]; //發(fā)送數(shù)據(jù) sc.Send(System.Text.Encoding.Default.GetBytes(this.answermessage.Text.Trim())); } } } } 我不想對(duì)上面的代碼部分講更多的解釋?zhuān)绻阌胁幻靼椎膯?wèn)題跟貼留言,,上面的這代碼還是很容易理解的,注:這兒使用的是單線程技術(shù) 下面我們?cè)賮?lái)看客戶(hù)端的代碼: 相對(duì)而言客戶(hù)端的代碼更加簡(jiǎn)單 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Net.Sockets; using System.Net; using System.Threading; namespace mytcpchat { public partial class Form1 : Form { private Thread th; Socket c; public Form1() { InitializeComponent(); Control.CheckForIllegalCrossThreadCalls = false; } private void Form1_Load(object sender, EventArgs e) { } public void Client() { try { IPEndPoint ServerIPEP = new IPEndPoint(IPAddress.Parse(this.comboBox1.Text.Trim()), 5656); c = new Socket(ServerIPEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); c.Connect((EndPoint)ServerIPEP); c.Send(System.Text.Encoding.Default.GetBytes(this.sendmessage.Text.Trim())); byte[] data = new byte[2048]; while (true) { int rect = c.Receive(data); byte[] chat = new byte[rect]; Buffer.BlockCopy(data, 0, chat, 0, rect); this.message.AppendText(System.Text.Encoding.Default.GetString(chat)); } } catch (Exception ex) { // MessageBox.Show(ex.Message); } } private void button1_Click(object sender, EventArgs e) { try { th = new Thread(new ThreadStart(Client));//新建一個(gè)用于監(jiān)聽(tīng)的線程 th.Start();//打開(kāi)新線程 } catch (Exception ex) { MessageBox.Show(ex.Message); } } } } OK,,你現(xiàn)在可能會(huì)說(shuō),,老溫給我源代碼吧 注意:你閱讀上面部分之后,,你能解決的網(wǎng)絡(luò)環(huán)境還處于A網(wǎng)段到B網(wǎng)段是直通的情況,沒(méi)有雙方處于內(nèi)網(wǎng)中,,也就是發(fā)起的一方必須知道S端的公網(wǎng)IP地址,,那么我兩個(gè)網(wǎng)絡(luò)可能存在兩個(gè)局域網(wǎng)中會(huì)怎么樣呢?不是發(fā)不通了,,下面我們來(lái)繼續(xù)解決這個(gè)問(wèn)題,,先來(lái)講一下打洞的概念,比較枯燥,,但是為了成功的喜悅值得,! 網(wǎng)絡(luò)上的大多數(shù)UDP原理研究分析后,自己總結(jié)了下面的結(jié)果. 1、client A 登錄 通過(guò)http服務(wù)器 webservices 驗(yàn)證成功獲取自身信息與好友列表信息等,。 2,、Http服務(wù)器登記client A的NAT后的IP地址與端口 3、Client B登錄 通過(guò)http服務(wù)器 webservices 驗(yàn)證成功獲取自身信息與好友列表信息等,。 4,、Http服務(wù)器登記client B的NAT后的IP地址與端口 5、Client A想發(fā)送消息給Client B,,向HTtp服務(wù)器獲取Client B的在線IP地址 6,、Client A獲得Client B的IP地址后并發(fā)送UDP信息到Client B 7、Client A與Client B請(qǐng)求失敗,,信息丟失,,此時(shí)Client A報(bào)告Http服務(wù)器要求服務(wù)器幫忙對(duì)Client B進(jìn)行通知 8、Http服務(wù)器接到此命令后,,將Client A的IP地址發(fā)給Client B,,要求他連接 9、Client B收到HTTP服務(wù)器的信息后發(fā)送請(qǐng)求到 Client A 10,、由于此時(shí)Client A NAT已經(jīng)存在Clinet B的session,,所以此時(shí) Client A與Client B建立鏈接成功。 11,、Client A發(fā)送消息到 Client B成功,,不經(jīng)HTTP服務(wù)器中轉(zhuǎn) 總結(jié):換句話說(shuō)就是HTTP服務(wù)器啟動(dòng)了“和事老”的功能。為不信任的兩個(gè)人發(fā)出命令從而使他們達(dá)到了一種互相承認(rèn)而此建立鏈接關(guān)系 其中最主要的是Server 的一個(gè)方法就是發(fā)送消息到被打洞的客戶(hù)端 客戶(hù)端發(fā)送消息需要判斷該消息是否發(fā)送成功,不成功則要求請(qǐng)求打洞.若成功則不需要要求打洞!客戶(hù)端在接收到服務(wù)器的打洞指令后,則作出UDP發(fā)送響應(yīng) 接下來(lái)我們一起來(lái)利用一個(gè)例子進(jìn)行學(xué)習(xí):p2pDemo工程 這是一個(gè)基于控制臺(tái)應(yīng)用程序,。該代碼乃本人一位朋友編寫(xiě),,故代碼將不貼出來(lái),如果學(xué)習(xí)的可以下載進(jìn)行學(xué)習(xí),,然后在此提出留言進(jìn)行討論,! 本人強(qiáng)烈推薦您學(xué)習(xí),你不管搞C#/C++/JAVA,學(xué)習(xí)的是它的打洞原理 在本人參與的項(xiàng)目應(yīng)用中,既然我們?cè)诰帉?xiě)通訊程序,,加密與解密我們一定要考慮進(jìn)去,。 首先我們來(lái)看加密與解密部分: 公鑰加密私鑰解密, 沒(méi)問(wèn)題,也可以說(shuō)是"公共密鑰加密系統(tǒng)" 私鑰加密公鑰解密,一般不這么說(shuō),,應(yīng)叫"私鑰簽名,公鑰驗(yàn)證",也可以說(shuō)是“公共密鑰簽名系統(tǒng)” 再來(lái)說(shuō)一下"公共密鑰簽名系統(tǒng)"目的:(如果暈就多看幾遍,,這個(gè)沒(méi)搞清,后面的代碼就更暈) A欲傳(信息)給B,,但又怕B不確信該信息是A發(fā)的,。 1.A選計(jì)算(信息)的HASH值,如用MD5方式計(jì)算,得到:[MD5(信息)] 2.然后用自已的私鑰加密HASH值,,得到:[私鑰(MD5(信息))] 3.最后將信息與密文一起傳給B:傳給B:[(信息) + 私鑰(MD5(信息))] B接到 :[(信息) + 私鑰(MD5(信息))] 1.先用相同的HASH算法算出(信息)的HASH值,,這里也使用MD5方式 得到: [MD5(信息)!] 2. 再用A的公鑰解密 [ 私鑰(MD5(信息))] [公鑰(私鑰(MD5(信息)))] = [(MD5(信息)] 如能解開(kāi),證明該 [ 私鑰(MD5(信息))]是A發(fā)送的 3.再比效[MD5(信息)!]與[(MD5(信息)] 如果相同,表示(信息)在傳遞過(guò)程中沒(méi)有被他人修改過(guò)。 下面我們?cè)賮?lái)講一下數(shù)字簽名技術(shù): 我們對(duì)加解密算法已經(jīng)有了一定理解,可以進(jìn)一步討論"數(shù)字簽名"(注意不要與數(shù)字認(rèn)證混淆)的問(wèn)題了,即 如何給一個(gè)計(jì)算機(jī)文件進(jìn)行簽字,。數(shù)字簽字可以用對(duì)稱(chēng)算法實(shí)現(xiàn),,也可以用公鑰算法實(shí)現(xiàn)。但前者除了文件簽字者和文件接受者雙方,,還需要第三方認(rèn)證,,較麻煩; 通過(guò)公鑰加密算法的實(shí)現(xiàn)方法,,由于用秘密密鑰加密的文件,,需要靠公開(kāi)密鑰來(lái)解密,因此這可以作為數(shù)字簽名,,簽名者用秘密密鑰加密一個(gè)簽名(可以包括姓名,、 證件號(hào)碼、短信息等信息),,接收人可以用公開(kāi)的,、自己的公開(kāi)密鑰來(lái)解密,如果成功,,就能確保信息來(lái)自該公開(kāi)密鑰的所有人,。 我想讀者最關(guān)注的是如果利用C#進(jìn)行這方面的編程,由于種種原因本人不能將項(xiàng)目中的代碼全部公開(kāi),,將原理重要點(diǎn)公布,,如下: 序列化類(lèi): using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; namespace common { public class SerializeData { public static byte[] Serialize(object obj) { BinaryFormatter binaryF = new BinaryFormatter(); MemoryStream ms = new MemoryStream(1024 * 10); binaryF.Serialize(ms, obj); ms.Seek(0, SeekOrigin.Begin); byte[] buffer = new byte[(int)ms.Length]; ms.Read(buffer, 0, buffer.Length); ms.Close(); return buffer; } public static object Deserialize(byte[] buffer) { BinaryFormatter binaryF = new BinaryFormatter(); MemoryStream ms = new MemoryStream(buffer, 0, buffer.Length, false); object obj = binaryF.Deserialize(ms); ms.Close(); return obj; } } } TDES加密算法,注意加密的長(zhǎng)度,,一定要多測(cè),,注意雙字節(jié)!害了本人不少天 using System; using System.Collections.Generic; using System.Text; using System.Net.Sockets; using System.IO; using System.Security.Cryptography; using System.Text.RegularExpressions; namespace common { public class TripleDES { public static int GetStrLength(string content) { int length = 0; for (int i = 0; i < content.Length; i++) { if (Regex.IsMatch(content[i].ToString(), @"[\一-\龥](méi)+") || Regex.IsMatch(content[i].ToString(), @"[^\x00-\x7F]+")) { length += 2; } else { length++; } } return length; } //MD5散列方法 public string MD5(string content) { MD5 md5 = new MD5CryptoServiceProvider(); byte[] bt = Encoding.Default.GetBytes(content);//將待加密字符轉(zhuǎn)為 字節(jié)型數(shù)組 byte[] resualt = md5.ComputeHash(bt);//將字節(jié)數(shù)組轉(zhuǎn)為加密的字節(jié)數(shù)組 return BitConverter.ToString(resualt).Replace("-", ""); //將數(shù)字轉(zhuǎn)為string 型去掉內(nèi)部的無(wú)關(guān)字符 } //使用對(duì)稱(chēng)加密加密字符串 public byte[] EncryptText(string str, byte[] Key, byte[] IV) { //創(chuàng)建一個(gè)內(nèi)存流 MemoryStream memoryStream = new MemoryStream(); //使用傳遞的密鑰和IV創(chuàng)建加密流 CryptoStream cryptoStream = new CryptoStream(memoryStream, new TripleDESCryptoServiceProvider().CreateEncryptor(Key, IV), CryptoStreamMode.Write); //將傳遞的字符串轉(zhuǎn)換為字?jǐn)?shù)組 byte[] toEncrypte = Encoding.Default.GetBytes(str); try { //將字節(jié)數(shù)組寫(xiě)入加密流,并清除緩沖區(qū) cryptoStream.Write(toEncrypte, 0, toEncrypte.Length); cryptoStream.FlushFinalBlock(); //得到加密后的字節(jié)數(shù)組 byte[] encryptedBytes = memoryStream.ToArray(); return encryptedBytes; } catch { //SetListBox("加密出錯(cuò):" + err.Message); return null; } finally { cryptoStream.Close(); memoryStream.Close(); } } //使用對(duì)稱(chēng)加密算法,解密接收的字符串 public string DecryptText(byte[] dataBytes, byte[] Key, byte[] IV, int datalength) { //根據(jù)加密后的字節(jié)數(shù)組創(chuàng)建五個(gè)內(nèi)存流 MemoryStream memoryStream = new MemoryStream(dataBytes); //使用傳遞的密鑰,、IV和內(nèi)存流創(chuàng)建解密流 CryptoStream cryptoStream = new CryptoStream(memoryStream, new TripleDESCryptoServiceProvider().CreateDecryptor(Key, IV), CryptoStreamMode.Read); //創(chuàng)建一個(gè)字節(jié)數(shù)組保存解密后的數(shù)據(jù) byte[] decryptBytes = new byte[datalength]; try { //從解密流中將解密后的數(shù)據(jù)讀到字節(jié)數(shù)組中 cryptoStream.Read(decryptBytes, 0, decryptBytes.Length); //得到解密后的字符串 string decryptedString = Encoding.Default.GetString(decryptBytes); return decryptedString; } catch { // SetListBox("解密出錯(cuò)" + err.Message); return null; } finally { cryptoStream.Close(); memoryStream.Close(); } } } } 注意:在S端進(jìn)行多線程編寫(xiě)時(shí),,一定要考慮臨界資源,以及線程中對(duì)界面上的控件的操作,,在C#中是不允許的,,可以用委托來(lái)實(shí)現(xiàn)它。 題外話:在現(xiàn)有的游戲開(kāi)發(fā)中或大規(guī)則性能要求場(chǎng)合,,還會(huì)有更好的線程模型,,比如IOCP,EPOLL模型等,!它是專(zhuān)門(mén)用來(lái)處理這方面的一種模型,。 老溫網(wǎng)絡(luò)編程源碼下載 http://www.cnblogs.com/Files/wenweifeng/老溫網(wǎng)絡(luò)編程.rar 解壓密碼為:laowen |
|
來(lái)自: 圓錐的布袋 > 《待分類(lèi)》