c# 網(wǎng)絡(luò)編程之tcp
一、概述
UDP和TCP是網(wǎng)絡(luò)通訊常用的兩個(gè)傳輸協(xié)議,C#一般可以通過Socket來實(shí)現(xiàn)UDP和TCP通訊,由于.NET框架通過UdpClient、TcpListener 、TcpClient這幾個(gè)類對(duì)Socket進(jìn)行了封裝,使其使用更加方便, 本文就通過這幾個(gè)封裝過的類講解一下相關(guān)應(yīng)用。
二、基本應(yīng)用:連接、發(fā)送、接收
服務(wù)端建立偵聽并等待連接:
TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 9000);
tcpListener.Start();
if (tcpListener.Pending())
{
TcpClient client = tcpListener.AcceptTcpClient();
Console.WriteLine("Connected");
}
服務(wù)端是通過AcceptTcpClient方法獲得TcpClient對(duì)象,而客戶端是直接創(chuàng)建TcpClient對(duì)象。
TcpClient tcpClient = new TcpClient();
tcpClient.Connect("127.0.0.1", 9000);
發(fā)送數(shù)據(jù)TcpClient對(duì)象創(chuàng)建后,發(fā)送接收都通過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ì)丟包,但存在粘包問題。(嚴(yán)格來說粘包這個(gè)說法是不嚴(yán)謹(jǐn)?shù)?,因?yàn)門CP通訊是基于流的,沒有包的概念,包只是使用者自己的理解。) 下面分析一下粘包產(chǎn)生的原因及解決辦法。
TCP數(shù)據(jù)通訊是基于流來實(shí)現(xiàn)的,類似一個(gè)隊(duì)列,當(dāng)有數(shù)據(jù)發(fā)送過來時(shí),操作系統(tǒng)就會(huì)把發(fā)送過來的數(shù)據(jù)依次放到這個(gè)隊(duì)列中,對(duì)發(fā)送者而言,數(shù)據(jù)是一片一片發(fā)送的,所以自然會(huì)認(rèn)為存在數(shù)據(jù)包的概念,但對(duì)于接收者而言,如果沒有及時(shí)去取這些數(shù)據(jù),這些數(shù)據(jù)依次存放在隊(duì)列中,彼此之間并無明顯間隔,自然就粘包了。
還有一種情況粘包是發(fā)送端造成的,有時(shí)我們調(diào)用發(fā)送代碼時(shí),操作系統(tǒng)可能并不會(huì)立即發(fā)送,而是放到緩存區(qū),當(dāng)緩存區(qū)達(dá)到一定數(shù)量時(shí)才真正發(fā)送。
要解決粘包問題,大致有以下幾個(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ì)粘起來;
以上兩個(gè)方案都要求發(fā)送端需要立即發(fā)送,不可緩存數(shù)據(jù)。而且這兩種方案都有缺陷:首先,第一種方案:如果要包大小一致的話,如果約定的包比較大,肯定有較多數(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ù)處理:首先通過定義特殊的字符組合作為包頭和包尾,如果傳輸ASCII字符,可以用0x02表示開始(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)景下,也可以采用前面兩種解決方案。
四、 一個(gè)完整的例程
服務(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);
}
}
}
客戶端:
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");
});
}
}
}
傳送門:
C#網(wǎng)絡(luò)編程入門系列包括三篇文章:
以上就是c# 網(wǎng)絡(luò)編程之tcp的詳細(xì)內(nèi)容,更多關(guān)于c# 網(wǎng)絡(luò)編程tcp的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- C# TcpClient網(wǎng)絡(luò)編程傳輸文件的示例
- C# 網(wǎng)絡(luò)編程之UDP
- c# 網(wǎng)絡(luò)編程之http
- 深入學(xué)習(xí)C#網(wǎng)絡(luò)編程之HTTP應(yīng)用編程(下)
- 深入學(xué)習(xí)C#網(wǎng)絡(luò)編程之HTTP應(yīng)用編程(上)
- 淺談C#網(wǎng)絡(luò)編程詳解篇
- 詳解C# 網(wǎng)絡(luò)編程系列:實(shí)現(xiàn)類似QQ的即時(shí)通信程序
- 總結(jié)C#網(wǎng)絡(luò)編程中對(duì)于Cookie的設(shè)定要點(diǎn)
- C# Socket網(wǎng)絡(luò)編程實(shí)例
- C#網(wǎng)絡(luò)編程基礎(chǔ)之進(jìn)程和線程詳解
- c# socket網(wǎng)絡(luò)編程接收發(fā)送數(shù)據(jù)示例代碼
- C#開發(fā)之Socket網(wǎng)絡(luò)編程TCP/IP層次模型、端口及報(bào)文等探討
- C#網(wǎng)絡(luò)編程中常用特性介紹
相關(guān)文章
C#實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器功能(窗體)
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
C#遍歷操作系統(tǒng)下所有驅(qū)動(dòng)器的方法
這篇文章主要介紹了C#遍歷操作系統(tǒng)下所有驅(qū)動(dòng)器的方法,涉及C#中DriveInfo類GetDrivers方法的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-04-04
C#實(shí)現(xiàn)的三種模擬自動(dòng)登錄和提交POST信息的方法
這篇文章主要介紹了C#實(shí)現(xiàn)的三種模擬自動(dòng)登錄和提交POST信息的方法,分別列舉了WebBrowser、WebClient及HttpWebRequest實(shí)現(xiàn)自動(dòng)登錄及提交POST的相關(guān)實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
C# 并行和多線程編程——認(rèn)識(shí)和使用Task
這篇文章主要介紹了C# 并行和多線程編程——認(rèn)識(shí)和使用Task的的相關(guān)資料,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下2021-02-02
DataTables List互相轉(zhuǎn)換的實(shí)現(xiàn)類示例
這篇文章主要介紹了將DataTable轉(zhuǎn)換為L(zhǎng)ist,將List轉(zhuǎn)換為DataTable的實(shí)現(xiàn)類實(shí)例方法,大家參考使用吧2013-11-11
SQL Server存儲(chǔ)過程在C#中調(diào)用的簡(jiǎn)單實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于SQL Server存儲(chǔ)過程在C#中調(diào)用的簡(jiǎn)單實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用SQL Server存儲(chǔ)過程具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
C#實(shí)現(xiàn)JSON解析器MojoUnityJson功能(簡(jiǎn)單且高效)
MojoUnityJson 是使用C#實(shí)現(xiàn)的JSON解析器 ,算法思路來自于游戲引擎Mojoc的C語(yǔ)言實(shí)現(xiàn) Json.h。這篇文章主要介紹了C#實(shí)現(xiàn)JSON解析器MojoUnityJson的方法,需要的朋友可以參考下2018-01-01
C#用Parallel.Invoke方法盡可能并行執(zhí)行提供的每個(gè)線程
本文主要介紹了C#用Parallel.Invoke方法盡可能并行執(zhí)行提供的每個(gè)線程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01

