前面介绍了基于socket方式的以太网通讯,接下来给大家介绍基于tcp包的通讯。内容分为基于mm32f3270以太网client的使用与基于mm32f3270以太网server的使用。
首先,对tcp有个简单的介绍:
tcp是一种面向连接的、可靠的、基于字节流的传输层通信协议。即客户端和服务器之间在交换数据之前会先建立一个tcp连接,才能相互传输数据。并且提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
tcp优点:可靠、稳定,tcp的可靠体现在tcp在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。
tcp的缺点:慢,效率低,占用系统资源高,易被攻击,tcp在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的cpu、内存等硬件资源。由于tcp存在确认机制和三次握手机制,这些是导致tcp容易被人利用,实现dos、ddos、cc等攻击。
接下来,介绍client 的使用实现:
demo使用mb-039开发板,在工程中使用lwip+freertos,实验展示如何制作一个client端,并发送数据,实验使用到的硬件如下:
如图是mb-039(完整原理图可以通过mm32官网下载)的eth部分。
各个信号引脚对应如下:
在进行client实验前,我们先了解需要使用到的api:
1)netconn_new ()2)netconn_connect ()3)netconn_write ()
每一个函数实现的功能:
01、netconn_new ()
netconn_new的功能为创建一个新的连接结构,结构类型可以为tcp/udp其源码如下:
struct netconn*netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto, netconn_callback callback){ struct netconn* conn; api_msg_var_declare(msg); api_msg_var_alloc_return_null(msg); conn = netconn_alloc(t, callback); if (conn != null) { err_t err; api_msg_var_ref(msg).msg.n.proto = proto; api_msg_var_ref(msg).conn = conn; err = netconn_apimsg(lwip_netconn_do_newconn, api_msg_var_ref(msg)); if (err != err_ok) { lwip_assert(freeing conn without freeing pcb, conn->pcb.tcp == null); lwip_assert(conn has no recvmbox, sys_mbox_valid( conn->recvmbox));#if lwip_tcp lwip_assert(conn->acceptmbox shouldn't exist, !sys_mbox_valid( conn->acceptmbox));#endif /* lwip_tcp */#if !lwip_netconn_sem_per_thread lwip_assert(conn has no op_completed, sys_sem_valid( conn->op_completed)); sys_sem_free( conn->op_completed);#endif /* !lwip_netconn_sem_per_thread */ sys_mbox_free( conn->recvmbox); memp_free(memp_netconn, conn); api_msg_var_free(msg); return null; } } api_msg_var_free(msg); return conn;}
从源码中可以看出,其功能为申请并初始化一个netconn结构体,同时在netconn_alloc函数中为conn变量创建一个接收邮箱(recvmbox),和一个信号量(conn->op_completed)。内存申请成功后使用netconn_apimsg函数构建一个消息,使用os的系统邮箱发送给内核,请求以太网协议栈去执行lwip_netconn_do_newconn()函数,在执行时使用conn->op_completed进行信号量同步,任务处理完成后,释放一个信号量表示任务完成。
02、netconn_connect ()
netconn_connect作用为建立连接,在调用时将服务器端ip地址、端口号和本地的netconn结构绑定,源码如下:
err_t netconn_connect(struct netconn* conn, const ip_addr_t* addr, u16_t port){ api_msg_var_declare(msg); err_t err; lwip_error(netconn_connect: invalid conn, (conn != null), return err_arg;);#if lwip_ipv4 /* don't propagate null pointer (ip_addr_any alias) to subsequent functions */ if (addr == null) { addr = ip4_addr_any; }#endif /* lwip_ipv4 */ api_msg_var_alloc(msg); api_msg_var_ref(msg).conn = conn; api_msg_var_ref(msg).msg.bc.ipaddr = api_msg_var_ref(addr); api_msg_var_ref(msg).msg.bc.port = port; err = netconn_apimsg(lwip_netconn_do_connect, api_msg_var_ref(msg)); api_msg_var_free(msg); return err;}
从源码中可以看出,其功能为使用netconn_apimsg创建一个消息,通过执行lwip_netconn_do_connect进行信号量的同步,将addr、port与conn进行绑定。
03、netconn_write ()
netconn_write()为处于稳定状态的tcp协议发送数据。tcp协议数据以数据流的方式传递,因此只需要知道地址、长度及需要发送的数据即可,其实际函数为netconn_write_vectors_partly(源码较长,就不贴出来了)。重点关注一下官方api文档对于apiflags参数的介绍:
* @param apiflags combination of following flags :* - netconn_copy: data will be copied into memory belonging to the stack* - netconn_more: for tcp connection, psh flag will be set on last segment sent* - netconn_dontblock: only write the data if all data can be written at once
apiflags的值为netconn_copy时,dataptr指针指向的数据将会被拷贝到为这些数据分配的内部缓冲区,在调用本函数之后可以直接对这些数据进行修改而不会影响数据,但是拷贝的过程是需要消耗系统资源的,cpu需要参与数据的拷贝,而且还会占用新的内存空间。
apiflags值为netconn_nocopy时,数据不会被拷贝而是直接使用dataptr指针来引用。但是这些数据在函数调用后不能立即被修改,因为这些数据可能会被放在当前tcp连接的重传队列中,以防对方未收到数据进行重传,而这段时间是不确定的。但是如果用户需要发送的数据在rom中(静态数据),这样子就无需拷贝数据,直接引用数据即可。
apiflags值为netconn_more时,那么接收端在组装这些tcp报文段的时候,会将报文段首部的psh标志置一,这些数据完成组装的时候,将会被立即递交给上层应用。
apiflags值为netconn_dontblock时,表示在内核发送缓冲区满的时候,再调用netconn_write()函数将不会被阻塞,而是会直接返回一个错误代码err_val告诉应用程序发送数据失败,应用程序可以自行处理这些数据,在适当的时候进行重传操作。
apiflags值为netconn_noautorcvd时,表示在tcp协议接收到数据的时候,调用netconn_recv_data_tcp()函数的时候不会去更新接收窗口,只能由用户自己调用netconn_tcp_recvd()函数完成接收窗口的更新操作。
了解了以上3个api,我们开始创建client工程:
static void client(void* thread_param){ struct netconn* conn; int ret; ip4_addr_t ipaddr; uint8_t send_buf[] = this is mm32f3270 tcp client demon; //(1) while(1) { conn = netconn_new(netconn_tcp); //(2) if (conn == null) { // (3) printf(create conn failed!n); vtaskdelay(10); continue; } ip4_addr( ipaddr, dest_ip_addr0, dest_ip_addr1, dest_ip_addr2, dest_ip_addr3); // (4) ret = netconn_connect(conn, ipaddr, dest_port); // (5) if (ret == -1) { printf(connect failed!n); netconn_close(conn); vtaskdelay(10); continue; } while (1) { ret = netconn_write(conn, send_buf, sizeof(send_buf), 0); // (6) vtaskdelay(1000); } }}
1)将需要发送的数据装填进send_buf中
2)申请一个内存区域,类型为tcp
3)如果conn为空表示申请内存失败
4)将地址赋值给ipaddr
5)创建连接,如果失败则删除conn
6)执行数据发送
到这里已经完成了工程的创建,但是还有一步比较重要的,配置我们的ip,将数据发送给服务器端,则需要知道服务器的地址。打开命令行窗口输入:ipconfig
pc地址为:192.168.105.34,在sys_arch.h文件中对dest_ip_addr0 、dest_ip_addr1、dest_ip_addr2、dest_ip_addr3进行修改,dest_port随意修改,值得注意的同一个设备是如果创建多个网卡,port成不同的值即可,后面我们会进行这类实验,设备ip需要设置在同一个网段内通信才能进行ip_addr0、ip_addr1、ip_addr2,需要与pc地址保持一致,ip_addr3可以随意设置(和pc地址不一致即可)。
#define dest_ip_addr0 192#define dest_ip_addr1 168#define dest_ip_addr2 105#define dest_ip_addr3 34#define dest_port 5001#define ip_addr0 192#define ip_addr1 168#define ip_addr2 105#define ip_addr3 137
将程序下载入开发板中,使用sscom工具进行如下设置:
点击侦听:
可以正常侦听并接收到数据,表明实验成功。实验程序请登录我们的官网下载mm32f3270 sdk,工程路径如下:
~mm32f3270_lib_samples_v0.90demo_appethernet_demoeth_rtosfreertos_client
来源:灵动mm32mcu
隔离变压器作用是什么
带反激直流电源的双通道模拟输入/模拟输出
换脸游戏走红引发人脸隐私担忧
智能温室控制系统对智慧农业发展的影响意义深远
基于Arduino的文本语音转换器和语音控制灯
基于MM32F3270以太网Client使用
独立的FPGA已经走向终结?
新益昌副总经理袁满保《设备创新迎合小尺寸封装的新需求》的主题演讲
电动汽车是新的智能手机吗
受益特高压建设提速,电力物联网发展前景广阔
中药材黄曲霉毒素检测仪,检测智能新时代
光伏企业阿特斯以68亿日元的价格出售太阳能电站
联电与意法半导体携手开发65奈米CMOS影像感测器技术
转换器的类型及作用原理
为什么低ESR在电容器设计中很重要
12月手机新品潮来袭,换新机三千元档位如何选择?
强强联合共同推动人工智能向终端侧迈进
大陆的封测厂利润较之中国台湾的大厂,仍有很大差距
LPMS1000-全自动压力校验系统
深受车企追捧 碳化硅使电动汽车充电效率大幅提升