一、先简单介绍下TcpClient,TcpListener,IPEndPoint类的作用
1: TcpClient
此类是微软基于Tcp封装类,用于简化Tcp客户端的开发,主要通过构造带入主机地址或者IPEndPonint对象,然后调用Connect进行和服务器点对点的连接,连接成功后通
过GetStream方法返回NetworkStream对象
2: TcpListener
此类也是微软基于Tcp封装类,用于监听服务端或客户端的连接请求,一旦有连接请求信息,立刻交给TcpClient的AcceptTcpClient方法捕获,Start方法用于开始监听
3: IPEndPonint
处理IP地址和端口的封装类
4:IPAddress
提供包含计算机在 IP 网络上的地址的工具类
二、使用NetworkStream的注意事项和局限性
NetworkStream的工作环境和其他流有着很大的差别,
再回到第一节关于NetworkStream的知识点,在使用时有几点必须注意
1 再次强调NetworkStream是稳定的,面向连接的,所以它只适合TCP协议的环境下工作
所以一旦在UDP环境中,虽然编译不会报错,但是会跳出异常
2 我们可以通过NetworkStream简化Socket开发
3 如果要建立NetworkStream一个新的实例,则必须使用已经连接的Socket
4 NetworkStream 使用后不会自动关闭提供的socket,必须使用NetworkStream构造函数时指定Socket所有权(NetworkStream 的构造函数中设置)。
6 NetworkStream支持异步读写操作
7 NetworkStream的局限性
可惜的是NetworkStream基于安全上的考虑不支持 Posion属性或Seek方法,寻找或改变流的位置,如果试图强行使用会报出NotSupport的异常
支持传递数据的种类没有直接使用Socket来的多
三、NetworkStream的构造
1.NetworkStream (Socket) 为指定的 Socket 创建 NetworkStream 类的新实例
2.NetworkStream (Socket, Boolean ownsSocket) 用指定的 Socket 所属权为指定的 Socket
ownsSocket表示指示NetworkStream是否拥有该Socket
3.NetworkStream (Socket, FileAccess) 用指定的访问权限为指定的 Socket 创建
FileAccess 值的按位组合,这些值指定授予所提供的 Socket 上的 NetworkStream 的访问类型
4.NetworkStream (Socket, FileAccess, Boolean ownsSocket) 。
四、对于NetworkStream构造函数的理解相信大家经过前文的解释也能够掌握了,但是有几点必须强调下
1如果用构造产生NetworkStream的实例,则必须使用连接的Socket
2 如果该NetworkStream拥有对Socket的所有权,则在使用NetworkStream的Close方法时会同时关闭Socket,
否则关闭NetworkStream时不会关闭Socket
3, 能够创建对指定Socket带有读写权限的NetworkStream
五、NetworkStream的属性
1. CanSeek :用于指示流是否支持查找,它的值始终为 false
2. DataAvailable 指示在要读取的 NetworkStream 上是否有可用的数据。一般来说通过判断这个属性来判断NetworkStream中是否有数据
3. Length:NetworkStream不支持使用Length属性,强行使用会发生NotSupportedException异常
4.Position: NetworkStream不支持使用Position属性,强行使用会发生NotSupportedException异常
六、NetworkStream的方法
同样,NetworkStream的方法大致重写或继承了Stream的方法
但是以下方法必须注意:
1 int Read(byte[] buffer,int offset,int size)
该方法将数据读入 buffer 参数并返回成功读取的字节数。如果没有可以读取的数据,则 Read 方法返回 0。Read 操作将读取尽可能多的可用数据,
直至达到由 size 参数指定的字节数为止。如果远程主机关闭了连接并且已接收到所有可用数据,Read 方法将立即完成并返回零字节。
2 long Seek(long offset, SeekOrigin origin)
将流的当前位置设置为给定值。此方法当前不受支持,总是引发 NotSupportedException。
3 void Write(byte[] buffer, int offset,int size)
Write方法在指定的 offset 处启动,并将 buffer 内容中的 size 字节发送到网络。Write 方法将一直处于阻止状态(可以用异步解决),直到发送了请求的字节数或引发 SocketException 为止。如果收到 SocketException,可以使用 SocketException.ErrorCode 属性获取特定的错误代码。
七、简单使用规则
1、在tcp连接中,Networkstream可以重复读取,重复写入,不用关掉连接。
2、关掉NetworkStream会自动关闭掉Tcp连接
3、NetworkStream不需要使用Flush方法,数据会自动发送。
4、NetworkStream.read会阻塞线程直到有新的数据过来,所以,有同学会发现,发送端不释放,接收端不能接收到数据。接收前先判断 DataAvailable 没有数据的不进行Read就能实时收到数据了
5、如果发送端发送快,接收端接收慢,会造成数据堆叠,即接收端一次可能接收到发送端多次发送的数据流,可以在接收端返回确认接收完成后,再让发送端发送新数据。
6、怎么才能把NetworkStream流读完整。下面是个简单例子:
if(stream.DataAvailable)
{
int reciveByteCount = 0;
var headerByte = new byte[8];
await stream.ReadAsync(headerByte,0,8);
long pictureByteLength = BitConverter.ToInt64(headerByte,0);
var buffer = new byte[1024];
System.IO.MemoryStream ms = new System.IO.MemoryStream();
int tempCount = 0;
do
{
tempCount = await stream.ReadAsync(buffer,0,buffer.Length);
await ms.WriteAsync(buffer,0,tempCount);
receiveByteCount += tempCount;
}while(receiveByteCount<pictureByteLength);
}
八、networkstream是封装成了全双工的, 可以一边发一边收的。
class Program
{
static async Task Main(string[] args)
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var sae = new SocketAsyncEventArgs();
sae.RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, 5678);
using (var mres = new ManualResetEventSlim())
{
sae.Completed += (s, e) => mres.Set();
if (socket.ConnectAsync(sae))
{
if (!mres.Wait(30))
{
throw new TimeoutException("Could not connect to " + sae.RemoteEndPoint);
}
}
}
var stream = new NetworkStream(socket);
ReadOnlyMemory<byte> buffer = Encoding.UTF8.GetBytes("Hello World!");
Console.WriteLine("Sending data...");
await stream.WriteAsync(buffer);
Memory<byte> readBuffer = new byte[1024];
await stream.ReadAsync(readBuffer);
Console.WriteLine("Recieved data: " + Encoding.UTF8.GetString(readBuffer.Span));
Console.Read();
socket.Close();
}
}