第19章      RL-TCPnet之BSD Socket服务器

本章节为大家讲解RL-TCPnet的BSD Socket,学习本章节前,务必要优先学习第18章的Socket基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。另外RL-TCPnet的socket仅支持UDP协议和TCP协议,我们本章节仅讲解了TCP协议方式的BSD Socket。

本章教程含STM32F407开发板和STM32F429开发板。

19.1  初学者重要提示

19.2  BSD Socket相关的头文件定义

19.3  BSD Socket函数

19.4  BSD Socket的参数配置特别说明

19.5  函数htonl,htons,ntohl和ntohs

19.6  BSD Socket配置说明(Net_Config.c)

19.7  BSD Socket调试说明(Net_Debug.c)

19.8  BSD Socket通信的实现方法

19.9  网络调试助手和板子的操作步骤

19.10  实验例程说明(RTX)

19.11  总结

19.1  初学者重要提示

  1. 学习本章节前,务必保证已经学习了第18章的基础知识。
  2. 本章要掌握的函数稍多,可以先学会基本的使用,然后再深入了解这些函数使用时的注意事项,争取能够熟练运用。
  3. Socket编程的这些函数要熟练使用还是要花些时间和精力的,每个函数的注意事项在函数讲解和例子中都有说明,需要大家多做练习,实际体会下。
  4. BSD Socket编程需要多任务的支持,所以没有做裸机的例子,而RTX,uCOS-III和FreeRTOS的都做了。

19.2  BSD Socket相关的头文件定义

学习socket编程的函数之前,优先认识下BSD Socket头文件中相关的定义,后面讲解函数的时候要用到:

/* BSD Socket Address Family */

#define AF_UNSPEC          0      /* Unspecified                             */

#define AF_INET            1      /* Internet Address Family (UDP, TCP)      */

#define AF_NETBIOS         2      /* NetBios-style addresses                 */

/* BSD Protocol families, same as address families */ //--------------(1)

#define PF_UNSPEC          AF_UNSPEC

#define PF_INET            AF_INET

#define PF_NETBIOS         AF_NETBIOS

/* BSD Socket Type */

#define SOCK_STREAM        1      /* Stream Socket (Connection oriented)     */

#define SOCK_DGRAM         2      /* Datagram Socket (Connectionless)        */

/* BSD Socket Protocol */

#define IPPROTO_TCP        1      /* TCP Protocol                            */

#define IPPROTO_UDP        2      /* UDP Protocol                            */

/* BSD Internet Addresses */  //--------------(2)

#define INADDR_ANY     0x00000000 /* All IP addresses accepted               */

#define INADDR_NONE    0xffffffff /* No IP address accepted                  */

/* BSD Socket Return values */  //--------------(3)

#define SCK_SUCCESS         0     /* Success                                 */

#define SCK_ERROR         (-1)    /* General Error                           */

#define SCK_EINVALID      (-2)    /* Invalid socket descriptor               */

#define SCK_EINVALIDPARA  (-3)    /* Invalid parameter                       */

#define SCK_EWOULDBLOCK   (-4)    /* It would have blocked.                  */

#define SCK_EMEMNOTAVAIL  (-5)    /* Not enough memory in memory pool        */

#define SCK_ECLOSED       (-6)    /* Connection is closed or aborted         */

#define SCK_ELOCKED       (-7)    /* Socket is locked in RTX environment     */

#define SCK_ETIMEOUT      (-8)    /* Socket, Host Resolver timeout           */

#define SCK_EINPROGRESS   (-9)    /* Host Name resolving in progress         */

#define SCK_ENONAME       (-10)   /* Host Name not existing                  */

/* BSD Socket typedef's */ //--------------(4)

typedef struct sockaddr {         /* << Generic Socket Address structure >>  */

  U16  sa_family;                 /* Address family                          */

  char sa_data[];               /* Direct address (up to 14 bytes)         */

} SOCKADDR;

#pragma push

#pragma anon_unions

typedef struct in_addr {          /* << Generic IPv4 Address structure >>    */ //--------------(5)

  union {

    struct {

      U8 s_b1,s_b2,s_b3,s_b4;     /* IP address, byte access                 */

    };

    struct {

      U16 s_w1,s_w2;              /* IP address, short int access            */

    };

    U32 s_addr;                   /* IP address in network byte order        */

  };

} IN_ADDR; 

#pragma pop

typedef struct sockaddr_in {      /* << IPv4 Socket Address structure >>     */ //--------------(6)

  S16 sin_family;                 /* Socket domain                           */

  U16 sin_port;                   /* Port                                    */

  IN_ADDR sin_addr;               /* IP address                              */

  S8  sin_zero[];                /* reserved                                */

} SOCKADDR_IN;

typedef struct hostent {          /* << BSD Host Entry structure >>          */

  char *h_name;                   /* Official name of host                   */

  char **h_aliases;               /* Pointer to an array of alias names      */

  S16  h_addrtype;                /* Address Type: AF_INET, AF_NETBIOS       */

  S16  h_length;                  /* Length of address in bytes              */

  char **h_addr_list;             /* Pointer to an array of IPv4 addresses   */

} HOSTENT;
  1. BSD Socket的地址族(Address Family)和协议栈(Protocol family)的定义是一样的。
  2. INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或所有地址,任意地址。程序中使用的话表示监控某一端口的所有IP地址消息,一般主要用于有多个网卡或者IP地址的情况。开发板只用了DM9161的网口,就是监听这个网口的IP地址。
  3. 这里是socket函数的返回值类型,比较重要,下面要讲解的大部分函数都要用到。
  4. 上面代码中第4个标记和第6个标记,其实是一样的,只是第6个标记中的结构体分出更多的结构体成员。这点要知道,下面要讲解的函数也要用到。
  5. IPv4地址结构体,支持字节、半字和字的访问,方便了程序使用。
  6. 同第4条说明。

19.3  BSD Socket函数

使用如下14个函数可以实现RL-TCPnet的socket通信:

  • accept
  • bind
  • closesocket
  • connect
  • gethostbyname
  • getpeername
  • getsockname
  • ioctlsocket
  • listen
  • recv
  • recvfrom
  • send
  • sendto
  • socket

关于这14个函数的讲解及其使用方法可以看教程第 3 章 3.4 小节里面说的参考资料 rlarm.chm 文件:

这里我们重点的说以下 7个函数,因为本章节配套的例子使用的是这7个函数:

  • accept
  • bind
  • closesocket
  • listen
  • recv
  • send
  • socket

关于这些函数注意以下三点:

  1. BSD接口函数执行的是标准Berkeley Socket通信接口,但不是将BSD Socket的所有API都实现了。
  2. BSD Socket是线程安全的,也就是支持多任务,因此使用Socket是务必需要多任务支持的。使用的RTOS不限,任何主流的RTOS都可以支持。
  3. BSD Socket的底层是由前面章节讲解的TCP和UDP实现的。

19.3.1 函数socket

函数原型:

int socket (

    int family,         /* 地址族  */

    int type,           /* 通信类型 */

int protocol);      /* 通信协议 */                   

函数描述:

函数socket用于创建一个socket。

  1. 第1个参数用于指定地址族,当前仅支持AF_INET。
  2. 第2个参数是通信类型,有如下两种可选。
  3. 第3个参数是协议类型,支持以下三种。
  4. 返回值有以下几种:
    • 创建成功的话,返回socket的句柄。
    • 返回SCK_EINVALIDPARA,表示函数参数无效或者参数不支持。
    • 返回SCK_ERROR,表示创建失败。

使用这个函数要注意以下问题:

  1. 调用任何其它BSD Socket函数之前,务必优先调用函数socket。
  2. 返回负值表示错误。所有错误类型代表的数值,详见本章节19.2小节。

使用举例:

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, sd, res;

     SOCKADDR_IN addr;

     SOCKADDR_IN ReAddr;

     while ()

     {

          /* 创建一个socket

            第1个参数AF_INET:当前仅支持这个类型的地址族。

            第2个参数SOCK_STREAM:表示数据流通信类型,即使用的TCP。

            第3个参数0 :配置为0的话,自动跟第2个参数进行协议匹配,这里就是TCP协议。

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 端口号设置为1001 */

         addr.sin_port        = htons(LocalPort_NUM);

          /* 与函数socket中的AF_INET作用一样 */

         addr.sin_family      = PF_INET;

         /*

            INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或所有地址,

            任意地址。用在这里的话就表示监控端口号为ddr.sin_port的所有IP地址消息。一般主要用

            于有多个网卡或者IP地址的情况。开发板只用了DM9161的网口,就是监听这个网口的IP地址。

         */

         addr.sin_addr.s_addr = INADDR_ANY;

          /* 给socket绑定IP和端口号 */

         bind (sock, (SOCKADDR *)&addr, sizeof(addr));

         /* 设置监听,最大监听1个连接 */

         listen (sock, );

          /*

            等待soket连接请求,有的话,自动创建1个新的socket进行连接通信,没有的话,等待连接。

            注意,能够accept的个数受到listen函数的限制,而listen函数又受到Net_Config.c中宏定义

            BSD_NUMSOCKS 的限制。

         */

         len = sizeof(ReAddr);

         sd = accept (sock, (SOCKADDR *)&ReAddr, &len);

         printf_debug ("远程客户端请求连接IP: %d.%d.%d.%d\n", ReAddr.sin_addr.s_b1,

                                                             ReAddr.sin_addr.s_b2,

                                                                       ReAddr.sin_addr.s_b3,

                                                             ReAddr.sin_addr.s_b4);

         printf_debug ("远程客户端端口号: %d\n", ntohs(ReAddr.sin_port));

          /* 关闭监听socket,这个监听socket是调用函数socket后自动创建的 */

         closesocket (sock);

         sock = sd;

         /* 省略 */

     }

} 

19.3.2 函数bind

函数原型:

int bind (

    int sock,              /* Socket句柄 */

    const SOCKADDR *addr,  /* 本地地址指针变量 */

    int addrlen);          /* SOCKADDR结构体变量大小,单位字节 */             

函数描述:

函数bind用于给创建的socket分配一个名称,主要是IP地址和端口号。

  1. 第1个参数是Socket句柄,即函数socket的返回值。
  2. 第2个参数是SOCKADDR类型结构体指针变量,此结构体变量中定义了IP地址和端口号。
  3. 第3个参数是结构体变量SOCKADDR的大小,单位字节。
  4. 返回值有以下几种:
    • 返回SCK_SUCCESS,表示函数调用成功。
    • 返回SCK_EINVALID,表示函数socket句柄参数无效。
    • 返回SCK_EINVALIDPARA,表示其它参数无效或者不支持。

使用这个函数要注意以下问题:

  1. 调用此函数之前,务必优先调用函数socket。
  2. 返回负值表示错误。所有错误类型代表的数值,详见本章节19.2小节。

