LWIP学习之Socket(应用篇)

LWIP学习之Socket(应用篇)

原文链接:https://blog.csdn.net/qq_39854159/article/details/120940325

Socket接口编程
https://blog.csdn.net/qq_39854159/article/details/120693512?spm=1001.2014.3001.5501

1 客户端和服务器流程图

LWIP学习之Socket(应用篇)
LWIP学习之Socket(应用篇)
其流程图就像打客服电话一样,服务器可以理解成中国移动客服电话一样,我们便是客户端。
服务器就需要使用Socket()注册一个电话,使用bind()绑定一个电话号码10086,listen()就是一个客服在监听着电话。然后进入accept()阻塞之中。毕竟这个时候没人打电话也不需要处理什么东西。
这个时候我们客户端也可以使用socket()注册一个电话,然后使用connect()拨打10086。
原本闲着没事的客服听到叮铃铃电话响了,就会从accept()的阻塞态跳出,期间会进行著名的TCP三握手。经过“喂~” “喂?” “喂~”之后,就可以投诉为什么给我张套餐费用巴拉巴拉……。然后10086给出他的解释。
客户端使用close()函数怒挂了电话。通过四次挥手之后,10086也使用close()挂上了电话。

代码分析:
若对以上流程图用到的基本socket API有不懂的可以看下我的另外一篇文章《Socket API篇》
这里代码就使用野火的《LwIP应用开发实战指南》中的例程。

2 服务器

 #include "tcpecho.h"

 #include "lwip/opt.h"

 #if LWIP_SOCKET
 #include <lwip/sockets.h>

 #include "lwip/sys.h"
 #include "lwip/api.h"
 /*--------------------------------------------------------------------*/

 #define PORT              5001
 #define RECV_DATA         (1024)


 static void
 tcpecho_thread(void *arg)
 {
     int sock = -1,connected;
     char *recv_data;
     struct sockaddr_in server_addr,client_addr;
     socklen_t sin_size;
     int recv_data_len;

     recv_data = (char *)pvPortMalloc(RECV_DATA); //申请1024字节内存,作为接收数据的存储区
     if (recv_data == NULL)
     {
         printf("No memory\n");                  //申请失败跳转到__exit
         goto __exit;
     }

     sock = socket(AF_INET, SOCK_STREAM, 0);    //创建一个socket套接字sock
     if (sock < 0)
     {
         printf("Socket error\n");				//创建失败跳转到__exit
         goto __exit;
     }

     server_addr.sin_family = AF_INET;        //选择IPV4协议族
     server_addr.sin_addr.s_addr = INADDR_ANY;//允许任何地址链接
     server_addr.sin_port = htons(PORT);      //设置端口号5001
     memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));

     if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)//使用bind()绑定之前配置的信息
     {
         printf("Unable to bind\n");
         goto __exit;
     }

     if (listen(sock, 5) == -1)//sock最多监听5个链接
     {
         printf("Listen error\n");
         goto __exit;
     }

     while (1)
     {
         sin_size = sizeof(struct sockaddr_in);

         connected = accept(sock, (struct sockaddr *)&client_addr, &sin_size);//阻塞等待客户端链接,若没有链接任务会在此进入一个阻塞态。

         printf("new client connected from (%s, %d)\n",
             inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));//打印客户端的地址端口信息
         {
             int flag = 1;

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

         while (1)
         {
             recv_data_len = recv(connected, recv_data, RECV_DATA, 0);
             //客户端连接上后会在此阻塞,等待接收数据

             if (recv_data_len <= 0)
             //若客户端断开连接,客户端发送FIN包,recv()会返回-1,跳出这个while循环回到accept()阻塞
                 break;

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

             write(connected,recv_data,recv_data_len);//写数据

         }
         if (connected >= 0)
             closesocket(connected);//若之前有客户的连接,这里会关闭客户端连接

         connected = -1;
     }
 __exit:
     if (sock >= 0) closesocket(sock);//服务器socket创建成功,则关闭服务器socket
     if (recv_data) free(recv_data);//若接收内存申请成功,则释放接收内存
 }

 void
 tcpecho_init(void)
 {
     sys_thread_new("tcpecho_thread", tcpecho_thread, NULL, 512, 4);
 }

3 客户端

 #include "client.h"

 #include "lwip/opt.h"

 #include "lwip/sys.h"
 #include "lwip/api.h"

 #include <lwip/sockets.h>

 #define PORT              5001
 #define IP_ADDR        "192.168.0.181"

 static void client(void *thread_param)
 {
     int sock = -1;
     struct sockaddr_in client_addr;

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

     while (1)
     {
         sock = socket(AF_INET, SOCK_STREAM, 0);//创建一个客户端socket叫sock
         if (sock < 0)
         {//若创建失败延时10ms继续创建
             printf("Socket error\n");
             vTaskDelay(10);
             continue;
         }

         client_addr.sin_family = AF_INET;//服务器设置的地址协议族IPV4
         client_addr.sin_port = htons(PORT);//服务器设置的端口号
         client_addr.sin_addr.s_addr = inet_addr(IP_ADDR);//服务器的地址
         memset(&(client_addr.sin_zero), 0, sizeof(client_addr.sin_zero));

         if (connect(sock,
                     (struct sockaddr *)&client_addr,
                     sizeof(struct sockaddr)) == -1)//连接服务器
         {//若连接失败则关闭socket()延时10ms跳出while(1)
             printf("Connect failed!\n");
             closesocket(sock);
             vTaskDelay(10);
             continue;
         }

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

         while (1)
         {
             if (write(sock,send_buf,sizeof(send_buf)) < 0)//写数据
                 break;

             vTaskDelay(1000);
         }

         closesocket(sock);//关闭套接字
     }

 }

 void
 client_init(void)
 {
     sys_thread_new("client", client, NULL, 512, 4);
 }

4 不足之处

以上是野火提供的socket的用法是重复型服务器,一次只能处理一个连接。当其他客户端使用connect()连接之后,若客户端不close(),那么服务器便会阻塞在recv()函数处。正确客户端连接便不会被服务器响应。

5 解决思路

使用select()函数。在recv()之前使用select()先检测是否正确的连接到来。
select()函数的返回值

当监视的相应的文件描述符集中满足条件时,比如说读文件描述符集中有数据到来时,内核(I/O)根据状态修 改文件描述符集,并返回一个大于0 的数。
当没有满足条件的文件描述符,且设置的timeval 监控时间超时时,select函数会返回一个为0的值。
当select返回负值时,发生错误。
现在FD队列中添加需要监控的客户端,如按键和com端口。
例子参考:

    FD_ZERO(&rds); 
    FD_SET(button_fd,&rds); //添加按键fd
    FD_SET(com,&rds)//添加com fd

    ret=select(fd+1,&rds,NULL,NULL,NULL); 

    if(ret<0) 
    { 
        printf("select failure\n"); 
        exit(1); 
    } 

    if(ret==0) 
    { 
        printf("select timeout\n"); 

    } 
     
    else if(FD_ISSET(button_fd,&rds)) //按键客户端连接
    { 
        read(button_fd,&but_status,sizeof(but_status));           
    } 
  
 
   else if(FD_ISSET(com_fd,&rds)) //com客户端连接
    { 
        read(.......);           
    } 

上一篇:Amplify Shader Editor(ASE)效果实现笔记(二)——顶点动画(原理向)


下一篇:【三维空间刚体运动表示】