C#网络编程订立协议和发送文件Part4.docx

上传人:b****6 文档编号:8441537 上传时间:2023-01-31 格式:DOCX 页数:13 大小:43.96KB
下载 相关 举报
C#网络编程订立协议和发送文件Part4.docx_第1页
第1页 / 共13页
C#网络编程订立协议和发送文件Part4.docx_第2页
第2页 / 共13页
C#网络编程订立协议和发送文件Part4.docx_第3页
第3页 / 共13页
C#网络编程订立协议和发送文件Part4.docx_第4页
第4页 / 共13页
C#网络编程订立协议和发送文件Part4.docx_第5页
第5页 / 共13页
点击查看更多>>
下载资源
资源描述

C#网络编程订立协议和发送文件Part4.docx

《C#网络编程订立协议和发送文件Part4.docx》由会员分享,可在线阅读,更多相关《C#网络编程订立协议和发送文件Part4.docx(13页珍藏版)》请在冰豆网上搜索。

C#网络编程订立协议和发送文件Part4.docx

C#网络编程订立协议和发送文件Part4

文件传输

前面两篇文章所使用的范例都是传输字符串,有的时候我们可能会想在服务端和客户端之间传递文件。

比如,考虑这样一种情况,假如客户端显示了一个菜单,当我们输入S1、S2或S3(S为Send缩写)时,分别向服务端发送文件Client01.jpg、Client02.jpg、Client03.jpg;当我们输入R1、R2或R3时(R为Receive缩写),则分别从服务端接收文件Server01.jpg、Server02.jpg、Server03.jpg。

那么,我们该如何完成这件事呢?

此时可能有这样两种做法:

∙类似于FTP协议,服务端开辟两个端口,并持续对这两个端口侦听:

一个用于接收字符串,类似于FTP的控制端口,它接收各种命令(接收或发送文件);一个用于传输数据,也就是发送和接收文件。

∙服务端只开辟一个端口,用于接收字符串,我们称之为控制端口。

当接到请求之后,根据请求内容在客户端开辟一个端口专用于文件传输,并在传输结束后关闭端口。

现在我们只关注于上面的数据端口,回忆一下在第二篇中我们所总结的,可以得出:

当我们使用上面的方法一时,服务端的数据端口可以为多个客户端的多次请求服务;当我们使用方法二时,服务端只为一个客户端的一次请求服务,但是因为每次请求都会重新开辟端口,所以实际上还是相当于可以为多个客户端的多次请求服务。

同时,因为它只为一次请求服务,所以我们在数据端口上传输文件时无需采用异步传输方式。

但在控制端口我们仍然需要使用异步方式。

从上面看出,第一种方式要好得多,但是我们将采用第二种方式。

至于原因,你可以回顾一下Part.1(基本概念和操作)中关于聊天程序模式的讲述,因为接下来一篇文章我们将创建一个聊天程序,而这个聊天程序采用第三种模式,所以本文的练习实际是对下一篇的一个铺垫。

1.订立协议

1.1发送文件

我们先看一下发送文件的情况,如果我们想将文件client01.jpg由客户端发往客户端,那么流程是什么:

1.客户端开辟数据端口用于侦听,并获取端口号,假设为8005。

2.假设客户端输入了S1,则发送下面的控制字符串到服务端:

[file=Client01.jpg,mode=send,port=8005]。

3.服务端收到以后,根据客户端ip和端口号与该客户端建立连接。

4.客户端侦听到服务端的连接,开始发送文件。

5.传送完毕后客户端、服务端分别关闭连接。

此时,我们订立的发送文件协议为:

[file=Client01.jpg,mode=send,port=8005]。

但是,由于它是一个普通的字符串,在上一篇中,我们采用了正则表达式来获取其中的有效值,但这显然不是一种好办法。

因此,在本文及下一篇文章中,我们采用一种新的方式来编写协议:

XML。

对于上面的语句,我们可以写成这样的XML:

这样我们在服务端就会好处理得多,接下来我们来看一下接收文件的流程及其协议。

NOTE:

这里说发送、接收文件是站在客户端的立场说的,当客户端发送文件时,对于服务器来收,则是接收文件。

1.2接收文件

接收文件与发送文件实际上完全类似,区别只是由客户端向网络流写入数据,还是由服务端向网络流写入数据。

1.客户端开辟数据端口用于侦听,假设为8006。

2.假设客户端输入了R1,则发送控制字符串:

到服务端。

3.服务端收到以后,根据客户端ip和端口号与该客户端建立连接。

4.客户端建立起与服务端的连接,服务端开始网络流中写入数据。

5.传送完毕后服务端、客户端分别关闭连接。

2.协议处理类的实现

和上面一章一样,在开始编写实际的服务端客户端代码之前,我们首先要编写处理协议的类,它需要提供这样两个功能:

1、方便地帮我们获取完整的协议信息,因为前面我们说过,服务端可能将客户端的多次独立请求拆分或合并。

比如,客户端连续发送了两条控制信息到服务端,而服务端将它们合并了,那么则需要先拆开再分别处理。

2、方便地获取我们所想要的属性信息,因为协议是XML格式,所以还需要一个类专门对XML进行处理,获得字符串的属性值。

2.1ProtocalHandler辅助类

我们先看下ProtocalHandler,它与上一篇中的RequestHandler作用相同。

需要注意的是必须将它声明为实例的,而非静态的,这是因为每个TcpClient都需要对应一个ProtocalHandler,因为它内部维护的patialProtocal不能共享,在协议发送不完整的情况下,这个变量用于临时保存被截断的字符串。

publicclassProtocolHandler{

   privatestringpartialProtocal;//保存不完整的协议

   

   publicProtocolHandler(){

       partialProtocal="";      

   }

   publicstring[]GetProtocol(stringinput){

       returnGetProtocol(input,null);

   }

   

   //获得协议

   privatestring[]GetProtocol(stringinput,ListoutputList){

       if(outputList==null)

           outputList=newList();

       if(String.IsNullOrEmpty(input))

           returnoutputList.ToArray();

       if(!

String.IsNullOrEmpty(partialProtocal))

           input=partialProtocal+input;

       stringpattern="(^.*?

)";

       //如果有匹配,说明已经找到了,是完整的协议

       if(Regex.IsMatch(input,pattern)){

           //获取匹配的值

           stringmatch=Regex.Match(input,pattern).Groups[0].Value;

           outputList.Add(match);

           partialProtocal="";

           //缩短input的长度

           input=input.Substring(match.Length);

           //递归调用

           GetProtocol(input,outputList);

       }else{

           //如果不匹配,说明协议的长度不够,

           //那么先缓存,然后等待下一次请求

           partialProtocal=input;

       }

       returnoutputList.ToArray();

   }

}

因为现在它已经不是本文的重点了,所以我就不演示对于它的测试了,本文所附带的代码中含有它的测试代码(我在ProtocolHandler中添加了一个静态类Test())。

2.2FileRequestType枚举和FileProtocol结构

因为XML是以字符串的形式在进行传输,为了方便使用,我们最好构建一个强类型来对它们进行操作,这样会方便很多。

我们首先可以定义FileRequestMode枚举,它代表是发送还是接收文件:

publicenumFileRequestMode{

   Send=0,

   Receive

}

接下来我们再定义一个FileProtocol结构,用来为整个协议字符串提供强类型的访问,注意这里覆盖了基类的ToString()方法,这样在客户端我们就不需要再手工去编写XML,只要在结构值上调用ToString()就OK了,会方便很多。

publicstructFileProtocol{

   privatereadonlyFileRequestModemode;

   privatereadonlyintport;

   privatereadonlystringfileName;

   publicFileProtocol

       (FileRequestModemode,intport,stringfileName){

       this.mode=mode;

       this.port=port;

       this.fileName=fileName;

   }

   publicFileRequestModeMode{

       get{returnmode;}

   }

   publicintPort{

       get{returnport;}

   }

   publicstringFileName{

       get{returnfileName;}

   }

   publicoverridestringToString(){

       returnString.Format("",fileName,mode,port);

   }

}

2.3ProtocolHelper辅助类

这个类专用于将XML格式的协议映射为我们上面定义的强类型对象,这里我没有加入try/catch异常处理,因为协议对用户来说是不可见的,而且客户端应该总是发送正确的协议,我觉得这样可以让代码更加清晰:

publicclassProtocolHelper{

   privateXmlNodefileNode;

   privateXmlNoderoot;

   

   publicProtocolHelper(stringprotocol){

       XmlDocumentdoc=newXmlDocument();

       doc.LoadXml(protocol);

       root=doc.DocumentElement;

       fileNode=root.SelectSingleNode("file");

   }

   //此时的protocal一定为单条完整protocal

   privateFileRequestModeGetFileMode(){

       stringmode=fileNode.Attributes["mode"].Value;

       mode=mode.ToLower();

       if(mode=="send")

           returnFileRequestMode.Send;

       else

           returnFileRequestMode.Receive;

   }

   //获取单条协议包含的信息

   publicFileProtocolGetProtocol(){

       FileRequestModemode=GetFileMode();

       stringfileName="";

       intport=0;

      fileName=fileNode.Attributes["name"].Value;

       port=Convert.ToInt32(fileNode.Attributes["port"].Value);

       returnnewFileProtocol(mode,port,fileName);

   }

}

OK,我们又耽误了点时间,下面就让我们进入正题吧。

