鸿蒙Hi3861学习十五-Huawei LiteOS-M(Socket客户端)

一、简介

        在网络编程的时候,不管是客户端还是服务端,都离不开Socket。那什么是Socket,这里做个简单介绍。详细的内容,可以参考这篇文章:WIFI学习一(socket介绍)_wifi socket_t_guest的博客-CSDN博客

         socket在计算机领域,被翻译为“套接字”。它是计算机之间进行通信的一种约定一种方式,通过这种方式,一台计算机可以接收或向另外一台计算机收发数据。

        socket是基于“打开open –> 读写write/read –> 关闭close”模式来设计的。socket可以看做是一种特殊的文件,通过一下socket函数来实现打开关闭读/写IO

        socket客户端编程的总体流程可以归结为以下步骤,初始化socket连接服务器(connect)读/写(write/read)关闭(close)

 二、API介绍

      socket

        函数功能:

        创建一个socket描述符,用来唯一标识一个socket。后续需要通过该描述符进行读写操作

        函数原型:

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

        参数:

        domain:IP地址类型。常用的类型有AF_INET(IPV4)、AF_INET6(IPV6)。

        type:数据传输方式/套接字类型。常用的类型有SOCK_STREAM(流格式套接字/面向连接的套接字 TCPSOCK_DGRAM(数据报套接字/无连接的套接字 UDP

扫描二维码关注公众号,回复: 15522613 查看本文章

        protocol:传输协议。 默认为0,系统自动推演使用的协议。也可以手动输入,常用的协议有,IPPROTO_TCPIPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。

        返回值:

        NULL:失败

        其他值:Sockcet描述符

        实例:

int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);  //创建TCP套接字
 
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字

      lwip_connect

        函数功能:

        客户端通过该函数与服务端建立连接

        函数原型:

#define lwip_connect      connect
int connect(int fd, const struct sockaddr *addr, socklen_t len)

        参数:

        fd:socket描述符,socket()函数返回

        addr:要连接的服务端相关信息,包括IP和端口等

        len:服务端相关信息的长度

        返回值:

        -1:失败

        0:成功

        实例:

    int sock_fd; 

    struct sockaddr_in socket_addr;
    memset(&socket_addr, 0, sizeof(socket_addr));

    socket_addr.sin_family = AF_INET;	//IPV4
    socket_addr.sin_port = htons(_PROT_);	//端口
    socket_addr.sin_addr.s_addr = inet_addr("192.168.3.198");	//IP地址转换

    socklen_t addr_length = sizeof(socket_addr);

    LOG_I( "connect port:%d, addr:%s",_PROT_,inet_ntoa(socket_addr.sin_addr));

    int ret  = 0;

    ret = lwip_connect(sock_fd, (struct sockaddr *)&socket_addr, addr_length);
    if(ret < 0) //失败{}

         如果网关即为服务端,这里相关信息可以这么写。

struct netif *sta_if = netifapi_netif_find("wlan0"); 
socket_addr.sin_addr.s_addr = inet_addr(ipaddr_ntoa(&sta_if->gw));    //网关IP

      lwip_write

        函数功能:

        向套接字写数据

        函数原型:

ssize_t lwip_write(int s, const void *data, size_t size)

        参数:

        s:socket描述符,socket()函数返回

        data:要发送的数据

        size:要发送数据的长度

        返回值:

        -1:失败

        其他值:成功发送的字节数

        实例:

int sock_fd;
const char *send_data = "This is a socket client test!\r\n";
if(lwip_write(sock_fd,send_data, strlen(send_data)) != -1)    //发送成功
{}

      lwip_read

        函数功能:

        从套接字中读取数据阻塞等待

        函数原型:

ssize_t lwip_read(int s, void *mem, size_t len)

        参数:

        s:socket描述符,socket()函数返回

        mem:接收到的数据存储的地址

        len:最大接收数据长度

        返回值:

        -1:失败

        其他值:成功则返回读取到的字节数,遇到文件结尾则返回0

        实例:

int sock_fd; 
char recvBuf[512] = {0};
if((temp_len = lwip_read(sock_fd, recvBuf, sizeof(recvBuf))) > 0) //读成功
{}

      lwip_close

        函数功能:

        关闭之前打开的套接字

        函数原型:

int closesocket(int s)

        参数:

        s:socket描述符,socket()函数返回

        返回值:

        0:成功

        其他值:失败

        实例:

int sock_fd; 
closesocket(sock_fd);

      sendto

        函数功能:

        发送数据到服务器端(一般用于UDP)。

        函数原型:

ssize_t sendto(int fd, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t alen)

        参数:

        fd:socket描述符,socket()的返回值。

        buf:要发送的数据

        len:要发送数据的长度

        flags:默认0

        addr:服务端相关信息。

        alen:服务端信息长度

        返回值:

        -1:失败

        其他值:成功发送的字节数

        实例:

int sock_fd;

//服务器的地址信息
struct sockaddr_in send_addr;
socklen_t addr_length = sizeof(send_addr);

//初始化预连接的服务端地址
send_addr.sin_family = AF_INET;
send_addr.sin_port = htons(_PROT_);
send_addr.sin_addr.s_addr = inet_addr("192.168.3.198");
addr_length = sizeof(send_addr);
sendto(sock_fd, send_data, strlen(send_data), 0, (struct sockaddr *)&send_addr, addr_length);

      recvfrom

        函数功能:

        接收socket传输过来的数据(一般用于UDP),阻塞等待

        函数原型:

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

        参数:

        s:socket描述符,socket()函数返回

        mem:接收到的数据存放的地址

        len:最大接收数据长度、

        flags:默认为0。

        addr:服务端相关信息。

        alen:服务端信息长度

        返回值:

        -1:失败

        其他值:接收到的数据长度

        实例:

    int sock_fd;    //在sock_fd 进行监听,在 new_fd 接收新的链接
    char recvBuf[512] = {0};
    
    //服务器的地址信息
    struct sockaddr_in send_addr;
    socklen_t addr_length = sizeof(send_addr);

    //初始化预连接的服务端地址
    send_addr.sin_family = AF_INET;
    send_addr.sin_port = htons(_PROT_);
    send_addr.sin_addr.s_addr = inet_addr("192.168.3.198");
    addr_length = sizeof(send_addr);


    recvfrom(sock_fd, recvBuf, sizeof(recvBuf), 0, (struct sockaddr *)&send_addr, &addr_length);

      lwip_setsockopt

        函数功能:

        设置套接字描述符选项

        函数原型:

#define lwip_setsockopt   setsockopt
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen)

        参数:

        s:socket描述符,socket()函数返回

        level:选项定义的层次

