1. Introducción
En la programación de redes, ya sea un cliente o un servidor, Socket es inseparable . Entonces, ¿qué es Socket? Aquí hay una breve introducción. Para obtener contenido detallado, puede consultar este artículo: WIFI Learning 1 (introducción de socket)_wifi socket_t_guest's Blog-CSDN Blog
Socket se traduce como " socket " en el campo de la informática . Es una convención o método de comunicación entre computadoras mediante el cual una computadora puede recibir o enviar datos a otra computadora.
El zócalo está diseñado según el modo de " abrir abrir –> leer y escribir escribir/leer –> cerrar cerrar ". Un socket se puede considerar como un archivo especial , que se puede abrir , cerrar y leer/escribir E/S a través de las siguientes funciones de socket .
El proceso general de programación del cliente de socket se puede resumir en los siguientes pasos: inicializar el socket , conectarse al servidor (conectar) , leer/escribir (escribir/leer) y cerrar (cerrar) .
2. Introducción a la API
enchufe
función función:
Cree un descriptor de socket para identificar de forma única un socket . Las operaciones posteriores de lectura y escritura deben realizarse a través de este descriptor .
Prototipo de función:
int socket(int domain, int type, int protocol)
parámetro:
dominio: tipo de dirección IP. Los tipos comúnmente utilizados son AF_INET (IPV4), AF_INET6 (IPV6).
tipo: método de transmisión de datos/tipo de socket. Los tipos comúnmente utilizados son SOCK_STREAM (socket de formato de flujo/socket orientado a la conexión TCP ) , SOCK_DGRAM (socket de datagrama/socket sin conexión UDP ) .
protocolo: protocolo de transporte. El valor predeterminado es 0 y el sistema deduce automáticamente el protocolo utilizado . También se puede ingresar manualmente.Los protocolos comúnmente utilizados incluyen IPPROTO_TCP , IPPTOTO_UDP , IPPROTO_SCTP, IPPROTO_TIPC, etc., que corresponden al protocolo de transmisión TCP, protocolo de transmisión UDP, protocolo de transmisión STCP y protocolo de transmisión TIPC. El tipo y el protocolo no se pueden combinar a voluntad , como SOCK_STREAM no se puede combinar con IPPROTO_UDP.
valor de retorno:
NULO: fallido
Otros valores: descriptor de socket
Ejemplo:
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字
lwip_conectar
función función:
El cliente establece una conexión con el servidor a través de esta función
Prototipo de función:
#define lwip_connect connect
int connect(int fd, const struct sockaddr *addr, socklen_t len)
parámetro:
fd: descriptor de socket, devuelto por la función socket() .
addr: información sobre el servidor a conectar, incluyendo IP y puerto, etc.
len: la longitud de la información relacionada con el servidor
valor de retorno:
-1: fallado
0: éxito
Ejemplo:
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) //失败{}
Si la puerta de enlace es el servidor , la información relevante aquí se puede escribir de la siguiente manera.
struct netif *sta_if = netifapi_netif_find("wlan0");
socket_addr.sin_addr.s_addr = inet_addr(ipaddr_ntoa(&sta_if->gw)); //网关IP
lwip_escribir
función función:
escribir datos en el zócalo
Prototipo de función:
ssize_t lwip_write(int s, const void *data, size_t size)
parámetro:
s: descriptor de socket, devuelto por la función socket() .
data: los datos a enviar
tamaño: la longitud de los datos a enviar
valor de retorno:
-1: fallado
Otros valores: número de bytes enviados con éxito
Ejemplo:
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_leer
función función:
Leer datos del socket . espera de bloqueo
Prototipo de función:
ssize_t lwip_read(int s, void *mem, size_t len)
parámetro:
s: descriptor de socket, devuelto por la función socket() .
mem: la dirección del almacenamiento de datos recibido
len: longitud máxima de datos recibidos
valor de retorno:
-1: fallado
Otros valores: Devuelve el número de bytes leídos si es correcto y devuelve 0 cuando encuentra el final del archivo.
Ejemplo:
int sock_fd;
char recvBuf[512] = {0};
if((temp_len = lwip_read(sock_fd, recvBuf, sizeof(recvBuf))) > 0) //读成功
{}
lwip_cerrar
función función:
Cerrar un socket previamente abierto
Prototipo de función:
int closesocket(int s)
parámetro:
s: descriptor de socket, devuelto por la función socket() .
valor de retorno:
0: éxito
Otros valores: fallar
Ejemplo:
int sock_fd;
closesocket(sock_fd);
enviar a
función función:
Envía datos al servidor (usualmente usado para UDP ).
Prototipo de función:
ssize_t sendto(int fd, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t alen)
parámetro:
fd: descriptor de socket, el valor de retorno de socket().
buf: los datos a enviar
len: la longitud de los datos a enviar
banderas: por defecto 0
addr: información relacionada con el servidor.
alen: longitud de la información del servidor
valor de retorno:
-1: fallado
Otros valores: número de bytes enviados con éxito
Ejemplo:
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);
recibido desde
función función:
Recibir los datos transmitidos por el socket (usualmente usado para UDP ), bloquear y esperar .
Prototipo de función:
ssize_t recvfrom(int s, void *mem, size_t len, int flags,
struct sockaddr *from, socklen_t *fromlen)
parámetro:
s: descriptor de socket, devuelto por la función socket() .
mem: la dirección donde se almacenan los datos recibidos
len: longitud máxima de datos recibidos,
banderas: el valor predeterminado es 0.
addr: información relacionada con el servidor.
alen: longitud de la información del servidor
valor de retorno:
-1: fallado
Otros valores: longitud de datos recibidos
Ejemplo:
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
función función:
Establecer opciones de descriptor de socket
Prototipo de función:
#define lwip_setsockopt setsockopt
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen)
parámetro:
s: descriptor de socket, devuelto por la función socket() .
nivel: el nivel definido por la opción
SOL_SOCKET,套接字层
IPPROTO_TCP,TCP层
IPPROTO_IP,IP层
IPPROTO_IPV6,IPV6层
optname: la opción que debe configurarse. Aquí solo se describen las opciones más utilizadas. Cuando el nivel es SOL_COCKET (capa de socket), optname puede elegir los siguientes valores:
/*设置发送超时*/
#define SO_SNDTIMEO 0x1005 /* send timeout */
/*设置接收超时*/
#define SO_RCVTIMEO 0x1006 /* receive timeout */
optval: apunta al búfer que almacena el nuevo valor de la opción a establecer
optlen: longitud del búfer
valor de retorno:
-1: fallado
0: éxito
Ejemplo:
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()
función función:
Al programar, a menudo encuentra problemas con el orden de los bytes de la red y el orden del host . En este momento, es necesario ajustar las cuatro funciones anteriores.
htonl()--"Host to Network Long"
ntohl()--"Network to Host Long"
htons()--"Host to Network Short"
ntohs()--"Network to Host Short"
Orden de host : big endian o little endian . Big-endian significa que los bytes de orden superior se organizan en el extremo de dirección inferior de la memoria, y los bytes de orden inferior se organizan en el extremo de dirección superior de la memoria (los bytes de orden superior van primero y siguen los bytes de orden bajo ). Little-endian significa que los bytes de orden inferior se organizan en el extremo de dirección inferior de la memoria, y los bytes de orden superior se organizan en el extremo de dirección superior de la memoria ( los bytes de orden inferior van primero y siguen los bytes de orden superior ).
Orden de bytes de red : El valor de 32 bits de 4 bytes se transmite en el siguiente orden: primero 0~7 bits, luego 8~15 bits, luego 16~23 bits y finalmente 24~31 bits. Este orden de transmisión se llama big-endian . Dado que todos los enteros binarios en el encabezado TCP/IP deben estar en este orden cuando se transmiten en la red, también se denomina orden de bytes de red .
Prototipo de función:
uint32_t htonl(uint32_t hostlong);
parámetro:
Valor antes de la conversión
valor de retorno:
valor convertido
Ejemplo:
server_sock.sin_addr.s_addr = htonl(INADDR_ANY); //地址,随意分配
dirección_inet()
función función:
Convierta una dirección de host de red (192.168.xx) en una dirección de orden de bytes de red .
Prototipo de función:
in_addr_t inet_addr(const char* cp)
parámetro:
cp: dirección de host de red (192.168.xx)
valor de retorno:
-1: Fallido. Cuando cp no es válido (255.255.255.0 u otro) devuelve -1.
Otros valores: dirección en orden de bytes de red
Ejemplo:
send_addr.sin_addr.s_addr = inet_addr("192.168.3.198");
inet_aton()
función función:
Convierta la dirección del host (192.168.xx) a valor binario .
Nota: esta función no se puede usar para la transmisión de red después de la conversión , y se debe llamar a la función htons o htonl para convertir el orden de bytes del host al orden de bytes de la red .
Prototipo de función:
int inet_aton(const char* cp, struct in_addr* inp)
parámetro:
cp: valor de entrada, dirección de host de red (192.168.xx)
inp: valor de salida , valor binario convertido
valor de retorno:
0: dirección de host no válida
Otros valores: la dirección del host es válida
Ejemplo:
struct sockaddr_in sin1;
inet_aton("127.0.0.1", &sin1.sin_addr);
guerra_inet()
función función:
Convierta una dirección en orden de bytes de red a ASICC (xxxx).
Nota: El espacio para la cadena se asigna de forma estática , lo que significa que cuando se llama a la función por segunda vez, se sobrescribirá el valor de salida de la llamada anterior .
Prototipo de función:
char *inet_ntoa(struct in_addr in)
parámetro:
in: El tipo es in_addr. Dirección en orden de bytes de red.
typedef uint32_t in_addr_t;
valor de retorno:
cadena convertida
Ejemplo:
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
guerra_ipaddr()
función función:
Convierte una dirección de tipo de dirección de red a ASICC (xxxx).
Nota: ipaddr_ntoa e inet_ntoa tienen la misma función , ambos convierten el parámetro pasado en una cadena . Sin embargo, los tipos de datos de los parámetros pasados son diferentes.El tipo del parámetro ipaddr_ntoa es un puntero a ip_addr_t , mientras que el parámetro inet_ntoa es un número entero de tipo uint32_t .
Prototipo de función:
char *ipaddr_ntoa(const ip_addr_t *addr)
parámetro:
dirección: dirección de red
valor de retorno:
cadena convertida
Ejemplo:
lwip_netif = netifapi_netif_find("ap0"); //获取网络借口,用于IP操作
LOG_I("ip addr:%s",ipaddr_ntoa(&lwip_netif->ip_addr));
ipaddr_aton()
función función:
Convierta la dirección del host (192.168.1.1) al tipo de byte de red
Prototipo de función:
int ipaddr_aton(const char *cp, ip_addr_t *addr)
parámetro:
cp: valor de entrada, dirección de host de red (192.168.xx)
addr: valor de salida, dirección de red, el tipo es 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;
valor de retorno:
0: éxito
Otros valores: fallar
Ejemplo:
struct netif *lwip_netif = NULL;
ipaddr_ntoa("192.168.1.1",&lwip_netif->ip_addr));
inet_pton()
función función:
Convierta la dirección IP decimal con puntos (192.168.xx) en un formato de datos para la transmisión de red
Prototipo de función:
int inet_pton(int af, const char *src, void *dst)
parámetro:
af: familia de direcciones, AF_INET (IPV4), AF_INET6 (IPV6).
src: valor de entrada, dirección IP en notación decimal con puntos (192.168.xx)
dst: valor de salida , formato de datos para transmisión de red
valor de retorno:
-1: Anormal
0: el valor de entrada es anormal
1: éxito
Ejemplo:
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()
función función:
Convierta el formato de datos utilizado para la transmisión de red en formato de dirección IP decimal con puntos (192.168.xx)
Prototipo de función:
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size)
parámetro:
af: familia de direcciones, AF_INET (IPV4), AF_INET6 (IPV6).
src: valor de entrada, datos en formato de datos de transmisión de red
dst: valor de salida , formato de dirección IP (192.168.xx)
tamaño: valor de entrada, tamaño de la unidad de almacenamiento de destino.
valor de retorno:
0: éxito
Otros valores: fallar. La longitud del tamaño de ENOSPC es demasiado pequeña.
Ejemplo:
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)
3. Ejemplo
Cree un TCP y un UDP aquí respectivamente
Ahora agregue el siguiente código al archivo 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");
}
}
Resultado: debido a que se usa la versión 1.0 de SDK, hay un problema con TCP, así que primero veamos los resultados de UDP, y el TCP de seguimiento se reemplazará con una versión diferente de SDK para compensar.