1. 引言

LwIP是嵌入式领域一个流行的以太网协议栈, LwIP开放源码,用C写成非常方便移植,并且支持socket接口,使用者可以集中精力处理应用功能。

本文是LwIP socket的一个使用小结,使用的测试平台是stm32+enc28j60+lwip+uc/OS-II。

2. 使用socket

一个基本的socket建立顺序是:

Server端:

  • socket()
  • bind()
  • listen()
  • accept()
  • recv()

Client端:

  • socket()
  • connect()
  • send()

lwip的socket和PC上的socket接口一致,只是底层实现用lwip的API进行了封装,可以参考lwip\src\include\lwip\sockets.h。

#if LWIP_COMPAT_SOCKETS
#define accept(a,b,c) lwip_accept(a,b,c)
#define bind(a,b,c) lwip_bind(a,b,c)
#define shutdown(a,b) lwip_shutdown(a,b)
#define closesocket(s) lwip_close(s)
#define connect(a,b,c) lwip_connect(a,b,c)
#define getsockname(a,b,c) lwip_getsockname(a,b,c)
#define getpeername(a,b,c) lwip_getpeername(a,b,c)
#define setsockopt(a,b,c,d,e) lwip_setsockopt(a,b,c,d,e)
#define getsockopt(a,b,c,d,e) lwip_getsockopt(a,b,c,d,e)
#define listen(a,b) lwip_listen(a,b)
#define recv(a,b,c,d) lwip_recv(a,b,c,d)
#define recvfrom(a,b,c,d,e,f) lwip_recvfrom(a,b,c,d,e,f)
#define send(a,b,c,d) lwip_send(a,b,c,d)
#define sendto(a,b,c,d,e,f) lwip_sendto(a,b,c,d,e,f)
#define socket(a,b,c) lwip_socket(a,b,c)
#define select(a,b,c,d,e) lwip_select(a,b,c,d,e)
#define ioctlsocket(a,b,c) lwip_ioctl(a,b,c) #if LWIP_POSIX_SOCKETS_IO_NAMES
#define read(a,b,c) lwip_read(a,b,c)
#define write(a,b,c) lwip_write(a,b,c)
#define close(s) lwip_close(s)
#define fcntl(a,b,c) lwip_fcntl(a,b,c)
#endif /* LWIP_POSIX_SOCKETS_IO_NAMES */ #endif /* LWIP_COMPAT_SOCKETS */

int socket(int domain, int type, int protocol);

服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket。

domain:协议族,常用的有AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE其中AF_INET代表使用ipv4地址

type:socket类型,常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等

protocol:协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

把一个地址族中的特定地址赋给socket

sockfd:socket描述字,也就是socket引用

addr:要绑定给sockfd的协议地址

addrlen:地址的长度

通常服务器在启动的时候都会绑定一个地址(如ip地址+端口号),用于提供服务。有些端口号是约定俗成的不能乱用,如80用作http,502用作modbus。

 

int listen(int sockfd, int backlog);

监听socket

sockfd:要监听的socket描述字

backlog:相应socket可以排队的最大连接个数

 

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

连接某个socket

sockfd:客户端的socket描述字

addr:服务器的socket地址

addrlen:socket地址的长度

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

TCP服务器监听到客户端请求之后,调用accept()函数取接收请求

sockfd:服务器的socket描述字

addr:客户端的socket地址

addrlen:socket地址的长度

size_t read(int fd, void *buf, size_t count);

读取socket内容

fd:socket描述字

buf:缓冲区

count:缓冲区长度

size_t write(int fd, const void *buf, size_t count);

向socket写入内容,其实就是发送内容

fd:socket描述字

buf:缓冲区

count:缓冲区长度

int close(int fd);

socket标记为以关闭 ,使相应socket描述字的引用计数-1,当引用计数为0的时候,触发TCP客户端向服务器发送终止连接请求。

3. 使用socket创建嵌入式WebServer

 要使用socket的前提是已经做好lwip和rtos的移植,如果低层驱动移植完毕,就可以使用socket快速创建应用。