SOL_SOCKET,套接字层
IPPROTO_TCP,TCP层
IPPROTO_IP,IP层
IPPROTO_IPV6,IPV6层

        optname:需要设置的选项。这里只介绍常用的选项。在level为SOL_COCKET(套接字层)时,optname可选一下值:

/*设置发送超时*/
#define SO_SNDTIMEO     0x1005 /* send timeout */
/*设置接收超时*/
#define SO_RCVTIMEO     0x1006 /* receive timeout */

         optval:指向存放选项待设置新值的缓冲区

        optlen:缓冲区长度

        返回值:

        -1:失败

        0:成功

        实例:

struct timeval timeout;
timeout.tv_sec  = 30;   //秒
timeout.tv_usec = 0;    //微秒
if (setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) < 0) {
    LOG_I(lwip_socket_example, "Setsockopt failed - set rcvtimeo\n");
}

      htonl()、htons()、ntohl()、ntohs()

        函数功能:

        在编程的时候,往往会遇到网络字节顺序主机顺序的问题。这时就需要以上四个函数进行调节了。

htonl()--"Host to Network Long"
 
ntohl()--"Network to Host Long"
 
htons()--"Host to Network Short"
 
ntohs()--"Network to Host Short"

        主机顺序大端或小端大端就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端(高位在前,低位在后)。小端就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端(低位在前,高位在后)。

        网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序

        函数原型:

uint32_t htonl(uint32_t hostlong);

        参数:

        转换前的值

        返回值:

        转换后的值

        实例:

server_sock.sin_addr.s_addr = htonl(INADDR_ANY);    //地址,随意分配

        

     inet_addr()      

        函数功能:   

        转换网络主机地址(192.168.x.x)为网络字节序排序地址

        函数原型:

in_addr_t inet_addr(const char* cp)

        参数:

        cp:网络主机地址(192.168.x.x)

        返回值:

        -1:失败。当cp无效时(255.255.255.0或其他)返回-1.

        其他值:网络字节排序地址

        实例:

send_addr.sin_addr.s_addr = inet_addr("192.168.3.198");

     inet_aton()      

        函数功能:   

        将主机地址(192.168.x.x)转化为二进制数值

        注:这个函数转换完后不能用于网络传输,还需要调用htonshtonl函数才能将主机字节顺序转换为网络字节顺序

        函数原型:

int inet_aton(const char* cp, struct in_addr* inp)

        参数:

        cp:输入值,网络主机地址(192.168.x.x)

        inp:输出值,转换后的二进制数值

        返回值:

        0:主机地址无效

        其他值:主机地址有效

        实例:

struct sockaddr_in sin1;	
inet_aton("127.0.0.1", &sin1.sin_addr);

     inet_ntoa()      

        函数功能:   

        将网络字节排序的地址转换为ASICC(x.x.x.x)。

        注:该字符串的空间为静态分配,这意味着在第二次调用该函数时,上一次调用的输出值将会被覆盖

        函数原型:

char *inet_ntoa(struct in_addr in)

        参数:

        in:类型为in_addr。网络字节排序地址。

typedef uint32_t in_addr_t;

        返回值:

        转化的字符串

        实例:

struct in_addr addr1,addr2;

addr1 = inet_addr("192.168.0.74");
addr2 = inet_addr("211.100.21.179");

printf("%s : %s\n", inet_ntoa(addr1), inet_ntoa(addr2)); //不可以这么用,结果会被覆盖  
printf("%s\n", inet_ntoa(addr1));   
printf("%s\n", inet_ntoa(addr2));

输出结果:
192.168.0.74 : 192.168.0.74          //从这里可以看出,printf里的inet_ntoa只运行了一次。

192.168.0.74  

211.100.21.179 

     ipaddr_ntoa()      

        函数功能:   

        将网络地址类型的地址转换为ASICC(x.x.x.x)。

        注:ipaddr_ntoa和inet_ntoa的功能相同,都是将传参转换为字符串。但是传参的数据类型不同ipaddr_ntoa参数的类型为ip_addr_t的指针,而inet_ntoa参数为uint32_t类型的整型

        函数原型:

char *ipaddr_ntoa(const ip_addr_t *addr)

        参数:

        addr:网络地址

        返回值:

        转化的字符串

        实例:

lwip_netif = netifapi_netif_find("ap0");  //获取网络借口,用于IP操作
LOG_I("ip addr:%s",ipaddr_ntoa(&lwip_netif->ip_addr));

     ipaddr_aton()      

        函数功能:   

        将主机地址(192.168.1.1)转化为网络字节类型

        函数原型:

int ipaddr_aton(const char *cp, ip_addr_t *addr)

        参数:

        cp:输入值,网络主机地址(192.168.x.x)

        addr:输出值,网络地址,类型为ip_addr_t.

typedef struct ip_addr {
  union {
    ip6_addr_t ip6;
    ip4_addr_t ip4;
  } u_addr;
  /** @ref lwip_ip_addr_type */
  u8_t type;
} ip_addr_t;

        返回值:

        0:成功

        其他值:失败

        实例:

struct netif *lwip_netif = NULL;
ipaddr_ntoa("192.168.1.1",&lwip_netif->ip_addr));

     inet_pton()      

        函数功能:   

        将点分十进制的IP地址(192.168.x.x)转化为用于网络传输的数据格式

        函数原型:

int inet_pton(int af, const char *src, void *dst)

        参数:

        af:地址族,AF_INET(IPV4)、AF_INET6(IPV6)。

        src:输入值,点分十进制的IP地址(192.168.x.x)

        dst:输出值,用于网络传输的数据格式

        返回值:

        -1:异常

        0:输入值异常

        1:成功

        实例:

struct sockaddr_in socket_addr;
inet_pton(AF_INET, "192.168.1.110", &socket_addr.sin_addr); 
/*
代替socket_addr.sin_addr.s_addr = inet_addr("192.168.1.110");	//IP地址转换
*/

     inet_ntop()      

        函数功能:   

        将用于网络传输的数据格式转化为点分十进制的IP地址格式(192.168.x.x)

        函数原型:

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size)

        参数:

        af:地址族,AF_INET(IPV4)、AF_INET6(IPV6)。

        src:输入值,网络传输数据格式的数据

        dst:输出值,IP地址格式(192.168.x.x)

        size:输入值,目标存储单元大小。

        返回值:

        0:成功

        其他值:失败。ENOSPC size长度太小。

        实例:

char str[INET_ADDRSTRLEN];
struct sockaddr_in socket_addr;

char *ptr = inet_ntop(AF_INET,&socket_addr.sin_addr, str, sizeof(str));      
// 代替 ptr = inet_ntoa(socket_addr.sin_addr)

三、实例

        这里分别创建一个TCP和一个UDP

         现在BUILD.gn文件中添加如下代码

    include_dirs = [
        "//utild/native/lite/include",
        "//base/iot_hardware/interfaces/kits/wifiiot_lite",
        "//utils/native/lite/include",
        "//kernel/liteos_m/components/cmsis/2.0",
        "//foundation/communication/interfaces/kits/wifi_lite/wifiservice",
        "//vendor/hisi/hi3861/hi3861/third_party/lwip_sack/include/",
    ]
/*TCP*/


#include <stdio.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"

#include "wifi_device.h"
#include "lwip/netifapi.h"
#include "lwip/api_shell.h"
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include "lwip/sockets.h"

#define LOG_I(fmt, args...)   printf("<%8ld> - [SOCKET_CLIENT]:"fmt"\r\n",osKernelGetTickCount(),##args);
#define LOG_E(fmt, args...)   printf("<%8ld>-[SOCKET_CLIENT_ERR]>>>>>>>>>>>>:"fmt"\r\n",osKernelGetTickCount(), ##args);

#define _PROT_ 7682


static const char *send_data = "Hello! I'm BearPi-HM_Nano UDP Client!\r\n";

static void SocketClientTask(void)
{
    int sock_fd;    //在sock_fd 进行监听,在 new_fd 接收新的链接
    char recvBuf[512] = {0};

    //连接Wifi
    extern int drv_wifi_connect(const char *ssid, const char *psk);
    drv_wifi_connect("Harmony_test_ap", "123123123");

    LOG_I("wifi connect success");

    //创建socket
    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        LOG_E("create socket failed!\r\n");
        exit(1);
    }

    LOG_I("socket TCP create done");

    struct sockaddr_in socket_addr;
    memset(&socket_addr, 0, sizeof(socket_addr));

    socket_addr.sin_family = AF_INET;	//IPV4
    socket_addr.sin_port = htons(_PROT_);	//端口
    socket_addr.sin_addr.s_addr = inet_addr("192.168.3.198");	//IP地址转换
    // socket_addr.sin_addr.s_addr = inet_addr(ipaddr_ntoa(&sta_if->gw));    //网关IP

    socklen_t addr_length = sizeof(socket_addr);

    LOG_I( "connect port:%d, addr:%s",_PROT_,inet_ntoa(socket_addr.sin_addr));

    int ret  = 0;
    do{
        ret = lwip_connect(sock_fd, (struct sockaddr *)&socket_addr, addr_length);
        if(ret < 0) //失败
        {
            LOG_I("socket connect fail");
            // lwip_close(sock_fd);    //关闭socket
            osDelay(100);
        }
    }while(1);

    LOG_I("socket connect success");

    struct timeval timeout;
    timeout.tv_sec = 5;       //秒
    timeout.tv_usec = 0;     //微秒
    if (lwip_setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) < 0) //设置接收超时
    {
        LOG_E("Setsockopt failed - set rcvtimeo\n");
    }

    LOG_I("set socket receive timeout:%d",timeout.tv_sec);

    int temp_len = 0;

    while(1)
    {
        bzero(recvBuf, sizeof(recvBuf));

        if(lwip_write(sock_fd,send_data, strlen(send_data)) != -1)
        {
            LOG_I("socket write success");
        }
        else
        {
            LOG_E("socket write fail");
        }

        temp_len = 0;
        if((temp_len = lwip_read(sock_fd, recvBuf, sizeof(recvBuf))) > 0) //读成功
        {
            LOG_I( "TCP client >>>>>read>>>>> data,len:%d,data:%s", temp_len, recvBuf);
        }
        else
        {
            LOG_E("socket client read fail");
        }
    }

    //关闭这个 socket
    closesocket(sock_fd);
}

