26.4 网络流的使用
System.Net和System.Net.Sockets命名空间为网络访问提供了简单接口。本章不会涉及过多的网络编程细节,我们研究的主要是NetworkStream在网络编程中的使用,如果读者需要更加深入的内容,请自行参阅相关资料或书籍。
我们通过一个例子来阐述NetworkStream的使用,这个例子包含客户端和服务端两个角色,尽管是两个角色,但是一个程序,通过启动时传入的参数来进行区别。
图26-4示意图阐述了这个例子。
图 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-5 服务端监听客户端的连接请求
然后作为客户端启动,连接服务端,并发送文本消息:
C:\WorkPlace\ConsoleApplication1\ConsoleApplication1\bin\Debug>ConsoleApplication1 connect 127.0.0.1
如图26-6所示:
图 26-6 客户端连接服务端并发送消息
在客户端和服务端建立连接后,服务端收到客户端发送的文本消息并显示,如图26-7所示。
图 26-7 建立连接并显示客户端发送的文本消息