zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    self 和 super 关键字
    NSString类
    函数和对象方法的区别
    求两个数是否互质及最大公约数
    TJU Problem 1644 Reverse Text
    TJU Problem 2520 Quicksum
    TJU Problem 2101 Bullseye
    TJU Problem 2548 Celebrity jeopardy
    poj 2586 Y2K Accounting Bug
    poj 2109 Power of Cryptography
  • 原文地址:https://www.cnblogs.com/pingwen/p/6684659.html
Copyright © 2011-2022 走看看