使用举例:

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, sd, res;

     SOCKADDR_IN addr;

     SOCKADDR_IN ReAddr;

     while ()

     {

          /* 创建一个socket

            第1个参数AF_INET:当前仅支持这个类型的地址族。

            第2个参数SOCK_STREAM:表示数据流通信类型,即使用的TCP。

            第3个参数0 :配置为0的话,自动跟第2个参数进行协议匹配,这里就是TCP协议。

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 端口号设置为1001 */

         addr.sin_port        = htons(LocalPort_NUM);

          /* 与函数socket中的AF_INET作用一样 */

         addr.sin_family      = PF_INET;

         /*

            INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或所有地址,

            任意地址。用在这里的话就表示监控端口号为ddr.sin_port的所有IP地址消息。一般主要用

            于有多个网卡或者IP地址的情况。开发板只用了DM9161的网口,就是监听这个网口的IP地址。

         */

         addr.sin_addr.s_addr = INADDR_ANY;

          /* 给socket绑定IP和端口号 */

          bind (sock, (SOCKADDR *)&addr, sizeof(addr));

         /* 设置监听,最大监听1个连接 */

         listen (sock, );

          /*

            等待soket连接请求,有的话,自动创建1个新的socket进行连接通信,没有的话,等待连接。

            注意,能够accept的个数受到listen函数的限制,而listen函数又受到Net_Config.c中宏定义

            BSD_NUMSOCKS 的限制。

         */

         len = sizeof(ReAddr);

         sd = accept (sock, (SOCKADDR *)&ReAddr, &len);

         printf_debug ("远程客户端请求连接IP: %d.%d.%d.%d\n", ReAddr.sin_addr.s_b1,

                                                             ReAddr.sin_addr.s_b2,

                                                                       ReAddr.sin_addr.s_b3,

                                                             ReAddr.sin_addr.s_b4);

         printf_debug ("远程客户端端口号: %d\n", ntohs(ReAddr.sin_port));

          /* 关闭监听socket,这个监听socket是调用函数socket后自动创建的 */

         closesocket (sock);

         sock = sd;

         /* 省略 */

     }

}

19.3.3 函数listen

函数原型:

int listen (

    int sock,          /* Socket句柄 */

    int backlog);      /* 可以监听的最大连接数 */           

函数描述:

函数listen用于设置创建的socket工作在监听模式,调用此函数前务必优先调用bind。

  1. 第1个参数是Socket句柄,即函数socket的返回值。
  2. 第2个参数是可以监听的最大连接数,连接请求会放在一个专门的队列里面。
  3. 返回值有以下几种:
    • 返回SCK_SUCCESS,表示函数调用成功。
    • 返回SCK_EINVALID,表示函数socket句柄参数无效。
    • 返回SCK_EINVALIDPARA,表示其它参数无效或者不支持。
    • 返回SCK_ERROR,表示没有socket可供使用了,即可用的socket数量小于第2个参数中设置的最大连接数。

使用这个函数要注意以下问题:

  1. 调用此函数之前,务必优先调用函数socket。
  2. 返回负值表示错误。所有错误类型代表的数值,详见本章节19.2小节。
  3. 可供用户使用的socket数量是在Net_Config.c文件中定义的:

    #define BSD_NUMSOCKS   5

    设置监听数量的时候切不可超过这里定义的数值,此宏定义可设置的范围是1-20个socket。

  4. 调用监听函数后,系统会自动开启一个socket用于监听连接请求。这个自动开启的socket不在宏定义BSD_NUMSOCKS配置的范围内。

使用举例:

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, sd, res;

     SOCKADDR_IN addr;

     SOCKADDR_IN ReAddr;

     while ()

     {

          /* 创建一个socket

            第1个参数AF_INET:当前仅支持这个类型的地址族。

            第2个参数SOCK_STREAM:表示数据流通信类型,即使用的TCP。

            第3个参数0 :配置为0的话,自动跟第2个参数进行协议匹配,这里就是TCP协议。

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 端口号设置为1001 */

         addr.sin_port        = htons(LocalPort_NUM);

          /* 与函数socket中的AF_INET作用一样 */

         addr.sin_family      = PF_INET;

         /*

            INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或所有地址,

            任意地址。用在这里的话就表示监控端口号为ddr.sin_port的所有IP地址消息。一般主要用

            于有多个网卡或者IP地址的情况。开发板只用了DM9161的网口,就是监听这个网口的IP地址。

         */

         addr.sin_addr.s_addr = INADDR_ANY;

          /* 给socket绑定IP和端口号 */

         bind (sock, (SOCKADDR *)&addr, sizeof(addr));

         /* 设置监听,最大监听1个连接 */

          listen (sock, );

          /*

            等待soket连接请求,有的话,自动创建1个新的socket进行连接通信,没有的话,等待连接。

            注意,能够accept的个数受到listen函数的限制,而listen函数又受到Net_Config.c中宏定义

            BSD_NUMSOCKS 的限制。

         */

         len = sizeof(ReAddr);

         sd = accept (sock, (SOCKADDR *)&ReAddr, &len);

         printf_debug ("远程客户端请求连接IP: %d.%d.%d.%d\n", ReAddr.sin_addr.s_b1,

                                                             ReAddr.sin_addr.s_b2,

                                                                       ReAddr.sin_addr.s_b3,

                                                             ReAddr.sin_addr.s_b4);

         printf_debug ("远程客户端端口号: %d\n", ntohs(ReAddr.sin_port));

          /* 关闭监听socket,这个监听socket是调用函数socket后自动创建的 */

         closesocket (sock);

         sock = sd;

         /* 省略 */

     }

}

19.3.4 函数accept

函数原型:

int accept (

    int sock,          /* Socket 句柄 */

    SOCKADDR *addr,    /* 远程连接的指针变量 */

int *addrlen);     /* 远程连接SOCKADDR结构体大小的指针变量,结构体大小的单位是字节  */      

函数描述:

函数accept用于接受监听socket队列中的连接请求,如果队列中有挂起的连接请求,调用accept函数后会把连接请求从监听socket队列中删除并创建一个新的socket用于连接。监听socket仍然保持打开,继续监听新的连接请求。

如果系统检测到当前工程是工作在多任务环境,即用户使能了RTX操作系统或者其它RTOS(注意,其它RTOS是无法识别的,需要在MDK中Option->C/C++的预定义宏中加上__RTX才可以识别,这个在前面相应RTOS的移植章节有说明),函数accept会工作在阻塞模式,等待连接请求。反之,如果用户没有使能RTX操作系统或者其它RTOS,函数accept会工作在非阻塞模式,调用此函数后会立即返回,如果返回的是SCK_EWOULDBLOCK,就需要用户再次调用函数accept进行查询。

  1. 第1个参数是Socket句柄,即函数socket的返回值,必须得是SOCK_STREAM类型的socket。
  2. 第2个参数是SOCKADDR类型结构体指针变量,通过此参数来记录远程连接的IP地址和端口号。
  3. 第3个参数是用于记录远程连接地址结构体长度的指针变量。
  4. 返回值有以下几种:
    • 返回大于0的数值,即新创建的socket句柄,表示函数调用成功。
    • 返回SCK_EINVALID,表示函数socket句柄参数无效。
    • 返回SCK_EWOULDBLOCK,表示函数accept要进入阻塞态,而此函数是工作在非阻塞方式。
    • 返回SCK_ECLOSED,表示远程连接请求已经取消,连接关闭。

使用这个函数要注意以下问题:

  1. 调用此函数之前,务必优先调用函数socket。
  2. 返回负值表示错误。所有错误类型代表的数值,详见本章节19.2小节。

使用举例:

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, sd, res;

     SOCKADDR_IN addr;

     SOCKADDR_IN ReAddr;

     while ()

     {

          /* 创建一个socket

            第1个参数AF_INET:当前仅支持这个类型的地址族。

            第2个参数SOCK_STREAM:表示数据流通信类型,即使用的TCP。

            第3个参数0 :配置为0的话,自动跟第2个参数进行协议匹配,这里就是TCP协议。

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 端口号设置为1001 */

         addr.sin_port        = htons(LocalPort_NUM);

          /* 与函数socket中的AF_INET作用一样 */

         addr.sin_family      = PF_INET;

         /*

            INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或所有地址,

            任意地址。用在这里的话就表示监控端口号为ddr.sin_port的所有IP地址消息。一般主要用

            于有多个网卡或者IP地址的情况。开发板只用了DM9161的网口,就是监听这个网口的IP地址。

         */

         addr.sin_addr.s_addr = INADDR_ANY;

          /* 给socket绑定IP和端口号 */

         bind (sock, (SOCKADDR *)&addr, sizeof(addr));

         /* 设置监听,最大监听1个连接 */

         listen (sock, );

          /*

            等待soket连接请求,有的话,自动创建1个新的socket进行连接通信,没有的话,等待连接。

            注意,能够accept的个数受到listen函数的限制,而listen函数又受到Net_Config.c中宏定义

            BSD_NUMSOCKS 的限制。

         */

         len = sizeof(ReAddr);

         sd = accept (sock, (SOCKADDR *)&ReAddr, &len);

         printf_debug ("远程客户端请求连接IP: %d.%d.%d.%d\n", ReAddr.sin_addr.s_b1,

                                                             ReAddr.sin_addr.s_b2,

                                                                       ReAddr.sin_addr.s_b3,

                                                             ReAddr.sin_addr.s_b4);

         printf_debug ("远程客户端端口号: %d\n", ntohs(ReAddr.sin_port));

          /* 关闭监听socket,这个监听socket是调用函数socket后自动创建的 */

         closesocket (sock);

         sock = sd;

         /* 省略 */

     }

}

19.3.5 函数recv

函数原型:

int recv (

    int sock,          /* Socket 句柄 */

    char *buf,         /* 接收数据的缓冲区地址 */

    int len,           /* 接收数据的缓冲区大小,单位字节 */

int flags);        /* 消息标志 */    

函数描述:

函数recv用于接收socket队列中传入的数据。SOCK_STREAM和SOCK_DGRAM类型的socket都可以使用此函数。要读取的数据大小由此函数的第3个参数决定。

如果系统检测到当前工程是工作在多任务环境,即用户使能了RTX操作系统或者其它RTOS(注意,其它RTOS是无法识别的,需要在MDK中Option->C/C++的预定义宏中加上__RTX才可以识别,这个在前面相应RTOS的移植章节有说明),函数recv会工作在阻塞模式,等待远程设备发来的数据包。反之,如果用户没有使能RTX操作系统或者其它RTOS,函数recv会工作在非阻塞模式,调用此函数后会立即返回,如果返回的是SCK_EWOULDBLOCK,就需要用户再次调用函数recv查询是否有数据,也就是需要用户不断的调用函数recv进行轮询。

  1. 第1个参数是Socket句柄,即函数socket的返回值。
  2. 第2个参数是接收数据的缓冲地址。这里有两种情况需要注意:
    • 如果是SOCK_STREAM类型的socket,数据缓冲区的空间不够存放接收到的数据,可以通过多次调用函数recv进行接收。
    • 如果是SOCK_DGRAM类型的socket,数据缓冲区的空间不够存放接收到的数据,多余的数据会被抛弃掉。
  3. 第3个参数是接收数据的缓冲区大小,单位字节。
  4. 第4个参数是消息标志,有如下两种选择:一般情况下,这个参数填数值0即可,表示这两个选择都不使用。
  5. 返回值有以下几种:
    • 返回大于0的数值,表示复制到接收数据缓冲区的数据大小,单位字节。
    • 返回SCK_EINVALID,表示函数socket句柄参数无效。
    • 返回SCK_ECLOSED,表示远程连接已经关闭。
    • 返回SCK_EWOULDBLOCK,表示函数recv要进入阻塞态,而此函数是工作在非阻塞方式。
    • 返回SCK_ETIMEOUT,表示阻塞模式的情况下,等待的时间超时。
    • 返回SCK_ELOCKED,表示其它任务正在使用此socket。