void app_socket_client_init(void)
{
    osThreadAttr_t attr;

    attr.name = "UDPClientTask";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 10240;
    attr.priority = osPriorityNormal;

    if (osThreadNew((osThreadFunc_t)SocketClientTask, NULL, &attr) == NULL)
    {
        LOG_E("[UDPClientDemo] Falied to create UDPClientTask!\n");
    }
}
/*UDP*/


#include <stdio.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"

#include "wifi_device.h"
#include "lwip/netifapi.h"
#include "lwip/api_shell.h"
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include "lwip/sockets.h"

#define LOG_I(fmt, args...)   printf("<%8ld> - [SOCKET_CLIENT]:"fmt"\r\n",osKernelGetTickCount(),##args);
#define LOG_E(fmt, args...)   printf("<%8ld>-[SOCKET_CLIENT_ERR]>>>>>>>>>>>>:"fmt"\r\n",osKernelGetTickCount(), ##args);

#define _PROT_ 7682


static const char *send_data = "This is a UDP client test!\r\n";

static void SocketClientTask(void)
{
    int sock_fd;    //在sock_fd 进行监听,在 new_fd 接收新的链接
    char recvBuf[512] = {0};

    //连接Wifi
    extern int drv_wifi_connect(const char *ssid, const char *psk);
    drv_wifi_connect("Harmony_test_ap", "123123123");

    LOG_I("wifi connect success");


    //创建socket
    if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        LOG_E("create socket failed!\r\n");
        exit(1);
    }

    LOG_I("socket UDP create done");

    //服务器的地址信息
    struct sockaddr_in send_addr;
    socklen_t addr_length = sizeof(send_addr);

    //初始化预连接的服务端地址
    send_addr.sin_family = AF_INET;
    send_addr.sin_port = htons(_PROT_);
    send_addr.sin_addr.s_addr = inet_addr("192.168.3.198");
    addr_length = sizeof(send_addr);

    //总计发送 count 次数据
    while (1)
    {
        bzero(recvBuf, sizeof(recvBuf));

        //发送数据到服务远端
        sendto(sock_fd, send_data, strlen(send_data), 0, (struct sockaddr *)&send_addr, addr_length);

        LOG_I("socket send done");

        //线程休眠一段时间
        sleep(10);
        // osDelay(500);

        LOG_I("socket wait receive data");

        //接收服务端返回的字符串
        recvfrom(sock_fd, recvBuf, sizeof(recvBuf), 0, (struct sockaddr *)&send_addr, &addr_length);
        LOG_I("%s:%d=>%s\n", inet_ntoa(send_addr.sin_addr), ntohs(send_addr.sin_port), recvBuf);
    } 

    //关闭这个 socket
    closesocket(sock_fd);
}

void app_socket_client_init(void)
{
    osThreadAttr_t attr;

    attr.name = "UDPClientTask";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 10240;
    attr.priority = osPriorityNormal;

    if (osThreadNew((osThreadFunc_t)SocketClientTask, NULL, &attr) == NULL)
    {
        LOG_E("[UDPClientDemo] Falied to create UDPClientTask!\n");
    }
}

        结果:因为使用的版本为1.0版本的SDK,TCP有问题,所以,这里先看UDP的结果,后续TCP会更换更改版本的SDK补上。

 

猜你喜欢

转载自blog.csdn.net/qq_26226375/article/details/130708762