Visual C#.Net网络程序开发-Tcp篇

Visual C#.Net 网络程序开发 网络程序开发-Tcp 篇

支持 Http、Tcp 和 Udp 的类组成了 TCP/IP 三层模型(请求响应层、应用协议层、传输层)的中 间层-应用协议层, 该层的类比位于最底层的 Socket 类提供了更高层次的抽象, 它们封装 TCP 和 UDP 套接字的创建,不需要处理连接的细节,这使得我们在编写套接字级别的协议时, 可以更多地尝试使用 TCPClient 、 UDPClient 和 TcpListener,而不是直接向 Socket 中写。 它们之间的这种层次关系示意如下: 可见, TcpClient 类基于 Socket 类构建, 这是它能够以更高的抽象程度提供 TCP 服务的基 础。 正因为这样, 许多应用层上的通讯协议, 比如 FTP(File Transfers Protocol)文件传输协议、 HTTP(Hypertext Transfers Protocol)超文本传输协议等都直接创建在 TcpClient 等类之上。 TCPClient 类使用 TCP 从 Internet 资源请求数据。TCP 协议建立与远程终结点的连接,然 后使用此连接发送和接收数据包。 TCP 负责确保将数据包发送到终结点并在数据包到达时以 正确的顺序对其进行组合。 从名字上就可以看出,TcpClient 类专为客户端设计,它为 TCP 网络服务提供客户端连接。 TcpClient 提供了通过网络连接、发送和接收数据的简单方法。 若要建立 TCP 连接,必须知道承载所需服务的网络设备的地址(IPAddress)以及该服务用于 通讯的 TCP 端口 (Port)。 Internet 分配号码机构 (Internet Assigned Numbers Authority, IANA) 定义公共服务的端口号(你可以访问 http://www.iana.org/assignments/port-numbers 获得这 方面更详细的资料)。IANA 列表中所没有的服务可使用 1,024 到 65,535 这一范围中的端 口号。要创建这种连接,你可以选用 TcpClient 类的三种构造函数之一: 1、public TcpClient()当使用这种不带任何参数的构造函数时,将使用本机默认的 ip 地址并将 使用默认的通信端口号 0。这样情况下,如果本机不止一个 ip 地址,将无法选择使用。以下 语句示例了如何使用默认构造函数来创建新的 TcpClient: TcpClient tcpClientC = new TcpClient();

2、public TcpClient(IPEndPoint)使用本机 IPEndPoint 创建 TcpClient 的实例对象。上一篇介绍 过了, IPEndPoint 将网络端点表示为 IP 地址和端口号, 在这里它用于指定在建立远程主机连 接时所使用的本地网络接口(IP 地址)和端口号,这个构造方法为使用本机 IPAddress 和 Port 提供了选择余地。下面的语句示例了如何使用本地终结点创建 TcpClient 类的实例: IPHostEntry ipInfo=Dns.GetHostByName("www.tuha.net");//主机信息 IPAddressList[] ipList=ipInfo.AddressList;//IP 地址数组 IPAddress ip=ipList[0];//多 IP 地址时一般用第一个

IPEndPoint ipEP=new IPEndPoint(ip,4088);//得到网络终结点 try{ TcpClient tcpClientA = new TcpClient(ipLocalEndPoint); } catch (Exception e ) { Console.WriteLine(e.ToString()); }

到这里,你可能会感到困惑,客户端要和服务端创建连接,所指定的 IP 地址及通信端口号 应该是远程服务器的呀! 事实上的确如此, 使用以上两种构造函数, 你所实现的只是 TcpClient 实例对象与 IP 地址和 Port 端口的绑定, 要完成连接, 你还需要显式指定与远程主机的连接, 这可以通过 TcpClient 类的 Connect 方法来实现, Connet 方法使用指定的主机名和端口号将 客户端连接到 远程主机: 1)、public void Connect(IPEndPoint); 使用指定的远程网络终结点将客户端连接到远程 TCP 主机。 public void Connect(IPAddress, int); 使用指定的 IP 地址和端口号将客户端连接到 TCP 主机。 public void Connect(string, int); 将客户端连接到指定主机上的指定端口。 需要指出的是,Connect 方法的所有重载形式中的参数 IPEndPoint 网络终 结点、 IPAddress 以及表现为 string 的 Dns 主机名和 int 指出的 Port 端口均指的是远程服务器。 以下示例语句使用主机默认 IP 和 Port 端口号 0 与远程主机建立连接: TcpClient tcpClient = new TcpClient();//创建 TcpClient 对象实例 try{ tcpClient.Connect("www.contoso.com",11002);//建立连接 } catch (Exception e ){ Console.WriteLine(e.ToString()); }