使用这个函数要注意以下问题:

  1. 调用此函数之前,务必优先调用函数socket。
  2. 返回负值表示错误。所有错误类型代表的数值,详见本章节19.2小节。
  3. 实际读取的字节数可以小于第3个参数配置的大小。
  4. 函数recv的溢出时间是由Net_Config.c文件中的宏定义:

#define BSD_RCVTOUT    10

来配置的,单位秒。用户可以配置的数值范围是0-600秒,如果配置为0的话,表示无限等待。

使用举例:

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, sd, res;

     SOCKADDR_IN addr;

     SOCKADDR_IN ReAddr;

     while ()

     {

         /* 创建一个socket

            第1个参数AF_INET:当前仅支持这个类型的地址族。

            第2个参数SOCK_STREAM:表示数据流通信类型,即使用的TCP。

            第3个参数0 :配置为0的话,自动跟第2个参数进行协议匹配,这里就是TCP协议。

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 端口号设置为1001 */

         addr.sin_port        = htons(LocalPort_NUM);

          /* 与函数socket中的AF_INET作用一样 */

         addr.sin_family      = PF_INET;

          /*

            INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或所有地址,

            任意地址。用在这里的话就表示监控端口号为ddr.sin_port的所有IP地址消息。一般主要用

            于有多个网卡或者IP地址的情况。开发板只用了DM9161的网口,就是监听这个网口的IP地址。

         */

         addr.sin_addr.s_addr = INADDR_ANY;

         /* 给socket绑定IP和端口号 */

         bind (sock, (SOCKADDR *)&addr, sizeof(addr));

          /* 设置监听,最大监听1个连接 */

         listen (sock, );

         /*

            等待soket连接请求,有的话,自动创建1个新的socket进行连接通信,没有的话,等待连接。

            注意,能够accept的个数受到listen函数的限制,而listen函数又受到Net_Config.c中宏定义

            BSD_NUMSOCKS 的限制。

         */

         len = sizeof(ReAddr);

         sd = accept (sock, (SOCKADDR *)&ReAddr, &len);

         printf_debug ("远程客户端请求连接IP: %d.%d.%d.%d\n", ReAddr.sin_addr.s_b1,

                                                             ReAddr.sin_addr.s_b2,

                                                                       ReAddr.sin_addr.s_b3,

                                                             ReAddr.sin_addr.s_b4);

         printf_debug ("远程客户端端口号: %d\n", ntohs(ReAddr.sin_port));

         /* 关闭监听socket,这个监听socket是调用函数socket后自动创建的 */

         closesocket (sock);

         sock = sd;

         while ()

         {

              /*

                socket数据接收函数,使用这个函数注意以下事项:

                1. 此函数的溢出时间受受到Net_Config.c中宏定义 BSD_RCVTOUT 的限制。溢出时间到会自动退出。

                2. 这个函数接收到一次数据包就会返回,大于或者小于设置的缓冲区大小都没有关系,如果数据量

                   大于接收缓冲区大小,用户只需多次调用函数recv进行接收即可。

                3. 实际接收到数据大小通过判断此函数的返回值即可。

              */

              res = recv (sock, dbuf, sizeof(dbuf), );

              if (res <= )

              {

                   printf_debug("退出接收函数,重新开始监听%s\r\n", ReVal_Table[abs(res)]);

                   break;

              }

              else

              {

                   printf_debug("Receive Data Length = %d\r\n", res);

              }

         }

         /*

            溢出时间到,远程设备断开连接等,程序都会执行到这里,我们在这里关闭socket,

            程序返回到第一个大while循环的开头重新创建socket并监听。

         */

         closesocket (sock);

     }

}

19.3.6 函数send

函数原型:

int send (

    int sock,          /* Socket 句柄 */

    const char *buf,   /* 数据发送缓冲区地址 */

    int len,           /* 数据发送缓冲区大小,单位字节 */

    int flags);        /* 消息标志 */

函数描述:

函数send用于数据发送。SOCK_STREAM和SOCK_DGRAM类型的socket都可以使用此函数。通常主要用于SOCK_STREAM类socket。

如果系统检测到当前工程是工作在多任务环境,即用户使能了RTX操作系统或者其它RTOS(注意,其它RTOS是无法识别的,需要在MDK中Option->C/C++的预定义宏中加上__RTX才可以识别,这个在前面相应RTOS的移植章节有说明),函数send工作在阻塞模式,等待发送完成后才会返回,如果用户没有使能RTX操作系统或者其它RTOS,函数send会工作在非阻塞模式,调用此函数后会立即返回,而函数send返回的数值代表已经发送的字节数,如果要发送的数据不能通过一次数据包就发送完,剩下的将不再发送,此时函数send的返回值是小于第3个参数中设置的发送缓冲区大小。

  1. 第1个参数是Socket句柄,即函数socket的返回值。
  2. 第2个参数是发送数据的缓冲地址,发送的数据大小不限,只要不超过32位int型定义即可,如果要发送的数据不能通过一次数据包就发送完,将分多次进行发送。
  3. 第3个参数是发送数据的缓冲区大小,单位字节。
  4. 第4个参数是消息标志,有如下两种选择:一般情况下,这个参数填数值0即可,表示不使用这个选项。
  5. 返回值有以下几种:
    • 返回大于0的数值,表示已经成功发送的字节数。
    • 返回SCK_EINVALID,表示函数socket句柄参数无效。
    • 返回SCK_EINVALIDPARA,表示其它参数无效或者不支持。
    • 返回SCK_EWOULDBLOCK,表示函数recv要进入阻塞态,而此函数是工作在非阻塞方式。
    • 返回SCK_ECLOSED,表示远程连接已经关闭。
    • 返回SCK_ERROR,表示socket底层的UDP或者TCP通信出错。
    • 返回SCK_ELOCKED,表示其它任务正在使用此socket。

使用这个函数要注意以下问题:

  1. 调用此函数之前,务必优先调用函数socket。
  2. 返回负值表示错误。所有错误类型代表的数值,详见本章节19.2小节。

使用举例:

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, sd, res;

     SOCKADDR_IN addr;

     SOCKADDR_IN ReAddr;

     while ()

     {

         /* 创建一个socket

            第1个参数AF_INET:当前仅支持这个类型的地址族。

            第2个参数SOCK_STREAM:表示数据流通信类型,即使用的TCP。

            第3个参数0 :配置为0的话,自动跟第2个参数进行协议匹配,这里就是TCP协议。

         */

         sock = socket (AF_INET, SOCK_STREAM, );

          /* 端口号设置为1001 */

         addr.sin_port        = htons(LocalPort_NUM);

         /* 与函数socket中的AF_INET作用一样 */

         addr.sin_family      = PF_INET;

         /*

            INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或所有地址,

            任意地址。用在这里的话就表示监控端口号为ddr.sin_port的所有IP地址消息。一般主要用

            于有多个网卡或者IP地址的情况。开发板只用了DM9161的网口,就是监听这个网口的IP地址。

         */

         addr.sin_addr.s_addr = INADDR_ANY;

         /* 给socket绑定IP和端口号 */

         bind (sock, (SOCKADDR *)&addr, sizeof(addr));

         /* 设置监听,最大监听1个连接 */

         listen (sock, );

         /*

            等待soket连接请求,有的话,自动创建1个新的socket进行连接通信,没有的话,等待连接。

            注意,能够accept的个数受到listen函数的限制,而listen函数又受到Net_Config.c中宏定义

            BSD_NUMSOCKS 的限制。

         */

         len = sizeof(ReAddr);

         sd = accept (sock, (SOCKADDR *)&ReAddr, &len);

         printf_debug ("远程客户端请求连接IP: %d.%d.%d.%d\n", ReAddr.sin_addr.s_b1,

                                                             ReAddr.sin_addr.s_b2,

                                                                       ReAddr.sin_addr.s_b3,

                                                             ReAddr.sin_addr.s_b4);

         printf_debug ("远程客户端端口号: %d\n", ntohs(ReAddr.sin_port));

         /* 关闭监听socket,这个监听socket是调用函数socket后自动创建的 */

         closesocket (sock);

         sock = sd;

         while ()

         {

              /*

                socket数据接收函数,如果recv工作在阻塞模式,使用这个函数注意以下事项:

                1. 此函数的溢出时间受到Net_Config.c中宏定义 BSD_RCVTOUT 的限制。溢出时间到会自动退出。

                2. 这个函数接收到一次数据包就会返回,大于或者小于设置的缓冲区大小都没有关系,如果数据量

                   大于接收缓冲区大小,用户只需多次调用函数recv进行接收即可。

                3. 实际接收到数据大小通过判断此函数的返回值即可。

              */

              res = recv (sock, dbuf, sizeof(dbuf), );

              if (res <= )

              {

                   printf_debug("退出接收函数,重新开始监听%s\r\n", ReVal_Table[abs(res)]);

                   break;

              }

              else

              {

                   printf_debug("Receive Data Length = %d\r\n", res);

                   switch(dbuf[])

                   {

                       /* 字符命令 1 */

                       case '':

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '\r';

                            sendbuf[] = '\n';                       

                            res = send (sock, (char *)sendbuf, , );

                            if (res < )

                            {

                                 printf_debug("函数send发送数据失败\r\n");

                            }

                            else

                            {

                                 printf_debug("函数send发送数据成功\r\n");                                

                            }

                            break;

                       /* 其它数值不做处理 */

                       default:                    

                            break;

                   }

              }

         }

         /*

            溢出时间到,远程设备断开连接等,程序都会执行到这里,我们在这里关闭socket,

            程序返回到第一个大while循环的开头重新创建socket并监听。

         */

         closesocket (sock);

     }

}

19.3.7 函数closesocket

函数原型:

int closesocket (

    int sock);         /* Socket 句柄 */

函数描述:

函数closesocket用于关闭socket并释放占用的内存。

如果系统检测到当前工程是工作在多任务环境,即用户使能了RTX操作系统或者其它RTOS(注意,其它RTOS是无法识别的,需要在MDK中Option->C/C++的预定义宏中加上__RTX才可以识别,这个在前面相应RTOS的移植章节有说明),函数closesocket工作在阻塞模式,等待关闭成功了才会返回,如果用户没有使能RTX操作系统或者其它RTOS,函数closesocket会工作在非阻塞模式,调用此函数后会立即返回,如果返回的是SCK_EWOULDBLOCK,就需要用户再次调用函数closesocket进行关闭,直到成功关闭为止。

  • 第1个参数是Socket句柄,即函数socket的返回值。