本例是一个简单的WebServer。

const unsigned char htmldata[] = "\
<html>\
<head><title> LWIP</title></head>\
<center><p>A WebServer Based on LwIP v1.4.1 Hello world!</center>\
</html>";
const unsigned char errhtml[] = "\
<html>\
<head>\
<title>Error!</title>\
</head>\
<body>\
<h1> - Page not found</h1>\
</body>\
</html>"; /**
* @brief serve tcp connection
* @param conn: connection socket
* @retval None
*/
void http_server(int conn)
{
int buflen = ;
int ret;
unsigned char recv_buffer[]; /* Read in the request */
ret = read(conn, recv_buffer, buflen);
if(ret <= )
{
close(conn);
Printf("read failed\r\n");
return;
} Printf("http server response!\r\n");
if(strncmp((char *)recv_buffer, "GET /lwip", ) == )
{
write(conn, htmldata, sizeof(htmldata)-);
}
else
{
write(conn, errhtml, sizeof(errhtml)-);
}
/* Close connection socket */
close(conn);
} /**
* @brief http_task
* @param arg: pointer on argument(not used here)
* @retval None
*/
static void http_task(void *arg)
{
int sock, newconn, size;
struct sockaddr_in address, remotehost; /* create a TCP socket */
if ((sock = socket(AF_INET, SOCK_STREAM, )) < )
{
Printf("can not create socket");
return;
} /* bind to port 80 at any interface */
address.sin_family = AF_INET;
address.sin_port = htons();
address.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr *)&address, sizeof (address)) < )
{
Printf("can not bind socket");
close(sock);
return;
}
/* listen for connections (TCP listen backlog = 1) */
listen(sock, );
size = sizeof(remotehost);
while ()
{
newconn = accept(sock, (struct sockaddr *)&remotehost, (socklen_t *)&size);
if (newconn >= )
{
http_server(newconn);
}
else
{
close(newconn);
}
}
} /**************************************************************
* void http_task_init(void)
*
* This function initializes the service.
**************************************************************/
void http_task_init(void)
{
sys_thread_new( CHARGEN_THREAD_NAME, http_task, , , TCPIP_THREAD_PRIO+); //函数栈在移植sys_thread_new中实现
}

4. 使用socket创建Modbus TCP应用

Modbus TCP在网络传输层次,就是一串有特定含义的数据包的交互,LwIP层次并不识别是什么数据。所以从这个角度来讲,Modbus TCP移植和其他TCP应用的移植没有任何差别。

透过表面看本质,只有拨开外层重重包装看本质,我们才能从纷杂的事件中找到问题的重点,然后剥离不相关的部分,一次解决一个问题。基于这个思想,Modbus TCP应用可以直接划分为2个层次,底层是驱动部分,负责一包数据从网络上接收上来或发送出去,上层是Modbus的协议部分,就是Modbus寄存器的操作等。从这个角度来说,只要数据传输正确了,那么怎么处理就是另一个问题了,比如可以共用Modbus RS485的代码等。

下面测试了Modbus TCP的数据传输。

Modbus TCP设计有几个重要的点:

  1)Modbus是连续通信,不能和http一样完成一次连接后就断开,所以要不停的read,当读出错时在close(conn)关闭连接。

  2)Modbus可能存在通信失败情况,需要关闭socket后再重新建立socket。

  3)Modbus作为工业协议,应用场景下一般不会多个客户端连接一台机器,并且多个客户端连接一台机器,寄存器的读写互斥会是一个大问题,所以常见的做法是一旦连接成功,就关闭socket禁止其他连接进来。客户端主动断开后再重新建立socket然后进入listen状态。