3、 public TcpClient(string, int);初始化 TcpClient 类的新实例并连接到指定主机上的指定端口。 与前两个构造函数不一样,这个构造函数将自动建立连接,你不再需要额外调用 Connect 方法,其中 string 类型的参数表示远程主机的 Dns 名,如:www.tuha.net。 以下示例语句调用这一方法实现与指定主机名和端口号的主机相连: try{

TcpClient tcpClientB = new TcpClient("www.tuha.net", 4088); } catch (Exception e ) { Console.WriteLine(e.ToString()); } 前面我们说,TcpClient 类创建在 Socket 之上,在 Tcp 服务方面提供了更高层次的抽象,体现在 网络数据的发送和接受方面,是 TcpClient 使用标准的 Stream 流处理技术, 使得它读写数据更 加方便直观,同时,.Net 框架负责提供更丰富的结构来处理流,贯穿于整个.Net 框架中的流 具有更广泛的兼容性, 构建在更一般化的流操作上的通用方法使我们不再需要困惑于文件的 实际内容(HTML、XML 或其他任何内容),应用程序都将使用一致的方法(Stream.Write、 Stream.Read) 发送和接收数据。另外,流在数据从 Internet 下载的过程中提供对数据的即 时访问,可以在部分数据到达时立即开始处理,而不需要等待应用程序下载完整个数据 集。.Net 中通过 NetworkStream 类实现了这些处理技术。 NetworkStream 类包含在.Net 框架的 System.Net.Sockets 命名空间里,该类专门提供用于网 络访问的基础数据流。NetworkStream 实现通过网络套接字发送和接收数据的标准.Net 框 架流机制。 NetworkStream 支持对网络数据流的同步和异步访问。 NetworkStream 从 Stream 继承,后者提供了一组丰富的用于方便网络通讯的方法和属性。 同其它继承自抽象基类 Stream 的所有流一样,NetworkStream 网络流也可以被视为一个数 据通道,架设在数据来源端(客户 Client)和接收端(服务 Server)之间,而后的数据读取及 写入均针对这个通道来进行。 .Net 框架中,NetworkStream 流支持两方面的操作: 1、 写入流。写入是从数据结构到流的数据传输。 示 意 图

2、读取流。读取是从流到数据结构(如字节数组)的数据传输。 示 意 图 与普通流 Stream 不同的是,网络流没有当前位置的统一概念,因此不支持查找和对数据流 的随机访问。相应属性 CanSeek 始终返回 false,而 Seek 和 Position 方法也将引发 NotSupportedException。 基于 Socket 上的应用协议方面, 你可以通过以下两种方式获取 NetworkStream 网络数据流: 1、使用 NetworkStream 构造函数:public NetworkStream(Socket, FileAccess, bool);(有重载方 法),它用指定的访问权限和指定的 Socket 所属权为指定的 Socket 创建 NetworkStream 类的新实例, 使用前你需要创建 Socket 对象实例, 并通过 Socket.Connect 方法建立与远程服

务端的连接,而后才可以使用该方法得到网络传输流。示例如下: Socket s=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//创建 客户端 Socket 对象实例 try{ s.Connect("www.tuha.net",4088);//建立与远程主机的连接 } catch(Exception e){ MessageBox.show("连接错误:" +e.Message); } try{ NetworkStream stream=new NetworkStream(s,FileAccess.ReadWrite,false);//取得网络传输流 }

