20170821-20170827C#工作学习周总结

来源:互联网 发布:广东省干部培训网络 编辑:程序博客网 时间:2024/06/05 06:10

Data:2017-08-24

Tips:

indexof() :string对象的一个方法,在字符串中从前向后定位字符和字符串;所有的返回值都是指在字符串的绝对位置,如为空则为- 1 ;


网络文件传输

本周主要做了一个仿服务器客户端文件传输的实例,以下以该实例作为总结:

需求:

分别实现客户端和服务器的功能(两个程序),之间可实现文件传输,考虑断点续传和文件完整性校验。

解决步骤及要考虑的问题:

不同主机之间信息是如何传递的?(网络、识别独一无二的机器)

怎么读写文件?怎么发文件?

怎么校验已接收文档的正确性?

C# 中怎么实现?

详细描述

1、不同主机之间信息是如何传递的?

毫无疑问是网络,但是这个概念就大了去了,把什么事都抛给网络算什么事?所以得解释,网络到底是什么。经典的ISO七层模型将网络划分为七层,从底向上分别是:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,而TCP/IP参考模型分为四个层次:主机到网络层、网络互连层、传输层和应用层,而我们常说的以TCP还是UDP的方式传送,就是在传输层定义的两种服务质量不同的协议,所以这里引出了:传输协议的概念。

1.1 什么是传输协议呢?

顾名思义,协议就是双方达成一致的文件或军要求执行实现的标准。比如,A和B商量好,见面的时候手里拿着红色玫瑰花,认物不认人,这就是协议。而传输协议,即指有信息交互关系发生的双方,以某一个都认同的数据处理方式去收发数据,这便是传输协议。目前在传输层上的两种服务质量不同的协议,分别是TCP(传输控制协议TCP(transmission control protocol))和UDP(用户数据报协议UDP(user datagram protocol)),自定义协议都是基于这两种协议某一种建立起来的。

TCP协议是一个面向连接的、可靠的协议。它将一台主机发出的字节流无差错地发往互联网上的其他主机。在发送端,它负责把上层传送下来的字节流分成报文段并传递给下层。在接收端,它负责把收到的报文进行重组后递交给上层。TCP协议还要处理端到端的流量控制,以避免缓慢接收的接收方没有足够的缓冲区接收发送方发送的大量数据。  
  UDP协议是一个不可靠的、无连接协议,主要适用于不需要对报文进行排序和流量控制的场合。

文件,说白了也是一种消息,所以传文件和传消息的本质是一样的。

现在协议的问题搞清楚了,那怎么建立连接呢?如何让物理上独立的两台主机发生数据关联?——套接字。

1.2 什么是套接字?

刚才我们有讲到,主机之间要建立联系,茫茫人海中,怎么找到你呢?肯定是有独一无二的标识来标记每一个独立的个体,比如,我们合法公民都有身份证号,唯一识别的。计算机,或者说,其他需要连入网络的机器要怎么识别呢?IP地址。

什么是IP地址?

Internet Protocol Address,又译为网际协议地址),缩写为IP地址(英语:IP Address),是分配给网络上使用网际协议(Internet Protocol, IP)的设备的数字标签——维基百科

什么是端口(虚拟)?

如果把IP地址比作一间房子 ,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口可以有65536(即:2^16)个之多!端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535(2^16-1)。——百度百科

而通过IP地址和端口号,便能连入一台设备。套接字——socket,便是IP + port,一个IP地址和一个端口号合称为一个套接字(Socket)。每个TCP、UDP数据段中都包含源端口和目标端口字段。一个套接字对(Socket pair)可以唯一地确定互连网络中每个TCP连接的双方(客户IP地址、客户端口号、服务器IP地址、服务器端口号)。 平常所说的FTP、TELNET、SMTP、DNS、TFTP、SNMP、RIP等都是指建立在TCP/IP基础上的应用层协议。

2、怎么读写文件?怎么发文件?

什么是文件?个人理解就是按照一定格式存取的固定大小的存储单元。怎么理解这句话呢?首先,文本文件其实是一种无格式文件,那什么是格式?就是存储和解析的方式。放在计算机最底层来讲,都是以01机器码存储的,那么怎么识别文件原来的样子呢?自然是解析方式的不同了——为什么.xlxs不能以.docx的格式打开?.mp4的格式为什么不能以.txt格式打开?就是解析方式的问题,比如说,本来应该8-2-4字节的方式读取,如果用了4-2-8的格式读取,自然是乱码错误的。指定存储格式,便是加密的过程,跟摩斯码差不多的,看你怎么选~

