IPv6 Socket API实例

IPv4/v6 Socket编程异同

在基于Socket API进行开发的应用程序中,IPv4/v6都使用基本相同的编程模型;如connect、accept、listen、send/sendto、recv/recvfrom等Socket API函数在IPv4/v6中用法也基本一致,而IPv4与IPv6间的主要差异体现在了与地址相关的API函数上,其具体差异如下图所示:

sockapi

getaddrinfo()函数在lwIP中实现不完善,这里只介绍inet_pton()函数的用法:

函数原型:inet_pton(int family, const char _strptr, void _addrptr)

返回值:若成功返回1,若输入的不是有效表达式返回0,若出错则返回-1

参数:family:可以是AF_INET,也可以是AF_INET6,如果以不被支持的地址族作为参数,函数将返回错误,并置errno为EAFNOSUPPORT

作用:该函数尝试转换由strptr指向的字符串,并通过addrptr指针存放二进制结果,完成了表达式到IPv6地址的转换。

关于getaddrinfo()、inet_ntop()函数的用法请参见Unix网络编程卷一。

PC测试程序

往往对IPv4的TCP/UDP的SERVer/Client进行测试时,Windows下有许多网络调试工具,而IPv6的测试主要是在Linux下利用Socket API编写的测试程序,具体请见GitHub,请在Linux的命令行下完成测试。

TCP Server例子

下面是一个在RT-Thread上使用BSD socket接口的TCP服务端例子。当把这个代码加入到RT-Thread时,它会自动向finsh命令行添加一个tcpserver6命令,在finsh上执行tcpserver6()函数即可启动这个TCP服务端,该TCP服务端在端口10001上进行监听。

当有TCP客户向这个服务端进行连接后,只要服务端接收到数据,它就会立即向客户端发送“This is TCP Server from RT-Thread.”的字符串。

如果服务端接收到q或Q字符串时,服务器将主动关闭这个TCP连接。如果服务端接收到exit字符串时,那么将退出服务。

  1. #include <rtthread.h>
  2. #include <lwip/sockets.h> /* 使用BSD Socket需包含socket.h */
  3.  
  4. #define SERV_PORT 10001 /* 服务器使用的端口号 */
  5. #define BUF_SIZE 1024 /* 接收缓冲区的长度 */
  6. #define BACKLOG 5 /* 请求队列的长度 */
  7.  
  8. static const char send_data[] = "This is TCP Server from RT-Thread.";
  9.  
  10. void tcpserver6(void)
  11. {
  12. int sockfd, clientfd;
  13. struct sockaddr_in6 server_addr6, client_addr6;
  14. int bytes_received;
  15. char *recv_data;
  16. rt_uint32_t sin_size;
  17. rt_bool_t stop = RT_FALSE;
  18.  
  19. /* 分配接收用的数据缓冲 */
  20. recv_data = rt_malloc(BUF_SIZE);
  21. if(recv_data == RT_NULL)
  22. {
  23. /* 分配内存失败,返回 */
  24. rt_kprintf("No memory\n");
  25. return ;
  26. }
  27.  
  28. /* 创建一个socket,family类型为PF_INET6,套接字类型为SOCK_STREAM */
  29. if((sockfd = socket(PF_INET6, SOCK_STREAM, 0)) == -1)
  30. {
  31. rt_kprintf("Socket error\n");
  32. rt_free(recv_data);
  33. return ;
  34. }
  35.  
  36. /* 初始化服务端地址 */
  37. server_addr6.sin6_family = AF_INET6;
  38. memcpy(server_addr6.sin6_addr.s6_addr, IP6_ADDR_ANY, 16);
  39. server_addr6.sin6_port = htons(SERV_PORT);
  40.  
  41. /* 绑定Socket到服务端地址 */
  42. if(bind(sockfd, (struct sockaddr *)&server_addr6, sizeof(struct sockaddr)) == -1)
  43. {
  44. rt_kprintf("Bind error\n");
  45. rt_free(recv_data);
  46. return ;
  47. }
  48. /* 在Socket上进行监听 */
  49. if(listen(sockfd, BACKLOG) == -1)
  50. {
  51. rt_kprintf("Listen error\n");
  52. rt_free(recv_data);
  53. return ;
  54. }
  55.  
  56. rt_sprintf(recv_data, "%4d", SERV_PORT);
  57. rt_kprintf("\nTCPServer Waiting for client on port %s...\n", recv_data);
  58.  
  59. while(stop != RT_TRUE)
  60. {
  61. sin_size = sizeof(struct sockaddr_in6);
  62. /*接收客户端的连接*/
  63. clientfd = accept(sockfd, (struct sockaddr *)&client_addr6, &sin_size);
  64. rt_kprintf("I got a connection from (IP:%s, PORT:%d\n)", inet6_ntoa(client_addr6.sin6_addr), ntohs(client_addr6.sin6_port));
  65. /* 客户端连接的处理 */
  66. while(1)
  67. {
  68. /* 发送数据到connected socket */
  69. send(clientfd, send_data, strlen(send_data), 0);
  70.  
  71. /* 接收数据,但并不一定能够收到BUF_SIZE大小的数据 */
  72. bytes_received = recv(clientfd, recv_data, BUF_SIZE, 0);
  73. if(bytes_received <= 0)
  74. {
  75. /* 接收失败,关闭这个Connected Socket */
  76. closesocket(clientfd);
  77. break;
  78. }
  79. /* 收到数据,加入字符串结束符*/
  80. recv_data[bytes_received] = '\0';
  81. if(strcmp(recv_data, "q") == 0 || strcmp(recv_data, "Q") == 0)
  82. {
  83. /* 关闭连接 */
  84. closesocket(clientfd);
  85. break;
  86. }
  87. else if(strcmp(recv_data, "exit") == 0)
  88. {
  89. /* 关闭服务端 */
  90. closesocket(clientfd);
  91. stop = RT_TRUE;
  92. break;
  93. }
  94. else
  95. {
  96. /* 打印收到的数据 */
  97. rt_kprintf("RECEIVED DATA = %s\n", recv_data);
  98. }
  99. }
  100. }
  101.  
  102. closesocket(sockfd);
  103. rt_free(recv_data);
  104.  
  105. return ;
  106. }
  107. #ifdef RT_USING_FINSH
  108. #include <finsh.h>
  109. FINSH_FUNCTION_EXPORT(tcpserver6, start tcp server via ipv6 );
  110. #endif

