c# 网络编程之tcp
一、概述
UDP和TCP是网络通讯常用的两个传输协议,C#一般可以通过Socket来实现UDP和TCP通讯,由于.NET框架通过UdpClient、TcpListener、TcpClient这几个类对Socket进行了封装,使其使用更加方便, 本文就通过这几个封装过的类讲解一下相关应用。
二、基本应用:连接、发送、接收
服务端建立侦听并等待连接:
TcpListenertcpListener=newTcpListener(IPAddress.Parse("127.0.0.1"),9000);
tcpListener.Start();
if(tcpListener.Pending())
{
TcpClientclient=tcpListener.AcceptTcpClient();
Console.WriteLine("Connected");
}
服务端是通过AcceptTcpClient方法获得TcpClient对象,而客户端是直接创建TcpClient对象。
TcpClienttcpClient=newTcpClient();
tcpClient.Connect("127.0.0.1",9000);
发送数据TcpClient对象创建后,发送接收都通过TcpClient对象完成。
发送数据:
TcpClienttcpClient=newTcpClient();
tcpClient.Connect("127.0.0.1",9000);
NetworkStreamnetStream=tcpClient.GetStream();
intLen=1024;
byte[]datas=newbyte[Len];
netStream.Write(datas,0,Len);
netStream.Close();
tcpClient.Close();
接收数据:
TcpClientclient=tcpListener.AcceptTcpClient();
Console.WriteLine("Connected");
NetworkStreamstream=client.GetStream();
varremote=client.Client.RemoteEndPoint;
byte[]data=newbyte[1024];
while(true)
{
if(stream.DataAvailable)
{
intlen=stream.Read(data,0,1024);
Console.WriteLine($"From:{remote}:Received({len})");
}
Thread.Sleep(1);
}
三、粘包问题
和UDP不太一样,TCP连接不会丢包,但存在粘包问题。(严格来说粘包这个说法是不严谨的,因为TCP通讯是基于流的,没有包的概念,包只是使用者自己的理解。)下面分析一下粘包产生的原因及解决办法。
TCP数据通讯是基于流来实现的,类似一个队列,当有数据发送过来时,操作系统就会把发送过来的数据依次放到这个队列中,对发送者而言,数据是一片一片发送的,所以自然会认为存在数据包的概念,但对于接收者而言,如果没有及时去取这些数据,这些数据依次存放在队列中,彼此之间并无明显间隔,自然就粘包了。
还有一种情况粘包是发送端造成的,有时我们调用发送代码时,操作系统可能并不会立即发送,而是放到缓存区,当缓存区达到一定数量时才真正发送。
要解决粘包问题,大致有以下几个方案。
1、约定数据长度,发送端的数据都是指定长度,比如1024;接收端取数据时也取同样长度,不够长度就等待,保证取到的数据和发送端一致;
2、接收端取数据的频率远大于发送端,比如发送端每1秒发送一段数据,接收端每0.1秒去取一次数据,这样基本可以保证数据不会粘起来;
以上两个方案都要求发送端需要立即发送,不可缓存数据。而且这两种方案都有缺陷:首先,第一种方案:如果要包大小一致的话,如果约定的包比较大,肯定有较多数据冗余,浪费网络资源,如果包较小,连接就比较频繁,效率不高。
其次,第二种方案:这个方案只能在理想环境下可以实现,当服务端遭遇一段时间的计算压力时可能会出现意外,不能完全保证。
比较完善的解决方案就是对接收到的数据进行预处理:首先通过定义特殊的字符组合作为包头和包尾,如果传输ASCII字符,可以用0x02表示开始(STX),用0x03表示结束(ETX),比如:STX‘H'‘e'‘l'‘l'‘o'ETX(二进制数据:0248656C6C6F03)。如果数据较长可以在包头留出固定位置存放包长度,如:
02000548656C6C6F03
其中0205就表示正文长度为5个字节,可以进行校验。
虽然第三种方案比较严谨,但相对复杂,在传输比较可靠、应用比较简单的场景下,也可以采用前面两种解决方案。
四、一个完整的例程
服务端:
usingSystem;
usingSystem.Collections.Generic;
usingSystem.IO;
usingSystem.Linq;
usingSystem.Net;
usingSystem.Net.Sockets;
usingSystem.Text;
usingSystem.Threading;
usingSystem.Threading.Tasks;
namespaceTCPServer
{
classProgram
{
staticvoidMain(string[]args)
{
TcpListenertcpListener=newTcpListener(IPAddress.Parse("127.0.0.1"),9000);
tcpListener.Start();
while(true)
{
if(tcpListener.Pending())
{
TcpClientclient=tcpListener.AcceptTcpClient();
Console.WriteLine("Connected");
Task.Run(()=>
{
NetworkStreamstream=client.GetStream();
varremote=client.Client.RemoteEndPoint;
while(true)
{
if(stream.DataAvailable)
{
byte[]data=newbyte[1024];
intlen=stream.Read(data,0,1024);
stringName=Encoding.UTF8.GetString(data,0,len);
varsenddata=Encoding.UTF8.GetBytes("Hello:"+Name);
stream.Write(senddata,0,senddata.Length);
}
if(!client.IsOnline())
{
Console.WriteLine("ConnectClosed.");
break;
}
Thread.Sleep(1);
}
});
}
Thread.Sleep(1);
}
}
}
publicstaticclassTcpClientEx
{
publicstaticboolIsOnline(thisTcpClientclient)
{
return!((client.Client.Poll(15000,SelectMode.SelectRead)&&(client.Client.Available==0))||!client.Client.Connected);
}
}
}
客户端:
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Diagnostics;
usingSystem.IO;
usingSystem.Linq;
usingSystem.Net.Sockets;
usingSystem.Text;
usingSystem.Threading;
usingSystem.Threading.Tasks;
namespaceTCP_Clent
{
classProgram
{
staticvoidMain(string[]args)
{
ThreadPool.SetMinThreads(100,100);
ThreadPool.SetMaxThreads(200,200);
Parallel.For(1,10,x=>
{
SendData("Tom");
});
Console.WriteLine("AllCompleted!");
Console.ReadKey();
}
privatestaticvoidSendData(stringName)
{
Task.Run(()=>
{
Console.WriteLine("Start");
TcpClienttcpClient=newTcpClient();
tcpClient.Connect("127.0.0.1",9000);
Console.WriteLine("Connected");
NetworkStreamnetStream=tcpClient.GetStream();
Task.Run(()=>
{
Thread.Sleep(100);
while(true)
{
if(!tcpClient.Client.Connected)
{
break;
}
if(netStream==null)
{
break;
}
try
{
if(netStream.DataAvailable)
{
byte[]data=newbyte[1024];
intlen=netStream.Read(data,0,1024);
varmessage=Encoding.UTF8.GetString(data,0,len);
Console.WriteLine(message);
}
}
catch
{
break;
}
Thread.Sleep(10);
}
});
for(inti=0;i<100;i++)
{
byte[]datas=Encoding.UTF8.GetBytes(Name);
intLen=datas.Length;
netStream.Write(datas,0,Len);
Thread.Sleep(1000);
}
netStream.Close();
netStream=null;
tcpClient.Close();
Console.WriteLine("Completed");
});
}
}
}
传送门:
C#网络编程入门系列包括三篇文章:
(一)C#网络编程入门之UDP
(二)C#网络编程入门之TCP
(三)C#网络编程入门之HTTP
以上就是c#网络编程之tcp的详细内容,更多关于c#网络编程tcp的资料请关注毛票票其它相关文章!