/**
* @brief serve modbus_tcp connection
* @param conn: connection socket
* @retval None
*/
void modbus_tcp_server(int conn)
{
int buflen = ;
int ret;
unsigned char recv_buffer[];
int i;

Printf("start modbus tcp\r\n");
ret = read(conn, recv_buffer, buflen);
while ( ret > )
{
ret = read(conn, recv_buffer, buflen);
Printf("\r\n>:"); // debug print
for(i=; i<ret; i++)
{
Printf("%x ", recv_buffer[i]);
}
Printf("\r\n>");
} close(conn);
Printf("close modbus tcp\r\n");
} /**
* @brief modbus_task
* @param arg: pointer on argument(not used here)
* @retval None
*/
static void modbus_task(void *arg)
{
int sock, newconn, size;
struct sockaddr_in address, remotehost; while()
{
/* create a TCP socket */
if ((sock = socket(AF_INET, SOCK_STREAM, )) < )
{
Printf("can not create socket\r\n");
OSTimeDlyHMSM(, , , );
continue;
} address.sin_family = AF_INET;
address.sin_port = htons(); // mosbus tcp port
address.sin_addr.s_addr = INADDR_ANY; if (bind(sock, (struct sockaddr *)&address, sizeof (address)) < )
{
Printf("can not bind socket\r\n");
close(sock);
OSTimeDlyHMSM(, , , );
continue;
} /* listen for incoming connections (TCP listen backlog = 1) */
listen(sock, ); size = sizeof(remotehost);
newconn = accept(sock, (struct sockaddr *)&remotehost, (socklen_t *)&size);
if (newconn >= )
{
close(sock); //一次只接受一个连接
Printf("connect socket\r\n");
modbus_tcp_server(newconn);
}
else
{
close(sock);
close(newconn);
}
}
} /**************************************************************
* void modbus_task_init(void)
*
* This function initializes the service.
**************************************************************/
void modbus_task_init(void)
{
sys_thread_new( CHARGEN_THREAD_NAME, modbus_task, , , TCPIP_THREAD_PRIO+); //函数栈在sys_thread_new中实现
}

本例旨在测试LwIP的socket,所以并没有完整的实现modbus TCP,但是其中的几行测试代码足以说明Mosbus TCP通信正常与否。

      ret = read(conn, recv_buffer, buflen);
Printf("\r\n>:"); // debug print
for(i=; i<ret; i++)
{
Printf("%x ", recv_buffer[i]);
}
Printf("\r\n>");

可以启动一个modbus poll来测试这段代码。

如下,软件连接成功说明已经完成socket连接,但是有通信error这是因为没有实现协议处理导致的。

modbus poll的通信数据监控,没有做回复处理所以此处看到的全是Tx:

再看上面Printf函数的串口输出,其中LED ON/OFF是另外一个task在运行。

可以看到,所有modbus poll发送的数据包都被modbus_tcp_server函数正确接收,如果加上协议处理,那么就是一个完整的modbus TCP应用。

WebServer