TCP Client例子

下面则是另一个如在RT-Thread上使用BSD socket接口的TCP客户端例子。当把这个代码加入到RT-Thread时,它会自动向finsh 命令行添加一个tcpclient6命令,在finsh上执行tcpclient6()函数即可启动这个TCP服务端。

当TCP客户端连接成功时,它会接收服务端传过来的数据。当有数据接收到时,如果是以q或Q开头,它将主动断开这个连接;否则会把接收的数据在控制终端中打印出来,然后发送“This is TCP Client from RT-Thread.”的字符串。

  1. #include <rtthread.h>
  2. #include <lwip/netdb.h> /*为解析主机名,需包含netdb.h */
  3. #include <lwip/sockets.h>
  4.  
  5. #define SERV_PORT 12345 /* 服务器端口号 */
  6. #define SERVADDR "4006:e024:680:c6e:223:8bff:fe59:de90" /* 由于未实现Scope id,请不要使用link-local Address */
  7. #define BUF_SIZE 1024 /* 缓冲区的长度 */
  8. static const char send_data[] = "This is TCP Client from RT-Thread.";
  9.  
  10. void tcpclient6(void)
  11. {
  12. char* recv_data;
  13. int sockfd, bytes_received;
  14. struct sockaddr_in6 server_addr6;
  15. int status = 0;
  16.  
  17. /* 分配接收用的数据缓冲 */
  18. recv_data = rt_malloc(BUF_SIZE);
  19. if(recv_data == RT_NULL)
  20. {
  21. /* 分配内存失败,返回 */
  22. rt_kprintf("No memory\n");
  23. return ;
  24. }
  25.  
  26. /* 创建一个socket,family类型为PF_INET6,套接字类型为SOCK_STREAM */
  27. if((sockfd = socket(PF_INET6, SOCK_STREAM, 0)) == -1)
  28. {
  29. rt_kprintf("Socket error\n");
  30. rt_free(recv_data);
  31. return ;
  32. }
  33.  
  34. /* 初始化预连接的服务端地址 */
  35. memset(&server_addr6, 0, sizeof(server_addr6));
  36. server_addr6.sin6_family = AF_INET6;
  37. server_addr6.sin6_port = htons(SERV_PORT);
  38. /* 将字符串转换为IPv6地址 */
  39. if(inet_pton(AF_INET6, SERVADDR, &server_addr6.sin6_addr.s6_addr) != 1)
  40. {
  41. rt_kprintf("inet_pton() error\n");
  42. rt_free(recv_data);
  43. return ;
  44. }
  45.  
  46. /* 连接到服务端 */
  47. status = connect(sockfd, (struct sockaddr *)&server_addr6, sizeof(server_addr6));
  48. if(status < 0)
  49. {
  50. /* 连接失败 */
  51. rt_kprintf("Connect error:%d\n", status);
  52. rt_free(recv_data);
  53. return ;
  54. }
  55.  
  56. while(1)
  57. {
  58. /* 从socket连接中接收最大BUF_SIZE字节数据 */
  59. bytes_received = recv(sockfd, recv_data, BUF_SIZE -1, 0);
  60. if(bytes_received <= 0)
  61. {
  62. /* 接收失败,关闭这个连接 */
  63. closesocket(sockfd);
  64. rt_free(recv_data);
  65. break;
  66. }
  67. /* 收到数据,加入字符串结束符*/
  68. recv_data[bytes_received] = '\0';
  69. if(strcmp(recv_data, "q") == 0 || strcmp(recv_data, "Q") == 0)
  70. {
  71. /* 关闭这个连接 */
  72. closesocket(sockfd);
  73. rt_free(recv_data);
  74. break;
  75. }
  76. else
  77. {
  78. /* 打印收到的数据 */
  79. rt_kprintf("\nReceived data = %s ", recv_data);
  80. }
  81. /* 发送数据到服务端 */
  82. send(sockfd, send_data, strlen(send_data), 0);
  83. }
  84.  
  85. return;
  86. }
  87.  
  88. #ifdef RT_USING_FINSH
  89. #include <finsh.h>
  90. FINSH_FUNCTION_EXPORT(tcpclient6, startup tcp client via ipv6);
  91. #endif