使用这个函数要注意以下问题:

  1. 调用此函数之前,务必优先调用函数socket。
  2. 返回负值表示错误。所有错误类型代表的数值,详见本章节19.2小节。

使用举例:

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, sd, res;

     SOCKADDR_IN addr;

     SOCKADDR_IN ReAddr;

     while ()

     {

          /* 创建一个socket

            第1个参数AF_INET:当前仅支持这个类型的地址族。

            第2个参数SOCK_STREAM:表示数据流通信类型,即使用的TCP。

            第3个参数0 :配置为0的话,自动跟第2个参数进行协议匹配,这里就是TCP协议。

         */

         sock = socket (AF_INET, SOCK_STREAM, );

          /* 端口号设置为1001 */

         addr.sin_port        = htons(LocalPort_NUM);

         /* 与函数socket中的AF_INET作用一样 */

         addr.sin_family      = PF_INET;

         /*

            INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或所有地址,

            任意地址。用在这里的话就表示监控端口号为ddr.sin_port的所有IP地址消息。一般主要用

            于有多个网卡或者IP地址的情况。开发板只用了DM9161的网口,就是监听这个网口的IP地址。

         */

         addr.sin_addr.s_addr = INADDR_ANY;

         /* 给socket绑定IP和端口号 */

         bind (sock, (SOCKADDR *)&addr, sizeof(addr));

         /* 设置监听,最大监听1个连接 */

         listen (sock, );

         /*

            等待soket连接请求,有的话,自动创建1个新的socket进行连接通信,没有的话,等待连接。

            注意,能够accept的个数受到listen函数的限制,而listen函数又受到Net_Config.c中宏定义

            BSD_NUMSOCKS 的限制。

         */

         len = sizeof(ReAddr);

         sd = accept (sock, (SOCKADDR *)&ReAddr, &len);

         printf_debug ("远程客户端请求连接IP: %d.%d.%d.%d\n", ReAddr.sin_addr.s_b1,

                                                             ReAddr.sin_addr.s_b2,

                                                                       ReAddr.sin_addr.s_b3,

                                                             ReAddr.sin_addr.s_b4);

         printf_debug ("远程客户端端口号: %d\n", ntohs(ReAddr.sin_port));

         /* 关闭监听socket,这个监听socket是调用函数socket后自动创建的 */

         closesocket (sock);

         sock = sd;

         while ()

         {

              /*

                socket数据接收函数,如果recv工作在阻塞模式,使用这个函数注意以下事项:

                1. 此函数的溢出时间受到Net_Config.c中宏定义 BSD_RCVTOUT 的限制。溢出时间到会自动退出。

                2. 这个函数接收到一次数据包就会返回,大于或者小于设置的缓冲区大小都没有关系,如果数据量

                   大于接收缓冲区大小,用户只需多次调用函数recv进行接收即可。

                3. 实际接收到数据大小通过判断此函数的返回值即可。

              */

              res = recv (sock, dbuf, sizeof(dbuf), );

              if (res <= )

              {

                   printf_debug("退出接收函数,重新开始监听%s\r\n", ReVal_Table[abs(res)]);

                   break;

              }

              else

              {

                   printf_debug("Receive Data Length = %d\r\n", res);

                   switch(dbuf[])

                   {

                       /* 字符命令 1 */

                       case '':

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '\r';

                            sendbuf[] = '\n';                       

                            res = send (sock, (char *)sendbuf, , );

                            if (res < )

                            {

                                 printf_debug("函数send发送数据失败\r\n");

                            }

                            else

                            {

                                 printf_debug("函数send发送数据成功\r\n");                                

                            }

                            break;

                       /* 其它数值不做处理 */

                       default:                    

                            break;

                   }

              }

         }

         /*

            溢出时间到,远程设备断开连接等,程序都会执行到这里,我们在这里关闭socket,

            程序返回到第一个大while循环的开头重新创建socket并监听。

         */

         closesocket (sock);

     }

}

19.4 函数htonl,htons,ntohl和ntohs

网络传输一般采用大端序,也被称之为网络字节序,或网络序。IP协议中定义大端序为网络字节序。Berkeley套接字定义了一组转换函数,用于16bit和32bit整数在网络序和本机字节序之间的转换。htonl,htons用于本机序转换到网络序;ntohl,ntohs用于网络序转换到本机序。RL-TCPnet的具体定义如下:

#ifdef __BIG_ENDIAN

 #define U32_LE(v)      (U32)(__rev(v))

 #define U16_LE(v)      (U16)(__rev(v) >> 16)

 #define U32_BE(v)      (U32)(v)

 #define U16_BE(v)      (U16)(v)

#else

 #define U32_BE(v)      (U32)(__rev(v))

 #define U16_BE(v)      (U16)(__rev(v) >> 16)

 #define U32_LE(v)      (U32)(v)

 #define U16_LE(v)      (U16)(v)

#endif

#define ntohs(v)        U16_BE(v)

#define ntohl(v)        U32_BE(v)

#define htons(v)        ntohs(v)

#define htonl(v)        ntohl(v)

htonl,其实是host to network, l 的意思是返回类型是long,其它的函数同理。知道了这个,方便大家记住这几个函数。

19.5 BSD Socket的参数配置特别说明

虽然BSD Socket的配置中仅有这几个参数,但是这几个参数都非常重要:

BSD_NUMSOCKS

用于配置可创建的BSD Socket数量,范围1-20。

加大这个配置的话,同时需要加大配置向导中UDP和TCP的数量。另外特别注意一种情况,我们创建了socket服务器之后,通过函数listen可以设置最大监听的连接,每通过函数accept接收一个连接请求都会创建新的socket,而新的socket就是来自这里配置的数量,这点要特别注意。

BSD_SRVSOCKS

定义BSD Socket中可以采用TCP通信协议的服务器个数。创建socket服务器的时候要注意。

BSD_RCVTOUT

socket接收函数recv工作在阻塞状态时的溢出时间设置,单位秒,范围0-600秒。配置为0,表示无限等待。

BSD_GETHOSTEN

启用或禁用Berkeley风格的主机名解析。

最后,由于BSD Socket是基于TCP和UDP实现的,所以配置向导中TCP和UDP的参数配置,同样适用于BSD Socket,这点也要特别注意。

19.6 BSD Socket配置说明(Net_Config.c)

(本章节配套例子的配置与本小节的说明相同)

RL-TCPnet的配置工作是通过配置文件Net_Config.c实现。在MDK工程中打开文件Net_Config.c,可以看到下图所示的工程配置向导:

RL-TCPnet要配置的选项非常多,我们这里把几个主要的配置选项简单介绍下。

System Definitions

(1)Local Host Name

局域网域名。

这里起名为armfly,使用局域网域名限制为15个字符。

(2)Memory Pool size

参数范围1536-262144字节。

内存池大小配置,单位字节。另外注意一点,配置向导这里显示的单位是字节,如果看原始定义,MDK会做一个自动的4字节倍数转换,比如我们这里配置的是8192字节,那么原始定义是#define MEM_SIZE  2048,也就是8192/4 = 2048。

(3)Tick Timer interval

可取10,20,25,40,50,100,200,单位ms。

系统滴答时钟间隔,也就是网络协议栈的系统时间基准,默认情况下,取值100ms。

Ethernet Network Interface

以太网接口配置,勾选了此选项就可以配置了,如果没有使能DHCP的话,将使用这里配置的固定IP

(1)MAC Address

局域网内可以随意配置,只要不跟局域网内其它设备的MAC地址冲突即可。

(2)IP Address

IP地址。

(3)Subnet mask

子网掩码。

(4)Default Gateway

默认网关。

Ethernet Network Interface

以太网接口配置,这个配置里面还有如下两项比较重要的配置需要说明。

(1)NetBIOS Name Service

NetBIOS局域网域名服务,这里打上对勾就使能了。这样我们就可以通过前面配置的Local Host Name局域网域名进行访问,而不需要通过IP地址访问了。

(2)Dynaminc Host Configuration

即DHCP,这里打上对勾就使能了。使能了DHCP后,RL-TCPnet就可以从外接的路由器上获得动态IP地址。

UDP Sockets

UDP Sockets配置,打上对勾就使能了此项功能

(1)Number of UDP Sockets

用于配置可创建的UDP Sockets数量,这里配置了5个。

范围1 – 20。

TCP Sockets

TCP Sockets配置,打上对勾就使能了此项功能

(1)Number of TCP Sockets

用于配置可创建的TCP Sockets数量。

(2)Number of Retries

范围0-20。

用于配置重试次数,TCP数据传输时,如果在设置的重试时间内得不到应答,算一次重试失败,这里就是配置的最大重试次数。

(3)Retry Timeout in seconds

范围1-10,单位秒。

重试时间。如果发送的数据在重试时间内得不到应答,将重新发送数据。

(4)Default Connect Timeout in seconds

范围1-600,单位秒。

用于配置默认的保持连接时间,即我们常说的Keep Alive时间,如果时间到了将断开连接。常用于HTTP Server,Telnet Server等。

(5)Maximum Segment Size

范围536-1460,单位字节。

MSS定义了TCP数据包能够传输的最大数据分段。

(6)Receive Window Size

范围536-65535,单位字节。

TCP接收窗口大小。

BSD Socket Interface

BSD Socket配置,打上对勾就使能了此项功能

(1)BSD_NUMSOCKS

用于配置可创建的BSD Socket数量。

范围1-20。

(2)BSD_SRVSOCKS

定义的BSD Socket中可以采用TCP通信协议的服务器个数。

(3)BSD_RCVTOUT

socket接收函数recv工作在阻塞状态时的溢出时间设置,单位秒。

范围0-600秒,配置为0的话,表示无限等待。

(4)BSD_GETHOSTEN

启用或禁用Berkeley风格的主机名解析。

19.7 BSD Socket调试说明(Net_Debug.c)

(重要说明,RL-TCPnet的调试是通过串口打印出来的)

RL-TCPnet的调试功能是通过配置文件Net_Debug.c实现。在MDK工程中打开文件Net_Debug.c,可以看到下图所示的工程配置向导:

Print Time Stamp

勾选了此选项的话,打印消息时,前面会附带时间信息。

其它所有的选项

默认情况下,所有的调试选项都关闭了,每个选项有三个调试级别可选择,这里我们以BSD Debug为例,点击下拉列表,可以看到里面有Off,Errors only和Full debug三个调试级别可供选择,每个调试选项里面都是这三个级别。

Off:表示关闭此选项的调试功能。

Errors only:表示仅在此选项出错时,将其错误打印出来。

Full debug:表示此选项的全功能调试。

具体测试,我们这里就不做了,大家可以按照第11章讲解的调试方法进行测试。

19.8 BSD Socket通信的实现方法

有了本章节19.6小节的配置后,剩下的问题就是socket的创建和socket数据收发的实现。这里特别注意socket的实现流程和socket相关函数的使用注意事项,在程序里面都有注释。

19.8.1 创建BSD Socket服务器

