一、简介
在网络编程的时候,不管是客户端还是服务端,都离不开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)); //网关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)转化为二进制数值。
注:这个函数转换完后不能用于网络传输,还需要调用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补上。