UDP Server例子

下面是一个在RT-Thread上使用BSD socket接口的UDP服务端例子,当把这个代码加入到RT-Thread操作系统时,它会自动向finsh命令行添加一个udpserver6命令,在finsh上执行udpserver6()函数即可启动这个UDP服务端,该UDP服务端在端口10012上进行监听。

当服务端接收到数据时,它将把数据打印到控制终端中;如果服务端接收到exit字符串时,那么服务端将退出服务。

  1. #include <rtthread.h>
  2. #include <lwip/sockets.h>
  3.  
  4. #define SERV_PORT 10012
  5. #define BUF_SIZE 1024
  6.  
  7. static const char send_data[] = "This is UDP Server from RT-Thread.";
  8.  
  9. void udpserver6(void)
  10. {
  11. int sockfd;
  12. struct sockaddr_in6 server_addr6;
  13. struct sockaddr_in6 client_addr6;
  14. int bytes_read;
  15. char *recv_data;
  16. rt_uint32_t addr_len;
  17.  
  18. /* 分配接收用的数据缓冲 */
  19. recv_data = rt_malloc(BUF_SIZE);
  20. if(recv_data == RT_NULL)
  21. {
  22. /* 分配内存失败,返回 */
  23. rt_kprintf("No memory\n");
  24. return ;
  25. }
  26.  
  27. /* 创建一个socket,family类型为PF_INET6,套接字类型为SOCK_DGRAM */
  28. if((sockfd = socket(AF_INET6, SOCK_DGRAM, 0)) == -1)
  29. {
  30. rt_kprintf("Socket error\n");
  31. rt_free(recv_data);
  32. return ;
  33. }
  34.  
  35. /* 初始化服务端地址 */
  36. server_addr6.sin6_family = AF_INET6;
  37. server_addr6.sin6_port = htons(SERV_PORT);
  38. memcpy(server_addr6.sin6_addr.s6_addr, IP6_ADDR_ANY, 16);
  39.  
  40. /* 绑定Socket到服务端地址 */
  41. if(bind(sockfd, (struct sockaddr *)&server_addr6, sizeof(struct sockaddr)) == -1)
  42. {
  43. /* 绑定地址失败 */
  44. rt_kprintf("Bind error\n");
  45. rt_free(recv_data);
  46. return ;
  47. }
  48. rt_sprintf(recv_data, "%4d", SERV_PORT);
  49. rt_kprintf("UDPServer Waiting for client on port %s...\n", recv_data);
  50.  
  51. addr_len = sizeof(struct sockaddr);
  52. while(1)
  53. {
  54. /* 从socket中收取最大BUF_SIZE字节数据 */
  55. bytes_read = recvfrom(sockfd, recv_data, BUF_SIZE - 1, 0, (struct sockaddr *)&client_addr6, &addr_len);
  56. /* 收到数据,加入字符串结束符*/
  57. recv_data[bytes_read] = '\0';
  58. /* 输出接收的数据 */
  59. rt_kprintf("\n(%s, %d) said:", inet6_ntoa(client_addr6.sin6_addr), ntohs(client_addr6.sin6_port));
  60. rt_kprintf("%s", recv_data);
  61.  
  62. if(strcmp(recv_data, "exit") == 0)
  63. {
  64. /* 关闭服务端 */
  65. closesocket(sockfd);
  66. rt_free(recv_data);
  67. break;
  68. }
  69. }
  70. return ;
  71. }
  72.  
  73. #ifdef RT_USING_FINSH
  74. #include <finsh.h>
  75. FINSH_FUNCTION_EXPORT(udpserver6, startup udp server via ipv6);
  76. #endif

