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

时间:2023-11-24 13:50:56

第20章      RL-TCPnet之BSD Socket客户端

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

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

20.1  初学者重要提示

20.2  BSD Socket相关的头文件定义

20.3  BSD Socket函数

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

20.5  函数htonl,htons,ntohl和ntohs

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

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

20.8  BSD Socket通信的实现方法

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

20.10  实验例程说明(RTX)

20.11  总结

20.1  初学者重要提示

  1. 学习本章节前,务必保证已经学习了第18章的基础知识。
  2. 本章要掌握的函数稍多,可以先学会基本的使用,然后再深入了解这些函数使用时的注意事项,争取能够熟练运用。
  3. Socket编程的这些函数要熟练使用还是要花些时间和精力的,每个函数的注意事项在函数讲解和例子中都有说明,需要大家多做练习,实际体会下。
  4. BSD Socket编程需要多任务的支持,所以没有做裸机的例子,而RTX、uCOS-III和FreeRTOS的都做了。
  5. BSD Socket客户端的例子相比服务器的例子稍麻烦些,因为客户端的例子需要用户知道远程服务器的IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个宏定义在文件app_tcpnet_lib.c开头。具体按照本章节20.9小节进行操作即可。

20.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条说明。

20.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 文件:

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

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

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

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

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

20.3.1 函数socket

函数原型:

int socket (

    int family,         /* 地址族  */

    int type,           /* 通信类型 */

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

函数描述:

函数socket用于创建一个socket。

  1. 第1个参数用于指定地址族,当前仅支持AF_INET。
  2. 第2个参数是通信类型,有如下两种可选。【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端
  3. 第3个参数是协议类型,支持以下三种。【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端
  4. 返回值有以下几种:
    • 创建成功的话,返回socket的句柄。
    • 返回SCK_EINVALIDPARA,表示函数参数无效或者参数不支持。
    • 返回SCK_ERROR,表示创建失败。

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

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

使用举例:

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, res;

     unsigned long sck_mode;

     SOCKADDR_IN addr;

     while ()

     {

         /* 创建一个socket

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

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

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

         */

         sock = socket (AF_INET, SOCK_STREAM, );

          /* 设置使能KEEP ALIVE,让客户端和服务器保存连接 */

         sck_mode = ;

         res = ioctlsocket (sock, FIO_KEEP_ALIVE, &sck_mode);

         if (res == SCK_SUCCESS)

         {

              printf_debug("KEEP ALIVE设置成功\r\n");

         }

         else

         {

              printf_debug("KEEP ALIVE设置失败\r\n");

         }

         /* 端口号设置为1001 */

         addr.sin_port = htons(PORT_NUM);

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

         addr.sin_family = PF_INET;

         addr.sin_addr.s_b1 = IP1;

         addr.sin_addr.s_b2 = IP2;

         addr.sin_addr.s_b3 = IP3;

         addr.sin_addr.s_b4 = IP4;

         /* 客户端连接远程服务器,如果远程服务器还未创建,此函数会立即返回 */

         res = connect (sock, (SOCKADDR *)&addr, sizeof (addr));

         printf_debug("客户端连接远程服务器状态%s\r\n", ReVal_Table[abs(res)]);

         /* 省略 */

     }

}

20.3.2 函数connect

函数原型:

int connect (

    int sock,          /* Socket 句柄 */

    SOCKADDR *addr,    /* 远程地址指针变量*/

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

函数描述:

函数用于配置要连接的远程IP地址和端口,如果是SOCK_STREAM类型socket,将跟远程IP建立连接,这种情况主要用于本地客户端连接远程服务器,其实就是TCP通信。如果是SOCK_DGRAM类型的socket,调用此函数起到一个地址过滤的作用,设置要通信的远程IP和端口号,其实就是UDP通信,而用户再次调用这个函数,可以换一个远程IP和端口号。

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

  1. 第1个参数是Socket句柄,即函数socket的返回值。
  2. 第2个参数是SOCKADDR类型结构体指针变量,此结构体变量中定义了要远程访问的IP地址和端口号。
  3. 第3个参数是结构体变量SOCKADDR的大小,单位字节。
  4. 返回值有以下几种:
    • 返回SCK_SUCCESS,表示连接成功。
    • 返回SCK_EINVALID,表示函数socket句柄参数无效。
    • 返回SCK_EINVALIDPARA,表示其它参数无效或者不支持。
    • 返回SCK_EWOULDBLOCK,表示函数connect要进入阻塞态,而此函数是工作在非阻塞方式。
    • 返回SCK_ECLOSED,表示远程端点已经关闭连接。
    • 返回SCK_ERROR,表示socket底层的UDP或者TCP通信出错。

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

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

使用举例:

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, res;

     unsigned long sck_mode;

     SOCKADDR_IN addr;

     while ()

     {

         /* 创建一个socket

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

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

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

         */

         sock = socket (AF_INET, SOCK_STREAM, );

          /* 设置使能KEEP ALIVE,让客户端和服务器保存连接 */

         sck_mode = ;

         res = ioctlsocket (sock, FIO_KEEP_ALIVE, &sck_mode);

         if (res == SCK_SUCCESS)

         {

              printf_debug("KEEP ALIVE设置成功\r\n");

         }

         else

         {

              printf_debug("KEEP ALIVE设置失败\r\n");

         }

         /* 端口号设置为1001 */

         addr.sin_port = htons(PORT_NUM);

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

         addr.sin_family = PF_INET;

         addr.sin_addr.s_b1 = IP1;

         addr.sin_addr.s_b2 = IP2;

         addr.sin_addr.s_b3 = IP3;

         addr.sin_addr.s_b4 = IP4;

         /* 客户端连接远程服务器,如果远程服务器还未创建,此函数会立即返回 */

         res = connect (sock, (SOCKADDR *)&addr, sizeof (addr));

         printf_debug("客户端连接远程服务器状态%s\r\n", ReVal_Table[abs(res)]);

         /* 省略 */

     }

}

20.3.3 函数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. 返回负值表示错误。所有错误类型代表的数值,详见本章节20.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;

         /* 省略 */

     }

}

20.3.4 函数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. 返回负值表示错误。所有错误类型代表的数值,详见本章节20.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;

         /* 省略 */

     }

}

20.3.5 函数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. 返回负值表示错误。所有错误类型代表的数值,详见本章节20.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;

         /* 省略 */

     }

}

20.3.6 函数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个参数是消息标志,有如下两种选择:【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端一般情况下,这个参数填数值0即可,表示这两个选择都不使用。
  5. 返回值有以下几种:
    • 返回大于0的数值,表示复制到接收数据缓冲区的数据大小,单位字节。
    • 返回SCK_EINVALID,表示函数socket句柄参数无效。
    • 返回SCK_ECLOSED,表示远程连接已经关闭。
    • 返回SCK_EWOULDBLOCK,表示函数recv要进入阻塞态,而此函数是工作在非阻塞方式。
    • 返回SCK_ETIMEOUT,表示阻塞模式的情况下,等待的时间超时。
    • 返回SCK_ELOCKED,表示其它任务正在使用此socket。

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

  1. 调用此函数之前,务必优先调用函数socket。
  2. 返回负值表示错误。所有错误类型代表的数值,详见本章节20.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, res;

     unsigned long sck_mode;

     SOCKADDR_IN addr;

     while ()

     {

         /* 创建一个socket

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

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

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

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 设置使能KEEP ALIVE,让客户端和服务器保存连接 */

         sck_mode = ;

         res = ioctlsocket (sock, FIO_KEEP_ALIVE, &sck_mode);

         if (res == SCK_SUCCESS)

         {

              printf_debug("KEEP ALIVE设置成功\r\n");

         }

         else

         {

              printf_debug("KEEP ALIVE设置失败\r\n");

         }

          /* 端口号设置为1001 */

         addr.sin_port = htons(PORT_NUM);

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

         addr.sin_family = PF_INET;

         addr.sin_addr.s_b1 = IP1;

         addr.sin_addr.s_b2 = IP2;

         addr.sin_addr.s_b3 = IP3;

         addr.sin_addr.s_b4 = IP4;

          /* 客户端连接远程服务器,如果远程服务器还未创建,此函数会立即返回 */

         res = connect (sock, (SOCKADDR *)&addr, sizeof (addr));

         printf_debug("客户端连接远程服务器状态%s\r\n", ReVal_Table[abs(res)]);

         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)]);

                   if((res == SCK_EINVALID) || (res == SCK_ECLOSED))

                   {

                        break;  

                   }

              }

              else

              {

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

              }

         }

         /*

            远程服务器断开连接和sock句柄无效,程序都会执行到这里,我们在这里关闭socket,

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

         */

         closesocket (sock);

     }

}

