Lwip TCP/UDP客户端、服务器详解

4 篇文章 0 订阅
1 篇文章 0 订阅

一、TCP客户端

        tcp客户端实现是比较简单的,大致分为以下几个步骤:

     (1)申请套接字。

     (2)绑定远端服务器的ip地址和端口。

     (3)连接远端服务器。

     (4)接收和发送数据。

#define PORT 5001
#define RECV_DATA (1024)
#define SERV_IP_ADDR "192.168.31.39"
#define SERV_PORT 5001

/*tcp客户端*/
void tcp_client(void *arg){
  int sock=-1;
  struct sockaddr_in Serv_addr;
  char*recv_data;
  int recv_data_len;
  /*为recv_data申请内存空间 申请成功返回内存空间首地址 失败返回NULL*/
  recv_data=(char*)pvPortMalloc(RECV_DATA);
  if(recv_data==NULL){
      printf("Mallo memory failed\r\n");
  }
  while(1){
    /* 为sockaddr_in结构体成员赋值,用于以下的connect绑定  参数protocol在TCP/UDP两种协议下均为0  */
    /*套接字申请成功返回Socket描述符(int类型) 失败返回-1*/
    sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
      printf("Socket error\n");
      vTaskDelay(10);
      continue;
    }
    /*TCP/IP – IPv4*/
    Serv_addr.sin_family=AF_INET;
    /*绑定远端服务器的端口*/
    Serv_addr.sin_port=htons(SERV_PORT);
    /*绑定远端服务器的ip*/
    Serv_addr.sin_addr.s_addr=inet_addr(SERV_IP_ADDR);
	  /* 清空sockaddr_in结构体内存空间   sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节 */
    memset(&(Serv_addr.sin_zero), 0, sizeof(Serv_addr.sin_zero)); 
     /* 连接远端服务器 */
    if (connect(sock, (struct sockaddr *)&Serv_addr, sizeof(struct sockaddr)) == -1) 
    {
			printf("Connect failed!\n");
			closesocket(sock);
			vTaskDelay(10);
			continue;
    }   
    printf("Connect to tcp server successful!\n");   
     while(1)
    {						
			/* 成功接收到数据,返回接收的数据长度 */
      recv_data_len = recv(sock, recv_data, RECV_DATA, 0);
			if (recv_data_len <= 0) 
        break;       
			/* 串口打印接收的数据内容 */
			printf("recv:%s\n",recv_data);
			/* 发送数据内容 */
			write(sock,recv_data,recv_data_len);				
    }
  }  
}

现象:

 电脑作为TCP服务器,单片机为TCP客户端来连接服务器,通过电脑服务端往单片机发送112233、555533,单片机接收到消息后将消息原路发送给电脑。

部分函数解析:

(1)int socket(int domain,int type,int protocol)

该函数用于申请套接字。

参数domain:套接字采用的协议簇,常用的有AF_INET--ipv4      AF_INET6--ipv6

参数type:套接字采用的服务类型,SOCK_STREAM表示可靠的面对连接的socket连接(TCP),SOCK_DGRAM提供面向消息的无保障连接(UDP),SOCK_RAW表示原始的套接字。

参数protocol:套接字所采用的协议,在TCP/UDP两种协议下均为0。

返回值:套接字申请成功返回Socket描述符(int类型) 失败返回-1。

(2)htons、ntohs、htonl、ntohl

这些函数用于大小端转换。

htons:host to network short long,将主机字节序转化成网络字节序,即将无符号16位整型转化成大端模数。

ntohs:network  to host short long,将网络字节序转化成主机字节序,即将大端模式转化成无符号16位整型。

htonl:host to network long 

ntohl:network to host long

htonl、ntohl两函数也类似,只不过是在无符号32位整型与网络字节序之间转换。

(3)inet_ntoa、inet_addr

inet_ntoa:将无符号32位地址数据(uint32_t)转化成char*类型的字符串。

