第8章Socket编程初步.ppt
第八章 Socket编程初步,学习目的,认识TCP/IP协议和Socket能够掌握基于TCP协议的Socket编程能够掌握基于UDP协议的Socket编程,主要内容,8.1 TCP/IP协议和Socket8.2 基于TCP协议的Socket编程8.3 基于UDP协议Socket编程,8.1 TCP/IP协议和Socket,8.1.1 TCP/IP协议 把分布在不同地理区域的计算机和网络设备利用通信设备互连,使各个计算机之间能够相互通信,实现信息和资源共享,就组成了计算机网络。网络的目的是为了通信,共享资源。通信即传输数据,为了传输数据各个网络系统应遵守一定规则,这个规则叫网络传输协议。当前广泛采用的网络协议是TCP/IP协议。,网络中有成千上万台计算机,应允许任何两台计算机之间进行通信,为了区分不同的计算机,必须给每一台连网计算机一个唯一的编号,这个编号在TCP/IP协议中叫计算机的IP地址,它是一个32位二进制数,一般用四个十进制数表示,中间用点隔开,每个十进制数允许值为0-255(一个字节),例如,202.112.10.105,这种记录方法叫点数记法。一个计算机要和网络中其他计算机连接,必须有自己的IP地址。C#语言使用IPAddress类表示IP地址,用静态方法Parse可将IP地址字符串转换为IPAddress实例。,例如:IPAddress ip=IPAddress.Parse(“127.0.0.1”);/127.0.0.1表示本机IP地址IPAddress类提供了几个静态只读字段,其中字段Any表示本地系统所有可用的IP地址,字段Broadcast表示本地网络广播地址。Dns类提供了一系列静态的方法,其中GetHostAddresses方法获取指定主机的IP地址,返回一个IPAddress类型的数组。例如:IPAddress ip=Dns.GetHostAddresses();/获得CCTV网站的所有IP地址,Dns类GetHostName方法,获取本机主机名。例如:string hostname=Dns.GetHostName();IPAddress ip=Dns.GetHostAddresses(hostname);一台计算机上可能运行多个网络通信软件,它们的IP地址是相同的。为了访问IP地址相同的不同网络通信软件,可为运行的每个网络通信软件编号,这个编号叫端口号。IPEndPoint类包含了IP地址和端口信息,IPEndPoint类常用的构造函数public IPEndPoint(IPAddress,int);/第一个参数指定IP地址,第二个参数指定端口号,8.1.1 套接字(Socket),套接字可以理解为编写网络通信软件的函数库,在套接字中封装了为进行网络通信而设计的一组公共函数,网络通信软件通过调用这些公共函数,完成和在网络其他计算机中运行的指定网络通信软件间的双向通信。在.Net中,System.Net.Sockets 命名空间为开发人员提供了开发基于Socket套接字的网络通信程序的一些类,包括Socket类、TcpClient类、TcpListener类和UdpClient类,如果开发基于TCP/IP网络协议网络通信程序,可以使用TcpClient类、TcpListener类和UdpClient类,使用上比较简单,本书所有例子基本上都是使用这三个类。如果为了提高效率或者采用其他网络通信协议,可采用Socket类。,套接字有两种不同的类型:一种是流套接字,又称面向连接的协议,如 TCP;另一种是数据报套接字,又称无连接协议,例如 UDP。网络连接,一旦建立了这种连接,就可以在设备之间可靠的传输数据,建立连接后数据以流的形式在被连接的两个计算机中运行程序间进行流动。这有些像打电话。基于流套接字的网络通信一般采用客户机/服务器模式。基于数据报套接字,采用不连接方式,两个计算机中运行程序间使用单个信息包进行数据传输,这种方式类似邮局,不保证数据包按照发送顺序传送,也可能丢失。,Socket类的构造方法定义如下,其中,addressFamily 参数指定 Socket 使用的寻址方案,socketType 参数指定 Socket 的类型,protocolType 参数指定 Socket 使用的协议。public Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolType);生成基于 TCP协议的Socket类对象的例子如下:Socket s=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);一旦创建基于 TCP协议连接的Socket类对象,在客户端将通过Connect方法连接到指定的服务器,通过Send/SendTo方法向远程服务器发送数据,通过Receive/ReceiveFrom从服务端接收数据;,而在服务器端,需要使用Bind方法将Socket对象绑定到本地指定的IP地址和端口号,并通过Listen方法侦听该接口上的请求,当侦听到用户端的连接时,调用Accept完成连接的操作,创建新的Socket以处理传入的连接请求。使用完 Socket 后,使用 Shutdown 方法禁用 Socket,并使用 Close 方法关闭 Socket。生成基于 UDP协议的Socket类对象的例子如下:Socket s=new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);由于UDP不存在固定连接,所以可直接使用SendTo方法发送数据,用ReceiveFrom方法接收数据,如不再使用 Socket对象,用 Shutdown 方法禁用Socket对象,用 Close 方法关闭 Socket对象。,8.2 基于TCP协议的Socket编程,本节详细介绍编写基于TCP协议的Socket程序方法和步骤。在System.Net.Sockets命名空间下,TcpClient类与TcpListener类是两个专门用于TCP协议编程的类。这两个类封装了底层的套接字,并分别提供了对Socket进行封装后的同步和异步操作的方法,降低了TCP应用编程的难度。TcpClient类用于连接、发送和接收数据。TcpListener类则用于监听是否有传入的连接请求。基于TCP协议的网络通信一般采用客户机/服务器模式,因此必须分别建立客户机和服务器程序。,8.2.1 TcpClient类,利用TcpClient类提供的方法,可以通过网络进行连接、发送和接收网络数据流。该类的构造函数有四种重载形式,常用属性方法如下:构造函数TcpClient():创建一个TcpClient类对象,该对象自动选择客户端IP地址和尚未使用的端口号。可用Connect方法与服务器端进行连接。例如:TcpClient tcpClient=new TcpClient();tcpClient.Connect(,51888);构造函数TcpClient(AddressFamily family):创建的TcpClient类对象也能自动选择客户端IP地址和尚未使用的端口号,但是使用AddressFamily枚举指定了哪种网络协议。可用Connect方法与服务器端进行连接。例如:TcpClient tcpClient=new TcpClient(AddressFamily.InterNetwork);tcpClient.Connect(,51888);,构造函数TcpClient(IPEndPoint iep):iep指定了客户端IP地址与端口号。当客户端主机有一个以上IP地址时,可使用此构造函数选择要使用的客户端主机IP地址。例如:IPAddress address=Dns.GetHostAddresses(Dns.GetHostName();IPEndPoint iep=new IPEndPoint(address0,51888);TcpClient tcpClient=new TcpClient(iep);tcpClient.Connect(,51888);构造函数TcpClient(string hostname,int port):这是使用最方便的一种构造函数。该构造函数可直接指定服务器端域名和端口号,而且不需使用connect方法。客户端主机的IP地址和端口号则自动选择。例如:TcpClient tcpClient=new TcpClient(,51888);,方法Connect():和服务器进行连接,参数分别是要连接服务器IP地址和端口号。方法Close():释放此 TcpClient 实例,而不关闭基础连接。方法GetStream():返回用于发送和接收数据的 NetworkStream。见后边例子。属性SendTimeout和ReceiveTimeout:等待发送和接收成功完成时间,超过这个时间,将产生SocketException异常。属性SendBufferSize和ReceiveBufferSize:发送和接收缓冲区大小。属性Connected:是否已和服务器连接。属性Client:TcpClient类对象使用的Socket类对象。,8.2.2 TcpListener类,TcpListener类用于监听和接收传入的连接请求。该类的构造函数及常用函数如下:TcpListener(IPEndPoint iep):该构造函数建立监听对象,如一个客户端和IPEndPoint类型对象ip指定的服务器IP地址与端口号进行连接,将被该类对象监听到。TcpListener(IPAddress localAddr,int port):建立TcpListener对象,该对象将监听和服务器IP地址为localAddr、端口号为port进行连接的客户端。,AcceptTcpClient():等待连接,直到有新的连接,获取并返回一个可以用来接收和发送数据的封装了Socket的TcpClient对象后,才执行后续语句。这种方式称作同步阻塞方式。AcceptSocket():在同步阻塞方式下获取并返回一个可以用来接收和发送数据的Socket对象。Start()和Start(int backlog):启动监听。整型参数backlog为请求队列的最大长度,即最多允许的客户端连接个数。Stop():停止监听请求。,8.2.3 服务器程序,使用TCP和流套接字建立服务器,服务器将等待来自客户机的连接请求。在接到请求后,服务器建立和客户机的连接,利用这个连接,服务器和客户机实现通信。IE浏览器(客户机)和Web服务器(网站)就是一个典型的客户机/服务器模式,IE浏览器向Web服务器请求网页,Web服务器接到请求,发送请求的网页到IE浏览器。C#语言使用TCP和流套接字建立服务器需要五步。,具体步骤如下:(1)System.Net.Sockets命名空间的TcpListener类对象用来等待来自客户机的连接请求,TcpListener类采用TCP协议。首先要创建TcpListener类对象。IPAddress ip=IPAddress.Parse(127.0.0.1);TcpListener server=new TcpListener(ip,1300);(2)使用TcpListener类方法Start()启动监听,代码如下:Server.Start();Server.Start(200);,(3)使用TcpListener类方法AcceptSocket()等待来自客户机的连接请求,如果没有客户机的连接请求,程序将被阻塞,既不能执行这条语句的后续语句。如果有一个客户机的连接请求,将返回一个Socket或TcpClient类对象,将继续执行后续语句。代码如下:Socket socket=server.AcceptSocket();TcpClient tcpClient=server.AcceptTcpClient();得到Socket或TcpClient类对象,已经和客户机建立了连接,就可以和客户机进行通信。在通信时,将不再侦听其他客户机的连接要求。很多服务器是不允许这种情况发生的,例如Web服务器必须随时等待众多的浏览器的访问。解决的方法是建立一个线程用来和这个客户机进行通信,而TcpListener类对象server将继续侦听其他客户机的连接要求。,(4)如果使用server.AcceptSocket方法建立连接,返回的Socket类对象,就可以使用Socket类的Send方法发送数据(返回TcpClient用法见下节)。代码如下:byte msg=Encoding.UTF8.GetBytes(This is a test);int i=socket.Send(msg);可以使用Socket类的方法Receive接收数据(TcpClient用法见下节),代码如下:byte bytes=new byte256;i=socket.Receive(bytes);(5)最后,如果不再通信,使用Socket类的Close方法终止连接,代码如下:Socket.Close();,8.2.4 客户机程序,网络中的计算机可以运行客户机端网络程序访问服务器,例如,通过IE浏览器(客户机)可以访问Internet中的Web服务器(网站),浏览网页。编写运行于客户机端的网络程序需要四个步骤。具体步骤如下:(1)创建System.Net.Sockets命名空间的TcpClient类对象用来和服务器建立连接,代码如下:TcpClient tcpClient=new TcpClient();tcpClient.Connect(localhost,1300);Connect方法的第一个参数也可以是远程服务器的域名,例如,。如果知道远程服务器的IP地址,可以采用如下代码:TcpClient tcpClient=new TcpClient();IPAddress ServerIP=IPAddress.Parse(202.206.96.204);tcpClient.Connect(ServerIP,1300);,(2)使用TcpClient类的GetStream方法得到一个NetworkStream类对象,用来对服务器进行读写。NetworkStream netStream=tcpClient.GetStream();(3)使用NetworkStream类对象读写服务器数据,具体代码如下:if(netStream.CanWrite)Byte sendBytes=Encoding.UTF8.GetBytes(Is anybody there?);netStream.Write(sendBytes,0,sendBytes.Length);if(netStream.CanRead)byte bytes=new bytetcpClient.ReceiveBufferSize;netStream.Read(bytes,0,(int)tcpClient.ReceiveBufferSize);string returndata=Encoding.UTF8.GetString(bytes);(4)关闭NetworkStream类对象后,关闭和服务器的连接。netStream.Close();tcpClient.Close();,8.2.5 TCP协议Socket实例,本节首先实现一个时间服务器,客户端访问这个时间服务器系统,可以得到时间服务器系统所在地点的时间,在例子中时间服务器直接使用侦听线程和客户机通信,因此本例仅支持客户机顺序访问和多次访问,但由于服务器发送时间的代码很少,很快能够完成,所以客户机程序感觉没有延迟很快就能得到时间。这是一个最简单的基于TCP协议的Socket程序实例,通过这个例子读者可以清楚地理解Socket编程的基本步骤。实际服务器要比这个时间服务器复杂的多,一般情况下,服务器和客户机通信也许需要较多的时间,例如客户机访问文件下载服务器下载文件,服务器直接使用侦听线程和客户机通信显然不能实现多客户机同时访问服务器功能。例8.3和例8.4实现了一个文件下载系统,该系统实现了多客户机同时访问服务器功能。,【例8.1】本例实现一个时间服务器,客户端访问这个时间服务器系统,可以得到时间服务器系统所在地点的时间。这是一个最简单的Scoket编程实例。具体实现步骤如下:(1)建立一个新项目。在Window1.xaml.cs头部增加命名空间引用语句:using System.Net;using System.Net.Sockets;using System.Threading;(2)在Window1类增加变量:Thread thread;(3)修改Window1构造函数如下:public Window1()InitializeComponent();thread=new Thread(new ThreadStart(ListenThreadMethod);thread.IsBackground=true;thread.Start();Title=时间服务器;,(4)侦听工作不能在主线程中进行,否则当侦听工作被阻塞后,将不能执行其他任何语句。读者可以试验一下,首先去掉构造函数中已增加的语句,然后增加一个按钮,为按钮增加单击事件处理函数,在函数中,调用下边函数,编译运行后,单击按钮,程序可以得到时间,但是将不能使用关闭按钮关闭程序。因此侦听工作必须在另一线程中进行。在线程为Window1类定义一个侦听线程方法如下:public void ListenThreadMethod()TcpListener server=null;Socket socket=null;try IPAddress ip=IPAddress.Parse(127.0.0.1);server=new TcpListener(ip,1300);server.Start();catch MessageBox.Show(不能建立服务器);return;,while(true)try socket=server.AcceptSocket();string s=DateTime.Now.ToString();byte msg=Encoding.UTF8.GetBytes(s);socket.Send(msg);catch finally if(socket!=null)socket.Close();由于thread.IsBackground=true,在关闭程序后侦听线程自动结束,并收回被程序占用的所有资源,因此不必在主窗体的Closing事件处理函数中用代码关闭这些对象。编译得到可执行文件。,【例8.2】本例实现客户机从例8.1的时间服务器得到时间并显示。具体步骤如下:(1)建立一个新项目。在Window1.xaml.cs头部增加命名空间引用语句:using System.Net;using System.Net.Sockets;(2)在Window1类增加变量:TcpClient tcpClient;NetworkStream netStream;(3)在窗体中放置1个TextBlock控件用来显示时间,增加1个Button控件,标题为“得到时间”,按钮的单击事件函数如下:,private void button1_Click(object sender,RoutedEventArgs e)tcpClient=new TcpClient();tcpClient.ReceiveTimeout=5000;try tcpClient.Connect(localhost,1300);netStream=tcpClient.GetStream();if(netStream.CanRead)byte bytes=new bytetcpClient.ReceiveBufferSize;netStream.Read(bytes,0,(int)tcpClient.ReceiveBufferSize);textBlock1.Text=Encoding.UTF8.GetString(bytes);,catch textBlock1.Text=连接超时,连接不成功;finally if(netStream!=null)netStream.Close();tcpClient.Close();首先运行时间服务器程序,再运行客户机程序,单击客户机程序的标题为“得到时间”按钮,显示当前时间。关闭时间服务器程序,再一次单击客户机程序的标题为“得到时间”按钮,显示连接超时,连接不成功。,8.2.6 异步TCP编程,本节实现一个文件下载服务器,允许多个客户机同时下载文件。由于下载文件的时间一般比较长,因此当客户机和文件下载服务器建立连接后,下载文件的工作必须在另一个线程中进行。实现方法是每当一个客户和文件下载服务器建立连接后,文件下载服务器将建立一个新线程,在这个线程中将文件传送给客户机,而文件下载服务器继续侦听新的客户机的连接。如果使用Thread类创建下载线程,数据传送完成后创建的线程必须撤销,线程反复建立撤销,占用较多资源。NetworkStream类传送数据方法BeginWrite在线程池中申请一个线程,在线程中使用建立的连接开始向客户机传送数据后,立刻退出该方法继续后续语句,使文件下载服务器能继续侦听新客户机的连接。,传送数据完成后,将自动调用一个指定方法,完成传送数据后的善后工作。采用方法BeginWrite的优点是传送数据的线程在线程池中,由系统统一管理,安全可靠,线程池中的线程是预先定义的,不必反复建立和撤销,节约资源。一般把这种方式称为异步方式,上节用Thread类创建下载线程方法称为同步方式。System.Net.Sockets命名空间的所有阻塞函数都有异步方法,见下表,具体的使用方法请参见帮助文档。因此TcpListener类也可以使用异步方法,考虑到侦听线程不会反复建立撤销,一直在使用,因此本节例子实现文件下载服务器监听没有使用异步方法。,实现文件下载服务器,一定传送文件。从上边的例子可以看到,在网路中使用字节数组进行传送,因此传送文件,首先要把文件变为字节数组,接收文件,则必须把字节数组变为文件。文件变为字节数组的具体步骤如下:FileStream fs=new FileStream(d:/g1.bin,FileMode.Open);/参数1是要传输的文件byte data=new bytefs.Length;long n=fs.Read(data,0,(int)fs.Length);/将文件读到字节数组data中,n为所读字节数 fs.Close();,字节数组变为文件具体步骤如下:FileStream fs=new FileStream(d:/g1.bin,FileMode.Create);/参数1是保存文件的全路径fs.Write(data,0,data.Length);/写data字节数组中的所有数据到文件fs.Close();,【例8.3】本例建立文件下载服务器,使用异步TCP发送数据,具体步骤如下:(1)建立一个新应用项目。在Window1.xaml.cs头部增加命名空间引用语句:using System.Net;using System.Net.Sockets;using System.Threading;using System.IO;(2)在Window1类增加变量:private Thread Listenerthread;TcpListener tcpListener=null;,(3)修改Window1类构造函数如下:public Window1()InitializeComponent();Listenerthread=new Thread(new ThreadStart(ListenerthreadMethod);Listenerthread.IsBackground=true;Listenerthread.Start();Title=异步文件下载服务器;(4)在Window1类定义侦听线程方法如下:public void ListenerthreadMethod()TcpClient tcpClient=null;NetworkStream netStream=null;,try IPAddress ip=IPAddress.Parse(127.0.0.1);tcpListener=new TcpListener(ip,1300);tcpListener.Start();catch MessageBox.Show(不能建立服务器);return;FileStream fs=new FileStream(E:应用程序设计教程(第二版)geng.txt,FileMode.Open);byte data=new bytefs.Length;fs.Read(data,0,(int)fs.Length);fs.Close();while(true)try tcpClient=tcpListener.AcceptTcpClient();netStream=tcpClient.GetStream();,netStream.BeginWrite(data,0,data.Length,new AsyncCallback(DownLoadCallBackF),netStream);catch(5)为Window1类定义DownLoadCallBackF方法如下。编译得到可执行文件。public void DownLoadCallBackF(IAsyncResult ar)NetworkStream netStream=(NetworkStream)ar.AsyncState;try netStream.EndWrite(ar);catch MessageBox.Show(写流文件失败);finally if(netStream!=null)netStream.Close();,【例8.4】本例实现客户机,从例8.3的文件下载服务器下载文件。具体步骤如下:(1)建立一个新项目。在Window1.xaml.cs头部增加命名空间引用语句:using System.Net;using System.Net.Sockets;using System.Threading;using System.IO;(2)在窗体中放置1个Button控件,标题为“下载文件”,事件函数:private void button1_Click(object sender,RoutedEventArgs e)TcpClient tcpClient=new TcpClient();tcpClient.BeginConnect(localhost,1300,new AsyncCallback(DownLoadCallBackF),tcpClient);,(3)由于NetworkStream不支持属性Length,即下载文件长度未知,不能用NetworkStream的Read或BeginRead一次把文件读出,因此定义一个通用方法从一个TcpClient类对象读出未知长度的字节数组。public byte ReadFromTcpClient(TcpClient tcpClient)List data=new List();NetworkStream netStream=null;byte bytes=new bytetcpClient.ReceiveBufferSize;int n=0;try netStream=tcpClient.GetStream();if(netStream.CanRead)do n=netStream.Read(bytes,0,(int)tcpClient.ReceiveBufferSize);,if(n=(int)tcpClient.ReceiveBufferSize)data.AddRange(bytes);else if(n!=0)byte bytes1=new byten;for(int i=0;i n;i+)bytes1i=bytesi;data.AddRange(bytes1);while(netStream.DataAvailable);bytes=data.ToArray();catch MessageBox.Show(读数据失败);bytes=null;,finally if(netStream!=null)netStream.Close();tcpClient.Close();return bytes;(4)在Window1类中定义回调方法DownLoadCallBackF:public void DownLoadCallBackF(IAsyncResult ar)TcpClient tcpClient=(TcpClient)ar.AsyncState;try tcpClient.EndConnect(ar);catch MessageBox.Show(连接失败);return;byte bytes=ReadFromTcpClient(tcpClient);FileStream fs=null;,try fs=new FileStream(d:/g2.txt,FileMode.Create);fs.Write(bytes,0,bytes.Length);catch MessageBox.Show(写文件失败);finally if(fs!=null)fs.Close();(5)如在下载完成前关闭程序,回调函数被撤销,线程池中的下载线程仍将继续,只是无法调用回调函数保存文件,下载线程结束。编译运行,运行例8.3文件下载服务器。在指定目录下使用记事本创建geng.txt文件。单击本例的按钮,可以看到D盘根目录中下载的g2.txt文件。(6)为了说明DownLoadCallBackF方法不在主线程中运行,在Window1窗体中增加2个TextBlock控件,分别用来显示主线程和DownLoadCallBackF方法所在线程ID。,(7)在Window1类增加语句:public delegate void ShowDelegate(string s);(8)在Window1类中定义修改TextBlock控件Text属性方法。public void showID(string sID)textBlock2.Text=sID;(9)在Window1类构造函数最后增加如下语句:textBlock1.Text=主线程ID:+Thread.CurrentThread.ManagedThreadId.ToString();(10)在DownLoadCallBackF方法的第一条语句前增加如下语句:string s=DownLoadCallBackF方法所在线程ID:+Thread.CurrentThread.ManagedThreadId.ToString();this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,new ShowDelegate(showID),s);(11)编译运行,单击下载按钮,可看到两个线程ID号的确不同。,8.2.7 基于TCP协议的P2P技术,P2P技术也叫对等互联或点对点技术,与TCP和UDP不同,P2P不是一种新的网络协议,而是利用现有网络协议完成网络数据或资源共享的一种技术,实现网路上任意一台计算机和其它计算机直接通信,P2P技术不区分谁是客户机,谁是服务器,所有的客户机都是对等的,既是客户机,又是服务器。实现P2P技术可以采用TCP协议,也可以采用UDP协议。由于HTTP协议是基于TCP协议的,一般防火墙都允许访问Web网站,也就是说允许TCP协议数据通过。但有些防火墙不允许UCP数据通过。本节例8.5采用TCP协议实现了一个P2P客终端,后边章节将采用UDP协议实现了P2P终端。,【例8.5】本例采用TCP协议实现多人聊天室。TCP协议多人聊天室至少有如下功能:可以通过IP地址和端口号向其他计算机发送信息,一般从好友列表中选择其他计算机的IP地址和端口号。实现方法是使用TcpClient类对象和其他计算机程序的TcpListener类对象建立连接,并传送信息,这是客户端功能。本例发送格式为:本机IP地址:本程序侦听程序所使用的端口号说:发送的信息,例如:127.0.0.1:1500说:我上线了。随时侦听是否有其他计算机发来的信息,这可以使用TcpListener类对象用来等待其他计算机的连接请求,并接受信息,这是服务器功能。因此一个P2P终端可以认为同时具有服务器和客户机功能的通信软件。当成功地接受到其他计算机P2P终端的信息,要将该P2P终端的IP地址和端口号增加到好友列表中。允许使用者手工增加好友P2P终端的IP地址和端口号。,设计界面如图8.1。具体的步骤如下:(1)建立一个新项目。在Window1.xaml.cs头部增加命名空间引用语句:using System.Net;using System.Net.Sockets;using System.Threading;using System.IO;using System.Collections.ObjectModel;(2)首先得到侦听类TcpListener对象使用的IP地址和端口号,可能有多个程序运行,要占用一些端口号,必须试验找到一个可以使用的端口号。为Window1类增加变量:static int MyPort=1499;IPAddress myIPAddress=null;TcpListener tcpListener=null;,(3)放置1个TextBlock控件,属性Name=textBlock1,用来显示侦听IP地址和端口号。(4)在Window1类构造函数最后增加语句:Title=P2P终端;myIPAddress=(IPAddress)Dns.GetHostAddresses(Dns.GetHostName().GetValue(0);MyPort+;for(int i=0;i 51;i+)try tcpListener=new TcpListener(myIPAddress,MyPort);tcpListener.Start();textBlock1.Text=本机IP地址和端口号:+myIPAddress.ToString()+:+MyPort.ToString();break;,catch MyPort+;if(i=50)MessageBox.Show(不能建立服务器,可能计算机网络有问题);this.Close();(5)编译运行,将在textBlock1处显示本程序使用的IP地址和端口号。请注意,显示的实际上是侦听类TcpListener对象使用的IP地址和端口号,其他计算机必须和这个IP地址和端口号进行连接后发送字符串数据,本程序才能接受到发送来的字符串数据。,(6)首先实现第一个功能。在窗体中放置1个TextBlock控件,属性Text=好友列表。在TextBlock控件下侧放置ListView控件到窗体,其XAML标记如下。,(7)在Window1类中增加变量:string IPAndPort;public class StateObject public TcpClient tcpClient=null;public NetworkStream netStream=null;public byte buffer;public string friendIPAndPort=null;public struct FriendIPAndPort public stri