精华内容
下载资源
问答
  • SPI协议代码

    千次阅读 多人点赞 2020-06-01 09:29:17
    软件模拟SPI程序代码 概述:  通过两个MCU(STM32F103)来模拟SPI的主从机,完成主机发送从机接收,便于理解SPI协议。 SPI协议简介 ●SPI接口介绍  SCK:时钟信号,由主设备产生,所以主设备SCK信号为输出模式,从...

    软件模拟SPI程序代码


    概述:
       通过两个MCU(STM32F103)来模拟SPI的主从机,完成主机发送从机接收,便于理解SPI协议。

    SPI协议简介

    ●SPI接口介绍

    SCK:时钟信号,由主设备产生,所以主设备SCK信号为输出模式,从设备的SCK信号为输入模式。
      CS:使能信号,由主设备控制从设备,,所以主设备CS信号为输出模式,从设备的CS信号为输入模式。
      MOSI:主设备数据输出,从设备数据输入,所以主设备MOSI信号为输出模式,从设备的MOSI信号为输入模式。
      MISO:主设备数据输入,从设备数据输出,所以主设备MISO信号为输入模式,从设备的MISO信号为输出模式。
      
    SPI接口连接图
    在这里插入图片描述
      注意:MOSI和MISO不能交叉连接(可以把主从机理解为一个整体系统,MOSI为系统主机发送从机接收的数据线,MISO为主机接收从机发送的数据线)。

    ●SPI数据传输方向

    SPI作为全双工的的串行通信协议,数据传输时高位在前,低位在后。主机和从机公用由主机产生的SCK信号,所以在每个时钟周期内主机和从机有1bit的数据交换(因为MOSI和MISO数据线上的数据都是在时钟的边沿处被采样)。
      如下图:
     在这里插入图片描述
     SPI协议规定数据采样是在SCK的上升沿或下降沿时刻(由SPI模式决定,下面会说到),观察上图,在SCK的边沿处(上升沿或下降沿),主机会在MISO数据线上采样(接收来从机的数据),从机会在MOSI数据线上采样(接收来自主机的数据),所以每个时钟周期中会有一bit的数据交换。
     SPI数据交换

    ●SPI传输模式

    SPI总线传输一共有4种模式,这4种模式分别由时钟极性(CPOL)和时钟相位(CPHA)来定义。
       在这里插入图片描述

    CPOL CPHA
    规定了SCK时钟信号空闲状态的电平 规定了数据是在SCK时钟的上升沿还是下降沿被采样
    ----------- ------------------------------------
    模式0:CPOL=0,CPHA =0 SCK空闲为低电平,数据在SCK的上升沿被采样(提取数据)
    模式1:CPOL=0,CPHA =1 SCK空闲为低电平,数据在SCK的下降沿被采样(提取数据)
    模式2:CPOL=1,CPHA =0 SCK空闲为高电平,数据在SCK的下降沿被采样(提取数据)
    模式3:CPOL=1,CPHA =1 SCK空闲为高电平,数据在SCK的上升沿被采样(提取数据)

    以模式0为例:
    SCK空闲为低电平,数据在SCK的上升沿被采样(提取数据),在SCK的下降沿切换数据线的数据。
    在这里插入图片描述
      ◐在时钟的第1个上升沿(游标1处)(采样点)
      MOSI上数据为1,则在此边沿从机采样(提取)数据为1,采样点在MOSI数据线的中间。
      MISO上数据为0,则在此边沿主机采样(提取)数据为0,采样点在MISO数据线的中间。
      ◐在时钟的第1个下降沿(游标2处)(切换点)
      MOSI上数据由1切换为0,,数据在时钟下降沿时切换数据。
      MISO上数据由0切换为1,,数据在时钟下降沿时切换数据。
     ◐在时钟的第2~8个上升沿(采样点),主机在MISO上采样数据,从机在MOSI上采样数据。
     ◐在时钟的第2~8个下降沿(切换点),主机在MISO上切换数据,从机在MOSI上切换数据。

    通过两个单片机模拟SPI来加深理解

    利用了STM32F103VET6和STM32F103C8T6(身边只有这两块了)两款MUC。

    ※硬件连接方式

    主机- STM32F103VET6 从机-STM32F103C8T6
    (主机产生) SCK→ →SCK(从机被动)
    (主机产生) CS→ →CS (从机被动)
    (主机发送)MOSI → →MOSI (从机接收)
    (主机接收) MISO ← ←MISO (从机发送)

    ●注意:MOSI连接MOSI,MISO连接MISO(不能像串口那样交叉连接)。

    ✯SPI模式

    采用模式0(CPOL=0,CPHA =0):SCK空闲为低电平,数据在SCK的上升沿被采样(提取数据) ,在SCK的下降沿被切换。

    ✯程序思路

    ★主机拉低CS开始传输数据,在SCK上升沿之前保持MOSI上有稳定的数据输出(因为从机要在SCK的上升沿去采样(提取数据),所以主机在SCK上升沿之前要完成发送数据的放置)。
      ★从机在CS拉低后(CS有下降沿)开始数据的接收(在SCK的上升沿采集MOSI上的数据)。

    ✯主机C代码+波形

    /*SPI发送函数*/
    //时钟的上升沿采样数据,下降沿切换数据   先发送高位
    void SPI_Write(uint8_t Data)
    {
    	uint8_t i=0;
    	CS_L;	//片选拉低开始传输数据
    	/*循环8次,发送8bit数据*/
    	for(i=0;i<8;i++)	
    	{
    		
    		/*切换数据*/
    		if(Data&0x80)//通过8次循环移位,将一个字节的数据,由高到低一位一位的放置到数据线上
    		{
    			MOSI_H;
    		}
    		else
    		{
    			MOSI_L;
    		}
    		SCK_L;//产生下降沿,准备切换数据
    		delay_us(1);//(可忽略,这里是因为接收时此单片机外部中断上升沿触发有时延,SCK太快无法准确提取数据)
    		SCK_H;	//产生上升沿(从机在此上升沿时采集数据)
    		Data <<= 1;
    		
    	}
    	MOSI_L;
    	SCK_L;
    	CS_H;	//片选拉高等待下次数据传输
    }
    int main()
    {
    	int i=0,j=0;
    	SysTick_init();
    	SPI_GPIO_Config();
    	while(1)
    	{
    		SPI_Write(0xA5);
    	}
    }
    

    ●注意:上面1us的延时[delay_us(1)]此处可以忽略,这里是因为接收时此单片机外部中断上升沿触发有时延,SCK太快无法准确提取数据,利用其他方式解析从机数据的请忽略。(详细了解请参考博文:STM32外部中断边沿触发存在延时问题)。
      ★代码解析:要了解代码思路,就要时刻记得我们采用SPI的是模式0(SCK空闲为低电平,数据在SCK的上升沿被采样(提取数据),下降沿被切换 ),所以1Byte数据放置完毕后,SCK要拉低,CS要拉高,MOSI要恢复默认电平,但是每Bit数据在SCK拉低时被放置到MOSI数据线(因为SCK上升沿前要确保稳定的数据(因为接收数据最好是在数据的中间采样),这样从机才可以在上升沿采样到正确的数据)。所谓放置数据,其实就是在每次SCK拉高之前对MOSI引脚赋值。比如我们发送的数据为0xA5(1010_0101)。
    在这里插入图片描述
    在上图中标号2处的下降沿处切换数据,上升沿之前保证了MOSI上(游标1)处有稳定的1bit数据(1),随后的7个上升沿也一样分析。
    ✯主机产生的波形
    在这里插入图片描述)
      ★波形解析:通道1数据:SCK
            通道2数据:MOSI
            通道3数据:CS
       在上图中可以观察到整个数据的传输是在片选CS为低的时刻进行的。在SCK下降沿时主机对MOSI数据线上的数据进行了切换,在SCK上升沿之前完成了1bit数据的发送。完成1Byte数据的发送后,SC置高,CS置高,MOSI置低,为下一帧数据做准备。

    ✯从机C代码+波形

    /*SPI接收数据*/
    uint8_t SPI_Read()
    {
    	/*CS下降沿*/
    	if(CS_Trigger_Falling == 1)
    	{
    		CS_Trigger_Falling = 0;
    		/*SCK上升沿*/
    		for(i=0;i<8;i++)
    		{
    			while(SCK_Trigger_Rising != 1);//等待上升沿
    			SCK_Trigger_Rising = 0;
    			Data_Rec<<=1;
    			if(MOSI_State)//在SCK上升沿时提取数据
    			{		
    				Data_Rec ++;	
    				Rec_Data1[i] = 1;	
    			}
    			else{}
    		}
    	}
    	return Data_Rec;
    }
    int main()
    {
    	SysTick_init();
    	SPI_GPIO_Config();
    	EXTI_PB1_Config();
    	EXTI_PA2_Config();
    	while(1)
    	{
    	Get_Data = SPI_Read();
    
    	}
    }
    /*外部中断0中断*/
    void EXTI1_IRQHandler(void)//中断服务函数
    {
    	if(EXTI_GetITStatus(EXTI_Line1) != RESET )//reset为清零(!=reset等价于IT=1)
    	{
    	SCK_Trigger_Rising = 1;
    	EXTI_ClearITPendingBit(EXTI_Line1);
    	}
    
    
    }
    void EXTI2_IRQHandler(void)//中断服务函数
    {
    	if(EXTI_GetITStatus(EXTI_Line2) != RESET )//reset为清零(!=reset等价于IT=1)
    	{
    	CS_Trigger_Falling = 1;
    	EXTI_ClearITPendingBit(EXTI_Line2);
    	
    	}
    
    
    }
    

    ★代码解析:从机采用了外部中断的方式去采集CS的下降沿和SCK的上升沿(从机以CS下降沿为数据接收的开始,以SCK的上升沿作为每bit数据的采样点)。(★★★有好的方法欢迎指导)
      ●CS下降沿提取波形:图中紫色信号为CS下降沿点。
      在这里插入图片描述
      ●SCK上升沿提取波形:下图中紫色信号为SCK上升沿的提取(即从机接收MOSI数据线上的采样点)。
    在这里插入图片描述
      ●提取数据(数据采样):紫色信号处(采样点)MOSI上的数据即为从机接收到的数据,仔细观察采样点几乎在稳定数据的中间点(因为之前所说的边沿检测存在延迟,所以采样点略微偏移中心点,参考链接STM32外部中断边沿触发存在延时问题)。
    在这里插入图片描述
    ✯从机接收数据结果:0xA5
    在这里插入图片描述

    如有兴趣可查看类似的
    IIC协议详解

    ★★★如有错误欢迎指导。

    展开全文
  • 代码开源协议

    千次阅读 2012-02-18 13:42:51
    代码开源有多种协议,最出名的必然是GPL。本文先介绍GPL,然后介绍一下BSD, Apache Licence2.0, LGPL, MIT, MPL. 另外还有QPL, QNCL, JABBER, COMMON, IBM等开源协议,不一一介绍。 GPL: GNU Public License ...
    代码开源有多种协议,最出名的必然是GPL。本文先介绍GPL,然后介绍一下BSD, Apache Licence2.0, LGPL, MIT, MPL. 另外还有QPL, QNCL, JABBER, COMMON, IBM等开源协议,不一一介绍。
    • GPL: GNU Public License
    摘要:必须公开源码;如果使用了GPL协议的源码,该工程的所有源码均需要遵守GPL协议。
    GPL的出发点是代码的开源/免费使用和引用/修改/衍生代码的开源/免费使用,但不允许修改后和衍生的代码做为闭源的商业软件发布和销售。
    GPL保证你有发布自由软件的自由(如果你愿意,你可以对此项服务收取一定的费用);保证你能收到源程序或者在你需要时能得到它;保证你能修改软件或将它的一部分用于新的自由软件;而且还保证你知道你能做这些事情。
    GPL协议还具有传染性,即:只要这种修改文本在整体上或者其某个部分来源于遵循GPL的程序,该修改文本的整体就必须按照GPL流通,不仅该修改文本的源码必须向社会公开,而且对于这种修改文本的流通不准许附加修改者自己作出的限制。因此,一项遵循GPL流通的程序不能同非自由的软件合并。
    GPL协议最主要的几个原则:
    1、确保软件自始至终都以开放源代码形式发布,保护开发成果不被窃取用作商业发售。任何一套软件,只要其中使用了受GPL协议保护的第三方软件的源程序,并向非开发人员发布时,软件本身也就自动成为受GPL保护并且约束的实体。也就是说,此时它必须开放源代码。
    2、GPL大致就是一个左侧版权(Copyleft)的体现。你可以去掉所有原作的版权信息,只要你保持开源,并且随源代码、二进制版附上GPL的许可证就行,让后人可以很明确地得知此软件的授权信息。GPL精髓就是,只要使软件在完整开源的情况下,尽可能使使用者得到自由发挥的空间,使软件得到更快更好的发展。
    3、无论软件以何种形式发布,都必须同时附上源代码。
    4、开发或维护遵循GPL协议开发的软件的公司或个人,可以对使用者收取一定的服务费用。但必须无偿提供软件的完整源代码,不得将源代码与服务做捆绑或任何变相捆绑销售。
    • BSD:Berkeley Software Distribution license
    摘要:有限的三个限制条件下,为所欲为。
    BSD开源协议是一个给于使用者很大自由的协议。其允许自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有软件再发布。
    当你发布使用了BSD协议的代码,或者以BSD协议代码为基础做二次开发自己的产品时,需要满足三个条件:
    1. 如果再发布的产品中包含源代码,则在源代码中必须带有原来代码中的BSD协议。
    2. 如果再发布的只是二进制类库/软件,则需要在类库/软件的文档和版权声明中包含原来代码中的BSD协议。
    3. 不可以用开源代码的作者/机构名字和原来产品的名字做市场推广。
    • Apache Licene 2.0
    摘要:为所欲为
    Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布(作为开源或商业软件)。需要满足的条件也和BSD类似:
    1. 需要给代码的用户一份Apache Licence
    2. 如果你修改了代码,需要在被修改的文件中说明。
    3. 在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议,商标,专利声明和其他原来作者规定需要包含的说明。
    4. 如果再发布的产品中包含一个Notice文件,则在Notice文件中需要带有Apache Licence。你可以在Notice中增加自己的许可,但不可以表现为对Apache Licence构成更改。
    5. Apache Licence也是对商业应用友好的许可。使用者也可以在需要的时候修改代码来满足需要并作为开源或商业产品发布/销售。
    • LGPL
    摘要:isModified?"you dont need to open source and can sell":LGPL;
    LGPL是GPL为类库使用设计的开源协议。LGPL允许商业软件通过类库引用方式使用LGPL类库而不需要开源商业软件的代码。这使得采用LGPL协议的开源代码可以被商业软件作为类库医用并发布和销售。如果修改了LGPL协议的代码或者衍生,则所有修改的代码设计修改部分的额外代码和衍生的代码必须采用LGPL协议。
    • MIT
    摘要:作者只想保留版权。
    无论你是以二进制还是源代码发布包含别人代码的作品,必须包含原许可协议的声明。
    • MPL
    摘要:免费重发布、免费修改,但要求修改后的代码版权归软件的发起者
    Mozilla Public License, MPL License,允许免费重发布、免费修改,但要求修改后的代码版权归软件的发起者。这种授权维护了商业软件的利益,它要求基于这种软件得修改无偿贡献版权给该软件。这样,围绕该软件得所有代码得版权都集中在发起开发人得手中。但MPL是允许修改,无偿使用得。MPL软件对链接没有要求。

    展开全文
  • java代码实现FTP协议

    千次阅读 2020-02-29 17:44:05
    前几节我们完成了ftp协议的主要讲解,同时使用wireshark抓包了解ftp数据协议包的特征,本节我们使用代码完成ftp协议代码将模仿ftp客户端,它与服务器建立连接后,使用用户名和密码登陆服务器,然后获得服务器的...

    前几节我们完成了ftp协议的主要讲解,同时使用wireshark抓包了解ftp数据协议包的特征,本节我们使用代码完成ftp协议,代码将模仿ftp客户端,它与服务器建立连接后,使用用户名和密码登陆服务器,然后获得服务器的当前目录内容,继而通过数据连接获取服务器推送目录具体信息,最后客户端关闭,下面我们看看具体的代码实现,首先在工程目录下新建名为FTPClient的类,相关实现如下:

    package Application;
    
    import java.net.InetAddress;
    
    import utils.IFTPDataReceiver;
    import utils.ITCPHandler;
    
    public class FTPClient implements ITCPHandler, IFTPDataReceiver{
    	private  TCPThreeHandShakes  tcp_socket = null;  
    	private  int data_port = 0;
    	private FTPDataReceiver data_receiver = null;
    	private String server_ip;
    	@Override
    	public void connect_notify(boolean connect_res) {
    		 if (connect_res == true) {
    			 System.out.println("connect ftp server ok!");
    		 }
    	}
    
    	@Override
    	public void send_notify(boolean send_res, byte[] packet_send) {
    		// TODO Auto-generated method stub
    		
    	}
    
    	@Override
    	public void recv_notify(byte[] packet_recv) {
    		try {
    			String server_return = new String(packet_recv, "ASCII");
    			System.out.println("receive info from ftp server: " +  server_return);
    			String return_code = server_return.substring(0, 3);
    			String return_str = server_return.substring(3);
    			if (return_code.equals("220")) {
    				System.out.println("receive code 220: " + return_str);
    				send_command("USER chenyi\r\n");
    			}
    			if (return_code.equals("331")) {
    				System.out.println("receive code 331: " + return_str);
    				//服务器请求用户名密码
    				send_command("PASS 1111\r\n");
    			}
    			if (return_code.equals("230")) {
    				System.out.println("receive code 230: " + return_str);
    				//用户登录成功
    				send_command("PWD\r\n"); //获取服务器文件目录
    			}
    			if (return_code.equals("257")) {
    				System.out.println("receive code 257: " + return_str);
    				send_command("PASV\r\n");
    			}
    			if (return_code.equals("227")) {
    				System.out.println("receive code 227: " + return_str);
    				int ip_port_index = return_str.indexOf("("); 
    				String port_str = return_str.substring(ip_port_index);
    				int ip_count = 4; //经过4个逗号就能找到端口
    				while (ip_count > 0) {
    					int idx = port_str.indexOf(',');
    					ip_count--;
    					port_str = port_str.substring(idx + 1);
    				}
    				int idx = port_str.indexOf(',');
    				String p1 = port_str.substring(0, idx);
    				port_str = port_str.substring(idx + 1);
    				idx = port_str.indexOf(')');
    				String p2 = port_str.substring(0, idx);
    				int port = Integer.parseInt(p1) * 256 + Integer.parseInt(p2);
    				System.out.println("get data port : " + port);
    				data_port = port;
    				send_command("TYPE A\r\n"); //通知服务器以ASCII的方式传输数据
    			}
    			if  (return_code.equals("200")) { //服务器同意使用ASCII方式传递数据
    				System.out.println("receive code 200: " + return_str);
    				send_command("LIST\r\n");//要求服务器传输当前目录下的文件信息
    				data_receiver = new FTPDataReceiver(server_ip, data_port, this);
    				data_receiver.get_data();
    			}
    			if (return_code.equals("150")) { //服务器通知数据发送完毕
    				System.out.println("receive code 150: " + return_str);
    				tcp_socket.tcp_close();
    			}
    			//tcp_socket.tcp_close();
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		
    	}
    	
    	private void send_command(String command) {
    		try {
    			tcp_socket.tcp_send(command.getBytes());
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    
    	@Override
    	public void connect_close_notify(boolean close_res) {
    		// TODO Auto-generated method stub
    		
    	}
    	
    	public void run() {
    		 try {
    			InetAddress ip = InetAddress.getByName("192.168.2.127"); //连接ftp服务器
    			server_ip = "192.168.2.127";
    			short port = 20000;
    			tcp_socket = new TCPThreeHandShakes(ip.getAddress(), port, this);
    			tcp_socket.tcp_connect();
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    
    	@Override
    	public void receive_ftp_data(byte[] data) {
    		System.out.println("Successfuly get ftp data");
    		String ftp_data = new String(data);
    		System.out.println("content of ftp_data: " + ftp_data);
    	}
    
    }
    

    代码实现中recv_notify用来解读服务器返回的信息,前三个字符是服务器的返回码,后面字符串是对返回码的解释。这里值得关注的是当客户端向服务器发送PSAV命令后,服务器返回码为227,其中的字符串包含了用于数据传输的端口,代码需要解读返回字符串,然后计算出端口,并像服务器发送TYPE A命令告诉服务器通过ASCII模式传输数据。最后启动新的tcp连接去接收数据。一旦在数据端口与服务器实现三次握手后,服务器会主动给我们推送数据。在完成PSAV命令后,代码向服务器发送LIST命令,要求服务器给出当前目录下的所有文件信息,然后代码创建FTPDataReceiver实例,该对象负责通过数据端口与服务器连接,同时等待服务器推送数据,接收完数据后他把接收到的内容推送给FTPClient对象,我们看FTPDataReceiver的实现:

    package Application;
    
    import java.net.InetAddress;
    import java.nio.ByteBuffer;
    
    import utils.IFTPDataReceiver;
    import utils.ITCPHandler;
    
    public class FTPDataReceiver implements ITCPHandler{
    	private int data_port = 0;
    	private IFTPDataReceiver data_receiver = null;
    	private  TCPThreeHandShakes  tcp_socket = null;
    	private String server_ip = "";
    	private byte[] data_buffer = new byte[4096];
    	private ByteBuffer byteBuffer = null;
    	public FTPDataReceiver(String ip, int port, IFTPDataReceiver receiver) {
    		this.data_port = port;
    		this.data_receiver = receiver;
    		this.server_ip = ip;
    		byteBuffer = ByteBuffer.wrap(data_buffer);
    	}
    	
    	
        public void get_data() {
        	 try {
     			InetAddress ip = InetAddress.getByName(server_ip); //连接ftp服务器
     			tcp_socket = new TCPThreeHandShakes(ip.getAddress(), (short)data_port, this);
     			tcp_socket.tcp_connect();
     		} catch (Exception e) {
     			// TODO Auto-generated catch block
     			e.printStackTrace();
     		}	
        }
        
    	@Override
    	public void connect_notify(boolean connect_res) {
    		if (connect_res) {
    			System.out.println("ftp data connection ok");
    		} else {
    			System.out.println("ftp data connection fail");
    		}
    		
    	}
    
    	@Override
    	public void send_notify(boolean send_res, byte[] packet_send) {
    		// TODO Auto-generated method stub
    		
    	}
    
    	@Override
    	public void recv_notify(byte[] packet_recv) {
    		System.out.println("ftp receiving data");
    		byteBuffer.put(packet_recv);
    	}
    
    	@Override
    	public void connect_close_notify(boolean close_res) {
    		try {
    			tcp_socket.tcp_close();
    			data_receiver.receive_ftp_data(byteBuffer.array());
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		
    	}
    
    }
    

    完成上面代码后运行,我们可以得到如下结果:

    屏幕快照 2020-02-29 下午5.31.46.png

    从图中可以看到,我们代码成功接收了ftp服务器推送的目录信息。
    更详细的讲解和代码调试演示过程,请点击链接

    更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
    这里写图片描述

    展开全文
  • java实现HTTP协议:POST协议代码实现

    千次阅读 2020-06-16 10:26:59
    本节我们使用代码实现HTTP的POST协议流程。任何HTTP服务器都会支持客户端将文件上传,该功能的实现往往要走POST协议流程。为了使用代码实现该协议,首先需要一个目的HTTP服务器,我选择iPhone版本的福昕pdf阅读器,...

    本节我们使用代码实现HTTP的POST协议流程。任何HTTP服务器都会支持客户端将文件上传,该功能的实现往往要走POST协议流程。为了使用代码实现该协议,首先需要一个目的HTTP服务器,我选择iPhone版本的福昕pdf阅读器,它支持通过POST协议将文件从电脑上传到手机,在打开其上传功能后,在电脑输入相应网址就能看到如下画面:

    屏幕快照 2020-06-16 上午9.21.55.png

    点击”选择文件“按钮,然后选择要上传的文件,最后点击"upload"按钮,那么浏览器就会执行POST协议实现数据上传。我们先通过抓包的方式了解POST协议数据包的结构,在执行文件上传并抓包后,wireshark抓到的数据包如下:

    屏幕快照 2020-06-16 上午9.52.18.png

    在简单情况下,post流程只有两次数据包发送,一次是POST,它是客户端将数据通过HTTP数据包发送给服务器,另一个是服务器接收数据后将结果回复给客户端,我们看看POST数据包的内容:

    屏幕快照 2020-06-16 上午9.54.48.png

    POST数据包分为两部分,第一部分涉及HTTP协议控制,也就是上图中的第一部分,第一行通过POST关键字指明数据包目的,并通过包头字段的形式填写了一系列用于数据传输和控制的信息,这些包头在前面章节都有描述过。第二部分就是MIME Multipart Media Encapsulation部分,它用于封装上传数据,这部分需要详细解读。

    第一个需要了解的是Boundary,这个字符串由客户端自己生成,它的作用是将上传数据分隔开,每次遇到该字符串开始的地方,服务器就知道那是客户端要提交的数据内容,我将其内容展开以便读者查看:

    屏幕快照 2020-06-16 上午10.05.19.png

    可以看到First Boundary处是给定的Boundary字符串,它下面对应要上传给服务器的第一部分信息,Content-Type用于指定数据的格式,从中可以看到我上传的是一个文本文件,接下来就是文本内容,如果文本是字符串,它则直接显示,如果内容是二进制数据,那么它包含的就是数据对应的数字内容,从上面可以看到,文本中只包含了一行内容,那就是”this is only one line",这部分就对应要上传的数据内容。接着是第二部分要上传的内容,所以格式上再次以Boudary对应的字符串作为起始,关键字Content-Disposition用于说明数据的展现形式,数据内容应该是直接在网页中展示,还是作为附件呈现,在客户端以POST形式向服务器提交数据时,它的取值只能是form-data.

    第二部分数据没有通过字段Content-Type来指明,因此统一当做二进制数据,因此Data字段对应的就是一系列数字,实际上这些数字其实对应的是字符串"Upload",因此第二部分数据用来告诉服务器,当前数据是通过点击了"Upload"按钮后上传的,笔者在模拟该数据包时,如果不包含这部分数据,手机上的福昕pdf应用会奔溃掉。接下来我们看看如何使用代码实现简单的POST功能,首先要实现的是MIME这部分数据的封装:

    package Application;
    
    import java.util.Arrays;
    
    public class MIMETextPlainMediaEcnapusulation { //该类只封装简单的文本数据
       private String boundary_string = "----WebFormBoundaryAAABBBCCCDDD"; //用于传递数据的分割标志字符串
       private String post_file_name = "";
       private String content_part_header = "Content-Disposition: form-data; name=\"button\"; filename=\"";
       private String content_type = "Content-Type: text/plain\r\n\r\n";
       private String content_part = "";
       private String last_boundary = "\r\n--" + boundary_string + "--\r\n";
      
       public MIMETextPlainMediaEcnapusulation(String file_name) {
    	   this.post_file_name = file_name; //要上传的文件名
    	   
    	   
       }
       
       private void add_content_header() { //添加用于分割不同数据部分的boundary
    	   this.content_part += "--";
    	   this.content_part += boundary_string;
    	   this.content_part += "\r\n";
    	   this.content_part += content_part_header;
    	   this.content_part += post_file_name;
    	   this.content_part += "\r\n";
    	   this.content_part += content_type;
       }
       
       public void add_content(String content) { //调用该接口设置要上传给服务器的内容
    	   this.add_content_header();
    	   
    	   this.content_part += content;
    	   this.content_part += "\r\n";
    	   this.content_part += "--";
    	   this.content_part += boundary_string;
    	   this.content_part += "\r\n";
       }
       
       private void add_last_content_part() { //模拟最后upload数据部分,这部分与数据传输无关,但与服务器对接收数据的解读有关
    	   String last_content_disposition = "Content-Disposition: form-data; name=\"button\"\r\n\r\n";
    	   content_part += last_content_disposition;
    	   String content = "Upload\r\n";
    	   content_part += content;
    	   content_part += last_boundary;
       }
       
       public String get_mime_part() {
    	   this.add_last_content_part();
    	   return  content_part;
       }
       
       public String get_boundary_string() {
    	   return boundary_string;
       }
       
    }
    
    

    接下来实现的是数据的传输功能:

    package Application;
    
    import java.net.InetAddress;
    
    import utils.ITCPHandler;
    
    public class HTTPPostClient implements ITCPHandler {
    	private  TCPThreeHandShakes  tcp_socket = null;  
    	private HTTPEncoder httpEncoder = new HTTPEncoder();
        private String user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 1-14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/547.36";
        private String accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3q=0.9";
        private String accept_encoding = "gzip, deflate";
        private String accept_language = "zh-CN,zh;q=0.9,en;q=0.8";
        private String cache_control = "max-age=0";
        private String connection = "close";
        private static int HTTP_OK = 200;
        private String file_name = "my_test.txt";
        private String content_type = "multipart/form-data; boundary=";
        private MIMETextPlainMediaEcnapusulation mime_encoded = new MIMETextPlainMediaEcnapusulation(file_name);
        
        private void send_content() throws Exception {//设置http请求数据的头部信息
        	mime_encoded.add_content("This is test content for line1"); //文档里面的内容
        	String send_content = mime_encoded.get_mime_part();
        	String content_length = Integer.toString(send_content.length());
        	
        	httpEncoder.set_method(HTTPEncoder.HTTP_METHOD.HTTP_POST, "/");
    	    httpEncoder.set_header("Host","192.168.2.127:8888");
    		httpEncoder.set_header("Connection", connection);
    		httpEncoder.set_header("User-Agent", user_agent);
    		httpEncoder.set_header("Content-Length", content_length);
    		httpEncoder.set_header("Accept", accept);
    		httpEncoder.set_header("Accept-Encoding", accept_encoding);
    		httpEncoder.set_header("Accept-Language", accept_language);
    		httpEncoder.set_header("Cache-Control", cache_control);
    		httpEncoder.set_header("Content-type", content_type + mime_encoded.get_boundary_string());
    		httpEncoder.set_header("Upgrade-Insecure-Requests", "1");
    		httpEncoder.set_header("Origin",  "http://192.168.2.127:8888");
    		httpEncoder.set_header("Referer", "http://192.168.2.127:8888/");
    		 
    		String http_content = httpEncoder.get_http_content();
    		http_content += send_content;
    		System.out.println(http_content);
    		byte[] send_content_bytes = http_content.getBytes();
    		tcp_socket.tcp_send(send_content_bytes);
       }
        
    	@Override
    	public void connect_notify(boolean connect_res) {
    		if (connect_res == true) {
    			 System.out.println("connect http server ok!");
    			 try {
    					send_content();
    				} catch (Exception e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    		 } 
    		else {
    			System.out.println("connect http server fail!");
    		}	
    	}
    
    	@Override
    	public void send_notify(boolean send_res, byte[] packet_send) {
    		if (send_res) {
    			System.out.println("send request to http server!");
    		}
    	}
    	
    	private void close_connection() {
    		try {
    			tcp_socket.tcp_close();
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    
    	@Override
    	public void recv_notify(byte[] packet_recv) {
    		String content = new String(packet_recv);
    		int code = httpEncoder.get_return_code(content);
    		if  (code != HTTP_OK) {
    			System.out.println("http return error: " + code);
    		} else {
    			System.out.println("Http return 200 OK! Post Success!");
    		}
    		
    		close_connection();
    	}
    
    	@Override
    	public void connect_close_notify(boolean close_res) {
    		if (close_res) {
    			System.out.println("Close connection with http server!");
    		}
    	}
    
    	public void run() {
    		 try {
    			InetAddress ip = InetAddress.getByName("192.168.2.127"); //连接ftp服务器
    			short port = 8888;
    			tcp_socket = new TCPThreeHandShakes(ip.getAddress(), port, this);
    			tcp_socket.tcp_connect();
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    
    }
    
    

    在代码中构造了一个虚拟的"my_test.txt"文件上传给服务器,文件里面的内容就是代码设置的字符串"This is test content for line1",执行上面代码后,就相当于前面展示的通过按钮上传给定文件的流程,于是就会在iPhone上的福昕App中看到代码所虚拟的my-test文件,打开该文件就可以看到代码所虚拟的内容字符串,更详细的讲解和代码演示请参看视频。

    更详细的讲解和代码调试演示过程,请点击链接

    更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
    这里写图片描述

    展开全文
  • 1-实验结论: ...对比标准Ymodem协议,ST官方IAP代码中YModem协议少了以下部分: 发送端(secureCRT) 接收端(开发板) EOT----&gt; NAK----&lt; 2-YModem YModem协议是由XModem协议...
  • NTP协议代码实现

    万次阅读 2018-01-23 17:17:38
    NTP客户端的代码实现
  • 首先,QUIC为 传输层 协议,与TCP、UDP、SCTP同级。所以肯定会 在一定范围内 同现有的传输层协议构成竞争关系。二. 为什么不用TCPTCP由于基于操作系统内核实现,发展速度极慢,现有的TCP Fast Open实现等等虽然早已...
  • 滑动窗口协议C++代码

    热门讨论 2007-12-09 23:45:39
    滑动窗口协议C++代码
  • IIC协议代码实现

    千次阅读 2019-07-24 09:53:46
    1. IIC协议概述 IIC总线(Inter-Integrated Circuit)即集成电路总线,是PHILIPS公司设计出来的一种简单、双向、二线制、同步串行总线。IIC总线是一个多向控制总线,多个器件(从机)可以同时挂载到一个主机控制的...
  • leach协议matlab代码

    千次阅读 2019-04-23 21:13:56
    `LEACH协议 clear;%清除内存变量 xm=100;%x轴范围 ym=100;%y轴范围 sink.x=0.5xm;%基站x轴 sink.y=0.5ym;%基站y轴 n=100;%节点总数 p=0.1;%簇头概率 E0=0.02;%初始能量 ETX=500.000000000001;%传输能量,每bit ERX=...
  • Wireshark协议代码

    千次阅读 2017-10-17 23:08:23
    1 Siemens S7 https://github.com/wireshark/wireshark/tree/master/epan/dissectors/packet-s7comm.c 西门子PLC支持的通讯协议 2 MMS(IEC61850) ...
  • VC++ 串口通信modbus协议代码

    千次下载 热门讨论 2013-12-21 21:12:32
    VC串口通信modbus协议代码
  • CAN总线之ISO15765协议(内含协议解析伪代码

    万次阅读 多人点赞 2018-05-15 10:54:51
    ISO 15765协议是一种CAN总线上的诊断协议。其中ISO 15765-1包括物理层和数据链路层,ISO 15765-2对网络层进行说明,ISO 15765-3则是规定到应用层的具体服务。 下面重点看下网络层,根据ISO 15765-2中的定义,网络层...
  • HTTP协议错误代码大全

    千次阅读 2018-08-13 17:13:04
    转载源 错误代码(也称作状态代码),指为...某些常见的代码以粗体显示。 1xx(临时响应) 用于表示临时响应并需要请求者执行操作才能继续的状态代码代码 说明 100(继续) 请求者应当继续提出请求。服...
  • Http协议错误代码总结

    千次阅读 2016-11-30 11:56:14
    1xx 信息提示 ...101 切换协议 2xx 成功 这些状态代码表明服务器成功地接受了客户端请求 200 确定。客户端请求已成功 201 已创建 202 已接受 203 非权威性信息 204
  • Http协议错误代码大全

    千次阅读 2016-08-12 23:10:37
    某些常见的代码以粗体显示。 1xx(临时响应)用于表示临时响应并需要请求者执行操作才能继续的状态代码代码 说明100(继续) 请求者应当继续提出请求。服务器返回此代码则意味着,服务器已收到了请求的第一部分,现...
  • mqtt 协议客户端代码

    千次阅读 2016-04-12 16:30:05
    #include #include void my_message_callback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message) {  if(message->payloadlen){ ... printf("msg_call if\n");...
  • android sip协议通话代码实现

    千次阅读 2017-03-29 14:28:28
    android里面的voip通话基于sip协议,关于voip和sip协议的了解,请大家去参考相关文档,本文不做解释. sip协议的核心是SipService,它的注册与一般服务的注册不同,绝大多数...具体代码在PhoneGlobals.java中. Handler mHa
  • 通过代码看MAVLink协议 (一)

    千次阅读 2016-04-01 09:24:38
    通过代码看MAVLink协议 (一)最近因为毕设的缘故要看MAVLink协议,从里面读到一些传感器数据。 MAVLink的协议理解的差不多,但是大家都懂的,代码和实际上总有一些距离,所以,根据代码来看MAVLink协议可能会好一点...
  • 开源双模蓝牙协议代码结构介绍

    千次阅读 2020-09-04 08:10:50
    零. 概述 本文章主要讲下蓝牙设备类型class of device的概念,service class ,major device,minor device类型...第二篇:Transport层介绍,主要介绍蓝牙协议栈跟蓝牙芯片之前的硬件传输协议,比如基于UART的H4,H5,BCSP,
  • 协议代码改进的步骤

    千次阅读 2010-09-20 21:49:00
    1.首先要弄懂协议的原理2.根据原理找到相应的表示代码3.进行相应的修改
  • aodv协议代码分析

    千次阅读 2011-07-25 09:33:59
    转自:http://www.netforum.com.cn/forum_posts.asp?TID=9403 ns里实现的aodv是单播的,多播的aodv叫MAODV,网上有源代码...我觉得看代码之前最好先对协议有个了解,这样看起来会更有效率;另外,实现与 理论不一样,
  • FastCGI协议详解及代码实现

    千次阅读 2017-12-27 20:30:07
    FastCGI协议是在CGI协议的基础上发展出来的,如果想了解CGI协议,可以看我另一篇文章:动态web技术(二) --- CGI,FastCGI程序本身监听某个socket然后等待来自web服务器的连接,而不是像CGI程序是由web服务器 fork-...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 58,790
精华内容 23,516
关键字:

代码协议