20.3.7 函数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个参数是消息标志,有如下两种选择:【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端一般情况下,这个参数填数值0即可,表示不使用这个选项。
  5. 返回值有以下几种:
    • 返回大于0的数值,表示已经成功发送的字节数。
    • 返回SCK_EINVALID,表示函数socket句柄参数无效。
    • 返回SCK_EINVALIDPARA,表示其它参数无效或者不支持。
    • 返回SCK_EWOULDBLOCK,表示函数recv要进入阻塞态,而此函数是工作在非阻塞方式。
    • 返回SCK_ECLOSED,表示远程连接已经关闭。
    • 返回SCK_ERROR,表示socket底层的UDP或者TCP通信出错。
    • 返回SCK_ELOCKED,表示其它任务正在使用此socket。

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

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

使用举例:

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, res;

     unsigned long sck_mode;

     SOCKADDR_IN addr;

     while ()

     {

         /* 创建一个socket

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

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

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

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 设置使能KEEP ALIVE,让客户端和服务器保存连接 */

         sck_mode = ;

         res = ioctlsocket (sock, FIO_KEEP_ALIVE, &sck_mode);

         if (res == SCK_SUCCESS)

         {

              printf_debug("KEEP ALIVE设置成功\r\n");

         }

         else

         {

              printf_debug("KEEP ALIVE设置失败\r\n");

         }

         /* 端口号设置为1001 */

         addr.sin_port = htons(PORT_NUM);

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

         addr.sin_family = PF_INET;

         addr.sin_addr.s_b1 = IP1;

         addr.sin_addr.s_b2 = IP2;

         addr.sin_addr.s_b3 = IP3;

         addr.sin_addr.s_b4 = IP4;

         /* 客户端连接远程服务器,如果远程服务器还未创建,此函数会立即返回 */

         res = connect (sock, (SOCKADDR *)&addr, sizeof (addr));

         printf_debug("客户端连接远程服务器状态%s\r\n", ReVal_Table[abs(res)]);

         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)]);

                   if((res == SCK_EINVALID) || (res == SCK_ECLOSED))

                   {

                       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;

                   }

              }

         }

         /*

            远程服务器断开连接和sock句柄无效,程序都会执行到这里,我们在这里关闭socket,

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

         */

         closesocket (sock);

     }

}

20.3.8 函数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. 第1个参数是Socket句柄,即函数socket的返回值。

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

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

使用举例:

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, res;

     unsigned long sck_mode;

     SOCKADDR_IN addr;

     while ()

     {

         /* 创建一个socket

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

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

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

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 设置使能KEEP ALIVE,让客户端和服务器保存连接 */

         sck_mode = ;

         res = ioctlsocket (sock, FIO_KEEP_ALIVE, &sck_mode);

         if (res == SCK_SUCCESS)

         {

              printf_debug("KEEP ALIVE设置成功\r\n");

         }

         else

         {

              printf_debug("KEEP ALIVE设置失败\r\n");

         }

         /* 端口号设置为1001 */

         addr.sin_port = htons(PORT_NUM);

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

         addr.sin_family = PF_INET;

         addr.sin_addr.s_b1 = IP1;

         addr.sin_addr.s_b2 = IP2;

         addr.sin_addr.s_b3 = IP3;

         addr.sin_addr.s_b4 = IP4;

         /* 客户端连接远程服务器,如果远程服务器还未创建,此函数会立即返回 */

         res = connect (sock, (SOCKADDR *)&addr, sizeof (addr));

         printf_debug("客户端连接远程服务器状态%s\r\n", ReVal_Table[abs(res)]);

         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)]);

                   if((res == SCK_EINVALID) || (res == SCK_ECLOSED))

                   {

                       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;

                   }

              }

         }

         /*

            远程服务器断开连接和sock句柄无效,程序都会执行到这里,我们在这里关闭socket,

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

         */

         closesocket (sock);

     }

}