LwIP之socket应用--WebServer和Modbus TCP的更多相关文章

  1. 基于STM32和W5500的Modbus TCP通讯

    在最近的一个项目中需要实现Modbus TCP通讯,而选用的硬件平台则是STM32F103和W5500,软件平台则选用IAR EWAR6.4来实现. 1.移植千的准备工作 为了实现Modbus TCP ...

  2. modbus tcp 入门详解

    Modbus tcp 格式说明 通讯机制 附C#测试工具用于学习,测试   前言: 之前的博客介绍了如何用C#来读写modbus tcp服务器的数据,文章:http://www.cnblogs.com ...

  3. Modbus tcp 格式说明 通讯机制 附C#测试工具用于学习,测试

    前言: 之前的博客介绍了如何用C#来读写modbus tcp服务器的数据,文章:http://www.cnblogs.com/dathlin/p/7885368.html 当然也有如何创建一个服务器文 ...

  4. freemodbus modbus TCP 学习笔记

    1.前言     使用modbus有些时间了,期间使用过modbus RTU也使用过modbus TCP,通过博文和大家分享一些MODBUS TCP的东西.在嵌入式中实现TCP就需要借助一个以太网协议 ...

  5. 初识Modbus TCP/IP-------------C#编写Modbus TCP客户端程序(一)

    转自:http://blog.csdn.net/thebestleo/article/details/52269999 首先我要说明一下,本人新手一枚,本文仅为同样热爱学习的同学提供参考,有不 对的地 ...

  6. 【.NET6+Modbus】Modbus TCP协议解析、仿真环境以及基于.NET实现基础通信

    前言:随着工业化的发展,目前越来越多的开发,从互联网走向传统行业.其中,工业领域也是其中之一,包括各大厂也都在陆陆续续加入工业4.0的进程当中. 工业领域,最核心的基础设施,应该是与下位硬件设备或程序 ...

  7. 国标电表DLT645转MODBUS TCP协议转换器MRD-5021,工业设备,浪涌三级保护MRD

    DL/T645转ModbusTcp协议转换器 MRD-5021具有1 路RS485及1路以太网接口,最多支持同时采集5个DL/T645-1997或者5个2007协议国标电表设备,支持DL/T645协议 ...

  8. 开放型Modbus/TCP 规范

    修订版 1.0,1999 年3 月29 日Andy SwalesSchneider 电气公司aswales@modicon.com目录目录............................... ...

  9. EasyARM i.mx287学习笔记——通过modbus tcp控制GPIO

    0 前言     本文使用freemodbus协议栈,在EasyARM i.mx287上实现了modbus tcp从机. 在该从机中定义了线圈寄存器.当中线圈寄存器地址较低的4位和EasyARM的P2 ...

随机推荐

  1. 2017-2-21 C#基础 if条件语句,作用域

    今天学了if 条件语句和作用域.作用域可以用一句话来概括:儿子可以用爹的所有东西,爹不可以用儿子的任何东西.If条件语句我用几个练习题来解释. 1."请输入年份:" 判断是否是闰年 ...

  2. Scalatra--Introduction And Quick start

    Introduction Scalatra是一款轻易级Scala web框架,通过Scalatra可以很轻易创建web Application,由Linkedln开源并遵循了Ruby Web框架的Si ...

  3. Cloud9vue&vux上传github小步骤

    成功后创建出以下文件,再输入: git init 再输入:$ git remote add origin https://github.com/github用户名/vux1 然后:git add. 按 ...

  4. gulp快速入门&初体验

    前言 一句话先 gulp 是一个可以简单和自动化"管理"前端文件的构建工具 先说我以前的主要工作,我主要是做游戏服务端的,用c++/python,所以我对东西的概念理解难免要套到自 ...

  5. 联网html引用BootStrap

    以下是我写的一个联网html引用BootStrap的例子,可作为参考: <%@ Page Language="C#" AutoEventWireup="true&q ...

  6. 关于hive ,eclipse老是提示加载不到驱动

    忙活了好长时间,很纳闷为什么加载不上驱动,驱动包.hive的依赖包.hadoop的依赖包也引入了,各种百度最后: hadoop-2.2.0/share/hadoop/common/hadoop-com ...

  7. 从USB驱动器运行Windows 10

    我相信很多人和我一样.梦想着有个随身携带的U盘版操作系统.无论走到哪里,只要有电脑都可以随时运行自己配置好的操作系统.本篇博文就会一步步的教你如何从USB驱动器加载和运行Windows 10. 让我想 ...

  8. Oracle Developer Data Modeler项目实践 (转)

    http://www.Oracle.com/webfolder/technetwork/tutorials/obe/db/sqldevdm/r30/datamodel2moddm/datamodel2 ...

  9. 解决Gerrit的git unpack error问题和error Missing unknown ec867cebfd2be97c3603c45fac03c75dcf68d0ca

    参考链接:http://www.cnblogs.com/yuxc/p/3508964.html 解决方法: 由于帖子里面用的是mysql数据库,而我用的是h2数据库,还特意自己去找了H2数据库的进入方 ...

  10. JavaWeb之Filter、Listener

    昨天和大家介绍了一下JSON的用法,其实JSON中主要是用来和数据库交互数据的.今天给大家讲解的是Filter和Listener的用法. 一.Listenner监听器 1.1.定义 Javaweb中的 ...