3.客户端发送数据

3.1服务端的实现

我们还是将一个问题分成两部分来处理,先是发送数据,然后是接收数据。

我们先看发送数据部分的服务端。

如果你从第一篇文章看到了现在,那么我觉得更多的不是技术上的问题而是思路,所以我们不再将重点放到代码上,这些应该很容易就看懂了。

classServer{

   staticvoidMain(string[]args){

       Console.WriteLine("Serverisrunning...");

       IPAddressip=IPAddress.Parse("127.0.0.1");

       TcpListenerlistener=newTcpListener(ip,8500);

       listener.Start();          //开启对控制端口8500的侦听

       Console.WriteLine("StartListening...");

       while(true){

           //获取一个连接,同步方法,在此处中断

           TcpClientclient=listener.AcceptTcpClient();             

           RemoteClientwapper=newRemoteClient(client);

           wapper.BeginRead();

       }

   }

}

publicclassRemoteClient{

   privateTcpClientclient;

   privateNetworkStreamstreamToClient;

   privateconstintBufferSize=8192;

   privatebyte[]buffer;

   privateProtocolHandlerhandler;

   

   publicRemoteClient(TcpClientclient){

       this.client=client;

       //打印连接到的客户端信息

       Console.WriteLine("\nClientConnected!

{0}<--{1}",

           client.Client.LocalEndPoint,client.Client.RemoteEndPoint);

       //获得流

       streamToClient=client.GetStream();

       buffer=newbyte[BufferSize];

       handler=newProtocolHandler();

   }

   //开始进行读取

   publicvoidBeginRead(){      

       AsyncCallbackcallBack=newAsyncCallback(OnReadComplete);

       streamToClient.BeginRead(buffer,0,BufferSize,callBack,null);

   }

   //再读取完成时进行回调

   privatevoidOnReadComplete(IAsyncResultar){

       intbytesRead=0;

       try{

           lock(streamToClient){

               bytesRead=streamToClient.EndRead(ar);

               Console.WriteLine("Readingdata,{0}bytes...",bytesRead);

           }

           if(bytesRead==0)thrownewException("读取到0字节");

           stringmsg=Encoding.Unicode.GetString(buffer,0,bytesRead);

           Array.Clear(buffer,0,buffer.Length);       //清空缓存,避免脏读

           //获取protocol数组

           string[]protocolArray=handler.GetProtocol(msg);

           foreach(stringproinprotocolArray){

               //这里异步调用,不然这里可能会比较耗时

               ParameterizedThreadStartstart=

                   newParameterizedThreadStart(handleProtocol);

               start.BeginInvoke(pro,null,null);

           }

           //再次调用BeginRead(),完成时调用自身,形成无限循环

           lock(streamToClient){

               AsyncCallbackcallBack=newAsyncCallback(OnReadComplete);

               streamToClient.BeginRead(buffer,0,BufferSize,callBack,null);

           }

       }catch(Exceptionex){

           if(streamToClient!

=null)

               streamToClient.Dispose();

           client.Close();

           Console.WriteLine(ex.Message);     //捕获异常时退出程序

       }

   }

   //处理protocol

   privatevoidhandleProtocol(objectobj){

       stringpro=objasstring;

       ProtocolHelperhelper=newProtocolHelper(pro);

       FileProtocolprotocol=helper.GetProtocol();

       if(protocol.Mode==FileRequestMode.Send){

           //客户端发送文件,对服务端来说则是接收文件

           receiveFile(protocol);

       }elseif(protocol.Mode==FileRequestMode.Receive){

           //客户端接收文件,对服务端来说则是发送文件

           //sendFile(protocol);

       }

   }

   privatevoidreceiveFile(FileProtocolprotocol){

       //获取远程客户端的位置

       IPEndPointendpoint=client.Client.RemoteEndPointasIPEndPoint;

       IPAddressip=endpoint.Address;

       

       //使用新端口号,获得远程用于接收文件的端口

       endpoint=newIPEndPoint(ip,protocol.Port);

       //连接到远程客户端

       TcpClientlocalClient;

       try{

           localClient=newTcpClient();

           localClient.Connect(endpoint);

       }catch{

           Console.WriteLine("无法连接到客户端-->{0}",endpoint);

           return;

       }

       //获取发送文件的流

       NetworkStreamstreamToClient=localClient.GetStream();

       //随机生成一个在当前目录下的文件名称

       stringpath=

           Environment.CurrentDirectory+"/"+generateFileName(protocol.FileName);

       byte[]fileBuffer=newbyte[1024];//每次收1KB

       FileStreamfs=newFileStream(path,FileMode.CreateNew,FileAccess.Write);

       //从缓存buffer中读入到文件流中

       intbyte

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 机械仪表

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1