inet_addr:将char*类型的字符串转化成无符号32位地址数据(uint32_t)。

(4) memset (void *, int, size_t)

该函数用于将一段内存中的值全转化成指定的值,此处 memset(&(Serv_addr.sin_zero), 0, sizeof(Serv_addr.sin_zero));将结构体sockaddr_in中成员sin_zero[SIN_ZERO_LEN]赋值为0,用于保证sockaddr与sockaddr_in两个数据结构保持大小相同。

(5)connect

该函数用于连接远端服务器,参数1为所申请的套接字,参数2为远端服务器,参数3为远端服务器字节长度。

(6)recv

该函数用于TCP接收数据,参数1为所申请的套接字,参数2为接收内存空间的起始地址,参数3为内存的大小,若成功接收数据返回数据长度,若接收失败返回-1。

(7)write

该函数TCP发送数据,参数1为所申请的套接字,参数2为发送数据的起始地址,参数3为发送数据的大小。

二、TCP服务器

        TCP服务器创建步骤如下:

      (1)申请套接字

      (2)绑定服务器本地的ip、端口等信息

      (3)监听连接请求,与客户端连接

      (4)接收和发送数据

/*单片机作为服务器*/
/*tcp 服务端*/
void tcp_serv(void *arg){
    struct sockaddr_in serv_addr,client_addr;
    char *recv_data;
    int sock=-1;
    int remote_sock;
    socklen_t client_addr_len;
    int recv_data_len;
  /*初始化客户端addr长度  一定要初始化!!!否则会出错,接收到的客户端ip为0.0.0.0*/
    client_addr_len = sizeof(struct sockaddr_in);
  /*为recv_data申请内存空间 申请成功返回内存空间首地址 失败返回NULL*/
    recv_data = (char *)pvPortMalloc(RECV_DATA);
  /* 为sockaddr_in结构体成员赋值,用于以下的connect绑定  参数protocol在TCP/UDP两种协议下均为0  */
  /*套接字申请成功返回Socket描述符(int类型) 失败返回-1*/   
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0){
        printf("Socket 创建失败, 错误代码:%d\n", errno);
    }else{
        printf("Socket 创建成功");
    }
     /*将服务器上的网卡ip地址绑定为服务器的ip地址*/
        serv_addr.sin_addr.s_addr = INADDR_ANY;
     /*TCP/IP – IPv4*/
        serv_addr.sin_family = AF_INET;
     /*绑定服务器的端口*/ 
        serv_addr.sin_port = htons(PORT); // 小端模式转为大端模式
     /* 清空sockaddr_in结构体内存空间   sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节 */
        memset(&(serv_addr.sin_zero), 0, sizeof(serv_addr.sin_zero));
     /*将服务器的信息与所申请的套接字绑定起来  绑定失败返回-1 */
    if (bind(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
    {
        printf("Unable to bind\r\n");
        goto __exit;
    }
    /*sock为服务器套接字  5为等待连接队列的最大长度*/
    if (listen(sock, 5) == -1)
    {
        printf("Listen error\r\n");
        goto __exit;
    }
    while (1){
    /*accept为阻塞性函数,一值阻塞直到有客户端连接,所连接的客户端ip等信息存储于client_addr中,成功返回套接字的文件描述符,失败返回-1*/
        remote_sock = accept(sock, (struct sockaddr *) &client_addr, &client_addr_len);
        printf("new client connected from (%s, %d)\n",
        inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        {
        int flag = 1;
        setsockopt(remote_sock,
        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为阻塞性函数,一直等待直至接收到数据  成功接收到数据,返回接收的数据长度 */
        recv_data_len = recv(remote_sock, recv_data, RECV_DATA, 0);
        if (recv_data_len <= 0)
        break;
        printf("recv %d len data\n",recv_data_len);
        /* 发送数据内容 */
        write(remote_sock,recv_data,recv_data_len);
        }
        if (remote_sock >= 0)
        closesocket(remote_sock);
        remote_sock = -1;
    }
 __exit:
if (sock >= 0) closesocket(sock);
if (recv_data) free(recv_data);
}

现象:

电脑作为TCP客户端,单片机作为TCP服务端,通过串口打印可知其ip地址为192.168.31.48,用TCP客户端 来连接该ip,连接成功后往单片机分别发送5555、6666,单片机收到消息以后原路发回客户端。需要注意TCP连接为面向连接的、可靠的通讯协议,服务器一次只能与一个客户端可靠连接,但是可以同时有多个客户端申请连接(等待连接)。

部分函数解释:

  (1) serv_addr.sin_addr.s_addr = INADDR_ANY

INADDR_ANY表示地址0.0.0.0,泛指本地的所有ip地址,上述代码即绑定本地的所有网卡IP地址作为服务器地址,因为有时候本地连接的不止一张网卡。

(2)socklen_t client_addr_len = sizeof(struct sockaddr_in)

此处要特别注意,不能初始化为NULL,否则无法接收客户端的ip等信息!!!!

三、UDP服务器

        udp是一种无连接、不可靠的协议,因此其连接与TCP相比更为简单,但是数据的传输没有保障,多用于音频、视频等数据数据传输(传输速度快)。

        udp服务器创建步骤如下:

      (1)申请套接字

      (2)绑定服务器本地ip、端口等信息

      (3)接收、发送数据

/*UDP服务端程序*/
static void udp_serv(void *arg){
  int sock=-1;;
  char*recv_data;
  struct sockaddr_in udp_addr,client_addr;
  int recv_data_len;
  socklen_t client_addr_len=sizeof(struct sockaddr);//必须初始化,否则无法接收
  while(1){
    recv_data=(char*)pvPortMalloc(RECV_DATA);//开辟接收缓存区  返回缓存区首地址
    if (recv_data == NULL)
      {
      printf("No memory\n");
      goto __exit;
      }
    /*创建udp套接字  此处为UDP连接,参数2为SOCK_DGRAM*/
    sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0){
      printf("Socker error\n");
      goto __exit;
      }
      /*TCP/IP – IPv4*/
      udp_addr.sin_family=AF_INET;
      /*绑定服务器本地ip*/
      udp_addr.sin_addr.s_addr=INADDR_ANY;
      /*绑定服务器的端口*/
      udp_addr.sin_port=htons(PORT);
      memset(&(udp_addr.sin_zero),0,sizeof(udp_addr.sin_zero));
      /*将服务器本地信息与套接字绑定*/
      if(bind(sock,(struct sockaddr*)&udp_addr,sizeof(struct sockaddr)) == -1)
      {
      printf("Unable to bind\n");
      goto __exit;
      }
      while(1){
        /*接收数据  客户端的信息存储于client_addr中  接收成功返回数据长度*/
        recv_data_len=recvfrom(sock,recv_data,RECV_DATA,0,(struct sockaddr*)&client_addr,&client_addr_len);
        /* 显示发送端的 IP 地址 */
        printf("receive from %s\n",inet_ntoa(client_addr.sin_addr));//char *ip4addr_ntoa(const ip4_addr_t *addr);将32位地址数据转化char*类型的字符串
                                                                    //ipaddr_addr(const char *cp)将char*类型的字符串转化成32位地址数据
        /* 显示发送端发来的字串 */
        printf("recevce:%s",recv_data);
        /* 将字串返回给发送端 */
        sendto(sock,recv_data,recv_data_len,0,(struct sockaddr*)&client_addr,client_addr_len);
      }
  }
__exit:
      if (sock >= 0) closesocket(sock);
      if (recv_data) free(recv_data);
}

现象:

 

 由于UDP是一种无连接的协议,因此UDP服务器可以同时被多个UDP客户端“连接”,这里单片机作为UDP服务器,我分别将手机和电脑作为两个UDP客户端,同时连接单片机,并隔500ms发送一次数据,通过电脑端和手机端的网络调试助手可知,单片机在接收到数据后均原路发回了,串口调试端打印的信息:192.168.31.151为手机端UDP的ip地址,192.168.31.39为电脑端UDP的ip地址,可见UDP协议的传输速度是比较快的。

部分函数解释:

(1)socket

此处采用的是UDP连接,第二个参数为SOCK_DGRAM

(2)recvfrom、sendto

这两个函数用于UDP连接中接收和发送数据。

recvfrom(int s,void *mem,size_t len,int flags,struct sockaddr *from,socklen_t *fromlen)

参数s:连接的套接字

参数mem:保存接收数据的内存首地址

参数len:接收数据缓冲区大小

参数from:保存消息发送端的ip、port等信息

参数fromlen:client_addr的大小

返回值:接收成功,返回接收数据长度,失败返回-1

 sendto(int s,const void *dataptr,size_t size,int flags,const struct sockaddr *to,socklen_t tolen)

参数s:连接的套接字

参数dataptr:需要发送的数据首地址

参数size:发送数据缓冲区大小

参数to:接收端的ip、port等信息

参数tolen:client_addr的大小

返回值:接收成功,返回发送数据长度,失败返回-1

四、UDP客户端

        因为UDP为无连接协议,因此直接往指定服务器发送数据即可。

        创建UDP客户端步骤如下:

       (1)申请创建套接字

       (2)绑定远端服务器ip、端口等信息

       (3)往服务器发送数据和接收数据

char test_buf[]="sense_long is nb";
/*UDP客户端程序*/
static void udp_client(void *arg){
  int sock=-1;
  //char*recv_data;
  struct sockaddr_in Serve_addr;
  //int recv_data_len;
  socklen_t addrlen=sizeof(struct sockaddr);//必须初始化,否则无法接收**************************
  while(1){
    //recv_data=(char*)pvPortMalloc(RECV_DATA);//开辟接收缓存区
    //if (recv_data == NULL)
    //  {
     // printf("No memory\n");
     // goto __exit;
     // }
    sock=socket(AF_INET,SOCK_DGRAM,0);//创建udp套接字
    if(sock<0){
      printf("Socker error\n");
      goto __exit;
      }
      Serve_addr.sin_family=AF_INET;
       /*绑定服务器远端ip*/
      Serve_addr.sin_addr.s_addr=inet_addr("192.168.31.151");
       /*绑定服务器远端端口*/
      Serve_addr.sin_port=htons(PORT);
      memset(&(Serve_addr.sin_zero),0,sizeof(Serve_addr.sin_zero));
      //sendto(sock,test_buf,sizeof(test_buf),0,(struct sockaddr*)&Serve_addr,addrlen); 
  while(1){
       // recv_data_len=recvfrom(sock,recv_data,RECV_DATA,0,(struct sockaddr*)&Serve_addr,&addrlen);
        /* 显示发送端的 IP 地址 */
        //printf("receive from %s\n",inet_ntoa(Serve_addr.sin_addr));//char *ip4addr_ntoa(const ip4_addr_t *addr);将32位地址数据转化char*类型的字符串
                                                                    //ipaddr_addr(const char *cp)将char*类型的字符串转化成32位地址数据
        /* 显示发送端发来的字串 */
       // printf("recevce:%s",recv_data);
        /* 将字串返回给发送端 */
        sendto(sock,test_buf,sizeof(test_buf),0,(struct sockaddr*)&Serve_addr,addrlen);
        vTaskDelay(1000);
      }
  }
__exit:
      if (sock >= 0) closesocket(sock);
      //if (recv_data) free(recv_data);
}

现象如下:

单片机作为udp服务器,向服务器1s发送一条“sense_long is nb”。 

注意:上述实验均需要在同一子网(局域网)内进行,若需要跨网段通讯需要借助云平台。

电脑本地的ip地址可以通过CMD来获得,在cmd中输入ipconfig即可打印本地的ip地址

如果上述文章有帮助到您,麻烦点一个赞叭~

  • 25
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sense_long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值