2、通过 TcpClient.GetStream 方法:public NetworkStream etStream();它返回用于发送和接收 数据的基础网络流 NetworkStream。GetStream 通过将基础 Socket 用作它的构造函数参数 来创建 NetworkStream 类的实例。使用前你需要先创 TcpClient 对象实例并建立与远程主机 的连接,示例如下: TcpClient tcpClient = new TcpClient();//创建 TcpClient 对象实例 Try{ tcpClient.Connect("www.tuha.net",4088);//尝试与远程主机相连 } catch(Exception e){ MessageBox.Show("连接错误:"+e.Message); } try{ NetworkStream stream=tcpClient.GetStream();//获取网络传输流 } catch(Exception e) { MessageBox.Show("TcpClient 错误:"+e.Message); }

通过以上方法得到 NetworkStream 网络流之后,你就可以使用标准流读写方法 Write 和 Read 来发送和接受数据了。 以上是.Net 下使用 TcpClient 类实现客户端编程的技术资料,为了向客户端提供这些服务, 我们还需要编制相应的服务端程序,前一篇《Visual C#.Net 网络程序开发-Socket 篇》上曾经 提到, Socket 作为其他网络协议的基础, 既可以面向客户端开发, 也可以面向服务端开发, 在传输层面上使用较多,而在应用协议层面上,客户端我们采用构建于 Socket 类之上的 TcpClient 取代 Socket; 相应地, 构建于 Socket 之上的 TcpListener 提供了更高理念级别的 TCP

服务, 使得我们能更方便地编写服务端应用程序。 正是因为这样的原因, FTP 和 HTTP 这 像 样的应用层协议都是在 TcpListener 类的基础上建立的。 .Net 中的 TCPListener 用于监视 TCP 端口上的传入请求,通过绑定本机 IP 地址和相应端口 (这两者应与客户端的请求一致)创建 TcpListener 对象实例,并由 Start 方法启动侦听;当 TcpListener 侦听到用户端的连接后,视客户端的不同请求方式,通过 AcceptTcpClient 方法 接受传入的连接请求并创建 TcpClient 以处理请求, 或者通过 AcceptSocket 方法接受传入的 连接请求并创建 Socket 以处理请求。最后,你需要使用 Stop 关闭用于侦听传入连接的 Socket,你必须也关闭从 AcceptSocket 或 AcceptTcpClient 返回的任何实例。这个过程详细 解说如下: 首先,创建 TcpListener 对象实例,这通过 TcpListener 类的构造方法来实现: public TcpListener(port);//指定本机端口 public TcpListener(IPEndPoint)//指定本机终结点 public TcpListener(IPAddress,port)//指定本机 IP 地址及端口

以上方法中的参数在前面多次提到,这里不再细述,唯一需要提醒的是,这些参数均针对服 务端主机。下面的示例演示创建 TcpListener 类的实例: IPHostEntry ipInfo=Dns.Resolve("127.0.0.1");//主机信息 IPAddressList[] ipList=ipInfo.IPAddressList;//IP 数组 IPAddress ip=ipList[0];//IP try{ TcpListener tcpListener = new TcpListener(ipAddress, 4088);//创建 TcpListener 对象实例以侦听 用户端连接 } catch ( Exception e){ MessageBox.Show("TcpListener 错误:"+e.Message); }

随后,你需要调用 Start 方法启动侦听: public void Start();

其次,当侦听到有用户端连接时,需要接受挂起的连接请求,这通过调用以下两方法之一来 完成连接: public Socket AcceptSocket(); public TcpClient AcceptTcpClient();