怎么读文件?

这里要引入流Stream的概念。什么是流呢?这其实是一个形象的叫法。流的意思就是像水流一样长长的一串没空隙的东西。Stream is actually an object from which we can read or write a sequence of bytes.

读文件也即将文件以字节数组的方式读出。

怎么发文件?

文件既然已经都出来并且以字节数组的方式存储起来了,那么问题变成了:“如何将这个字节数组传出去”了。

3、怎么校验已接收文档的正确性?——MD5、SHA1、CRC

MD5

MD5 Message-Digest Algorithm,一种被广泛使用的密码散列函数,可以产生出一个128位16字节的散列值(hash value),用于确保信息传输完整一致。服务器预先提供一个MD5校验和,用户下载完文件以后,用MD5算法计算下载文件的MD5校验和,然后通过检查这两个校验和是否一致,就能判断下载的文件是否出错。

SHA1

是一种密码散列函数,美国国家安全局设计,并由美国国家标准技术研究所(NIST)发布为联邦数据处理标准。SHA-1可以生成一个被称为消息摘要的160[字节散列值,散列值通常的呈现形式为40个数。(2017年2月23日,Google公司公告宣称他们与CWI Amsterdam合作共同创建了两个有着相同的SHA-1值但内容不同的PDF文件,这代表SHA-1算法已被正式攻破。)——维基百科

CRC

Cyclic redundancy check,通称CRC是一种根据网络数据包或电脑文件等数据产生简短固定位数校验码的一种散列函数,主要用来检测或校验数据传输或者保存后可能出现的错误。生成的数字在传输或者存储之前计算出来并且附加到数据后面,然后接收方进行检验确定数据是否发生变化。一般来说,循环冗余校验的值都是32位的整数。由于本函数易于用二进制的电脑硬件使用、容易进行数学分析并且尤其善于检测传输通道干扰引起的错误,因此获得广泛应用。——维基百科

4、C#中要怎么实现?

这便是C#网络编程。做的时候很痛苦啊,根本没有任何关于这方面书本资料,所有的知识都要靠MSDN官方文档和前人们犯过的错中总结。基本概念上文已经做了概述,接下来详细谈怎么在C#.Net下实现。

IP地址的获取

参见上一周的总结【用于IP地址的.Net类】

建立连接【TCP的实现】

这里要清楚,在客户端和服务器之间正式开始数据交换之前,它们还是有区别的,服务端要在某一个端口监听,到底誰连入到了我接收消息的端口,而此处监听,可以针对某一台具体设备(其他设备即使连入也忽略掉),也可以是任意一台接入设备,也因此有监听IP范围不同。

以MSDN TCPListener类为例:

服务端建立监听

//核心代码TcpListener server = null;// Set the TcpListener on port 13000.Int32 port = 13000;IPAddress localAddr = IPAddress.Parse("127.0.0.1");// TcpListener server = new TcpListener(port);server = new TcpListener(localAddr, port);// Start listening for client requests.server.Start();Console.Write("Waiting for a connection... ");// Perform a blocking call to accept requests.// You could also user server.AcceptSocket() here.//下面这句话一旦执行,服务端便进入等待连接的状态,这句之后的语句开始执行,说明连接成功TcpClient client = server.AcceptTcpClient();

当然,既然是服务端和客户端都需要自己完成,那么写程序的时候最好两面同时开始写,这样才知道执行到哪一句该服务器响应了,再执行到哪一句该客户端响应了等等。所以,刚才实现好了服务端的监听,接下来应该是客户端的连接。

2010102414143479

//服务端主机IPstring hostIP = "127.0.0.1";//先建立IPAddress物件,IP為欲連線主機之IPIPAddress ipa = IPAddress.Parse(hostIP);//建立IPEndPointIPEndPoint ipe = new IPEndPoint(ipa, 1234);//先建立一个TcpClient;TcpClient tcpClient = new TcpClient();//开始连线Console.WriteLine("主机IP=" + ipa.ToString());Console.WriteLine("连线至主机中...\n");tcpClient.Connect(ipe);   if (tcpClient.Connected)   {       Console.WriteLine("连线成功!");   }

截至目前为止,客户端和服务端之间的通道已经打开,之后就可以传数据了!

传数据的方式有很多,我选择了NetworkStream【同步】,是它帮我成功传输了第一组数据,而后来的故事,却痛不欲生,可能还是对这个类理解不到位吧,但对于现在来讲,实现功能很重要!

首先我们从NetworkStream类讲起,引用MSDN的描述:

The NetworkStream class provides methods for sending and receiving data over Stream sockets in blocking mode.

Use the Write and Read methods for simple single thread synchronous blocking I/O. If you want to process your I/O using separate threads, consider using the BeginWrite and EndWrite methods, or the BeginRead and EndRead methods for communication.

Read and write operations can be performed simultaneously on an instance of the NetworkStream class without the need for synchronization. As long as there is one unique thread for the write operations and one unique thread for the read operations, there will be no cross-interference between read and write threads and no synchronization is required.

这三段话是幸福和痛苦的来源。可以看到,NetworkStream以异步阻塞模式发送/接收数据,关于同步异步及阻塞非阻塞模式,可以参考怎样理解阻塞非阻塞与同步异步的区别?

也就是说,一个线程可以一直写,另外一个线程可以一直读(当然,如果流里面没有数据自然不能读了),它们没有交叉打断的功能,只有在读的一方没有数据可读了,才会等待写的一方往流里面写数据。

而我们的数据帧是以帧头+数据的方式传送的,所以绝不能允许一直往流里边写数据的情况,会造成取数据的紊乱,解析结果乱码的错误。因此,应该手动加入控制,客户端读完一句后再让服务端往里边写,手动同步该过程。注意区分,同时和同步是针对不同的对象描述的,二者不等价。

可以在服务端/客户端这么写:

//服务端//还有数据可读的循环while (preData.Left > 0)                {                    clientStream.Read(structBytes, 0, client.ReceiveBufferSize);                    cmdFromClient = (Mode.ClientCmd)Convertor.BytesToStruct(structBytes, clientCmd.GetType());                    //客户端准备好接收数据                    if(cmdFromClient.IsReady)                    {                    }                 }
     while (true)            {                //把当前收到的字节数组分割为结构体和剩下的数据部分,而结构体的大小是知道的                //覆盖填写,先测试不支持断点的,即本地不记录当前下载位置,在V0.2改进                //收到的每一段均存在receiveBytes数组中                byte[] receiveBytes = new byte[client.ReceiveBufferSize];                //给服务器发通知,可以开始写下一波(第一波)了                clientCmd.IsReady = true;                clientBytes = Convertor.StructToBytes(clientCmd);                client.GetStream().Write(clientBytes, 0, clientBytes.Length);                int receiveBytesLength = netStream.Read(receiveBytes, 0, client.ReceiveBufferSize);                //下面这三行和上面的那三行实现与服务端同步                clientCmd.IsReady = true;                clientBytes = Convertor.StructToBytes(clientCmd);                client.GetStream().Write(clientBytes, 0, clientBytes.Length);                byte[] realReceiveBytes = receiveBytes.Skip(0).Take(receiveBytesLength).ToArray();                //取前一部分转换成结构体                byte[] cmdStructureBytes = Extension.SubArrayDeepClone(realReceiveBytes, 0, 16);                //将字节流转换为结构体                Mode.PreData preData = new Mode.PreData();                //DataCmdFromClient为命令结构体                Mode.PreData DataCmdFromServer = (Mode.PreData)Convertor.BytesToStruct(cmdStructureBytes, preData.GetType());                //1 去后面的部分转换为字节流                //byte[] dataBytes = realReceiveBytes.Skip(16).Take(DataCmdFromServer.Period + 16).ToArray();                //byte[] dataBytes = new byte[1000];                byte[] dataBytes = Extension.SubArrayDeepClone(realReceiveBytes, 16 , DataCmdFromServer.Period);                //运行时校验                string tmp_2 = Encoding.UTF8.GetString(dataBytes);                //2 字节流转文件流                fs.Write(dataBytes, 0, dataBytes.Length);                //解析命令——Predata,包括本次传送的字节数和本次传输完成之后剩余的字节数                //若果为0,则break掉                if (DataCmdFromServer.Left == 0)                {                    fs.Close();                    break;                }            }

至此,单线程同步传输程序完结!

原创粉丝点击