C#网络编程4.doc

上传人:大张伟 文档编号:8599879 上传时间:2020-12-02 格式:DOC 页数:23 大小:80KB
返回 下载 相关 举报
C#网络编程4.doc_第1页
第1页 / 共23页
C#网络编程4.doc_第2页
第2页 / 共23页
C#网络编程4.doc_第3页
第3页 / 共23页
亲,该文档总共23页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

《C#网络编程4.doc》由会员分享,可在线阅读,更多相关《C#网络编程4.doc(23页珍藏版)》请在三一文库上搜索。

1、C#网络编程-4C网络编程(订立协议和发送文件) Part。4文件传输前面两篇文章所使用的范例都是传输字符串,有的时候我们可能会想在服务端和客户端之间传递文件。比如,考虑这样一种情况,假如客户端显示了一个菜单,当我们输入S1、S2或S3(S为Send缩写)时,分别向服务端发送文件Client01.jpg、Client02.jpg、Client03。jpg;当我们输入R1、R2或R3时(R为Receive缩写),则分别从服务端接收文件Server01。jpg、Server02.jpg、Server03。jpg。那么,我们该如何完成这件事呢?此时可能有这样两种做法: 类似于FTP协议,服务端开辟两

2、个端口,并持续对这两个端口侦听:一个用于接收字符串,类似于FTP的控制端口,它接收各种命令(接收或发送文件);一个用于传输数据,也就是发送和接收文件。 服务端只开辟一个端口,用于接收字符串,我们称之为控制端口。当接到请求之后,根据请求内容在客户端开辟一个端口专用于文件传输,并在传输结束后关闭端口。 现在我们只关注于上面的数据端口,回忆一下在第二篇中我们所总结的,可以得出:当我们使用上面的方法一时,服务端的数据端口可以为多个客户端的多次请求服务;当我们使用方法二时,服务端只为一个客户端的一次请求服务,但是因为每次请求都会重新开辟端口,所以实际上还是相当于可以为多个客户端的多次请求服务。同时,因为

3、它只为一次请求服务,所以我们在数据端口上传输文件时无需采用异步传输方式。但在控制端口我们仍然需要使用异步方式。从上面看出,第一种方式要好得多,但是我们将采用第二种方式。至于原因,你可以回顾一下Part。1(基本概念和操作)中关于聊天程序模式的讲述,因为接下来一篇文章我们将创建一个聊天程序,而这个聊天程序采用第三种模式,所以本文的练习实际是对下一篇的一个铺垫.1。订立协议1。1发送文件我们先看一下发送文件的情况,如果我们想将文件client01.jpg由客户端发往客户端,那么流程是什么:1. 客户端开辟数据端口用于侦听,并获取端口号,假设为8005。 2. 假设客户端输入了S1,则发送下面的控制

4、字符串到服务端:, mode=send, port=8005. 3. 服务端收到以后,根据客户端ip和端口号与该客户端建立连接。 4. 客户端侦听到服务端的连接,开始发送文件。 5. 传送完毕后客户端、服务端分别关闭连接. 此时,我们订立的发送文件协议为:。jpg, mode=send, port=8005。但是,由于它是一个普通的字符串,在上一篇中,我们采用了正则表达式来获取其中的有效值,但这显然不是一种好办法。因此,在本文及下一篇文章中,我们采用一种新的方式来编写协议:XML.对于上面的语句,我们可以写成这样的XML:protocolclient01。jpg” mode=send port

5、=”8005 /这样我们在服务端就会好处理得多,接下来我们来看一下接收文件的流程及其协议。NOTE:这里说发送、接收文件是站在客户端的立场说的,当客户端发送文件时,对于服务器来收,则是接收文件。1。2接收文件接收文件与发送文件实际上完全类似,区别只是由客户端向网络流写入数据,还是由服务端向网络流写入数据。1. 客户端开辟数据端口用于侦听,假设为8006。 2. 假设客户端输入了R1,则发送控制字符串:protocolServer01.jpg mode=receive” port=”8006 /protocol到服务端。 3. 服务端收到以后,根据客户端ip和端口号与该客户端建立连接。 4. 客