相比前面章节TCP 服务器的创建,BSD Socket服务器的创建要稍麻烦些,需要多个函数配合使用,而且每个函数的使用都要理解全面些,具体这些函数的使用和注意事项在本章的19.3小节有讲解:

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, sd, res;

     SOCKADDR_IN addr;

     SOCKADDR_IN ReAddr;

     while ()

     {

          /* 创建一个socket

            第1个参数AF_INET:当前仅支持这个类型的地址族。

            第2个参数SOCK_STREAM:表示数据流通信类型,即使用的TCP。

            第3个参数0 :配置为0的话,自动跟第2个参数进行协议匹配,这里就是TCP协议。

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 端口号设置为1001 */

         addr.sin_port        = htons(LocalPort_NUM);

          /* 与函数socket中的AF_INET作用一样 */

         addr.sin_family      = PF_INET;

         /*

            INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或所有地址,

            任意地址。用在这里的话就表示监控端口号为ddr.sin_port的所有IP地址消息。一般主要用

            于有多个网卡或者IP地址的情况。开发板只用了DM9161的网口,就是监听这个网口的IP地址。

         */

         addr.sin_addr.s_addr = INADDR_ANY;

          /* 给socket绑定IP和端口号 */

         bind (sock, (SOCKADDR *)&addr, sizeof(addr));

         /* 设置监听,最大监听1个连接 */

         listen (sock, );

          /*

            等待soket连接请求,有的话,自动创建1个新的socket进行连接通信,没有的话,等待连接。

            注意,能够accept的个数受到listen函数的限制,而listen函数又受到Net_Config.c中宏定义

            BSD_NUMSOCKS 的限制。

         */

         len = sizeof(ReAddr);

         sd = accept (sock, (SOCKADDR *)&ReAddr, &len);

         printf_debug ("远程客户端请求连接IP: %d.%d.%d.%d\n", ReAddr.sin_addr.s_b1,

                                                             ReAddr.sin_addr.s_b2,

                                                                       ReAddr.sin_addr.s_b3,

                                                             ReAddr.sin_addr.s_b4);

         printf_debug ("远程客户端端口号: %d\n", ntohs(ReAddr.sin_port));

          /* 关闭监听socket,这个监听socket是调用函数socket后自动创建的 */

         closesocket (sock);

         sock = sd;

         /* 省略 */

     }

}

19.8.2 BSD Socket数据接收

BSD Socket编程需要多任务的支持,所以没有做裸机的例子,这里我们以RTX操作系统为例进行说明(其它的uCOS-III和FreeRTOS的思路是一样的)。配套了三个任务,一个是RL-TCPnet网络主任务,一个是网络系统时间基准更新任务,还有一个是socket任务。

  • 网络系统时间更新任务:
/*

*********************************************************************************************************

*    函 数 名: AppTaskStart

*    功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 6 

*********************************************************************************************************

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

     /* 创建任务 */

     AppTaskCreate();

     os_itv_set ();

    while()

    {

         os_itv_wait ();

         /* RL-TCPnet时间基准更新函数 */

          timer_tick ();

    }

}

特别注意,这里的网络时间基准函数timer_tick,必须要周期性调用,周期大小是由配置向导文件中参数Tick Timer interval决定的。默认情况下,我们都取100ms,所以这里的延迟一定要匹配。

  • RL-TCPnet网络主任务

主要是调用函数main_TcpNet即可。

/*

*********************************************************************************************************

*    函 数 名: AppTaskTCPMain

*    功能说明: RL-TCPnet网络主任务

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 5 

*********************************************************************************************************

*/

__task void AppTaskTCPMain(void)

{

     while ()

     {

         /* RL-TCPnet处理函数 */

         main_TcpNet();

         os_dly_wait();

     }

}
  • Socket任务

socket通信在此任务里面实现。socket的数据接收主要是通过函数recv来实现,使用这个函数除了要注意本章的19.3.5小节讲解的问题,还要注意下面要讲到的。

/*

*********************************************************************************************************

*    函 数 名: AppTaskSocket

*    功能说明: RL-TCPnet测试任务

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 4 

*********************************************************************************************************

*/

__task void AppTaskSocket(void)

{

     while ()

     {

         TCPnetTest();

     }

}

函数TCPnetTest的数据接收部分实现如下:

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, sd, res;

     SOCKADDR_IN addr;

     SOCKADDR_IN ReAddr;

     while ()

     {

         /* 创建一个socket

            第1个参数AF_INET:当前仅支持这个类型的地址族。

            第2个参数SOCK_STREAM:表示数据流通信类型,即使用的TCP。

            第3个参数0 :配置为0的话,自动跟第2个参数进行协议匹配,这里就是TCP协议。

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 端口号设置为1001 */

         addr.sin_port        = htons(LocalPort_NUM);

          /* 与函数socket中的AF_INET作用一样 */

         addr.sin_family      = PF_INET;

          /*

            INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或所有地址,

            任意地址。用在这里的话就表示监控端口号为ddr.sin_port的所有IP地址消息。一般主要用

            于有多个网卡或者IP地址的情况。开发板只用了DM9161的网口,就是监听这个网口的IP地址。

         */

         addr.sin_addr.s_addr = INADDR_ANY;

         /* 给socket绑定IP和端口号 */

         bind (sock, (SOCKADDR *)&addr, sizeof(addr));

         /* 设置监听,最大监听1个连接 */

         listen (sock, );

         /*

            等待soket连接请求,有的话,自动创建1个新的socket进行连接通信,没有的话,等待连接。

            注意,能够accept的个数受到listen函数的限制,而listen函数又受到Net_Config.c中宏定义

            BSD_NUMSOCKS 的限制。

         */

         len = sizeof(ReAddr);

         sd = accept (sock, (SOCKADDR *)&ReAddr, &len);

         printf_debug ("远程客户端请求连接IP: %d.%d.%d.%d\n", ReAddr.sin_addr.s_b1,

                                                             ReAddr.sin_addr.s_b2,

                                                                       ReAddr.sin_addr.s_b3,

                                                             ReAddr.sin_addr.s_b4);

         printf_debug ("远程客户端端口号: %d\n", ntohs(ReAddr.sin_port));

         /* 关闭监听socket,这个监听socket是调用函数socket后自动创建的 */

         closesocket (sock);

         sock = sd;

         while ()

         {

              /*

                socket数据接收函数,使用这个函数注意以下事项:

                1. 此函数的溢出时间受受到Net_Config.c中宏定义 BSD_RCVTOUT 的限制。溢出时间到会自动退出。

                2. 这个函数接收到一次数据包就会返回,大于或者小于设置的缓冲区大小都没有关系,如果数据量

                   大于接收缓冲区大小,用户只需多次调用函数recv进行接收即可。

                3. 实际接收到数据大小通过判断此函数的返回值即可。

              */

              res = recv (sock, dbuf, sizeof(dbuf), );

              if (res <= )

              {

                   printf_debug("退出接收函数,重新开始监听%s\r\n", ReVal_Table[abs(res)]);

                   break;

              }

              else

              {

                   printf_debug("Receive Data Length = %d\r\n", res);

              }

         }

         /*

            溢出时间到,远程设备断开连接等,程序都会执行到这里,我们在这里关闭socket,

            程序返回到第一个大while循环的开头重新创建socket并监听。

         */

         closesocket (sock);

     }

}

工作在阻塞模式的函数recv要特别注意下面的几个问题:

  1. 函数recv的溢出时间受Net_Config.c中宏定义 BSD_RCVTOUT 的限制。溢出时间到会自动退出。
  2. 这个函数接收到一次数据包就会返回,大于或者小于设置的缓冲区大小都没有关系,如果数据量大于接收缓冲区大小,用户只需多次调用函数recv进行接收即可。
  3. 实际接收到数据大小通过判断此函数的返回值即可。

19.8.3 BSD Socket数据发送

BSD Socket的数据发送是通过函数send实现的,这个函数的使用注意事项在本章节19.3.6小节有讲(RTX,uCOS-III和FreeRTOS是一样的):

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, sd, res;

     SOCKADDR_IN addr;

     SOCKADDR_IN ReAddr;

     while ()

     {

         /* 创建一个socket

            第1个参数AF_INET:当前仅支持这个类型的地址族。

            第2个参数SOCK_STREAM:表示数据流通信类型,即使用的TCP。

            第3个参数0 :配置为0的话,自动跟第2个参数进行协议匹配,这里就是TCP协议。

         */

         sock = socket (AF_INET, SOCK_STREAM, );

          /* 端口号设置为1001 */

         addr.sin_port        = htons(LocalPort_NUM);

         /* 与函数socket中的AF_INET作用一样 */

         addr.sin_family      = PF_INET;

         /*

            INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或所有地址,

            任意地址。用在这里的话就表示监控端口号为ddr.sin_port的所有IP地址消息。一般主要用

            于有多个网卡或者IP地址的情况。开发板只用了DM9161的网口,就是监听这个网口的IP地址。

         */

         addr.sin_addr.s_addr = INADDR_ANY;

         /* 给socket绑定IP和端口号 */

         bind (sock, (SOCKADDR *)&addr, sizeof(addr));

         /* 设置监听,最大监听1个连接 */

         listen (sock, );

         /*

            等待soket连接请求,有的话,自动创建1个新的socket进行连接通信,没有的话,等待连接。

            注意,能够accept的个数受到listen函数的限制,而listen函数又受到Net_Config.c中宏定义

            BSD_NUMSOCKS 的限制。

         */

         len = sizeof(ReAddr);

         sd = accept (sock, (SOCKADDR *)&ReAddr, &len);

         printf_debug ("远程客户端请求连接IP: %d.%d.%d.%d\n", ReAddr.sin_addr.s_b1,

                                                             ReAddr.sin_addr.s_b2,

                                                                       ReAddr.sin_addr.s_b3,

                                                             ReAddr.sin_addr.s_b4);

         printf_debug ("远程客户端端口号: %d\n", ntohs(ReAddr.sin_port));

         /* 关闭监听socket,这个监听socket是调用函数socket后自动创建的 */

         closesocket (sock);

         sock = sd;

         while ()

         {

              /*

                socket数据接收函数,如果recv工作在阻塞模式,使用这个函数注意以下事项:

                1. 此函数的溢出时间受到Net_Config.c中宏定义 BSD_RCVTOUT 的限制。溢出时间到会自动退出。

                2. 这个函数接收到一次数据包就会返回,大于或者小于设置的缓冲区大小都没有关系,如果数据量

                   大于接收缓冲区大小,用户只需多次调用函数recv进行接收即可。

                3. 实际接收到数据大小通过判断此函数的返回值即可。

              */

              res = recv (sock, dbuf, sizeof(dbuf), );

              if (res <= )

              {

                   printf_debug("退出接收函数,重新开始监听%s\r\n", ReVal_Table[abs(res)]);

                   break;

              }

              else

              {

                   printf_debug("Receive Data Length = %d\r\n", res);

                   switch(dbuf[])

                   {

                        /* 字符命令 1 */

                       case '':

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '\r';

                            sendbuf[] = '\n';                       

                            res = send (sock, (char *)sendbuf, , );

                            if (res < )

                            {

                                 printf_debug("函数send发送数据失败\r\n");

                            }

                            else

                            {

                                 printf_debug("函数send发送数据成功\r\n");                                

                            }

                            break;

                       /* 其它数值不做处理 */

                       default:                    

                            break;

                   }

              }

         }

         /*

            溢出时间到,远程设备断开连接等,程序都会执行到这里,我们在这里关闭socket,

            程序返回到第一个大while循环的开头重新创建socket并监听。

         */

         closesocket (sock);

     }

}