20.3.9 函数ioctlsocket

函数原型:

int ioctlsocket (

    int sock,              /* Socket 句柄 */

    long cmd,              /* 命令 */

    unsigned long *argp);  /* 命令参数指针变量 */

函数描述:

函数ioctlsocket用于配置socket的模式,任何类型的socket都可以使用此函数配置。

  1. 第1个参数是Socket句柄,即函数socket的返回值。
  2. 第2个参数是设置命令,支持的命令如下:【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端
  3. 第3个参数是命令参数,针对第2条中的每个命令都有相应可配置的参数。
  • 命令FIONBIO可设置的参数如下: 【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端
  • 命令FIO_DELAY_ACK可设置的参数如下: 【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端
  • 命令FIO_FLOW_CTRL可设置的参数如下: 【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端
  • 命令FIO_KEEP_ALIVE可设置的参数如下: 【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端

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

  1. 调用此函数之前,务必优先调用函数socket。
  2. 返回负值表示错误。所有错误类型代表的数值,详见本章节20.2小节。
  3. 工程是工作在多任务环境,即用户使能了RTX操作系统或者其它RTOS(注意,其它RTOS是无法识别的,需要在MDK中Option->C/C++的预定义宏中加上__RTX才可以识别,这个在前面相应RTOS的移植章节有说明),阻塞模式是自动使能的。

使用举例:

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, res;

     unsigned long sck_mode;

     SOCKADDR_IN addr;

     while ()

     {

         /* 创建一个socket

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

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

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

         */

          sock = socket (AF_INET, SOCK_STREAM, );

          /* 设置使能KEEP ALIVE,让客户端和服务器保存连接 */

         sck_mode = ;

         res = ioctlsocket (sock, FIO_KEEP_ALIVE, &sck_mode);

         if (res == SCK_SUCCESS)

         {

              printf_debug("KEEP ALIVE设置成功\r\n");

         }

         else

         {

              printf_debug("KEEP ALIVE设置失败\r\n");

         }

         /* 端口号设置为1001 */

         addr.sin_port = htons(PORT_NUM);

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

         addr.sin_family = PF_INET;

         addr.sin_addr.s_b1 = IP1;

         addr.sin_addr.s_b2 = IP2;

         addr.sin_addr.s_b3 = IP3;

         addr.sin_addr.s_b4 = IP4;

         /* 客户端连接远程服务器,如果远程服务器还未创建,此函数会立即返回 */

         res = connect (sock, (SOCKADDR *)&addr, sizeof (addr));

         printf_debug("客户端连接远程服务器状态%s\r\n", ReVal_Table[abs(res)]);

         /* 省略 */

     }

}

20.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,其它的函数同理。知道了这个,方便大家记住这几个函数。

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

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

【RL-TCPnet网络教程】第20章 RL-TCPnet之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,这点也要特别注意。

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

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

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

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

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

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

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。

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

Ethernet Network Interface

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

(1)MAC Address

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

(2)IP Address

IP地址。

(3)Subnet mask

子网掩码。

(4)Default Gateway

默认网关。

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

Ethernet Network Interface

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

(1)NetBIOS Name Service

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

(2)Dynaminc Host Configuration

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

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

UDP Sockets

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

(1)Number of UDP Sockets

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

范围1 – 20。

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

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接收窗口大小。

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

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风格的主机名解析。

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

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

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

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

Print Time Stamp

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

其它所有的选项

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

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

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

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

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

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

20.8 BSD Socket通信的实现方法

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

