• LwIP之socket应用--WebServer和Modbus TCP


    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进行了封装,可以参考lwipsrcincludelwipsockets.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>404 - Page not found</h1>
                </body>
            </html>"; 
    
    /**
      * @brief serve tcp connection  
      * @param conn: connection socket 
      * @retval None
      */
    void http_server(int conn) 
    {
      int buflen = 1500;
      int ret;
      unsigned char recv_buffer[1500];
                    
      /* Read in the request */
      ret = read(conn, recv_buffer, buflen); 
      if(ret <= 0)
      {
        close(conn);
        Printf("read failed
    ");
        return;
      }
        
        Printf("http server response!
    ");
        if(strncmp((char *)recv_buffer, "GET /lwip", 9) == 0)
        {
            write(conn, htmldata, sizeof(htmldata)-1);
        }
        else
        {
            write(conn, errhtml, sizeof(errhtml)-1);
        }
        /* 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, 0)) < 0) 
      {
        Printf("can not create socket");
        return;
      }
      
      /* bind to port 80 at any interface */
      address.sin_family = AF_INET;
      address.sin_port = htons(80);
      address.sin_addr.s_addr = INADDR_ANY;
      if (bind(sock, (struct sockaddr *)&address, sizeof (address)) < 0)
      {
        Printf("can not bind socket");
        close(sock);
        return;
      }
    /* listen for connections (TCP listen backlog = 1) */ listen(sock, 1); size = sizeof(remotehost); while (1) { newconn = accept(sock, (struct sockaddr *)&remotehost, (socklen_t *)&size); if (newconn >= 0) { 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, 0, 0, TCPIP_THREAD_PRIO+1); //函数栈在移植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 = 1500;
      int ret;
      unsigned char recv_buffer[1500];
      int i;
        
    Printf("start modbus tcp "); ret = read(conn, recv_buffer, buflen); while ( ret > 0 ) { ret = read(conn, recv_buffer, buflen); Printf(" >:"); // debug print for(i=0; i<ret; i++) { Printf("%x ", recv_buffer[i]); } Printf(" >"); } close(conn); Printf("close modbus tcp "); } /** * @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(1) { /* create a TCP socket */ if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { Printf("can not create socket "); OSTimeDlyHMSM(0, 0, 1, 0); continue; } address.sin_family = AF_INET; address.sin_port = htons(502); // mosbus tcp port address.sin_addr.s_addr = INADDR_ANY; if (bind(sock, (struct sockaddr *)&address, sizeof (address)) < 0) { Printf("can not bind socket "); close(sock); OSTimeDlyHMSM(0, 0, 2, 0); continue; } /* listen for incoming connections (TCP listen backlog = 1) */ listen(sock, 1); size = sizeof(remotehost); newconn = accept(sock, (struct sockaddr *)&remotehost, (socklen_t *)&size); if (newconn >= 0) { close(sock); //一次只接受一个连接
    Printf("connect socket ");
    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, 0, 0, TCPIP_THREAD_PRIO+2); //函数栈在sys_thread_new中实现 }

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

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

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

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

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

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

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

    WebServer

  • 相关阅读:
    int.Parse()及其异常判断
    三个框框的EditBox
    等价类的划分方法与EditorBox问题等价类划分
    初学软件测试
    软件测试方法的分类细谈
    浅谈软件测试之回归测试
    白盒测试——基本路径法
    初探灰盒测试——介于白盒测试与黑盒测试的测试
    对闰年测试的非法输入处理的思考
    等价类测试——进一步完善的Web输入合法验证
  • 原文地址:https://www.cnblogs.com/pingwen/p/6684659.html
Copyright © 2020-2023  润新知