点对点即Peer-To-Peer,通常简写为P2P。所谓网络中的点对点,其实可以看成是一种对等的网络模型。P2P其实是实现网络上不同计算机之间,不经过中继设备直接交换数据或服务的一种技术。P2P由于允许网络中任一台计算机可以直接连接到网络中其他计算机,并与之进行数据交换,这样既消除了中间环节,也使得网络上的沟通变得更容易、更直接。
P2P作为一种网络的模型,它有别于传统的客户/服务器模型。客户/服务器模型一般都有预定义的客户机和服务器。而在P2P模型转并没有明确的客户端和服务器,但其实在P2P模型中,每一台计算机既可以看成是服务器,也可以看成是客户机。在网络中,传统上的客户机/服务器通讯模型中,发送服务请求或者发送数据的计算机,一般称为客户机;而接收、处理服务或接收数据的计算机称为服务器。而在P2P网络模型中,计算机不仅接收数据,还有发送数据,不仅提出服务请求,还有接收对方的服务请求。
在下面介绍的用Visual C#实现的局域网点对点通讯程序,就有如下特点,在网络利用此通讯程序进行通讯的任一计算机,在通讯之前,都需要侦听端口号,接受其他机器的连接申请,并在连接建立后,就可以接收对方发送来的数据;同时也可以向其他机器提出连接申请,并在对方计算机允许建立连接请求后,发送数据到对方。可见在网络中利用此软件进行P2P网络通讯的任一计算机既是客户机,同样也是服务器。
一、本文介绍程序的设计、调试、运行的软件环境:
(1)微软公司视窗2000服务器版
(2)Visual Studio .Net正式版,.Net FrameWork SDK版本号3705
二、简介Visual C#实现网络点对点通讯程序中涉及到的主要类库:
在下面要介绍的用Visual C#实现网络点对点通讯程序中,将会使用到很多类,了解并掌握这些类,不仅对于完成本文中的程序,而且对于编写其他网络应用程序都是非常重要的。由于篇幅所限,完全介绍这些类是不可能的,下面就选择在用Visual C#实现网络点对点通讯程序时最为重要的类加以介绍。
1. Socket类:
Socket类是.Net FrameWork SDK中提供在.Net框架中实现Socket编程的托管版
本。在下面介绍的点对点通讯程序中,Socket类主要用于在向对方计算机传送信息。在Visual C#中,在利用Socket类实例,一般通过此Socket实例的Bind方法绑定到网络中指定的终结点,也可以通过其Connect方法向指定的终结点建立的连接。连接创建完毕,就可以使用其Send或SendTo方法将数据发送到Socket;同样使用其的Receive或ReceiveFrom方法从Socket中读取数据。在Socket使用完毕后,请使用其的Shutdown方法禁用Socket,并使用Close方法关闭Socket。否则会导致程序退出后,某些资源无法释放。表01和表02分别是Socket类的常用属性和方法及其简要说明:
属性 |
说明 |
AddressFamily |
获取Socket的地址族。 |
Available |
获取已经从网络接收且可供读取的数据量。 |
Blocking |
获取或设置一个值,该值指示Socket是否处于阻塞模式。 |
Connected |
获取一个值,该值指示Socket是否已连接到远程资源。 |
Handle |
获取Socket的操作系统句柄。 |
LocalEndPoint |
获取本地终结点。 |
ProtocolType |
获取Socket的协议类型。 |
RemoteEndPoint |
获取远程终结点。 |
SocketType |
获取Socket的类型。 |
表01:Socket类的常用属性及其说明
方法 |
说明 |
Accept |
创建新的Socket以处理传入的连接请求。 |
BeginAccept |
开始一个异步请求,以创建新的Socket来接受传入的连接请求。 |
BeginConnect |
开始对网络设备连接的异步请求。 |
BeginReceive |
开始从连接的Socket中异步接收数据。 |
BeginReceiveFrom |
开始从指定网络设备中异步接收数据。 |
BeginSend |
将数据异步发送到连接的 |
BeginSendTo |
向特定远程主机异步发送数据。 |
Bind |
使Socket与一个本地终结点相关联。 |
Close |
强制Socket连接关闭。 |
Connect |
建立到远程设备的连接。 |
EndAccept |
结束异步请求以创建新的Socket来接受传入的连接请求。 |
EndConnect |
结束挂起的异步连接请求。 |
EndReceive |
结束挂起的异步读取。 |
EndReceiveFrom |
结束挂起的、从特定终结点进行异步读取。 |
EndSend |
结束挂起的异步发送。 |
EndSendTo |
结束挂起的、向指定位置进行的异步发送。 |
GetSocketOption |
返回Socket选项的值。 |
IOControl |
为Socket设置低级别操作模式。 |
Listen |
将Socket置于侦听状态。 |
Poll |
确定Socket的状态。 |
Receive |
接收来自连接Socket的数据。 |
ReceiveFrom |
接收数据文报并存储源终结点。 |
Select |
确定一个或多个套接字的状态。 |
Send |
将数据发送到连接的 |
SendTo |
将数据发送到特定终结点。 |
SetSocketOption |
设置Socket选项。 |
Shutdown |
禁用某Socket上的发送和接收。 |
表02:Socket类的常用方法及其说明
2. NetworkStream类
NetworkStream类主要是提供用于网络访问的基础数据流。NetworkStream类有下列几个特点:
(1). NetworkStreamng能够通过网络套接字发送和接收数据。
(2). NetworkStream 支持以同步和异步方式访问网络数据流。
(3). NetworkStream不能够随机访问网络数据流。
虽然NetworkStream类有构造函数,但更多情况下,是通过TcpClient实例的GetStream方法来初始化NetworkStream实例。下面是具体代码:
TcpClient tcpc = new TcpClient ( "www.microsoft.com" , 80 ) ;
NetworkStream tcpStream = tcpc.GetStream ( ) ;
在本文介绍的点对点通讯程序中,是利用NetworkStream是传送数据的载体,当然也可以作为发送数据的载体,但NetworkStream在处理网络上数据传输时,有一个缺点,就是只能用以传输字符类型的数据。
在Visual C#中具体操作这个NetworkStream载体的就是StreamWriter类和StreamReader类,StreamWriter类和StreamReader类能够实现对这个网络的基础数据流的读写操作。当用StreamWriter对网络基础数据流进行写操作时,在程序功能上表现为传送数据,而当StreamReader类对基础数据流进行写操作时,在程序功能上表现为接收数据。表03和表04是NetworkStream类中一些常用的方法、属性及其说明。
方法 |
说明 |
BeginRead |
开始异步读者基础数据流。 |
BeginWrite |
开始异步写入基础数据流。 |
Close |
关闭流并可选择关闭基础套接字。 |
EndRead |
结束异步读取。 |
EndWrite |
结束异步写入。 |
Flush |
刷新流中的数据。 |
Read |
从流中读取数据。 |
Seek |
将流的当前位置设置为给定值。此方法始终引发NotSupportedException异常。 |
SetLength |
设置流的长度。此方法始终引发 NotSupportedException异常。 |
Write |
将数据写入流。 |
表03:NetworkStream类中常用的方法及其说明
属性 |
说明 |
CanRead |
获取当前流是否支持读取。 |
CanSeek |
获取流是否支持查找。该属性总是返回 false。 |
CanWrite |
获取当前流是否支持写入。 |
DataAvailable |
获取是否可以在流上读取数据。 |
Length |
流上可用数据的长度。此属性始终引发 NotSupportedException异常。 |
Position |
获取或设置流中的当前位置。此属性始终引发 NotSupportedException异常。 |
表04:NetworkStream类中属性及其说明
三、Visual C#实现网络点对点通讯程序中的关键步骤及其解决方法
Visual C#实现网络点对点通讯程序的关键步骤就是实现信息在网络中的发送和接收。数据接收使用的是Socket,数据发送使用的是NetworkStream。
1. 利用Socket来接收信息
为了更清楚的说明问题,程序在处理数据发送和接收时采用了不通的端口号,发送数据程序在缺省状态设定的端口号为“8889”。下面代码是侦听端口号“8889”,接受网络中对此端口号的连接请求,并在建立连接后,通过Socket接收远程计算机发送来的数据:
try
{
TcpListener tlListen1 = new TcpListener ( 8889 ) ;
//侦听端口号
tlListen1.Start ( ) ;
Socket skSocket = tlListen1.AcceptSocket ( ) ;
//接受远程计算机的连接请求,并获得用以接收数据的Socket实例
EndPoint tempRemoteEP = skSocket.RemoteEndPoint ;
//获得远程计算机对应的网络远程终结点
while ( true )
{
Byte [] byStream = new Byte[80] ;
//定义从远程计算机接收到数据存放的数据缓冲区
int i = skSocket.ReceiveFrom ( byStream , ref tempRemoteEP ) ;
//接收数据,并存放到定义的缓冲区中
string sMessage = System.Text.Encoding.UTF8.GetString ( byStream ) ;
//以指定的编码,从缓冲区中解析出内容
MessageBox.Show ( sMessage ) ;
//显示传送来的数据
}
}
catch ( System.Security.SecurityException )
{
MessageBox.Show ( "防火墙安全错误!" ,"错误" ,
MessageBoxButtons.OK , MessageBoxIcon.Exclamation ) ;
}
2. 利用NetworkStream来传送信息
在使用StreamWriter处理NetworkStream传送数据时,数据传送的编码类型是“UTF8”,下列代码是对IP地址为“10.138.198.213”的计算机的“8888”端口号提出连接申请,并在连接申请建立后,以UTF8编码发送字符串“您好,见到您很高兴”到对方,由于下列代码中的注释比较详细,这里就不具体介绍了,下列代码也是使用NetworkStream传送数据的典型代码:
try
{
TcpClient tcpc = new TcpClient ( "10.138.198.213" , 8888 ) ;
//对IP地址为“10.138.198.213”的计算机的8888端口提出连接申请
NetworkStream tcpStream = tcpc.GetStream ( ) ;
//如果连接申请建立,则获得用以传送数据的数据流
}
catch ( Exception )
{
MessageBox.Show ( "目标计算机拒绝连接请求!" ) ;
break ;
}
try
{
string sMsg = "您好,见到您很高兴" ;
StreamWriter reqStreamW = new StreamWriter ( tcpStream ) ;
//以特定的编码往向数据流中写入数据 ,默认为UTF8编码
reqStreamW.Write ( sMsg ) ;
//将字符串写入数据流中
reqStreamW.Flush ( ) ;
//清理当前编写器的所有缓冲区,并使所有缓冲数据写入基础流
}
catch ( Exception )
{
MessageBox.Show ( "无法发送信息到目标计算机!" ) ;
} 当然在具体用Visual C#实现网络点对点通讯程序时,还必须掌握很多其他方面的知识,如资源的回收。在用Visual C#编写网络应用程序的时候,很多朋友遇到这样的情况。当程序退出后,通过Windows的“资源管理器”看到的是进程数目并没有减少。这是因为程序中使用的线程可能并没有有效退出。虽然Thread类中提供了“Abort”方法用以中止进程,但并不能够保证成功退出。因为进程中使用的某些资源并没有回收。在某些情况下垃圾回收器也不能保证完全的回收资源,还是需要我们自己手动回收资源的。在本文介绍的程序中也涉及到资源手动回收的问题。实现方法可参阅下面具体实现步骤中的第十二步。
|