20.8.1 创建BSD Socket客户端

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

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, res;

     unsigned long sck_mode;

     SOCKADDR_IN addr;

     while ()

     {

         /* 创建一个socket

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

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

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

         */

         sock = socket (AF_INET, SOCK_STREAM, );

          /* 设置使能KEEP ALIVE,让客户端和服务器保存连接 */

         sck_mode = ;

         res = ioctlsocket (sock, FIO_KEEP_ALIVE, &sck_mode);

         if (res == SCK_SUCCESS)

         {

              printf_debug("KEEP ALIVE设置成功\r\n");

         }

         else

         {

              printf_debug("KEEP ALIVE设置失败\r\n");

         }

         /* 端口号设置为1001 */

         addr.sin_port = htons(PORT_NUM);

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

         addr.sin_family = PF_INET;

         addr.sin_addr.s_b1 = IP1;

         addr.sin_addr.s_b2 = IP2;

         addr.sin_addr.s_b3 = IP3;

         addr.sin_addr.s_b4 = IP4;

         /* 客户端连接远程服务器,如果远程服务器还未创建,此函数会立即返回 */

         res = connect (sock, (SOCKADDR *)&addr, sizeof (addr));

         printf_debug("客户端连接远程服务器状态%s\r\n", ReVal_Table[abs(res)]);

         /* 省略 */

     }

}

20.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来实现,使用这个函数除了要注意本章的20.3.6小节讲解的问题,还要注意下面要讲到的。

/*

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

*    函 数 名: AppTaskSocket

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

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 4 

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

*/

__task void AppTaskSocket(void)

{

     while ()

     {

         TCPnetTest();

     }

}

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

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, res;

     unsigned long sck_mode;

     SOCKADDR_IN addr;

     while ()

     {

         /* 创建一个socket

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

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

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

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 设置使能KEEP ALIVE,让客户端和服务器保存连接 */

         sck_mode = ;

         res = ioctlsocket (sock, FIO_KEEP_ALIVE, &sck_mode);

         if (res == SCK_SUCCESS)

         {

              printf_debug("KEEP ALIVE设置成功\r\n");

         }

         else

         {

              printf_debug("KEEP ALIVE设置失败\r\n");

         }

          /* 端口号设置为1001 */

         addr.sin_port = htons(PORT_NUM);

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

         addr.sin_family = PF_INET;

         addr.sin_addr.s_b1 = IP1;

         addr.sin_addr.s_b2 = IP2;

         addr.sin_addr.s_b3 = IP3;

         addr.sin_addr.s_b4 = IP4;

          /* 客户端连接远程服务器,如果远程服务器还未创建,此函数会立即返回 */

         res = connect (sock, (SOCKADDR *)&addr, sizeof (addr));

         printf_debug("客户端连接远程服务器状态%s\r\n", ReVal_Table[abs(res)]);

         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)]);

                   if((res == SCK_EINVALID) || (res == SCK_ECLOSED))

                   {

                       break;  

                   }

              }

              else

              {

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

              }

         }

         /*

            远程服务器断开连接和sock句柄无效,程序都会执行到这里,我们在这里关闭socket,

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

         */

         closesocket (sock);

     }

}

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

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

20.8.3 BSD Socket数据发送

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

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     char dbuf[];

     int len;

     int sock, res;

     unsigned long sck_mode;

     SOCKADDR_IN addr;

     while ()

     {

         /* 创建一个socket

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

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

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

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 设置使能KEEP ALIVE,让客户端和服务器保存连接 */

         sck_mode = ;

         res = ioctlsocket (sock, FIO_KEEP_ALIVE, &sck_mode);

         if (res == SCK_SUCCESS)

         {

              printf_debug("KEEP ALIVE设置成功\r\n");

         }

         else

         {

              printf_debug("KEEP ALIVE设置失败\r\n");

         }

         /* 端口号设置为1001 */

         addr.sin_port = htons(PORT_NUM);

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

         addr.sin_family = PF_INET;

         addr.sin_addr.s_b1 = IP1;

         addr.sin_addr.s_b2 = IP2;

         addr.sin_addr.s_b3 = IP3;

         addr.sin_addr.s_b4 = IP4;

         /* 客户端连接远程服务器,如果远程服务器还未创建,此函数会立即返回 */

         res = connect (sock, (SOCKADDR *)&addr, sizeof (addr));

         printf_debug("客户端连接远程服务器状态%s\r\n", ReVal_Table[abs(res)]);

         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)]);

                   if((res == SCK_EINVALID) || (res == SCK_ECLOSED))

                   {

                       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;

                   }

              }

         }

         /*

            远程服务器断开连接和sock句柄无效,程序都会执行到这里,我们在这里关闭socket,

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

         */

         closesocket (sock);

     }

}

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

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

