ESP32-socket编程
一.socket介绍
Socket(套接字):这是计算机网络中的一个重要概念,指的是一个端点,用于在网络中的两个程序之间建立通信。Socket可以理解为一种通信机制,它使得不同的程序能够在TCP/IP网络上相互传输数据。基本上,Socket为网络通信提供了接口,允许程序通过网络发送和接收数据。
Socket API:这是操作系统提供的一组应用程序接口,允许开发者使用Socket进行网络编程。它通常包括创建Socket、绑定Socket到一个地址、监听连接、接受连接和发送/接收数据等功能。常见的编程语言如Python、C、Java等都有相关的Socket库,可以方便地进行网络通信。
Socket 类型:
Stream Sockets(流套接字):通常用于TCP协议,提供有序、可靠的双向流数据传输。
Datagram Sockets(数据报套接字):通常用于UDP协议,适合需要较少流量的应用,传输速度快但不保证可靠性。
在通信过程中,socket一定是成对出现的。
二.socket结构体介绍
2.1 struct sockaddr_in
struct sockaddr_in {
u8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
#define SIN_ZERO_LEN 8
char sin_zero[SIN_ZERO_LEN];
};
-
介绍
通常在创建和使用socket时用作服务器和客户端的地址结构体。服务器可以使用该结构体来绑定到特定的端口和IP地址,通过 bind() 函数使用它;而客户端则可以使用它来指定要连接的服务器的地址,通过 connect() 函数使用。 -
成员
u8_t sin_len:
这个字段表示结构体的大小,通常在现代程序中可能不再使用,但在某些平台上,它对确保 API 的兼容性是有用的。sa_family_t sin_family:
这个字段指定地址的类型。在使用 IPv4 时,它的值通常设置为 AF_INET。这个字段帮助操作系统识别使用的是哪种协议。in_port_t sin_port:
该字段存储端口号,用于标识特定的服务或应用程序。注意,端口号需要转换为网络字节序(大端字节序),通常使用 htons() 函数进行转换。struct in_addr sin_addr:
这个字段持有目标 IP 地址,使用结构体 in_addr 来表示。通常,使用 inet_pton() 或 ** inet_addr() ** 等函数来将字符串形式的IP地址转换为该结构体。char sin_zero[SIN_ZERO_LEN]:
保留用,通常填充为零
三.socket——API函数介绍
3.1 IP地址转换函数与宏函数
3.1.1 int inet_pton(int af, const char *src, void *dst);
-
参数
int af:
sa_family_t sin_family:
地址族,指定要转换的地址类型。常见的值包括:
AF_INET
:用于 IPv4 地址
AF_INET6
:用于 IPv6 地址const char *src:
指向一个字符串的指针,表示要转换的源地址。这个地址通常为点分十进制格式的 IPv4 地址(例如 “192.168.1.1”),或是括号格式的 IPv6 地址(例如 “[2001:db8::1]”)。void *dst:
指向目标内存的指针,用于存储转换后的二进制地址。对于 IPv4,通常会将其存储为 struct in_addr 类型;对于 IPv6,通常会将其存储为 struct in6_addr 类型。
-
参数
- 返回值
- 返回值为 1 表示成功。
- 返回值为 0 表示提供的地址字符串不是有效地址格式。
返回值为 -1 表示出错(例如不支持的地址族),可以通过 errno 获取错误信息。
3.1.2 宏函数 inet_addr(const char *strptr)
是一个用于将 IPv4 地址的文本表示转换为网络字节序的二进制格式的函数
-
参数
const char *strptr:
指向一个以点分十进制形式表示的 IP 地址的字符串,例如 “192.168.1.1”。
-
返回值
- 如果转换成功,返回转换后的网络字节序的二进制形式的 IP 地址。
- 如果输入字符串不是有效的 IP 地址,返回 INADDR_NONE(通常为 0xFFFFFFFF),这表明地址无效。
3.2 socket通信API函数
3.2.1 socket函数
int socket(int domain,int type,int protocol)
-
参数
int domain:
指定套接字的地址域,也就是协议族。这决定了数据将如何在网络中传输。
常见的值:
AF_INET
:表示 IPv4 协议。
AF_INET6
:表示 IPv6 协议。
AF_UNIX
:表示本地(Unix)套接字,用于在同一台主机上的进程间通信。int type:
地址族,指定要转换的地址类型。常见的值包括:
AF_INET
:用于 IPv4 地址
AF_INET6
:用于 IPv6 地址int protocol:
指定套接字的类型,定义了套接字的特性和通信方式。
常见的值:
SOCK_STREAM
:面向连接的流套接字,提供可靠的双向字节流(通常用于 TCP)。
SOCK_DGRAM
:无连接的数据报套接字,提供不可靠的消息传输(通常用于 UDP)。
SOCK_RAW
:原始套接字,允许直接对网络层协议进行操作(通常不常用,需要超级用户权限)。
-
返回值
- 返回一个整型的套接字描述符(socket descriptor),表示已创建的套接字。
- 如果创建失败,返回 -1,并设置 errno 以指示错误原因(例如,内存不足、地址族不支持等)。
3.2.2 conne函数
int connect(int s,const struct sockaddr *name,socklen_t namelen)
主要用于客户端
-
参数
-
int s:
这是一个套接字描述符,通常通过 socket 函数创建。它标识正在尝试连接的套接字。 -
const struct sockaddr *name
指向 sockaddr 结构的指针,包含目标服务器的地址信息。这可以是 sockaddr_in(用于 IPv4 地址)或 sockaddr_in6(用于 IPv6 地址)结构。该结构定义了要连接的地址(IP 地址和端口号)。 -
socklen_t namelen:
这个参数指定 name 指向的地址结构的长度。对于 sockaddr_in,可以使用 sizeof(struct sockaddr_in);对于 sockaddr_in6,使用 sizeof(struct sockaddr_in6)。 -
返回值
- 成功时返回 0。
- 失败时返回 -1,并设置 errno 值描述错误原因,例如连接失败、超时、目标不可达等。
3.2.3 bind函数
int bind(int s,const struct sockaddr *name, socklen_t namelen)
介绍:
bind 函数是在网络编程中用于将一个套接字(socket)与一个特定的地址(IP 地址和端口号)进行关联的函数。此函数通常在服务器端使用,以便让操作系统知道监听和接收的数据包应发送到哪个地址。
注意:bind 和 connect 是在网络编程中用于处理套接字的两个不同函数。它们的主要区别在于使用场景、目的和适用的角色(客户端与服务器)。以下是详细的比较:主要在服务器端使用,目的是使服务器能够监听来自客户端的连接请求或接收数据包。
-
参数
int s:
这是一个套接字描述符,通常通过 socket 函数创建。它标识正在尝试连接的套接字。const struct sockaddr *name
指向 sockaddr 结构的指针,包含目标服务器的地址信息。这可以是 sockaddr_in(用于 IPv4 地址)或 sockaddr_in6(用于 IPv6 地址)结构。该结构定义了要连接的地址(IP 地址和端口号)。socklen_t namelen:
这个参数指定 name 指向的地址结构的长度。对于 sockaddr_in,可以使用 sizeof(struct sockaddr_in);对于 sockaddr_in6,使用 sizeof(struct sockaddr_in6)。
-
返回值
- 成功时返回 0。
- 失败时返回 -1,并设置 errno 值描述错误原因,例如连接失败、超时、目标不可达等。
3.2.4 listen函数
int listen(int s, int backlog);
listen 函数的主要作用是在服务器端设置一个套接字,以便它可以接受来自客户端的连接请求。具体来说,它在以下几个方面起着重要作用:listen 主要用于 TCP 套接字,因为 TCP 是面向连接的协议,需要建立连接才能进行数据传输。对于 UDP 套接字,通常不需要调用 listen,因为 UDP 是无连接的。
-
参数
-
int s:
这是一个套接字描述符,通常通过 socket 和 bind 函数创建并绑定。此套接字用于等待来自客户端的连接请求。 -
int backlog
此参数指定在开始接受连接之前,系统可以排队的最大连接请求数。换句话说,如果有多个客户端同时尝试连接,而服务器尚未处理这些连接,则可以将这些连接请求保留在队列中,直到服务器准备好处理它们。
它的值通常设置为大于0的整数,意味着可以保持 backlog 个连接请求在排队状态(系统会根据具体实现限制这个值的大小)。注意,某些系统可能会对 backlog 的值设置上限。 -
返回值
- 成功时返回 0。
- 失败时返回 -1,并设置 errno 值来指示错误的原因,常见的错误包括:
套接字未处于连接状态(如未调用 bind)。
非法或过大的 backlog 值。。
3.2.5 send函数
ssize_t send(int s, const void *dataptr, size_t size, int flags);
-
参数
-
int s:
这是一个套接字描述符,通常通过 socket() 函数创建,并且已经通过 connect() 与远端主机建立了连接。这个套接字用于指定要通过哪个连接发送数据。 -
const void *dataptr
这是一个指向要发送数据的缓冲区的指针。这个数据可以是任何类型,通常是一个字符数组或字符串。发送的数据将根据 size 参数的指定长度进行处理。 -
size_t size:
这是要发送的数据字节数,指定 dataptr 指向的数据的长度。调用者需要确保这个大小不会超过缓冲区的实际大小,以防止缓冲区溢出。 -
int flags:
0:默认标志,表示使用常规发送操作 -
返回值
- 成功时,返回实际发送的字节数。
- 如果发送失败,返回 -1,并设置 errno 值来指示错误原因,例如:
套接字未连接(ENOTCONN)
网络不可达(ENETUNREACH)
连接被对方关闭(ECONNRESET)等
3.2.6 accept函数
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
accept 函数主要用于 TCP 协议中。当服务器使用 TCP 套接字与客户端进行通信时,accept 是建立连接的关键步骤。accept会阻塞等待客户端的连接
-
参数
-
int s:
这是一个套接字描述符,它是由 socket() 创建并通过 bind() 和 listen() 设置为监听状态的。s 表示服务器用于监听客户端请求的套接字。 -
struct sockaddr *addr
这是一个指向 sockaddr 结构体的指针,用于存储客户端的地址信息。当客户端成功连接后,accept 函数会填充这个结构体,提供客户端的地址和端口信息。
这个参数可以为 NULL,如果你不需要获取客户端的地址信息的话。 -
socklen_t *addrlen:
这是一个指向 socklen_t 类型变量的指针,表示 addr 结构体的大小。在调用 accept 之前,需要将这个值设置为 addr 指向的结构体的大小(例如,sizeof(struct sockaddr_in))。
在调用 accept 之后,这个值将被更新为实际填充的地址结构的大小。 -
返回值
- 成功时,返回一个新的套接字描述符,用于与连接的客户端进行通信。这个新套接字是独立的,允许你同时处理多个连接。
- 失败时返回 -1,并设置 errno 以指示错误原因。常见的错误包括:
套接字未处于监听状态(ENOTCONN)
由于无效的参数或资源限制等导致的连接错误(例如,ENOMEM)。
3.2.7 recv函数
ssize_t recv(int s,void *mem,size_t len,int flags)
阻塞等待客户端或者服务端,直到等待接收到消息
-
参数
-
int s:
这是一个套接字描述符,通常通过 socket() 函数创建,并且已经通过 connect() 与远端主机建立了连接。这个套接字用于指定要通过哪个连接发送数据。 -
void *mem
这是一个指向内存缓冲区的指针,用于存储接收到的数据。这个缓冲区应该预先分配足够的内存以容纳要接收的数据。 -
size_t size:
这个参数指定希望接收的字节数。它告知 recv 函数最多应该接收多少数据。如果可用数据超过这个大小,recv 可能只会返回部分数据。 -
int flags:
0:默认标志,表示使用常规发送操作 -
返回值
- 成功时,返回接收到的字节数。如果连接被对方关闭(EOF),返回 0。。
- 如果出错,返回 -1,并设置 errno 变量以指示错误原因,例如:
套接字无效(EBADF)
网络中断(EPIPE)
超时(ETIMEDOUT)等。
四.TCP通信协议
SYN:同步序列骗号(Synchronize Sequence Numbers)** 表示连接请求 **
ACK:(Acknowledge character)即是确认字符,在数据通信中,接收站发给发送站的一种传
输类控制字符。表示发来的** 数据已确认接收无误 **。
4.1TCP通信流程
-
server:
1.socket() 创建socket
2.bind() 绑定服务器地址结构
3.listen() 设置监听上限
4.accpet() 阻塞监听客户端连接
5.read() 读socket获取客户端数据
6.write(fd)
7.close() -
client:
1.socket() 创建socket
2.connect() 与服务器建立连接
3.write() 写数据到socket
4.read() 读转换后的数据显示读取结果
5.close()