前一个方法返回代表客户端的 Socket 对象, 随后可以通过 Socket 类的 Send 和 Receive 方 法与远程计算机通讯;后一个方法返回代表客户端的 TcpClient 对象,随后使用上面介绍的 TcpClient.GetStream 方法获取 TcpClient 的基础网络流 NetworkStream,并使用流读写 Read/Write 方法与远程计算机通讯。 最后,请记住关闭侦听器:public void Stop(); 同时关闭其他连接实例:public void Close(); 下面的示例完整体现了上面的过程: bool done = false; TcpListener listener = new TcpListener(13);// 创建 TcpListener 对象实例(13 号端口提供时间服 务) listener.Start();//启动侦听 while (!done) {//进入无限循环以侦听用户连接 TcpClient client = listener.AcceptTcpClient();//侦听到连接后创建客户端连接 TcpClient NetworkStream ns = client.GetStream();//得到网络传输流 byte[] byteTime = Encoding.ASCII.GetBytes(DateTime.Now.ToString());//预发送的内容(此为服 务端时间)转换为字节数组以便写入流 try { ns.Write(byteTime, 0, byteTime.Length);//写入流 ns.Close();//关闭流 client.Close();//关闭客户端连接 } catch (Exception e) { MessageBox.Show("流错误:"+e.Message) } 综合运用上面的知识,下面的实例实现了简单的网络通讯-双机互连,针对客户端和服务端 分别编制了应用程序。客户端创建到服务端的连接,向远程主机发送连接请求连接信号,并 发送交谈内容;远程主机端接收来自客户的连接,向客户端发回确认连接的信号,同时接收 并显示客户端的交谈内容。在这个基础上,发挥你的创造力,你完全可以开发出一个基于程 序语言(C#)级的聊天室! 客户端主要源代码: public void SendMeg()//发送信息 { try {

int port=Int32.Parse(textBox3.Text.ToString());//远程主机端口

try { tcpClient=new TcpClient(textBox1.Text,port);//创建 TcpClient 对象实例 } catch(Exception le) { MessageBox.Show("TcpClient Error:"+le.Message); } string strDateLine=DateTime.Now.ToShortDateString()+" "+DateTime.Now.ToLongTimeString();// 得到发送时客户端时间 netStream=tcpClient.GetStream();//得到网络流 sw=new StreamWriter(netStream);//创建 TextWriter,向流中写字符 string words=textBox4.Text;//待发送的话 string content=strDateLine+words;//待发送内容 sw.Write(content);//写入流 sw.Close();//关闭流写入器 netStream.Close();//关闭网络流 tcpClient.Close();//关闭客户端连接 } catch(Exception ex) { MessageBox.Show("Sending Message Failed!"+ex.Message); } textBox4.Text="";//清空 }

服务器端主要源代码: public void StartListen()//侦听特定端口的用户请求 { //ReceiveMeg(); isLinked=false; //连接标志 try { int port=Int32.Parse(textBox1.Text.ToString());//本地待侦听端口 serverListener=new TcpListener(port);//创建 TcpListener 对象实例 serverListener.Start(); //启动侦听 } catch(Exception ex) { MessageBox.Show("Can't Start Server"+ex.Message); return; } isLinked=true;

while(true)//进入无限循环等待用户端连接 { try { tcpClient=serverListener.AcceptTcpClient();//创建客户端连接对象 netStream=tcpClient.GetStream();//得到网络流 sr=new StreamReader(netStream);//流读写器 } catch(Exception re) { MessageBox.Show(re.Message); } string buffer=""; string received=""; received+=sr.ReadLine();//读流中一行 while(received.Length!=0) { buffer+=received; buffer+="\r\n"; //received=""; received=sr.ReadLine(); } listBox1.Items.Add(buffer);//显示 //关闭 sr.Close(); netStream.Close(); tcpClient.Close(); } }


相关文档

  • Visual C#.Net网络程序开发-Tcp篇(1)
  • Visual C#.Net网络程序开发-Tcp篇(3)
  • Visual C#.Net网络程序开发-Tcp篇(2)
  • C#.Net网络程序开发-Tcp篇
  • Visual C#.Net网络程序开发
  • Visual C#.Net 网络程序开发-Socket篇
  • Visual C#.NET程序开发
  • Visual C#.Net 网络程序开发-Socket
  • 3VISUAL C#.NET网络程序开发 TCP篇
  • 电脑版