19.9 网络调试助手和板子的操作步骤

我们这里使用下面这款调试助手,任何其它网络调试助手均可,不限制:http://bbs.armfly.com/read.php?tid=1568

19.9.1 获取板子IP地址

首先,强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址,而且在前面的配置向导使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址。测试方法如下:

  1. WIN+R组合键打开“运行”窗口,输入cmd。
  2. 弹出的命令窗口中,输入ping armfly。
  3. 输入ping armfly后,回车。

获得IP地址是192.168.1.11。

19.9.2 网络调试助手创建TCP客户端

  • 打开调试助手,点击左上角创建连接:

  • 弹出如下界面,类型选择TCP,目标IP设置为192.168.1.11,端口号1001,最后点击“创建”按钮。

特别说明,我们这里直接填局域网域名armfly也是没有问题的,即下面这样:

  • 点击“创建”按钮后的界面效果如下:

  • 点击“连接”按钮后的界面效果如下:

连接上后,串口软件也会打印出如下信息(波特率115200,数据位8,奇偶校验位无,停止位1):

19.9.3 BSD Socket数据收发

板子和网络调试助手建立连接后就可以相互收发数据了。程序的测试方法也比较简单,通过网络调试助手给板子发送不同的字符,板子回复不同的数据。

  1. 网络调试助手发送命令字符1,板子回复字符1到8以及回车和换行两个字符,共10个。 
  2. 网络调试助手发送命令字符2,板子回复1024个字符,前4个字符是abcd,最后4个字符是efgh,中间的1016个全部是字符0。

19.10         实验例程说明(RTX)

19.10.1   STM32F407开发板实验

配套例子:

V5-1024_RL-TCPnet实验_BSD Socket服务器之TCP(RTX)

实验目的:

  1. 学习RL-TCPnet的socket服务器创建和数据收发。

实验内容:

  1. 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
  2. 本例程创建了一个socket服务器,采用的TCP通信协议,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,本地端口被设置为1024。
  3. 用户可以在电脑端用网络调试软件创建TCP Client连接此服务器。
  4. 网络调试助手发送命令字符1,板子回复字符1到8以及回车和换行两个字符,共10个。
  5. 网络调试助手发送命令字符2,板子回复1024个字符,前4个字符是abcd,最后4个字符是efgh,中间的1016个全部是字符0。

实验操作:

详见本章节19.9小节。

配置向导文件设置(Net_Config.c):

详见本章节19.6小节。

调试文件设置(Net_Debug.c):

详见本章节19.7小节。

RTX配置:

RTX配置向导详情如下:

Task Configuration

(1)Number of concurrent running tasks

允许创建7个任务,实际创建了如下6个任务:

AppTaskUserIF任务   :按键消息处理。

AppTaskLED任务     :LED闪烁。

AppTaskMsgPro任务 :按键检测。

AppTaskSocket任务  :socket服务器任务。

AppTaskTCPMain任务:RL-TCPnet网络主任务。

AppTaskStart任务  :启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

(2)Number of tasks with user-provided stack

创建的6个任务都是采用自定义堆栈方式。

(3)Run in privileged mode

设置任务运行在非特权级模式。

RTX任务调试信息:

程序设计:

任务栈大小分配:

static uint64_t AppTaskUserIFStk[1024/8];   /* 任务栈 */

static uint64_t AppTaskLEDStk[1024/8];      /* 任务栈 */

static uint64_t AppTaskMsgProStk[1024/8];  /* 任务栈 */

static uint64_t AppTaskSocketStk[2048/8];   /* 任务栈 */

static uint64_t AppTaskTCPMainStk[2048/8]; /* 任务栈 */

static uint64_t AppTaskStartStk[1024/8];     /* 任务栈 */

将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数、浮点运算和uint64_t类型数据运算会出问题。

系统栈大小分配:

RTX初始化:

/*

*********************************************************************************************************

*    函 数 名: main

*    功能说明: 标准c程序入口。

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 创建启动任务 */

     os_sys_init_user (AppTaskStart,              /* 任务函数 */

                       ,                         /* 任务优先级 */

                       &AppTaskStartStk,          /* 任务栈 */

                       sizeof(AppTaskStartStk));  /* 任务栈大小,单位字节数 */

     while();

}

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*

*********************************************************************************************************

*    函 数 名: bsp_Init

*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次

*    形    参:无

*    返 回 值: 无

*********************************************************************************************************

*/

void bsp_Init(void)

{

     /*

         由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。

         启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。

         系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件

     */

     /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

     bsp_InitDWT();     /* 初始化DWT */

     bsp_InitUart();    /* 初始化串口 */

     bsp_InitKey();    /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */

     bsp_InitLed();    /* 初始LED指示灯端口 */

}

RTX任务创建:

/*

*********************************************************************************************************

*    函 数 名: AppTaskCreate

*    功能说明: 创建应用任务

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

static void AppTaskCreate (void)

{

     HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskUserIFStk,         /* 任务栈 */

                                           sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */

     HandleTaskLED = os_tsk_create_user(AppTaskLED,              /* 任务函数 */

                                        ,                       /* 任务优先级 */

                                        &AppTaskLEDStk,          /* 任务栈 */

                                        sizeof(AppTaskLEDStk));  /* 任务栈大小,单位字节数 */

     HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskMsgProStk,         /* 任务栈 */

                                           sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */

     HandleTaskSocket = os_tsk_create_user(AppTaskSocket,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskSocketStk,         /* 任务栈 */

                                           sizeof(AppTaskSocketStk)); /* 任务栈大小,单位字节数 */

    HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskTCPMainStk,         /* 任务栈 */

                                           sizeof(AppTaskTCPMainStk)); /* 任务栈大小,单位字节数 */

}

六个RTX任务的实现:

/*

*********************************************************************************************************

*    函 数 名: AppTaskUserIF

*    功能说明: 按键消息处理     

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 1  (数值越小优先级越低,这个跟uCOS相反)

*********************************************************************************************************

*/

__task void AppTaskUserIF(void)