UDP Client例子

下面是另一个在RT-Thread上使用BSD socket接口的UDP客户端例子。当把这个代码加入到RT-Thread时,它会自动向finsh命令行添加一个udpclient6命令,在finsh上执行udpclient6()函数即可启动这个UDP客户端。当UDP客户端启动后,它将发送“This is UDP Client from RT-Thread.”的字符串给服务端,然后接收数据并打印数据。

  1. #include <rtthread.h>
  2. #include <lwip/netdb.h> /*为解析主机名,需包含netdb.h */
  3. #include <lwip/sockets.h>
  4.  
  5. #define SERV_PORT 22345 /* 服务器端口号 */
  6. #define SERVADDR "4006:e024:680:c6e:223:8bff:fe59:de90" /* 由于未实现Scope id,请不要使用link-local Address */
  7. #define BUF_SIZE 1024 /* 缓冲区的长度 */
  8. static const char send_data[] = "This is UDP Client from RT-Thread.";
  9.  
  10. void udpclient6(void)
  11. {
  12. char *recv_data;
  13. int sockfd;
  14. struct sockaddr_in6 server_addr6, client_addr6;
  15. socklen_t clientlen;
  16.  
  17. /* 分配接收用的数据缓冲 */
  18. recv_data = rt_malloc(BUF_SIZE);
  19. if(recv_data == RT_NULL)
  20. {
  21. rt_kprintf("No memory\n");
  22. return ;
  23. }
  24.  
  25. /* 创建一个socket,family类型为PF_INET6,套接字类型为SOCK_DGRAM */
  26. if((sockfd = socket(PF_INET6, SOCK_DGRAM, 0)) == -1)
  27. {
  28. rt_kprintf("Socket error\n");
  29. rt_free(recv_data);
  30. return ;
  31. }
  32.  
  33. /* 初始化预连接的服务端地址 */
  34. memset(&server_addr6, 0, sizeof(server_addr6));
  35. server_addr6.sin6_family = AF_INET6;
  36. server_addr6.sin6_port = htons(SERV_PORT);
  37. /* 将字符串转换为IPv6地址 */
  38. if(inet_pton(AF_INET6, SERVADDR, &server_addr6.sin6_addr.s6_addr) != 1)
  39. {
  40. rt_kprintf("inet_pton() error\n");
  41. rt_free(recv_data);
  42. return ;
  43. }
  44.  
  45. /* 发送数据到服务端 */
  46. if(sendto(sockfd, send_data, sizeof(recv_data), 0, (struct sockaddr *)&server_addr6, sizeof(server_addr6)) < 0)
  47. {
  48. rt_kprintf("Sendto error\n");
  49. rt_free(recv_data);
  50. return ;
  51. }
  52.  
  53. rt_kprintf("Waiting for a reply...\n");
  54.  
  55. clientlen = sizeof(client_addr6);
  56. /* 接收数据 */
  57. if(recvfrom(sockfd, recv_data, BUF_SIZE, 0, (struct sockaddr *)&client_addr6, &clientlen) < 0)
  58. {
  59. /* 接收失败 */
  60. rt_kprintf("Recvfrom error\n");
  61. rt_free(recv_data);
  62. return ;
  63. }
  64. /* 打印数据 */
  65. rt_kprintf("got '%s'\n", recv_data);
  66. closesocket(sockfd);
  67. }
  68. #ifdef RT_USING_FINSH
  69. #include <finsh.h>
  70. FINSH_FUNCTION_EXPORT(udpclient6, start udp server via ipv6);
  71. #endif