一、简介
在网络编程的时候,不管是客户端还是服务端,都离不开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(流格式套接字/面向连接的套接字 TCP)、SOCK_DGRAM(数据报套接字/无连接的套接字 UDP)。

protocol:传输协议。 默认为0,系统自动推演使用的协议。也可以手动输入,常用的协议有,IPPROTO_TCP、IPPTOTO_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));    //网关IPlwip_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)转化为二进制数值。
注:这个函数转换完后不能用于网络传输,还需要调用htons或htonl函数才能将主机字节顺序转换为网络字节顺序。
函数原型:
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 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补上。

 


