{

     uint8_t ucKeyCode;

    while()

    {

         ucKeyCode = bsp_GetKey();

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1键按下 */

                   case KEY_DOWN_K1:

                       printf("K1键按下\r\n");        

                       break;  

                   /* K2键按下 */

                   case KEY_DOWN_K2:

                       printf("K2键按下\r\n");

                       break;

                   /* K3键按下 */

                   case KEY_DOWN_K3:

                       printf("K3键按下\r\n");

                       break;

                   /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

         os_dly_wait();

     }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskLED

*    功能说明: LED闪烁。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 2 

*********************************************************************************************************

*/

__task void AppTaskLED(void)

{

     const uint16_t usFrequency = ; /* 延迟周期 */

     /* 设置延迟周期 */

     os_itv_set(usFrequency);

    while()

    {

         bsp_LedToggle();

         /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/

         os_itv_wait();

    }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskMsgPro

*    功能说明: 按键检测

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 3 

*********************************************************************************************************

*/

__task void AppTaskMsgPro(void)

{

    while()

    {

         bsp_KeyScan();

         os_dly_wait();

    }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskSocket

*    功能说明: RL-TCPnet测试任务

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 4 

*********************************************************************************************************

*/

__task void AppTaskSocket(void)

{

     while ()

     {

         TCPnetTest();

     }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskTCPMain

*    功能说明: RL-TCPnet网络主任务

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 5 

*********************************************************************************************************

*/

__task void AppTaskTCPMain(void)

{

     while ()

     {

          /* RL-TCPnet处理函数 */

         main_TcpNet();

         os_dly_wait();

     }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskStart

*    功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 6 

*********************************************************************************************************

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

     /* 创建任务 */

     AppTaskCreate();

     os_itv_set ();

    while()

    {

         os_itv_wait ();

         /* RL-TCPnet时间基准更新函数 */

         timer_tick ();

    }

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,这里是创建了一个采用TCP通信协议的BSD Socket Server。

#include "includes.h" 

/*

*********************************************************************************************************

*                                      用于本文件的调试

*********************************************************************************************************

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

*********************************************************************************************************

*                              宏定义,本地端口

*********************************************************************************************************

*/

/* 这个是本地端口 */

#define LocalPort_NUM    1001

/*

*********************************************************************************************************

*                                         变量

*********************************************************************************************************

*/

/* RL-TCPnet API的返回值 */

const char * ReVal_Table[]=

{

     " 0: SCK_SUCCESS       Success                             ",

     "-1: SCK_ERROR         General Error                       ",

     "-2: SCK_EINVALID      Invalid socket descriptor           ",

     "-3: SCK_EINVALIDPARA  Invalid parameter                   ",

     "-4: SCK_EWOULDBLOCK   It would have blocked.              ",

     "-5: SCK_EMEMNOTAVAIL  Not enough memory in memory pool    ",

     "-6: SCK_ECLOSED       Connection is closed or aborted     ",

     "-7: SCK_ELOCKED       Socket is locked in RTX environment ",

     "-8: SCK_ETIMEOUT      Socket, Host Resolver timeout       ",

     "-9: SCK_EINPROGRESS   Host Name resolving in progress     ",

     "-10: SCK_ENONAME      Host Name not existing              ",

};

uint8_t sendbuf[];

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, sd, res;

     SOCKADDR_IN addr;

     SOCKADDR_IN ReAddr;

     while ()

     {

          /* 创建一个socket

            第1个参数AF_INET:当前仅支持这个类型的地址族。

            第2个参数SOCK_STREAM:表示数据流通信类型,即使用的TCP。

            第3个参数0 :配置为0的话,自动跟第2个参数进行协议匹配,这里就是TCP协议。

         */

         sock = socket (AF_INET, SOCK_STREAM, );

          /* 端口号设置为1001 */

         addr.sin_port        = htons(LocalPort_NUM);

         /* 与函数socket中的AF_INET作用一样 */

         addr.sin_family      = PF_INET;

         /*

            INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或所有地址,

            任意地址。用在这里的话就表示监控端口号为ddr.sin_port的所有IP地址消息。一般主要用

            于有多个网卡或者IP地址的情况。开发板只用了DM9161的网口,就是监听这个网口的IP地址。

         */

         addr.sin_addr.s_addr = INADDR_ANY;

          /* 给socket绑定IP和端口号 */

         bind (sock, (SOCKADDR *)&addr, sizeof(addr));

          /* 设置监听,最大监听1个连接 */

         listen (sock, );

          /*

            等待soket连接请求,有的话,自动创建1个新的socket进行连接通信,没有的话,等待连接。

            注意,能够accept的个数受到listen函数的限制,而listen函数又受到Net_Config.c中宏定义

            BSD_NUMSOCKS 的限制。

         */

         len = sizeof(ReAddr);

         sd = accept (sock, (SOCKADDR *)&ReAddr, &len);

         printf_debug ("远程客户端请求连接IP: %d.%d.%d.%d\n", ReAddr.sin_addr.s_b1,

                                                             ReAddr.sin_addr.s_b2,

                                                                       ReAddr.sin_addr.s_b3,

                                                             ReAddr.sin_addr.s_b4);

         printf_debug ("远程客户端端口号: %d\n", ntohs(ReAddr.sin_port));

          /* 关闭监听socket,这个监听socket是调用函数socket后自动创建的 */

         closesocket (sock);

         sock = sd;

         while ()

         {

              /*

                socket数据接收函数,如果recv工作在阻塞模式,使用这个函数注意以下事项:

                1. 此函数的溢出时间受到Net_Config.c中宏定义 BSD_RCVTOUT 的限制。溢出时间到会自动退出。

                2. 这个函数接收到一次数据包就会返回,大于或者小于设置的缓冲区大小都没有关系,如果数据量

                   大于接收缓冲区大小,用户只需多次调用函数recv进行接收即可。

                3. 实际接收到数据大小通过判断此函数的返回值即可。

              */

              res = recv (sock, dbuf, sizeof(dbuf), );

              if (res <= )

              {

                   printf_debug("退出接收函数,重新开始监听%s\r\n", ReVal_Table[abs(res)]);

                   break;

              }

              else

              {

                   printf_debug("Receive Data Length = %d\r\n", res);

                   switch(dbuf[])

                   {

                        /* 字符命令 1 */

                       case '':

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '\r';

                            sendbuf[] = '\n';                       

                            res = send (sock, (char *)sendbuf, , );

                            if (res < )

                            {

                                 printf_debug("函数send发送数据失败\r\n");

                            }

                            else

                            {

                                 printf_debug("函数send发送数据成功\r\n");                                

                            }

                            break;

                        /* 字符命令 2 */

                       case '':

                            /* 将数据缓冲区清成字符0,方便网络调试助手查看数据 */

                            len = sizeof(sendbuf);

                            memset(sendbuf, , len);

                            /* 这里仅初始化了数据包的前4个字节和最后4个字节 */

                            sendbuf[] = 'a';

                            sendbuf[] = 'b';

                            sendbuf[] = 'c';

                            sendbuf[] = 'd';

                            sendbuf[len - ] = 'e';

                            sendbuf[len - ] = 'f';

                            sendbuf[len - ] = 'g';

                            sendbuf[len - ] = 'h';                  

                            res = send (sock, (char *)sendbuf, len, );

                            if (res < )

                            {

                                 printf_debug("函数send发送数据失败%s\r\n", ReVal_Table[abs(res)]);

                            }

                            else

                            {

                                 printf_debug("函数send成功发送数据 = %d字节\r\n", res);                                

                            }

                            break;

                       /* 其它数值不做处理 */

                       default:                    

                            break;

                   }

              }

         }

         /*

            溢出时间到,远程设备断开连接等,程序都会执行到这里,我们在这里关闭socket,

            程序返回到第一个大while循环的开头重新创建socket并监听。

         */

         closesocket (sock);

     }

}

19.10.2   STM32F429开发板实验

配套例子:

V5-1024_RL-TCPnet实验_BSD Socket服务器之TCP(RTX)

实验目的:

  1. 学习RL-TCPnet的socket服务器创建和数据收发。

实验内容:

  1. 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
  2. 本例程创建了一个socket服务器,采用的TCP通信协议,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,本地端口被设置为1024。
  3. 用户可以在电脑端用网络调试软件创建TCP Client连接此服务器。
  4. 网络调试助手发送命令字符1,板子回复字符1到8以及回车和换行两个字符,共10个。
  5. 网络调试助手发送命令字符2,板子回复1024个字符,前4个字符是abcd,最后4个字符是efgh,中间的1016个全部是字符0。

实验操作:

详见本章节19.9小节。

配置向导文件设置(Net_Config.c):

详见本章节19.6小节。

调试文件设置(Net_Debug.c):

详见本章节19.7小节。

RTX配置:

RTX配置向导详情如下:

Task Configuration

(1)Number of concurrent running tasks

允许创建7个任务,实际创建了如下6个任务:

AppTaskUserIF任务   :按键消息处理。

AppTaskLED任务     :LED闪烁。

AppTaskMsgPro任务 :按键检测。

AppTaskSocket任务  :socket服务器任务

AppTaskTCPMain任务:RL-TCPnet网络主任务。

AppTaskStart任务  :启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

(2)Number of tasks with user-provided stack

创建的6个任务都是采用自定义堆栈方式。

(3)Run in privileged mode

设置任务运行在非特权级模式。

RTX任务调试信息:

程序设计:

任务栈大小分配:

static uint64_t AppTaskUserIFStk[1024/8];   /* 任务栈 */

static uint64_t AppTaskLEDStk[1024/8];      /* 任务栈 */

static uint64_t AppTaskMsgProStk[1024/8];  /* 任务栈 */

static uint64_t AppTaskSocketStk[2048/8];   /* 任务栈 */

static uint64_t AppTaskTCPMainStk[2048/8]; /* 任务栈 */

static uint64_t AppTaskStartStk[1024/8];     /* 任务栈 */

将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数、浮点运算和uint64_t类型数据运算会出问题。

系统栈大小分配:

RTX初始化:

/*

*********************************************************************************************************

*    函 数 名: main

*    功能说明: 标准c程序入口。

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 创建启动任务 */

     os_sys_init_user (AppTaskStart,              /* 任务函数 */

                       ,                         /* 任务优先级 */

                       &AppTaskStartStk,          /* 任务栈 */

                       sizeof(AppTaskStartStk));  /* 任务栈大小,单位字节数 */

     while();

}

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*

*********************************************************************************************************

*    函 数 名: bsp_Init

*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次

*    形    参:无

*    返 回 值: 无

*********************************************************************************************************

*/

void bsp_Init(void)

{

     /*

         由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。

         启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。

         系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件

     */

     /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

     SystemCoreClockUpdate();    /* 根据PLL配置更新系统时钟频率变量 SystemCoreClock */

     bsp_InitDWT();      /* 初始化DWT */

     bsp_InitUart();     /* 初始化串口 */

     bsp_InitKey();     /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */

     bsp_InitExtIO();    /* FMC总线上扩展了32位输出IO, 操作LED等外设必须初始化 */

     bsp_InitLed();      /* 初始LED指示灯端口 */

}

RTX任务创建:

/*

*********************************************************************************************************

*    函 数 名: AppTaskCreate

*    功能说明: 创建应用任务

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

static void AppTaskCreate (void)

{

     HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskUserIFStk,         /* 任务栈 */

                                           sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */

     HandleTaskLED = os_tsk_create_user(AppTaskLED,              /* 任务函数 */

                                        ,                       /* 任务优先级 */

                                        &AppTaskLEDStk,          /* 任务栈 */

                                        sizeof(AppTaskLEDStk));  /* 任务栈大小,单位字节数 */

     HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskMsgProStk,         /* 任务栈 */

                                           sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */

     HandleTaskSocket = os_tsk_create_user(AppTaskSocket,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskSocketStk,         /* 任务栈 */

                                           sizeof(AppTaskSocketStk)); /* 任务栈大小,单位字节数 */

    HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskTCPMainStk,         /* 任务栈 */

                                           sizeof(AppTaskTCPMainStk)); /* 任务栈大小,单位字节数 */

}

六个RTX任务的实现:

/*

*********************************************************************************************************

*    函 数 名: AppTaskUserIF

*    功能说明: 按键消息处理     

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 1  (数值越小优先级越低,这个跟uCOS相反)

*********************************************************************************************************

*/

__task void AppTaskUserIF(void)

{

     uint8_t ucKeyCode;

    while()

    {

         ucKeyCode = bsp_GetKey();

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1键按下 */

                   case KEY_DOWN_K1:

                       printf("K1键按下\r\n");        

                       break;  

                   /* K2键按下 */

                   case KEY_DOWN_K2:

                       printf("K2键按下\r\n");

                       break;

                   /* K3键按下 */

                   case KEY_DOWN_K3:

                       printf("K3键按下\r\n");

                       break;

                   /* 其他的键值不处理 */

                   default:                     

                       break;

              }

         }

         os_dly_wait();

     }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskLED

*    功能说明: LED闪烁。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 2 

*********************************************************************************************************

*/

__task void AppTaskLED(void)