20.9.1 获取板子IP地址

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

  1. WIN+R组合键打开“运行”窗口,输入cmd。【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端
  2. 弹出的命令窗口中,输入ping armfly。【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端
  3. 输入ping armfly后,回车。【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端

获得IP地址是192.168.1.11。

20.9.2 获取电脑的IP地址

获取电脑IP地址的方法很多,可以在网上邻居获取,也可以通过输入命令ipconfig获取,方法跟上面20.9.1小节中的方式一样:

  1. WIN+R组合键打开“运行”窗口,输入cmd。【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端
  2. 弹出的命令窗口中,输入ipconfig。【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端
  3. 输入ipconfig后,回车。

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

获得电脑的IP地址是192.168.1.2。

20.9.3 在程序中配置要访问的远程IP地址和端口

根据前面20.9.2小节获取的电脑端IP地址,需要大家配置程序中app_tcpnet_lib.c文件开头的宏定义,其中IP地址填前面获取的192.168.1.2,大家要根据电脑实际的IP地址填写。而端口号,我们这里随意配置一个即可,配置为1001,后面电脑端使用网络调试助手创建TCP服务器时,务必要跟这个端口号统一:

/*

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

*                              宏定义,远程服务器的IP和端口

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

*/

/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            2

#define PORT_NUM         1001

20.9.4 网络调试助手创建TCP服务器

  • 打开调试助手,点击左上角创建服务器:

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

  • 弹出如下界面,指定IP设置为192.168.1.2,一定要跟20.9.2小节中获得的电脑IP地址一致,端口号1001,最后点击确定:

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

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

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

  • 然后点击启动服务器:

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

如果开发板下载了socket客户端的程序,并且开发板已经上电,可以看到客户端连接已经加入:

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

20.9.5 BSD Socket数据收发

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

  • 网络调试助手发送命令字符1,板子回复字符1到8以及回车和换行两个字符,共10个。 【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端
  • 网络调试助手发送命令字符2,板子回复1024个字符,前4个字符是abcd,最后4个字符是efgh,中间的1016个全部是字符0。【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端

20.10         实验例程说明(RTX)

20.10.1   STM32F407开发板实验

配套例子:

V5-1027_RL-TCPnet实验_BSD Socket客户端之TCP(RTX)

实验目的:

  1. 学习RL-TCPnet的socket客户端创建和数据收发。

实验内容:

  1. 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。客户端的例子相比服务器的例子稍麻烦些,因为客户端的例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要根据实际情况重新配置,如果不会配置,看本例程对应的教程即可):

    #define IP1            192

    #define IP2            168

    #define IP3            1

    #define IP4            2

    #define PORT_NUM       1001

  2. 本例程创建了一个socket客户端,采用的TCP通信协议,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址。
  3. 测试例子前,需要用户先在电脑端用网络调试软件创建TCP Server,然后让板子的客户端去连接。
  4. 网络调试助手发送命令字符1,板子回复字符1到8以及回车和换行两个字符,共10个。
  5. 网络调试助手发送命令字符2,板子回复1024个字符,前4个字符是abcd,最后4个字符是efgh,中间的1016个全部是字符0。

实验操作:

详见本章节20.9小节。

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

详见本章节20.6小节。

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

详见本章节20.7小节。

RTX配置:

RTX配置向导详情如下:

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

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任务调试信息:

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

程序设计:

任务栈大小分配:

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类型数据运算会出问题。

系统栈大小分配:

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

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 Client。

#include "includes.h" 

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

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

*                              宏定义,远程服务器的IP和端口

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

*/

/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            2

