26.4 网络流的使用

System.Net和System.Net.Sockets命名空间为网络访问提供了简单接口。本章不会涉及过多的网络编程细节,我们研究的主要是NetworkStream在网络编程中的使用,如果读者需要更加深入的内容,请自行参阅相关资料或书籍。

我们通过一个例子来阐述NetworkStream的使用,这个例子包含客户端和服务端两个角色,尽管是两个角色,但是一个程序,通过启动时传入的参数来进行区别。

图26-4示意图阐述了这个例子。

26.4 网络流的使用 - 图1

图 26-4 网络示例代码的示意图

这个例子使用TCP作为传输协议,我们知道TCP协议是面向连接的,两个主机要进行通信,必须首先进行一个握手过程,以确认连接成功,之后才能传输实际的数据。例如,主机A要发送数据给主机B,首先要建立A到B的连接,要建立连接就要知道主机B的位置(IP地址和端口号)。

注意,在图26-4中,两个主机是对等的,但是按照约定,我们将发起请求的一方称为客户端(主机A),将另一方称为服务端(主机B)。

在.NET中,尽管可以直接对套接字编程,但是.NET提供了两个类对套接字的编程进行了封装,使得我们的编程更加方便,这两个类是TcpClient和TcpListener。TcpClient用于建立一个客户端连接,TcpListener用于侦听和接受传入的连接请求,TcpClient可以用来连接TcpListener。一旦连接建立成功则可以通过TcpClient的实例获取网络流:NetworkStream。下一步就简单了,可以使用StreamReader和StreamWriter对流进行读写。向流写入数据即意味着数据通过TCP协议传输到远程主机,读则意味着获取从远程主机发送来的数据。

如代码清单26-7所示。

代码清单26-7 在网络中使用NetworkStream using System;


using System.IO;using System.Net;

using System.Net.Sockets;

using System.Threading;

namespace ProgrammingCSharp4{

class IOSample{

public static void Main(string[]args){

//获取命令行传入的第1个参数

//listen表示监听,作为服务端;connect表示连接,作为客户端string action=args[0];

//获取命令行传入的第2个参数,表示主机的IP地址//服务端为本机IP地址,客户端为远程主机IP地址string ip=args[1];

//定义服务端的监听端口端口号为2010//客户端连接的服务端端口号与之相同int port=2010;

//根据传入的参数来判断当前程序作为服务端还是客户端switch(action)

{

case"listen":

//作为服务端,监听

DoListen(ip,port);break;

case"connect":

//作为客户端,连接服务端DoConnect(ip,port);break;

}

Console.ReadKey();}

//连接服务端,并发送三条文本消息

private static void DoConnect(string ip,int port){

//声明变量

TcpClient tc=null;

NetworkStream ns=null;StreamWriter sw=null;try

{

//初始化tc变量

tc=new TcpClient();

//连接服务端,ip地址需要转换为IPAddress类型,Connect//方法接收一个IPEndPoint类型的对象,后者包含IPAddress//类型和服务端端口号

tc.Connect(new IPEndPoint(IPAddress.Parse(ip),port));//通过tc获取一个NetworkStream实例

ns=tc.GetStream();

//实例化一个StreamWriter对象,注意字符集为UTF8

sw=new StreamWriter(ns,System.Text.UTF8Encoding.UTF8);sw.AutoFlush=true;

//将待发送的文本消息放到一个字符串数组中

string[]msgs=new string[3]{

"Hello",

"World!",

“——来自网络的问候”,};

foreach(string msg in msgs){

//向流写入消息,亦即向服务端发送数据sw.WriteLine(msg);

//等待100毫秒

Thread.Sleep(100);}

}

catch(Exception e){

Console.WriteLine(e.Message);}

finally{

//释放占用的网络资源sw.Close();

ns.Close();tc.Close();

}}

//作为服务端监听客户端连接,并显示客户端发送的文本消息

private static void DoListen(string ip,int port){

//声明并实例化一个TcpListener对象

TcpListener listener=new TcpListener(new

IPEndPoint(IPAddress.Parse(ip),port));//开始监听

listener.Start();

TcpClient client=new TcpClient();NetworkStream ns=null;

StreamReader sr=null;bool listening=true;try

{

Console.WriteLine("Listening……");while(listening)

{

//如果连接没有建立

if(!client.Connected){

//接受客户端的连接请求,连接建立

client=listener.AcceptTcpClient();Console.WriteLine("Connected.");

}

//从建立的连接获取NetworkStream实例ns=client.GetStream();

if(ns!=null&&ns.CanRead){

//使用StreamReader读取NetworkStream中的数据//编码格式为UTF8,支持传中文

sr=new StreamReader(ns,System.Text.UTF8Encoding.UTF8);

try

{

//从网络流读取消息数据

string msg=sr.ReadLine();

if(!string.IsNullOrEmpty(msg)){

//输出接收到的消息

Console.WriteLine(“接收到的消息:{0}”,msg);}

else

{

listening=false;}

}

catch(Exception e){

Console.WriteLine(e.Message);}

}}

}

finally{

//释放网络资源sr.Close();ns.Close();

client.Close();}

}}

}


以下为上述代码的运行结果。首先作为服务端启动,负责监听客户端连接,并显示客户端发送过来的文本数据。

C:\WorkPlace\ConsoleApplication1\ConsoleApplication1\bin\Debug>ConsoleApplication1 listen 127.0.0.1

如图26-5所示:

26.4 网络流的使用 - 图2

图 26-5 服务端监听客户端的连接请求

然后作为客户端启动,连接服务端,并发送文本消息:

C:\WorkPlace\ConsoleApplication1\ConsoleApplication1\bin\Debug>ConsoleApplication1 connect 127.0.0.1

如图26-6所示:

26.4 网络流的使用 - 图3

图 26-6 客户端连接服务端并发送消息

在客户端和服务端建立连接后,服务端收到客户端发送的文本消息并显示,如图26-7所示。

26.4 网络流的使用 - 图4

图 26-7 建立连接并显示客户端发送的文本消息