发新帖我要提问
12
返回列表
打印
[应用相关]

实战STM32F4以太网DP83848配合LWIP

[复制链接]
楼主: 晓伍
手机看帖
扫描二维码
随时随地手机跟帖
21
晓伍|  楼主 | 2021-8-1 14:38 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
4.使用lwip的socket通信
4.1 socket概念
Socket 英文原意是“孔”或者“插座”的意思,在网络编程中,通常将其称之为“套接字”,当前网络中的主流程序设计都是使用 Socket 进行编程的,因为它简单易用,更是一个标准,能在不同平台很方便移植。本章讲解的是 LwIP 中的 Socket 编程接口,因为 LwIP 作者为了能让更多开发者直接上手 LwIP 的编程,专门设计了 LwIP 的第三种编程接口——Socket API,它兼容 BSDSocket。

Socket 虽然是能在多平台移植,但是 LwIP 中的 Socket 并不完善,因为 LwIP 设计之初就是为了在嵌入式平台中使用,它只实现了完整 Socket 的部分功能,不过,在嵌入式平台中,这些功能早已足够。

在 Socket 中,它使用一个套接字来记录网络的一个连接,套接字是一个整数,就像我们操作文件一样,利用一个文件描述符,可以对它打开、读、写、关闭等操作,类似的,在网络中,我们也可以对 Socket 套接字进行这样子的操作,比如开启一个网络的连接、读取连接主机发送来的数据、向连接的主机发送数据、终止连接等操作。

4.2 socket API介绍
4.2.1 socket() API介绍

这个函数的功能是向内核申请一个套接字,lwip中的socket最终实现是lwip_socket,但是为了兼容BSD socket,所以重新define了一下,我们来看下

/** @ingroup socket */
#define socket(domain,type,protocol)              lwip_socket(domain,type,protocol)
参数:

domain: 表示该套接字使用的协议簇,对于 TCP/IP 协议来说,该值始终为 AF_INET。

type: 指定了套接字使用的服务类型,可能的类型有 3 种:

1)SOCK_STREAM:提供可靠的(即能保证数据正确传送到对方)面向连接的 Socket 服务,多用于资料(如文件)传输,如 TCP 协议。

2)SOCK_DGRAM:是提供无保障的面向消息的 Socket 服务,主要用于在网络上发广播信息,如 UDP 协议,提供无连接不可靠的数据报交付服务。

3)SOCK_RAW:表示原始套接字,它允许应用程序访问网络层的原始数据包,这个套接字用得比较少,暂时不用理会它。

protocol :指定了套接字使用的协议,在 IPv4 中,只有 TCP 协议提供 SOCK_STREAM 这种可靠的服务,只有 UDP 协议提供 SOCK_DGRAM 服务,对于这两种协议,protocol 的值均为 0。当申请套接字成功的时候,该函数返回一个 int 类型的值,也是 Socket 描述符,用户通过这个值可以索引到一个 Socket 连接结构——lwip_sock,当申请套接字失败时,该函数返回-1。

4.2.2 bind() API介绍

用于服务器端绑定套接字与网卡信息,lwip中的bind最终实现是lwip_bind,但是为了兼容BSD bind,所以重新define了一下,我们来看下

/** @ingroup socket */
#define bind(s,name,namelen)                      lwip_bind(s,name,namelen)
参数:

s :是表示要绑定的 Socket 套接字,注意了,这个套机字必须是从 socket() 函数中返回的索引,否则将无法完成绑定操作。

name: 是一个指向 sockaddr 结构体的指针,其中包含了网卡的 IP 地址、端口号等重要的信息,LwIP 为了更好描述这些信息,使用了 sockaddr 结构体来定义了必要的信息的字段,它常被用于Socket API 的很多函数中,我们在使用 bind() 的时候,只需要直接填写相关字段即可

namelen: 指定了 name 结构体的长度。

下面我们来说下name的struct,如下:

struct sockaddr {
  u8_t        sa_len;
  sa_family_t sa_family;
  char        sa_data[14];
};
为了便于理解,lwip重新定义了一下这个结构体

/* members are in network byte order */
struct sockaddr_in {
  u8_t            sin_len;
  sa_family_t     sin_family; /* 协议簇 */
  in_port_t       sin_port; /* 端口号 */
  struct in_addr  sin_addr; /* IP地址 */
#define SIN_ZERO_LEN 8
  char            sin_zero[SIN_ZERO_LEN];
};
4.2.3 connect() API介绍

它用于客户端中,将 Socket 与远端 IP 地址、端口号进行绑定,在 TCP 客户端连接中,调用这个函数将发生握手过程(会发送一个 TCP 连接请求),并最终建立新的 TCP 连接,而对于 UDP协议来说,调用这个函数只是在 UDP 控制块中记录远端 IP 地址与端口号,而不发送任何数据,参数信息与 bind() 函数是一样的