{

     const uint16_t usFrequency = ; /* 延迟周期 */

     /* 设置延迟周期 */

     os_itv_set(usFrequency);

    while()

    {

         bsp_LedToggle();

         /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/

         os_itv_wait();

    }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskMsgPro

*    功能说明: 按键检测

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 3 

*********************************************************************************************************

*/

__task void AppTaskMsgPro(void)

{

    while()

    {

         bsp_KeyScan();

         os_dly_wait();

    }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskSocket

*    功能说明: RL-TCPnet测试任务

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 4 

*********************************************************************************************************

*/

__task void AppTaskSocket(void)

{

     while ()

     {

         TCPnetTest();

     }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskTCPMain

*    功能说明: RL-TCPnet网络主任务

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 5 

*********************************************************************************************************

*/

__task void AppTaskTCPMain(void)

{

     while ()

     {

          /* RL-TCPnet处理函数 */

         main_TcpNet();

         os_dly_wait();

     }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskStart

*    功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 6 

*********************************************************************************************************

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

     /* 创建任务 */

     AppTaskCreate();

     os_itv_set ();

    while()

    {

         os_itv_wait ();

         /* RL-TCPnet时间基准更新函数 */

         timer_tick ();

    }

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,这里是创建了一个采用TCP通信协议的BSD Socket Server。

#include "includes.h" 

/*

*********************************************************************************************************

*                                      用于本文件的调试

*********************************************************************************************************

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

*********************************************************************************************************

*                              宏定义,本地端口

*********************************************************************************************************

*/

/* 这个是本地端口 */

#define LocalPort_NUM    1001

/*

*********************************************************************************************************

*                                         变量

*********************************************************************************************************

*/

/* RL-TCPnet API的返回值 */

const char * ReVal_Table[]=

{

     " 0: SCK_SUCCESS       Success                             ",

     "-1: SCK_ERROR         General Error                       ",

     "-2: SCK_EINVALID      Invalid socket descriptor           ",

     "-3: SCK_EINVALIDPARA  Invalid parameter                   ",

     "-4: SCK_EWOULDBLOCK   It would have blocked.              ",

     "-5: SCK_EMEMNOTAVAIL  Not enough memory in memory pool    ",

     "-6: SCK_ECLOSED       Connection is closed or aborted     ",

     "-7: SCK_ELOCKED       Socket is locked in RTX environment ",

     "-8: SCK_ETIMEOUT      Socket, Host Resolver timeout       ",

     "-9: SCK_EINPROGRESS   Host Name resolving in progress     ",

     "-10: SCK_ENONAME      Host Name not existing              ",

};

uint8_t sendbuf[];

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, sd, res;

     SOCKADDR_IN addr;

     SOCKADDR_IN ReAddr;

     while ()

     {

          /* 创建一个socket

            第1个参数AF_INET:当前仅支持这个类型的地址族。

            第2个参数SOCK_STREAM:表示数据流通信类型,即使用的TCP。

            第3个参数0 :配置为0的话,自动跟第2个参数进行协议匹配,这里就是TCP协议。

         */

         sock = socket (AF_INET, SOCK_STREAM, );

          /* 端口号设置为1001 */

         addr.sin_port        = htons(LocalPort_NUM);

         /* 与函数socket中的AF_INET作用一样 */

         addr.sin_family      = PF_INET;

         /*

            INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或所有地址,

            任意地址。用在这里的话就表示监控端口号为ddr.sin_port的所有IP地址消息。一般主要用

            于有多个网卡或者IP地址的情况。开发板只用了DM9161的网口,就是监听这个网口的IP地址。

         */

         addr.sin_addr.s_addr = INADDR_ANY;

          /* 给socket绑定IP和端口号 */

         bind (sock, (SOCKADDR *)&addr, sizeof(addr));

          /* 设置监听,最大监听1个连接 */

         listen (sock, );

          /*

            等待soket连接请求,有的话,自动创建1个新的socket进行连接通信,没有的话,等待连接。

            注意,能够accept的个数受到listen函数的限制,而listen函数又受到Net_Config.c中宏定义

            BSD_NUMSOCKS 的限制。

         */

         len = sizeof(ReAddr);

         sd = accept (sock, (SOCKADDR *)&ReAddr, &len);

         printf_debug ("远程客户端请求连接IP: %d.%d.%d.%d\n", ReAddr.sin_addr.s_b1,

                                                             ReAddr.sin_addr.s_b2,

                                                                       ReAddr.sin_addr.s_b3,

                                                             ReAddr.sin_addr.s_b4);

         printf_debug ("远程客户端端口号: %d\n", ntohs(ReAddr.sin_port));

          /* 关闭监听socket,这个监听socket是调用函数socket后自动创建的 */

         closesocket (sock);

         sock = sd;

         while ()

         {

              /*

                socket数据接收函数,如果recv工作在阻塞模式,使用这个函数注意以下事项:

                1. 此函数的溢出时间受到Net_Config.c中宏定义 BSD_RCVTOUT 的限制。溢出时间到会自动退出。

                2. 这个函数接收到一次数据包就会返回,大于或者小于设置的缓冲区大小都没有关系,如果数据量

                   大于接收缓冲区大小,用户只需多次调用函数recv进行接收即可。

                3. 实际接收到数据大小通过判断此函数的返回值即可。

              */

              res = recv (sock, dbuf, sizeof(dbuf), );

              if (res <= )

              {

                   printf_debug("退出接收函数,重新开始监听%s\r\n", ReVal_Table[abs(res)]);

                   break;

              }

              else

              {

                   printf_debug("Receive Data Length = %d\r\n", res);

                   switch(dbuf[])

                   {

                        /* 字符命令 1 */

                       case '':

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '';

                            sendbuf[] = '\r';

                            sendbuf[] = '\n';                       

                            res = send (sock, (char *)sendbuf, , );

                            if (res < )

                            {

                                 printf_debug("函数send发送数据失败\r\n");

                            }

                            else

                            {

                                 printf_debug("函数send发送数据成功\r\n");                                

                            }

                            break;

                        /* 字符命令 2 */

                       case '':

                            /* 将数据缓冲区清成字符0,方便网络调试助手查看数据 */

                            len = sizeof(sendbuf);

                            memset(sendbuf, , len);

                            /* 这里仅初始化了数据包的前4个字节和最后4个字节 */

                            sendbuf[] = 'a';

                            sendbuf[] = 'b';

                            sendbuf[] = 'c';

                            sendbuf[] = 'd';

                            sendbuf[len - ] = 'e';

                            sendbuf[len - ] = 'f';

                            sendbuf[len - ] = 'g';

                            sendbuf[len - ] = 'h';                  

                            res = send (sock, (char *)sendbuf, len, );

                            if (res < )

                            {

                                 printf_debug("函数send发送数据失败%s\r\n", ReVal_Table[abs(res)]);

                            }

                            else

                            {

                                 printf_debug("函数send成功发送数据 = %d字节\r\n", res);                                

                            }

                            break;

                       /* 其它数值不做处理 */

                       default:                    

                            break;

                   }

              }

         }

         /*

            溢出时间到,远程设备断开连接等,程序都会执行到这里,我们在这里关闭socket,

            程序返回到第一个大while循环的开头重新创建socket并监听。

         */

         closesocket (sock);

     }

}

19.11    总结

本章节就为大家讲解这么多,希望大家多做测试,争取可以熟练掌握这些API函数的使用。相对于前面章节的TCP和UDP编程,本章节的socket API函数还是要复杂些的,所以要多花点时间熟练掌握。

【RL-TCPnet网络教程】第19章 RL-TCPnet之BSD Socket服务器的更多相关文章

  1. 【安富莱TCPnet网络教程】HTTP通信实例

    第41章      HTTP超文本传输协议基础知识 本章节为大家讲解HTTP(HyperText Transfer Protocol,超文本传输协议),从本章节开始,正式进入嵌入式Web的设计和学习. ...

  2. 【RL-TCPnet网络教程】第21章 RL-TCPnet之高效的事件触发框架

    第21章       RL-TCPnet之高效的事件触发框架 本章节为大家讲解高效的事件触发框架实现方法,BSD Socket编程和后面章节要讲解到的FTP.TFTP和HTTP等都非常适合使用这种方式 ...

  3. 【RL-TCPnet网络教程】第26章 RL-TCPnet之DHCP应用

    第26章     RL-TCPnet之DHCP应用 本章节为大家讲解RL-TCPnet的DHCP应用,学习本章节前,务必要优先学习第25章的DHCP基础知识.有了这些基础知识之后,再搞本章节会有事半功 ...

  4. 【安富莱】【RL-TCPnet网络教程】第10章 RL-TCPnet网络协议栈移植(FreeRTOS)

    第10章     RL-TCPnet网络协议栈移植(FreeRTOS) 本章教程为大家讲解RL-TCPnet网络协议栈的FreeRTOS操作系统移植方式,学习了第6章讲解的底层驱动接口函数之后,移植就 ...

  5. 【RL-TCPnet网络教程】第9章 RL-TCPnet网络协议栈移植(uCOS-III)

    第9章        RL-TCPnet网络协议栈移植(uCOS-III) 本章教程为大家讲解RL-TCPnet网络协议栈的uCOS-III操作系统移植方式,学习了第6章讲解的底层驱动接口函数之后,移 ...

  6. 【RL-TCPnet网络教程】第32章 RL-TCPnet之Telnet服务器

    第32章      RL-TCPnet之Telnet服务器 本章节为大家讲解RL-TCPnet的Telnet应用,学习本章节前,务必要优先学习第31章的Telnet基础知识.有了这些基础知识之后,再搞 ...

  7. 【RL-TCPnet网络教程】第30章 RL-TCPnet之SNTP网络时间获取

    第30章      RL-TCPnet之SNTP网络时间获取 本章节为大家讲解RL-TCPnet的SNTP应用,学习本章节前,务必要优先学习第29章的NTP基础知识.有了这些基础知识之后,再搞本章节会 ...

  8. 【RL-TCPnet网络教程】第28章 RL-TCPnet之DNS应用

    第28章      RL-TCPnet之DNS应用 本章节为大家讲解RL-TCPnet的DNS应用,学习本章节前,务必要优先学习第27章的DNS基础知识.有了这些基础知识之后,再搞本章节会有事半功倍的 ...

  9. 【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端

    第20章      RL-TCPnet之BSD Socket客户端 本章节为大家讲解RL-TCPnet的BSD Socket,学习本章节前,务必要优先学习第18章的Socket基础知识.有了这些基础知 ...

随机推荐

  1. Java基础高级二(多线程)

    1.进程和线程的区别:线程是轻量级的,本省不会持太多资源,需要的时候向进程申请 2.线程的状态:创建,可执行,执行中,等待,休眠,阻塞 3.线程状态之间的转换 4.线程API:Thread类,Runn ...

  2. 字符串、数组方法实战--charAt(),split(),indexOf(),substring()

    这篇随笔根据两个面试题来实战一下数组.字符串的一些方法. 题一:一个字符串中找出出现次数最多的字符次数 var str = 'fuuhuhuhufaihuhfnkjNKCNIO';
 function ...

  3. C++类大小

    对于C++中类的大小,主要针对于无成员的空类大小,编译器会对该类进行优化,情况主要分为是否有虚表(虚函数)两种类型,对于无虚函数的类,该类大小均为1个字节(编译器插入一个char表示该类的存在),而出 ...

  4. 关于HTML页面布局要注意的问题

    1.用百分比设定元素宽度可能造成的错误 很多同学习惯使用百分比来设定页面元素(例如div,以下称作盒子,方便介绍)的宽度,这样做可能造成未知的错误,最常见的就是当页面被缩小,或者屏幕分辨率降低时,由于 ...

  5. .NET与你若仅仅如初见(一)

    难忘初次见到你,那是一个夏日的午后,可是天空中乌云密布.大雨来临前的一段时间总是非常闷热的,当我朦胧的睡眼看到你之后瞬间就清醒了,感觉空气也凉爽了起来.尽管仅仅一眼但就是被你那清新脱俗沉鱼落雁之美所征 ...

  6. Linux简介与安装

    Linux系统的组成 Linux 内核:内核是系统的"心脏",是运行程序与管理像磁盘和打印机等硬件设备的核心程序. Linux Shell:Shell是系统的用户界面,提供了用户与 ...

  7. Promise异步操作

    Promise是es6中新增加的类(new Promise),目的为了管理JS中异步编程,也叫“Promise”设计模式 Promise用来解决异步问题.本身是同步的,只是用来管理异步编程的一种模式 ...

  8. 【洛谷P2584】【ZJOI2006】GameZ游戏排名系统题解

    [洛谷P2584][ZJOI2006]GameZ游戏排名系统题解 题目链接 题意: GameZ为他们最新推出的游戏开通了一个网站.世界各地的玩家都可以将自己的游戏得分上传到网站上.这样就可以看到自己在 ...

  9. 聊聊Flume和Logstash的那些事儿

    在某个Logstash的场景下,我产生了为什么不能用Flume代替Logstash的疑问,因此查阅了不少材料在这里总结,大部分都是前人的工作经验下,加了一些我自己的思考在里面,希望对大家有帮助. 本文 ...

  10. 解决logstash启动缓慢问题

    在部署logstash时,头几次启动时长还可以,最后高达半小时以上启动启动不了,上网查资料说,系统的“熵”过低,导致jruby启动缓慢.需要安装haveged.但是我安装完后还是慢 https://h ...