欢迎来到淘文阁 - 分享文档赚钱的网站! | 帮助中心 好文档才是您的得力助手!
淘文阁 - 分享文档赚钱的网站
全部分类
  • 研究报告>
  • 管理文献>
  • 标准材料>
  • 技术资料>
  • 教育专区>
  • 应用文书>
  • 生活休闲>
  • 考试试题>
  • pptx模板>
  • 工商注册>
  • 期刊短文>
  • 图片设计>
  • ImageVerifierCode 换一换

    C#网络编程-4.doc

    • 资源ID:23886019       资源大小:215.50KB        全文页数:66页
    • 资源格式: DOC        下载积分:15金币
    快捷下载 游客一键下载
    会员登录下载
    微信登录下载
    三方登录下载: 微信开放平台登录   QQ登录  
    二维码
    微信扫一扫登录
    下载资源需要15金币
    邮箱/手机:
    温馨提示:
    快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。
    如填写123,账号就是123,密码也是123。
    支付方式: 支付宝    微信支付   
    验证码:   换一换

     
    账号:
    密码:
    验证码:   换一换
      忘记密码?
        
    友情提示
    2、PDF文件下载后,可能会被浏览器默认打开,此种情况可以点击浏览器菜单,保存网页到桌面,就可以正常下载了。
    3、本站不支持迅雷下载,请使用电脑自带的IE浏览器,或者360浏览器、谷歌浏览器下载即可。
    4、本站资源下载后的文档和图纸-无水印,预览文档经过压缩,下载后原文更清晰。
    5、试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。

    C#网络编程-4.doc

    Four short words sum up what has lifted most successful individuals above the crowd: a little bit more.-author-dateC#网络编程-4C#网络编程-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协议,服务端开辟两个端口,并持续对这两个端口侦听:一个用于接收字符串,类似于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:<protocol><file name="client01.jpg" mode="send" port="8005" /></protocol>这样我们在服务端就会好处理得多,接下来我们来看一下接收文件的流程及其协议。NOTE:这里说发送、接收文件是站在客户端的立场说的,当客户端发送文件时,对于服务器来收,则是接收文件。1.2接收文件接收文件与发送文件实际上完全类似,区别只是由客户端向网络流写入数据,还是由服务端向网络流写入数据。1. 客户端开辟数据端口用于侦听,假设为8006。 2. 假设客户端输入了R1,则发送控制字符串:<protocol><file name="Server01.jpg" mode="receive" port="8006" /></protocol>到服务端。 3. 服务端收到以后,根据客户端ip和端口号与该客户端建立连接。 4. 客户端建立起与服务端的连接,服务端开始网络流中写入数据。 5. 传送完毕后服务端、客户端分别关闭连接。 2.协议处理类的实现和上面一章一样,在开始编写实际的服务端客户端代码之前,我们首先要编写处理协议的类,它需要提供这样两个功能:1、方便地帮我们获取完整的协议信息,因为前面我们说过,服务端可能将客户端的多次独立请求拆分或合并。比如,客户端连续发送了两条控制信息到服务端,而服务端将它们合并了,那么则需要先拆开再分别处理。2、方便地获取我们所想要的属性信息,因为协议是XML格式,所以还需要一个类专门对XML进行处理,获得字符串的属性值。2.1 ProtocalHandler辅助类我们先看下ProtocalHandler,它与上一篇中的RequestHandler作用相同。需要注意的是必须将它声明为实例的,而非静态的,这是因为每个TcpClient都需要对应一个ProtocalHandler,因为它内部维护的patialProtocal不能共享,在协议发送不完整的情况下,这个变量用于临时保存被截断的字符串。public class ProtocolHandler     private string partialProtocal; / 保存不完整的协议        public ProtocolHandler()         partialProtocal = ""               public string GetProtocol(string input)         return GetProtocol(input, null);            / 获得协议    private string GetProtocol(string input, List<string> outputList)         if (outputList = null)            outputList = new List<string>();        if (String.IsNullOrEmpty(input)            return outputList.ToArray();        if (!String.IsNullOrEmpty(partialProtocal)            input = partialProtocal + input;        string pattern = "(<protocol>.*?</protocol>)"        / 如果有匹配,说明已经找到了,是完整的协议        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 = input;                return outputList.ToArray();    因为现在它已经不是本文的重点了,所以我就不演示对于它的测试了,本文所附带的代码中含有它的测试代码(我在ProtocolHandler中添加了一个静态类Test())。2.2 FileRequestType枚举和FileProtocol结构因为XML是以字符串的形式在进行传输,为了方便使用,我们最好构建一个强类型来对它们进行操作,这样会方便很多。我们首先可以定义FileRequestMode枚举,它代表是发送还是接收文件:public enum FileRequestMode     Send = 0,    Receive接下来我们再定义一个FileProtocol结构,用来为整个协议字符串提供强类型的访问,注意这里覆盖了基类的ToString()方法,这样在客户端我们就不需要再手工去编写XML,只要在结构值上调用ToString()就OK了,会方便很多。public struct FileProtocol     private readonly FileRequestMode mode;    private readonly int port;    private readonly string fileName;    public FileProtocol        (FileRequestMode mode, int port, string fileName)         this.mode = mode;        this.port = port;        this.fileName = fileName;        public FileRequestMode Mode         get return mode;         public int Port         get return port;         public string FileName         get return fileName;         public override string ToString()         return String.Format("<protocol><file name="0" mode="1" port="2" /></protocol>", fileName, mode, port);    2.3 ProtocolHelper辅助类这个类专用于将XML格式的协议映射为我们上面定义的强类型对象,这里我没有加入try/catch异常处理,因为协议对用户来说是不可见的,而且客户端应该总是发送正确的协议,我觉得这样可以让代码更加清晰:public class ProtocolHelper     private XmlNode fileNode;    private XmlNode root;        public ProtocolHelper(string protocol)         XmlDocument doc = new XmlDocument();        doc.LoadXml(protocol);        root = doc.DocumentElement;        fileNode = root.SelectSingleNode("file");        / 此时的protocal一定为单条完整protocal    private FileRequestMode GetFileMode()         string mode = fileNode.Attributes"mode".Value;        mode = mode.ToLower();        if (mode = "send")            return FileRequestMode.Send;        else            return FileRequestMode.Receive;        / 获取单条协议包含的信息    public FileProtocol GetProtocol()         FileRequestMode mode = GetFileMode();        string fileName = ""        int port = 0;        fileName = fileNode.Attributes"name".Value;        port = Convert.ToInt32(fileNode.Attributes"port".Value);        return new FileProtocol(mode, port, fileName);    OK,我们又耽误了点时间,下面就让我们进入正题吧。3.客户端发送数据3.1 服务端的实现我们还是将一个问题分成两部分来处理,先是发送数据,然后是接收数据。我们先看发送数据部分的服务端。如果你从第一篇文章看到了现在,那么我觉得更多的不是技术上的问题而是思路,所以我们不再将重点放到代码上,这些应该很容易就看懂了。class Server     static 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 = listener.AcceptTcpClient();                          RemoteClient wapper = new RemoteClient(client);            wapper.BeginRead();            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 = client;        / 打印连接到的客户端信息        Console.WriteLine("nClient Connected!0 <- 1",            client.Client.LocalEndPoint, client.Client.RemoteEndPoint);        / 获得流        streamToClient = client.GetStream();        buffer = new byteBufferSize;        handler = new ProtocolHandler();        / 开始进行读取    public void BeginRead()                AsyncCallback callBack = new AsyncCallback(OnReadComplete);        streamToClient.BeginRead(buffer, 0, BufferSize, callBack, null);        / 再读取完成时进行回调    private void OnReadComplete(IAsyncResult ar)         int bytesRead = 0;        try             lock (streamToClient)                 bytesRead = streamToClient.EndRead(ar);                Console.WriteLine("Reading data, 0 bytes .", bytesRead);                        if (bytesRead = 0) throw new Exception("读取到0字节");            string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);            Array.Clear(buffer,0,buffer.Length);        / 清空缓存,避免脏读            / 获取protocol数组            string protocolArray = handler.GetProtocol(msg);            foreach (string pro in protocolArray)                 / 这里异步调用,不然这里可能会比较耗时                ParameterizedThreadStart start =                    new ParameterizedThreadStart(handleProtocol);                start.BeginInvoke(pro, null, null);                        / 再次调用BeginRead(),完成时调用自身,形成无限循环            lock (streamToClient)                 AsyncCallback callBack = new AsyncCallback(OnReadComplete);                streamToClient.BeginRead(buffer, 0, BufferSize, callBack, null);                    catch(Exception ex)             if(streamToClient!=null)                streamToClient.Dispose();            client.Close();            Console.WriteLine(ex.Message);      / 捕获异常时退出程序                / 处理protocol    private void handleProtocol(object obj)         string pro = obj as string;        ProtocolHelper helper = new ProtocolHelper(pro);        FileProtocol protocol = helper.GetProtocol();        if (protocol.Mode = FileRequestMode.Send)             / 客户端发送文件,对服务端来说则是接收文件            receiveFile(protocol);        else if (protocol.Mode = FileRequestMode.Receive)             / 客户端接收文件,对服务端来说则是发送文件            / sendFile(protocol);                private void receiveFile(FileProtocol protocol)         / 获取远程客户端的位置        IPEndPoint endpoint = client.Client.RemoteEndPoint as IPEndPoint;        IPAddress ip = endpoint.Address;                / 使用新端口号,获得远程用于接收文件的端口        endpoint = new IPEndPoint(ip, protocol.Port);        / 连接到远程客户端        TcpClient localClient;        try             localClient = new TcpClient();            localClient.Connect(endpoint);        catch             Console.WriteLine("无法连接到客户端 -> 0", endpoint);            return;                / 获取发送文件的流        NetworkStream streamToClient = localClient.GetStream();        / 随机生成一个在当前目录下的文件名称        string path =             Environment.CurrentDirectory + "/" + generateFileName(protocol.FileName);        byte fileBuffer = new byte1024; / 每次收1KB        FileStream fs = new FileStream(path, FileMode.CreateNew, FileAccess.Write);        / 从缓存buffer中读入到文件流中        int bytesRead;        int totalBytes = 0;        do             bytesRead = streamToClient.Read(buffer, 0, BufferSize);                         fs.Write(buffer, 0, bytesRead);            totalBytes += bytesRead;            Console.WriteLine("Receiving 0 bytes .", totalBytes);        while (bytesRead > 0);        Console.WriteLine("Total 0 bytes received, Done!", totalBytes);        streamToClient.Dispose();        fs.Dispose();        localClient.Close();        / 随机获取一个图片名称    private string generateFileName(string fileName)         DateTime now = DateTime.Now;        return String.Format(            "0_1_2_3", now.Minute, now.Second, now.Millisecond, fileName        );    这里应该没有什么新知识,需要注意的地方有这么几个:· 在OnReadComplete()回调方法中的foreach循环,我们使用委托异步调用了handleProtocol()方法,这是因为handleProtocol即将执行的是一个读取或接收文件的操作,也就是一个相对耗时的操作。 · 在handleProtocol()方法中,我们深切体会了定义ProtocolHelper类和FileProtocol结构的好处。如果没有定义它们,这里将是不堪入目的处理XML以及类型转换的代码。 · handleProtocol()方法中进行了一个条件判断,注意sendFile()方法我屏蔽掉了,这个还没有实现,但是我想你已经猜到它将是后面要实现的内容。 · receiveFile()方法是实际接收客户端发来文件的方法,这里没有什么特别之处。需要注意的是文件存储的路径,它保存在了当前程序执行的目录下,文件的名称我使用generateFileName()生成了一个与时间有关的随机名称。 3.2客户端的实现我们现在先不着急实现客户端S1、R1等用户菜单,首先完成发送文件这一功能,实际上,就是为上一节SendMessage()加一个姐妹方法SendFile()。class Client     static void Main(string args)         ConsoleKey key;        ServerClient client = new ServerClient();        string filePath = Environment.CurrentDirectory + "/" + "Client01.jpg"        if(File.Exists(filePath)            client.BeginSendFile(filePath);                Console.WriteLine("nn输入"Q"键退出。");        do             key = Console.ReadKey(true).Key;        while (key != ConsoleKey.Q);    public class ServerClient     private const int BufferSize = 8192;    private byte buffer;    private TcpClient client;    private NetworkStream streamToServer;    public ServerClient()         try             client = new TcpClient();            client.Connect("localhost", 8500);      / 与服务器连接        catch (Exception ex)             Console.WriteLine(ex.Message);            return;                buffer = new byteBufferSize;        / 打印连接到的服务端信息        Console.WriteLine("Server 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.Write(temp, 0, temp.Length); / 发往服务器                        Console.WriteLine("Sent: 0", msg);        catch (Exception ex)             Console.WriteLine(ex.Message);            return;                / 发送文件 - 异步方法    public void BeginSendFile(string filePath)         ParameterizedThreadStart start =            new ParameterizedThreadStart(BeginSendFile);        start.BeginInvoke(filePath, null, null);        private void BeginSendFile(object obj)         string filePath = obj as string;        SendFile(filePath);        / 发送文件 - 同步方法    public void SendFile(string filePath)         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;        / 获取发送的协议字符串        string fileName = Path.GetFileName(filePath);        FileProtocol protocol =            new FileProtocol(FileRequestMode.Send, listeningPort, fileName);        string pro = protocol.ToString();        SendMessage(pro);       / 发送协议到服务端        / 中断,等待远程连接        TcpClient localClient = listener.AcceptTcpClient();        Console.WriteLine("Start sending file.");        NetworkStream stream = localClient.GetStream();        / 创建文件流        FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);                   byte fileBuffer = new byte1024;     / 每次传1KB        int bytesRead;        int totalBytes = 0;        / 创建获取文件发送状态的类        SendStatus status = new SendStatus(filePath);        / 将文件流转写入网络流        try             do                 Thread.Sleep(10);           / 为了更好的视觉效果,暂停10毫秒:-)                bytesRead = fs.Read(fileBuffer, 0, fileBuffer.Length);                                  stream.Write(fileBuffer, 0, bytesRead);                totalBytes += bytesRead;            / 发送了的字节数                status.PrintStatus(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()方法中可以看到,图片的位置为应用程序所在的目录,如果你跟我一样

    注意事项

    本文(C#网络编程-4.doc)为本站会员(豆****)主动上传,淘文阁 - 分享文档赚钱的网站仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知淘文阁 - 分享文档赚钱的网站(点击联系客服),我们立即给予删除!

    温馨提示:如果因为网速或其他原因下载失败请重新下载,重复下载不扣分。




    关于淘文阁 - 版权申诉 - 用户使用规则 - 积分规则 - 联系我们

    本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

    工信部备案号:黑ICP备15003705号 © 2020-2023 www.taowenge.com 淘文阁 

    收起
    展开