/** @ingroup socket */
#define connect(s,name,namelen)                   lwip_connect(s,name,namelen)
4.2.4 listen() API介绍

只能在 TCP 服务器中使用,让服务器进入监听状态,等待远端的连接请求, LwIP 中可以接收多个客户端的连接,因此参数 backlog 指定了请求队列的大小

/** @ingroup socket */
#define listen(s,backlog)                         lwip_listen(s,backlog)
4.2.5 accept() API介绍

用于 TCP 服务器中,等待着远端主机的连接请求,并且建立一个新的 TCP 连接,在调用这个函数之前需要通过调用 listen() 函数让服务器进入监听状态。accept() 函数的调用会阻塞应用线程直至与远程主机建立 TCP 连接。参数 addr 是一个返回结果参数,它的值由 accept() 函数设置,其实就是远程主机的地址与端口号等信息,当新的连接已经建立后,远端主机的信息将保存在连接句柄中,它能够唯一的标识某个连接对象。同时函数返回一个 int 类型的套接字描述符,根据它能索引到连接结构,如果连接失败则返回-1

/** @ingroup socket */
#define accept(s,addr,addrlen)                    lwip_accept(s,addr,addrlen)
4.2.6 read()、recv()、recvfrom() API介绍

read() 与 recv() 函数的核心是调用 recvfrom() 函数, recv() 与 read() 函数用于从 Socket 中接收数据,它们可以是 TCP 协议和 UDP 协议!

/** @ingroup socket */
#define recv(s,mem,len,flags)                     lwip_recv(s,mem,len,flags)
/** @ingroup socket */
#define recvmsg(s,message,flags)                  lwip_recvmsg(s,message,flags)
/** @ingroup socket */
#define recvfrom(s,mem,len,flags,from,fromlen)    lwip_recvfrom(s,mem,len,flags,from,fromlen)
men 参数记录了接收数据的缓存起始地址,len 用于指定接收数据的最大长度,如果函数能正确接收到数据,将会返回一个接收到数据的长度,否则将返回-1,若返回值为 0,表示连接已经终止,应用程序可以根据返回的值进行不一样的操作。recv() 函数包含一个 flags 参数,我们暂时可以直接忽略它,设置为 0 即可。注意,如果接收的数据大于用户提供的缓存区,那么多余的数据会被直接丢弃。

4.2.7 sendto() API介绍

这个函数主要是用于 UDP 协议传输数据中,它向另一端的 UDP 主机发送一个 UDP 报文,参数 data 指定了要发送数据的起始地址,而 size 则指定数据的长度,参数 flag 指定了发送时候的一些处理,比如外带数据等,此时我们不需要理会它,一般设置为 0 即可,参数 to 是一个指向 sockaddr 结构体的指针,在这里需要我们自己提供远端主机的 IP 地址与端口号,并且用 tolen 参数指定这些信息的长度!

/** @ingroup socket */
#define sendto(s,dataptr,size,flags,to,tolen)     lwip_sendto(s,dataptr,size,flags,to,tolen)
4.2.8 sendto() API介绍

send() 函数可以用于 UDP 协议和 TCP 连接发送数据。在调用 send() 函数之前,必须使用 connect()函数将远端主机的 IP 地址、端口号与 Socket 连接结构进行绑定。对于 UDP 协议,send() 函数将调用 lwip_sendto() 函数发送数据,而对于 TCP 协议,将调用 netconn_write_partly() 函数发送数据。相对于 sendto() 函数,参数基本是没啥区别的,但无需我们设置远端主机的信息,更加方便操作,因此这个函数在实际中使用也是很多的

/** @ingroup socket */
#define send(s,dataptr,size,flags)                lwip_send(s,dataptr,size,flags)
4.2.9 write() API介绍

这个函数一般用于处于稳定的 TCP 连接中传输数据,当然也能用于 UDP 协议中,它也是基于lwip_send 上实现的,但是无需我们设置 flag 参数

/** @ingroup socket */
#define write(s,dataptr,len)                      lwip_write(s,dataptr,len)
4.2.10 close() API介绍

close() 函数是用于关闭一个指定的套接字,在关闭套接字后,将无法使用对应的套接字描述符索引到连接结构,该函数的本质是对 netconn_delete() 函数的封装(真正处理的函数是 netconn_prepare_delete()),如果连接是 TCP 协议,将产生一个请求终止连接的报文发送到对端主机中,如果是 UDP 协议,将直接释放 UDP 控制块的内容!

/** @ingroup socket */
#define close(s)                                  lwip_close(s)


使用特权

评论回复
22
晓伍|  楼主 | 2021-8-1 14:39 | 只看该作者
4.3 socket创建一个TCP server做一个回显实验
代码如下:

#include "tcpecho.h"
#include "lwip/opt.h"
#if LWIP_SOCKET
#include <lwip/sockets.h>
#include "lwip/sys.h"
#include "lwip/api.h"
/*-----------------------------------------------------------------------------------*/
#define RECV_DATA         (1024)
#define LOCAL_PORT 5001
static void  
tcpecho_thread(void *arg)
{
  int sock = -1,connected;
  char *recv_data;
  struct sockaddr_in server_addr,client_addr;
  socklen_t sin_size;
  int recv_data_len;

  printf("本地端口号是%d\n\n",LOCAL_PORT);

  recv_data = (char *)pvPortMalloc(RECV_DATA);
  if (recv_data == NULL)
  {
      printf("No memory\n");
      goto __exit;
  }

  sock = socket(AF_INET, SOCK_STREAM, 0);
  if (sock < 0)
  {
      printf("Socket error\n");
      goto __exit;
  }

  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(LOCAL_PORT);
  memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));

  if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
  {
      printf("Unable to bind\n");
      goto __exit;
  }

  if (listen(sock, 5) == -1)
  {
      printf("Listen error\n");
      goto __exit;
  }

  while(1)
  {
    sin_size = sizeof(struct sockaddr_in);
    connected = accept(sock, (struct sockaddr *)&client_addr, &sin_size);
    printf("new client connected from (%s, %d)\n",
            inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
    {
      int flag = 1;

      setsockopt(connected,
                 IPPROTO_TCP,     /* set option at TCP level */
                 TCP_NODELAY,     /* name of option */
                 (void *) &flag,  /* the cast is historical cruft */
                 sizeof(int));    /* length of option value */
    }

    while(1)
    {
      recv_data_len = recv(connected, recv_data, RECV_DATA, 0);

      if (recv_data_len <= 0)  
        break;

      printf("recv %d len data\n",recv_data_len);

      write(connected,recv_data,recv_data_len);

    }
    if (connected >= 0)  
      closesocket(connected);

    connected = -1;
  }
__exit:
  if (sock >= 0) closesocket(sock);
  if (recv_data) free(recv_data);
}
/*-----------------------------------------------------------------------------------*/
void
tcpecho_init(void)
{
  sys_thread_new("tcpecho_thread", tcpecho_thread, NULL, 512, 4);
}
/*-----------------------------------------------------------------------------------*/
效果如下:



这个实验室我们STM32做TCP server,然后IP是静态IP(192.168.1.250),开启的TCP port是5001,


使用特权

评论回复
23
晓伍|  楼主 | 2021-8-1 14:41 | 只看该作者
4.4 socket创建一个TCP client间隔发送数据
#include "tcp_client.h"
#include "lwip/opt.h"
#include "lwip/sys.h"
#include "lwip/api.h"
#include <lwip/sockets.h>
#define DEST_IP_ADDR0 192
#define DEST_IP_ADDR1 168
#define DEST_IP_ADDR2 1
#define DEST_IP_ADDR3 102
#define DEST_PORT 5001
static void client(void *thread_param)
{
  int sock = -1;
  struct sockaddr_in client_addr;

  ip4_addr_t ipaddr;

  uint8_t send_buf[]= "This is a TCP Client test...\n";

  printf("目地IP地址:%d.%d.%d.%d \t 端口号:%d\n\n",      \
          DEST_IP_ADDR0,DEST_IP_ADDR1,DEST_IP_ADDR2,DEST_IP_ADDR3,DEST_PORT);

  printf("请将电脑上位机设置为TCP Server.在User/arch/sys_arch.h文件中将目标IP地址修改为您电脑上的IP地址\n\n");

  printf("修改对应的宏定义:DEST_IP_ADDR0,DEST_IP_ADDR1,DEST_IP_ADDR2,DEST_IP_ADDR3,DEST_PORT\n\n");

  IP4_ADDR(&ipaddr,DEST_IP_ADDR0,DEST_IP_ADDR1,DEST_IP_ADDR2,DEST_IP_ADDR3);
  while(1)
  {
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
      printf("Socket error\n");
      vTaskDelay(10);
      continue;
    }  
    client_addr.sin_family = AF_INET;      
    client_addr.sin_port = htons(DEST_PORT);   
    client_addr.sin_addr.s_addr = ipaddr.addr;
    memset(&(client_addr.sin_zero), 0, sizeof(client_addr.sin_zero));   
    if (connect(sock,  
               (struct sockaddr *)&client_addr,  
                sizeof(struct sockaddr)) == -1)  
    {
        printf("Connect failed!\n");
        closesocket(sock);
        vTaskDelay(10);
        continue;
    }                                            

    printf("Connect to server successful!\n");

    while (1)
    {
      if(write(sock,send_buf,sizeof(send_buf)) < 0)
        break;

      vTaskDelay(1000);
    }

    closesocket(sock);
  }
}
void
tcp_client_init(void)
{
  sys_thread_new("client", client, NULL, 512, 4);
}
效果如下:



这个实验室我们STM32做TCP client,然后连接pc的tcp server(PC IP地址为:192.168.1.102),PC的TCP port为5001


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则