6、户端建立起与服务端的连接,服务端开始网络流中写入数据。 5. 传送完毕后服务端、客户端分别关闭连接。 2。协议处理类的实现和上面一章一样,在开始编写实际的服务端客户端代码之前,我们首先要编写处理协议的类,它需要提供这样两个功能:1、方便地帮我们获取完整的协议信息,因为前面我们说过,服务端可能将客户端的多次独立请求拆分或合并。比如,客户端连续发送了两条控制信息到服务端,而服务端将它们合并了,那么则需要先拆开再分别处理。2、方便地获取我们所想要的属性信息,因为协议是XML格式,所以还需要一个类专门对XML进行处理,获得字符串的属性值。2。1 ProtocalHandler辅助类我们先看下Proto

7、calHandler,它与上一篇中的RequestHandler作用相同。需要注意的是必须将它声明为实例的,而非静态的,这是因为每个TcpClient都需要对应一个ProtocalHandler,因为它内部维护的patialProtocal不能共享,在协议发送不完整的情况下,这个变量用于临时保存被截断的字符串。public class ProtocolHandler private string partialProtocal; / 保存不完整的协议 public ProtocolHandler() partialProtocal = ”; public string GetProtocol(

8、string input) return GetProtocol(input, null); / 获得协议 private string GetProtocol(string input, List(); if (String.IsNullOrEmpty(input)) return outputList。ToArray(); if (!String.IsNullOrEmpty(partialProtocal)) input = partialProtocal + input; string pattern = ”(.*?/protocol)”; / 如果有匹配,说明已经找到了,是完整的协议

9、if (Regex。IsMatch(input, pattern) / 获取匹配的值 string match = Regex.Match(input, pattern)。Groups0.Value; outputList。Add(match); partialProtocal = ”; / 缩短input的长度 input = input。Substring(match.Length); / 递归调用 GetProtocol(input, outputList); else / 如果不匹配,说明协议的长度不够, / 那么先缓存,然后等待下一次请求 partialProtocal = inpu

10、t; return outputList.ToArray(); 因为现在它已经不是本文的重点了,所以我就不演示对于它的测试了,本文所附带的代码中含有它的测试代码(我在ProtocolHandler中添加了一个静态类Test().2.2 枚举和结构因为XML是以字符串的形式在进行传输,为了方便使用,我们最好构建一个强类型来对它们进行操作,这样会方便很多。我们首先可以定义枚举,它代表是发送还是接收文件:public enum Send = 0, Receive接下来我们再定义一个结构,用来为整个协议字符串提供强类型的访问,注意这里覆盖了基类的ToString()方法,这样在客户端我们就不需要再手工

11、去编写XML,只要在结构值上调用ToString()就OK了,会方便很多.public struct private readonly mode; private readonly int port; private readonly string ; public ( mode, int port, string ) this.mode = mode; this.port = port; this. = ; public Mode get return mode; public int Port get return port; public string get return ; publ

12、ic override string ToString() return String.Format(”protocol/protocol, , mode, port); 2。3 ProtocolHelper辅助类这个类专用于将XML格式的协议映射为我们上面定义的强类型对象,这里我没有加入try/catch异常处理,因为协议对用户来说是不可见的,而且客户端应该总是发送正确的协议,我觉得这样可以让代码更加清晰:public class ProtocolHelper private XmlNode ; private XmlNode root; public ProtocolHelper(stri

13、ng protocol) XmlDocument doc = new XmlDocument(); doc.LoadXml(protocol); root = doc。DocumentElement; = root。SelectSingleNode(”file); / 此时的protocal一定为单条完整protocal private Get() string mode = mode”.Value; mode = mode。ToLower(); if (mode = ”send) return ; else return 。Receive; / 获取单条协议包含的信息 public GetP

14、rotocol() mode = Get(); string = ”; int port = 0; = name。Value; port = Convert。ToInt32(port”.Value); return new (mode, port, ); OK,我们又耽误了点时间,下面就让我们进入正题吧.3.客户端发送数据3.1 服务端的实现我们还是将一个问题分成两部分来处理,先是发送数据,然后是接收数据。我们先看发送数据部分的服务端。如果你从第一篇文章看到了现在,那么我觉得更多的不是技术上的问题而是思路,所以我们不再将重点放到代码上,这些应该很容易就看懂了.class Server stat

15、ic void Main(string args) Console。WriteLine(”Server is running .。. ); IPAddress ip = IPAddress.Parse(127。0。0.1); TcpListener listener = new TcpListener(ip, 8500); listener。Start(); / 开启对控制端口 8500 的侦听 Console.WriteLine(”Start Listening .。.); while (true) / 获取一个连接,同步方法,在此处中断 TcpClient client = listene

16、r。AcceptTcpClient(); RemoteClient = new RemoteClient(client); (); public class RemoteClient private TcpClient client; private NetworkStream streamToClient; private const int BufferSize = 8192; private byte buffer; private ProtocolHandler handler; public RemoteClient(TcpClient client) this.client = c

17、lient; / 打印连接到的客户端信息 Console。WriteLine(”nClient Connected!0 1”, client.Client。LocalEndPoint, client.Client。RemoteEndPoint); streamToServer = client。GetStream(); / 发送消息到服务端 public void SendMessage(string msg) byte temp = Encoding.Unicode。GetBytes(msg); / 获得缓存 try lock (streamToServer) streamToServer。

18、Write(temp, 0, temp。Length); / 发往服务器 Console.WriteLine(”Sent: 0, msg); catch (Exception ex) Console。WriteLine(ex.Message); return; / 发送文件 异步方法 public void BeginSend ) ParameterizedThreadStart start = new ParameterizedThreadStart(BeginSendFile); start。BeginInvoke(, null, null); private void BeginSend

19、 obj) string = obj as string; Send); / 发送文件 - 同步方法 public void Send ) IPAddress ip = IPAddress。Parse(”127.0。0。1); TcpListener listener = new TcpListener(ip, 0); listener.Start(); / 获取本地侦听的端口号 IPEndPoint endPoint = listener.LocalEndpoint as IPEndPoint; int listeningPort = endPoint。Port; / 获取发送的协议字符串