#define PORT_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, res;

     unsigned long sck_mode;

     SOCKADDR_IN addr;

     while ()

     {

         /* 创建一个socket

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

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

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

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 设置使能KEEP ALIVE,让客户端和服务器保存连接 */

         sck_mode = ;

         res = ioctlsocket (sock, FIO_KEEP_ALIVE, &sck_mode);

         if (res == SCK_SUCCESS)

         {

              printf_debug("KEEP ALIVE设置成功\r\n");

         }

         else

         {

              printf_debug("KEEP ALIVE设置失败\r\n");

         }

         /* 端口号设置为1001 */

         addr.sin_port = htons(PORT_NUM);

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

         addr.sin_family = PF_INET;

         addr.sin_addr.s_b1 = IP1;

         addr.sin_addr.s_b2 = IP2;

         addr.sin_addr.s_b3 = IP3;

         addr.sin_addr.s_b4 = IP4;

         /* 客户端连接远程服务器,如果远程服务器还未创建,此函数会立即返回 */

         res = connect (sock, (SOCKADDR *)&addr, sizeof (addr));

         printf_debug("客户端连接远程服务器状态%s\r\n", ReVal_Table[abs(res)]);

          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)]);

                   if((res == SCK_EINVALID) || (res == SCK_ECLOSED))

                   {

                       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;

                   }

              }

         }

          /*

            远程服务器断开连接和sock句柄无效,程序都会执行到这里,我们在这里关闭socket,

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

         */

         closesocket (sock);

     }

}

20.10.2   STM32F429开发板实验

配套例子:

V6-1027_RL-TCPnet实验_BSD Socket客户端之TCP(RTX)

实验目的:

  1. 学习RL-TCPnet的socket客户端创建和数据收发。

实验内容:

  1. 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。客户端的例子相比服务器的例子稍麻烦些,因为客户端的例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要根据实际情况重新配置,如果不会配置,看本例程对应的教程即可):

    #define IP1            192

    #define IP2            168

    #define IP3            1

    #define IP4            2

    #define PORT_NUM       1001

  2. 本例程创建了一个socket客户端,采用的TCP通信协议,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址。
  3. 测试例子前,需要用户先在电脑端用网络调试软件创建TCP Server,然后让板子的客户端去连接。
  4. 网络调试助手发送命令字符1,板子回复字符1到8以及回车和换行两个字符,共10个。
  5. 网络调试助手发送命令字符2,板子回复1024个字符,前4个字符是abcd,最后4个字符是efgh,中间的1016个全部是字符0。

实验操作:

详见本章节20.9小节。

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

详见本章节20.6小节。

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

详见本章节20.7小节。

RTX配置:

RTX配置向导详情如下:

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

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任务调试信息:

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

程序设计:

任务栈大小分配:

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类型数据运算会出问题。

系统栈大小分配:

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

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 Client。

#include "includes.h" 

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

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

*                              宏定义,远程服务器的IP和端口

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

*/

/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            2

#define PORT_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, res;

     unsigned long sck_mode;

     SOCKADDR_IN addr;

     while ()

     {

         /* 创建一个socket

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

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

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

         */

         sock = socket (AF_INET, SOCK_STREAM, );

         /* 设置使能KEEP ALIVE,让客户端和服务器保存连接 */

         sck_mode = ;

         res = ioctlsocket (sock, FIO_KEEP_ALIVE, &sck_mode);

         if (res == SCK_SUCCESS)

         {

              printf_debug("KEEP ALIVE设置成功\r\n");

         }

         else

         {

              printf_debug("KEEP ALIVE设置失败\r\n");

         }

         /* 端口号设置为1001 */

         addr.sin_port = htons(PORT_NUM);

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

         addr.sin_family = PF_INET;

         addr.sin_addr.s_b1 = IP1;

         addr.sin_addr.s_b2 = IP2;

         addr.sin_addr.s_b3 = IP3;

         addr.sin_addr.s_b4 = IP4;

         /* 客户端连接远程服务器,如果远程服务器还未创建,此函数会立即返回 */

         res = connect (sock, (SOCKADDR *)&addr, sizeof (addr));

         printf_debug("客户端连接远程服务器状态%s\r\n", ReVal_Table[abs(res)]);

         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)]);

                   if((res == SCK_EINVALID) || (res == SCK_ECLOSED))

                   {

                       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;

                   }

              }

         }

          /*

            远程服务器断开连接和sock句柄无效,程序都会执行到这里,我们在这里关闭socket,

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

         */

         closesocket (sock);

     }

}

20.11    总结

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