您好,登錄后才能下訂單哦!
這篇文章給大家介紹在c#中使用Socket實(shí)現(xiàn)一個(gè)tcp協(xié)議,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。
UDP和TCP是網(wǎng)絡(luò)通訊常用的兩個(gè)傳輸協(xié)議,C#一般可以通過(guò)Socket來(lái)實(shí)現(xiàn)UDP和TCP通訊,由于.NET框架通過(guò)UdpClient、TcpListener 、TcpClient這幾個(gè)類(lèi)對(duì)Socket進(jìn)行了封裝,使其使用更加方便, 本文就通過(guò)這幾個(gè)封裝過(guò)的類(lèi)講解一下相關(guān)應(yīng)用。
服務(wù)端建立偵聽(tīng)并等待連接:
TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 9000); tcpListener.Start(); if (tcpListener.Pending()) { TcpClient client = tcpListener.AcceptTcpClient(); Console.WriteLine("Connected"); }
服務(wù)端是通過(guò)AcceptTcpClient方法獲得TcpClient對(duì)象,而客戶(hù)端是直接創(chuàng)建TcpClient對(duì)象。
TcpClient tcpClient = new TcpClient(); tcpClient.Connect("127.0.0.1", 9000);
發(fā)送數(shù)據(jù)TcpClient對(duì)象創(chuàng)建后,發(fā)送接收都通過(guò)TcpClient對(duì)象完成。
發(fā)送數(shù)據(jù):
TcpClient tcpClient = new TcpClient(); tcpClient.Connect("127.0.0.1", 9000); NetworkStream netStream = tcpClient.GetStream(); int Len = 1024; byte[] datas = new byte[Len]; netStream.Write(datas, 0, Len); netStream.Close(); tcpClient.Close();
接收數(shù)據(jù):
TcpClient client = tcpListener.AcceptTcpClient(); Console.WriteLine("Connected"); NetworkStream stream = client.GetStream(); var remote = client.Client.RemoteEndPoint; byte[] data = new byte[1024]; while (true) { if (stream.DataAvailable) { int len = stream.Read(data, 0, 1024); Console.WriteLine($"From:{remote}:Received ({len})"); } Thread.Sleep(1); }
和UDP不太一樣,TCP連接不會(huì)丟包,但存在粘包問(wèn)題。(嚴(yán)格來(lái)說(shuō)粘包這個(gè)說(shuō)法是不嚴(yán)謹(jǐn)?shù)模驗(yàn)門(mén)CP通訊是基于流的,沒(méi)有包的概念,包只是使用者自己的理解。) 下面分析一下粘包產(chǎn)生的原因及解決辦法。
TCP數(shù)據(jù)通訊是基于流來(lái)實(shí)現(xiàn)的,類(lèi)似一個(gè)隊(duì)列,當(dāng)有數(shù)據(jù)發(fā)送過(guò)來(lái)時(shí),操作系統(tǒng)就會(huì)把發(fā)送過(guò)來(lái)的數(shù)據(jù)依次放到這個(gè)隊(duì)列中,對(duì)發(fā)送者而言,數(shù)據(jù)是一片一片發(fā)送的,所以自然會(huì)認(rèn)為存在數(shù)據(jù)包的概念,但對(duì)于接收者而言,如果沒(méi)有及時(shí)去取這些數(shù)據(jù),這些數(shù)據(jù)依次存放在隊(duì)列中,彼此之間并無(wú)明顯間隔,自然就粘包了。
還有一種情況粘包是發(fā)送端造成的,有時(shí)我們調(diào)用發(fā)送代碼時(shí),操作系統(tǒng)可能并不會(huì)立即發(fā)送,而是放到緩存區(qū),當(dāng)緩存區(qū)達(dá)到一定數(shù)量時(shí)才真正發(fā)送。
要解決粘包問(wèn)題,大致有以下幾個(gè)方案。
1、 約定數(shù)據(jù)長(zhǎng)度,發(fā)送端的數(shù)據(jù)都是指定長(zhǎng)度,比如1024;接收端取數(shù)據(jù)時(shí)也取同樣長(zhǎng)度,不夠長(zhǎng)度就等待,保證取到的數(shù)據(jù)和發(fā)送端一致;
2、 接收端取數(shù)據(jù)的頻率遠(yuǎn)大于發(fā)送端,比如發(fā)送端每1秒發(fā)送一段數(shù)據(jù),接收端每0.1秒去取一次數(shù)據(jù),這樣基本可以保證數(shù)據(jù)不會(huì)粘起來(lái);
以上兩個(gè)方案都要求發(fā)送端需要立即發(fā)送,不可緩存數(shù)據(jù)。而且這兩種方案都有缺陷:首先,第一種方案:如果要包大小一致的話(huà),如果約定的包比較大,肯定有較多數(shù)據(jù)冗余,浪費(fèi)網(wǎng)絡(luò)資源,如果包較小,連接就比較頻繁,效率不高。
其次,第二種方案:這個(gè)方案只能在理想環(huán)境下可以實(shí)現(xiàn),當(dāng)服務(wù)端遭遇一段時(shí)間的計(jì)算壓力時(shí)可能會(huì)出現(xiàn)意外,不能完全保證。
比較完善的解決方案就是對(duì)接收到的數(shù)據(jù)進(jìn)行預(yù)處理:首先通過(guò)定義特殊的字符組合作為包頭和包尾,如果傳輸ASCII字符,可以用0x02表示開(kāi)始(STX),用0x03表示結(jié)束(ETX),比如:STX ‘H' ‘e' ‘l' ‘l' ‘o' ETX (二進(jìn)制數(shù)據(jù): 02 48 65 6C 6C 6F 03)。如果數(shù)據(jù)較長(zhǎng)可以在包頭留出固定位置存放包長(zhǎng)度, 如:
02 00 05 48 65 6C 6C 6F 03
其中02 05 就表示正文長(zhǎng)度為5個(gè)字節(jié),可以進(jìn)行校驗(yàn)。
雖然第三種方案比較嚴(yán)謹(jǐn),但相對(duì)復(fù)雜,在傳輸比較可靠、應(yīng)用比較簡(jiǎn)單的場(chǎng)景下,也可以采用前面兩種解決方案。
服務(wù)端:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace TCPServer { class Program { static void Main(string[] args) { TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 9000); tcpListener.Start(); while (true) { if (tcpListener.Pending()) { TcpClient client = tcpListener.AcceptTcpClient(); Console.WriteLine("Connected"); Task.Run(() => { NetworkStream stream = client.GetStream(); var remote = client.Client.RemoteEndPoint; while (true) { if (stream.DataAvailable) { byte[] data = new byte[1024]; int len = stream.Read(data, 0, 1024); string Name = Encoding.UTF8.GetString(data,0,len); var senddata = Encoding.UTF8.GetBytes("Hello:" + Name); stream.Write(senddata, 0, senddata.Length); } if (!client.IsOnline()) { Console.WriteLine("Connect Closed."); break; } Thread.Sleep(1); } }); } Thread.Sleep(1); } } } public static class TcpClientEx { public static bool IsOnline(this TcpClient client) { return !((client.Client.Poll(15000, SelectMode.SelectRead) && (client.Client.Available == 0)) || !client.Client.Connected); } } }
客戶(hù)端:
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace TCP_Clent { class Program { static void Main(string[] args) { ThreadPool.SetMinThreads(100, 100); ThreadPool.SetMaxThreads(200, 200); Parallel.For(1, 10, x => { SendData("Tom"); }); Console.WriteLine("All Completed!"); Console.ReadKey(); } private static void SendData(string Name) { Task.Run(() => { Console.WriteLine("Start"); TcpClient tcpClient = new TcpClient(); tcpClient.Connect("127.0.0.1", 9000); Console.WriteLine("Connected"); NetworkStream netStream = tcpClient.GetStream(); Task.Run(() => { Thread.Sleep(100); while (true) { if (!tcpClient.Client.Connected) { break; } if (netStream == null) { break; } try { if (netStream.DataAvailable) { byte[] data = new byte[1024]; int len = netStream.Read(data, 0, 1024); var message = Encoding.UTF8.GetString(data, 0, len); Console.WriteLine(message); } } catch { break; } Thread.Sleep(10); } }); for (int i = 0; i < 100; i++) { byte[] datas = Encoding.UTF8.GetBytes(Name); int Len = datas.Length; netStream.Write(datas, 0, Len); Thread.Sleep(1000); } netStream.Close(); netStream = null; tcpClient.Close(); Console.WriteLine("Completed"); }); } } }
關(guān)于在c#中使用Socket實(shí)現(xiàn)一個(gè)tcp協(xié)議就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。