20、string = Path.Get(); protocol = new (。Send, listeningPort, ); string pro = protocol。ToString(); SendMessage(pro); / 发送协议到服务端 / 中断,等待远程连接 TcpClient localClient = listener.AcceptTcpClient(); Console.WriteLine(”Start sending file。.); NetworkStream stream = localClient。GetStream(); / 创建文件流 fs = new (, ,

21、 。Read); byte = new byte1024; / 每次传1KB int bytesRead; int totalBytes = 0; / 创建获取文件发送状态的类 SendStatus status = new SendStatus(); / 将文件流转写入网络流 try do Thread.Sleep(10); / 为了更好的视觉效果,暂停10毫秒:-) bytesRead = fs。Read(, 0, 。Length); stream。Write(, 0, bytesRead); totalBytes += bytesRead; / 发送了的字节数 status。PrintS

22、tatus(totalBytes); / 打印发送状态 while (bytesRead 0); Console。WriteLine(”Total 0 bytes sent, Done!, totalBytes); catch Console。WriteLine(”Server has lost.。”); stream。Dispose(); fs.Dispose(); localClient。Close(); listener。Stop(); 接下来我们来看下这段代码,有这么两点需要注意一下: 在Main()方法中可以看到,图片的位置为应用程序所在的目录,如果你跟我一样处于调试模式,那么就在解

23、决方案的Bin目录下的Debug目录中放置三张图片Client01.jpg、Client02.jpg、Client03.jpg,用来发往服务端。 我在客户端提供了两个SendFile()方法,和一个BeginSendFile()方法,分别用于同步和异步传输,其中私有的SendFile()方法只是一个辅助方法。实际上对于发送文件这样的操作我们几乎总是需要使用异步操作。 SendMessage()方法中给streamToServer加锁很重要,因为SendFile()方法是多线程访问的,而在SendFile()方法中又调用了SendMessage()方法。 我另外编写了一个SendStatus类,

24、它用来记录和打印发送完成的状态,已经发送了多少字节,完成度是百分之多少,等等。本来这个类的内容我是直接写入在Client类中的,后来我觉得它执行的工作已经不属于Client本身所应该执行的领域之内了,我记得这样一句话:当你觉得类中的方法与类的名称不符的时候,那么就应该考虑重新创建一个类。我觉得用在这里非常恰当。 下面是SendStatus的内容:/ 即时计算发送文件的状态public class SendStatus private info; private long ; public SendStatus(string ) info = new (); = info.Length; public void PrintStatus(int sent) string percent = GetPercent(sent); Console.WriteLine(”Sending 0 bytes, 1% .。, sent,

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

当前位置:首页 > 科普知识


经营许可证编号:宁ICP备18001539号-1