IP 地址是网络中的主机地址,用于两台网络主机能够互相找到彼此,这也是网络通信能够成功进行的基础。IP 地址一般以点分十进制的字符串来表示,如192.168.1.1
。
我们日常访问的网站,其所在的服务器主机都有唯一的 IP 地址,网络中的主机不计其数,靠记 IP 地址的方式来区分不同的主机显然比较困难,并且同一个网站可能有多个不同的 IP 地址,或者 IP 地址会因为某种原因而更换。
因此,用域名表示网站地址的方式便应运而生,如我们常见的www.baidu.com
比 IP 地址更容易被记住。因为实际的网络通信报文中使用的仍然是 IP 地址,所以需要使用域名解析协议去获取域名背后所对应的 IP 地址。
下文的讲解均以 IPv4 协议为基础。
国际标准化组织(ISO)制定的一个用于计算机或通信系统的标准体系,一般被称为 OSI(Open System Interconnection)七层模型。它为网络通信协议的实现提供了一个标准,通信双方在相同的层使用相同的协议,即可进行通信;就同一台设备而言,下层协议为上层协议提供了调用接口,将上层协议打包为底层协议,最终发送到网络上进行传输。
这七层分别为:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。
为了简化协议实现或者方便理解,五层模型或者四层模型的概念也诞生了。四层模型一般被提及的比较多,包括:应用层、传输层、网络层、网络接口层。
上文中的 IP 地址则属于网络层。
网络层用于把该主机所有的网络数据转发到网卡,经由物理层电路发送到网络中去。
为了方便阐述,下文将按照四层模型来进行讲解。
IP 地址解决了网络中两台主机如何能够找到彼此,进而进行报文收发的问题。
试想下,一台主机上可能运行着多个应用程序,执行着不同的网络任务。这时,某台 IP 地址的主机收到了另一台主机的报文,这个报文数据要传递给哪个应用程序呢?
为了解决这个问题,人们基于网络层协议演化出了传输层协议,传输层协议为本地的网络应用分配不同的端口。收到网络层的报文后,根据不同的端口号,将数据递交给不同的应用。
为了应对不同的场景,传输层协议分为 UDP 和 TCP 协议。
UDP 协议具有以下特点:
根据不同的需求,基于 UDP 衍生出了一些应用层协议,不同的应用会默认指定一个端口号。端口号亦可根据实际情况更换。
常见的基于 UDP 的应用协议及端口如下:
熟知端口号 | 协议 | 说明 |
---|---|---|
0 | -- | 保留 |
7 | echo | 报文回送服务器 |
53 | nameserver | 域名服务器 |
67 | bootps | BOOT 或 DHCP 服务器 |
68 | bootpc | BOOT 或 DHCP 客户端 |
69 | TFTP | 简单文件传输协议 |
123 | NTP | 网络时间协议 |
161 | SNMP | 简单网络管理协议 |
TCP 协议具有以下特点:
根据不同的需求,基于 TCP 衍生出了一些应用层协议,不同的应用会默认指定一个端口号。端口号亦可根据实际情况更换。
常见的基于 TCP 的应用协议及端口如下:
熟知端口号 | 协议 | 说明 |
---|---|---|
0 | -- | 保留 |
7 | echo | 报文回送服务器 |
20 | FTP-DATA | 文件传输协议(数据) |
21 | FTP | 文件传输协议 |
23 | Telnet | 终端连接 |
25 | SMTP | 简单邮件传输协议 |
53 | DNS | 域名服务器 |
80 | HTTP | HTTP服务器 |
110 | POP3 | 邮局协议版本3 |
1080 | SOCKS | 代理服务器协议 |
确定TCP连接的五元组:协议类型(TCP)、本地 IP、本地端口、远端 IP、远端端口。
QuecPython 提供了usocket
模块,用于网络通信的 socket 编程。关于usocket
模块接口的用法,点此查看。
本节分为 TCP 网络编程、UDP 网络编程和多网卡网络编程三部分。
在开始 TCP 网络编程之前,我们先通过下图,初步了解下 TCP 服务器与客户端的 socket 编程模型:
上图的右侧是最简的 TCP 客户端编程的接口调用流程:
调用socket()
接口创建 socket 对象。
调用connect()
接口连接服务器。
调用send()
接口向服务器发送数据。
调用recv()
接口接收服务器下发的数据。
循环执行第 3 步和第 4 步,业务满足一定条件或连接断开,调用close()
接口关闭 socket,释放资源。
几乎所有编程语言实现的 socket 接口,默认都是阻塞模式的,即所有涉及到网络报文收发的接口,如connect()
、send()
、recv()
、close()
等,默认都是阻塞式接口。
开始之前,我们先了解上图中 5 个与 TCP 客户端编程相关的接口的用法:
usocket.socket(af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP)
:创建 socket 对象
af
:地址族,取值为usocket.AF_INET
和usocket.AF_INET6
,分别表示 IPv4 地址与 IPv6 地址。type
:socket 类型,取值为usocket.SOCK_STREAM
、usocket.SOCK_DGRAM
、usocket.SOCK_RAW
,分别表示 流套接字、数据报套接字与原始套接字。proto
:协议类型,取值为usocket.IPPROTO_TCP
、usocket.IPPROTO_UDP
、IPPROTO_TCP_SER
等,分别表示 TCP 协议、 UDP 协议、TCP 服务器等。sock.connect(address)
:建立连接
address
:包含 IP 地址字符串与端口号的元组或列表。sock.send(data)
:发送数据,返回实际发送的数据长度。
data
:bytes 类型的数据sock.recv(size)
:接收数据,返回接收到的 bytes 类型的数据
size
:读取数据的长度sock.close()
:关闭 socket。
IPPROTO_TCP_SER
是 QuecPython 特定的,非标的,在 QuecPython 中进行 TCP server 编程时,socket()
接口的第三个参数必须是IPPROTO_TCP_SER
。
我们可以基于此来实现一个最简单的 TCP 客户端功能:连接百度服务器,手动组织 HTTP 报文并发送给服务器,循环接收服务器下发的 html 页面内容,直至接收完毕。
代码示例如下:
import usocket
def tcp_client(address, port):
# Create a socket object
sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP)
print('socket object created.')
# Connect to the TCP server
sock.connect((address, port))
print('tcp link established: %s, %s' % (address, port))
# Package user data
data = 'GET / HTTP/1.1\r\n'
data += 'Host: ' + address + ':' + str(port) + '\r\n'
data += 'Connection: close\r\n'
data += '\r\n'
data = data.encode()
# Send the data
sock.send(data)
print('<-- send data:')
print(data)
# Receive the data
print('--> recv data:')
while True:
try:
data = sock.recv(1024)
print(data)
except:
# Connection ends until the data is fully received
print('tcp disconnected.')
sock.close()
break
上述示例中,我们组织的 HTTP 请求报文是:
GET / HTTP/1.1<cr><lf>
Host: <host_addr>:<port><cr><lf>
Connection: close<cr><lf>
<cr><lf>
该报文中:
<cr><lf>
:回车换行。<host_addr>
:服务器的域名或 IP 地址。<port>
:服务器的端口号。需要注意的是,上述 HTTP 报文中添加了
Connection: close
头域,用来通知服务器这是一个短连接,当 HTTP 请求完成时,服务器会主动断开连接。这么做的好处是:当服务器主动断开连接时,网络协议栈底层会抛出异常,用户可以通过捕获该异常,得知客户端也需要断开连接了。
这是为了快速实现 TCP 客户端测试代码的取巧做法。如果不增加
Connection: close
头域,对于 HTTP/1.1 协议来说,默认是长连接,服务器不会主动断开连接。此时就需要在客户端增加解析 HTTP 报文的代码,通过解析报文,判断数据被全部接收后,客户端才能断开连接。这会增加测试代码的复杂度,而且 HTTP 报文的解析也不在本文的阐述范围内。
请求报文组织完成后,通过encode()
方法将字符串转换为 utf-8 编码的 bytes 类型,即上述代码中的data = data.encode()
。而后调用sock.send(data)
接口发送数据。
服务器下发的数据可能会很长,我们无法预知,因此需要在 while 循环中反复调用sock.recv(1024)
接口读取数据,直到业务满足一定条件或连接异常,而后调用sock.close()
关闭连接。
上文中说道,由于自行组织的 HTTP 报文中添加了Connection: close
头域,服务器会主动断开连接,导致模组协议栈底层抛异常。所以在 while 循环中需要对sock.recv(1024)
做try
操作,当捕获到异常后,在except
下断开连接,同时退出循环。
在启动客户端代码之前,需先检查网络状态,在确认网络正常的情况下,调用tcp_client()
函数启动客户端。
import checkNet
if __name__ == '__main__':
stage, state = checkNet.waitNetworkReady(30)
if stage == 3 and state == 1: # Network connection is normal
print('Network connection successful.')
tcp_client('36.152.44.95', 80) # Start the client
else:
print('Network connection failed, stage={}, state={}'.format(stage, state))
上述代码中,我们直接使用了百度服务器的 IP 地址36.152.44.95
,运行结果如下:
Network connection successful.
socket object created.
tcp link established: 36.152.44.95, 80
<-- send data:
b'GET / HTTP/1.1\r\nHost: 36.152.44.95:80\r\nConnection: close\r\n\r\n'
--> recv data:
b'HTTP/1.1 200 OK\r\nAccept-Ranges: bytes\r\nCache-Control: no-cache\r\nContent-Length: 9508\r\nContent-Security-Policy: frame-ancestors......Baidu "</script></body></html>
tcp disconnected.
而一般情况下,用户更容易记住域名www.baidu.com
。这时需要我们调用域名解析的接口,将域名转换为 IP 地址,而不是直接记住这个 IP 地址。
域名解析的函数原型为usocket.getaddrinfo(host, port)
,说明如下:
参数:
host
:主机域名(IP 地址字符串亦可)。port
:端口号返回值:[(family, type, proto, canonname, sockaddr)]
family
:地址族。
type
- socket 类型。
proto
- 协议类型。
canonname
- 主机域名。
sockaddr
- 包含 IP 地址和端口号的列表。
了解了此接口的用法后,tcp_client()
函数实现中,直接以sockaddr
作为sock.connect()
函数的参数即可。
优化后的tcp_client()
函数实现如下(点此在 github 中下载完整代码):
import usocket
import checkNet
def tcp_client(address, port):
# Create a socket object
sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP)
print('socket object created.')
# Domain name resolution
sockaddr = usocket.getaddrinfo(address, port)[0][-1]
print('DNS for %s: %s' % (address, sockaddr[0]))
# Connect to the TCP server
sock.connect(sockaddr)
print('tcp link established.')
# Package user data
data = 'GET / HTTP/1.1\r\n'
data += 'Host: ' + address + ':' + str(port) + '\r\n'
data += 'Connection: close\r\n'
data += '\r\n'
data = data.encode()
# Send the data
sock.send(data)
print('<-- send data:')
print(data)
# Receive the data
print('--> recv data:')
while True:
try:
data = sock.recv(1024)
print(data)
except:
# Connection ends until the data is fully received
print('tcp disconnected.')
sock.close()
break
if __name__ == '__main__':
stage, state = checkNet.waitNetworkReady(30)
if stage == 3 and state == 1: # Network connection is normal
print('Network connection successful.')
tcp_client('www.baidu.com', 80) # Start the client
else:
print('Network connection failed, stage={}, state={}'.format(stage, state))
优化后的客户端代码,可以传递域名或者 IP 地址,更易使用。运行结果如下:
Network connection successful.
socket object created.
DNS for www.baidu.com: 36.152.44.95
tcp link established.
<-- send data:
b'GET / HTTP/1.1\r\nHost: 36.152.44.95:80\r\nConnection: close\r\n\r\n'
--> recv data:
b'HTTP/1.1 200 OK\r\nAccept-Ranges: bytes\r\nCache-Control: no-cache\r\nContent-Length: 9508\r\nContent-Security-Policy: frame-ancestors......Baidu "</script></body></html>
tcp disconnected.
对于蜂窝通信模组来说,一般情况下基站分配的 IP 地址是局域网地址,外部网络设备无法直接访问模组,这种情况下,模组做 TCP 服务器的意义不是很大。
但是对于专网卡来说,基站为其分配专网地址,并允许专网内的网络设备能够访问模组,此时模组是可以做 TCP 服务器的,这在电表类的产品中是比较常见的。
而且在Wi-Fi和以太网的网络中,是允许局域网设备访问该主机的。
所以我们仍有必要讲解下如何在模组中实现 TCP 服务器功能。
TCP 服务器与客户端的 socket 编程模型示意图的左侧展示了服务器编程的接口调用流程:
调用socket()
接口创建 socket 对象。
调用bind()
接口绑定本地的地址和端口。
调用listen()
接口监听客户端连接请求。
调用accept()
接口接受客户端连接请求。
调用recv()
接口接收客户端上行的数据。
调用send()
接口向客户端发送数据。
每一个客户端连接中,循环执行第 5 步和第 6 步,业务满足一定条件或连接断开,调用close()
接口关闭 socket,释放资源。
在接受客户端连接请求的线程中,循环执行第 4 步,以接受更多的客户端接入。
TCP 服务器编程调用的接口相比客户端,多了bind()
、listen()
、accept()
三个接口,说明如下:
socket.bind(address)
:绑定本地 IP 地址与端口。
address
:包含 IP 地址字符串与端口号的元组或列表。因为服务器一般需要固定的 IP 地址与端口,如果不进行绑定,客户端将无从得知服务器的 IP 地址和端口号。listen(backlog)
:监听客户端连接请求。
backlog
:除了正在处理的连接请求外,服务器允许后面排队等待处理的连接请求的个数。accept()
:接受客户端连接请求。了解了流程和接口用法,我们这里做一个实验:在模组中分别编写一个 TCP 服务器程序和一个 TCP 客户端程序,客户端周期性向服务器发送数据,而后等待服务器回送数据。
TCP 服务器代码如下:
import usocket
import _thread
def _client_conn_proc(conn, ip_addr, port):
while True:
try:
# Receive data sent by the client
data = conn.recv(1024)
print('[server] [client addr: %s, %s] recv data:' % (ip_addr, port), data)
# Send data back to the client
conn.send(data)
except:
# Exception occurred and connection closed
print('[server] [client addr: %s, %s] disconnected' % (ip_addr, port))
conn.close()
break
def tcp_server(address, port):
# Create a socket object
sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP_SER)
print('[server] socket object created.')
# Bind the server IP address and port
sock.bind((address, port))
print('[server] bind address: %s, %s' % (address, port))
# Listen for client connection requests
sock.listen(10)
print('[server] started, listening ...')
while True:
# Accept a client connection request
cli_conn, cli_ip_addr, cli_port = sock.accept()
print('[server] accept a client: %s, %s' % (cli_ip_addr, cli_port))
# Create a new thread for each client connection for concurrent processing
_thread.start_new_thread(_client_conn_proc, (cli_conn, cli_ip_addr, cli_port))
该段代码中,服务器每接入一个客户端,则新建一个线程,专门处理该客户端连接的相关事宜,做到并发处理。
TCP 客户端代码如下:
import usocket
import utime
def tcp_client(address, port):
# Create a socket object
sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP)
print('[client] socket object created.')
# Connect to the TCP server
print('[client] connecting: %s, %s' % (address, port))
sock.connect((address, port))
print('[client] connected.')
data = b'1234567890'
while True:
try:
# Send data to the server
sock.send(data)
print('[client] send data:', data)
# Read the data sent back by the server
data = sock.recv(1024)
print('[client] recv data:', data)
print('[client] -------------------------')
# Delay for 1 second
utime.sleep(1)
except:
# Connection ends until the data is fully received
print('[client] disconnected.')
sock.close()
break
主流程代码如下:
import checkNet
import _thread
import utime
import dataCall
if __name__ == '__main__':
stage, state = checkNet.waitNetworkReady(30)
if stage == 3 and state == 1: # Network connection is normal
print('[net] Network connection successful.')
# Get the IP address of the module
server_addr = dataCall.getInfo(1, 0)[2][2]
server_port = 80
# Start the server thread to listen for client connection requests
_thread.start_new_thread(udp_server, (server_addr, server_port))
# Delay for a while to ensure that the server starts successfully
print('sleep 3s to ensure that the server starts successfully.')
utime.sleep(3)
# Start the client
udp_client(server_addr, server_port)
else:
print('[net] Network connection failed, stage={}, state={}'.format(stage, state))
该段代码中调用了dataCall.getInfo()
接口获取模组的 IP 地址,该接口的返回值格式为(profileID, ipType, [state, reconnect, addr, priDNS, secDNS])
。说明如下:
profileID
:PDP 上下文场景 ID。该术语比较晦涩,简单的理解就是:蜂窝通信模组可以和基站建立多路连接,每一路对应一个不同的 IP 地址。一般情况下,一路连接就足以满足需求。如果想要获取指定某一路的 IP 地址,该参数填入对应的 ID 即可。ipType
:IP 协议类型。取值范围为 0-2,分别表示 IPv4协议、IPv6协议、IPv4与IPv6协议共存。初学者可以仅关心 IPv4 即可。state
:与基站间的网络连接状态。0 表示未连接,1 表示连接。reconnect
:重连标志。保留未用。addr
:IP 地址。priDNS
:主 DNS 服务器地址。secDNS
:辅 DNS 服务器地址。代码中的server_addr = dataCall.getInfo(1, 0)[2][2]
表示获取第 1 路连接的 IPv4 协议的 IP 地址,将其作为本地服务器的 IP 地址。
- 支持了 QuecPython 的蜂窝通信模组在上电后会自动进行第一路的蜂窝数据连接。
- 点此查看更多与蜂窝数据连接相关的接口用法。
- 点此在 github 中下载上述完整代码。
该实验代码的运行结果如下:
[net] Network connection successful.
sleep 3s to ensure that the server starts successfully.
[server] socket object created.
[server] bind address: 10.104.189.115, 80
[server] started, listening ...
[client] socket object created.
[client] connecting: 10.104.189.115, 80
[client] connected.
[client] send data: b'1234567890'
[server] accept a client: 10.104.189.115, 55187
[server] [client addr: 10.104.189.115, 55187] recv data: b'1234567890'
[client] recv data: b'1234567890'
[client] -------------------------
[client] send data: b'1234567890'
[server] [client addr: 10.104.189.115, 55187] recv data: b'1234567890'
[client] recv data: b'1234567890'
[client] -------------------------
...
在开始 UDP 网络编程之前,我们先通过下图,初步了解下 UDP 服务器与客户端的 socket 编程模型:
从图中可以看出,UDP 服务器也需要调用bind()
接口,绑定本地的 IP 地址和端口号,这是作为服务器所必须的接口调用。
同时,UDP 编程在接口调用上也有与 TCP 编程不同之处:
socket()
接口参数不同:
type
为usocket.SOCK_STREAM
,而 UDP 编程时,第二个参数type
为usocket.SOCK_DGRAM
。proto
为usocket.IPPROTO_TCP
或usocket.IPPROTO_TCP_SER
,而 UDP 编程时,第三个参数proto
为usocket.IPPROTO_UDP
。connect()
接口去连接服务器。sendto()
接口将数据发送出去即可。recvfrom()
接口接收数据。
sendto()
接口是否能真正将数据发送到目的地,视网络环境而定,如果无法找到目标 IP 地址对应的主机,则数据被丢弃。
接下来,我们做一个实验:在模组中分别编写一个 UDP 服务器程序和一个 UDP 客户端程序,客户端周期性向服务器发送数据,而后等待服务器回送数据。
有了前面 TCP 编程的经验,我们直接给出实验代码(点此在 github 中下载完整代码):
import usocket
import _thread
import utime
import checkNet
import dataCall
def udp_server(address, port):
# Create a socket object
sock = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM, usocket.IPPROTO_UDP)
print('[server] socket object created.')
# Bind server IP address and port
sock.bind((address, port))
print('[server] bind address: %s, %s' % (address, port))
while True:
# Read client data
data, sockaddr = sock.recvfrom(1024)
print('[server] [client addr: %s] recv data: %s' % (sockaddr, data))
# Send data back to the client
sock.sendto(data, sockaddr)
def udp_client(address, port):
# Create a socket object
sock = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM, usocket.IPPROTO_UDP)
print('[client] socket object created.')
data = b'1234567890'
while True:
# Send data to the server
sock.sendto(data, (address, port))
print('[client] send data:', data)
# Read data sent back from the server
data, sockaddr = sock.recvfrom(1024)
print('[client] [server addr: %s] recv data: %s' % (sockaddr, data))
print('[client] -------------------------')
# Delay for 1 second
utime.sleep(1)
if __name__ == '__main__':
stage, state = checkNet.waitNetworkReady(30)
if stage == 3 and state == 1: # Network connection is normal
print('[net] Network connection successful.')
# Get the IP address of the module
server_addr = dataCall.getInfo(1, 0)[2][2]
server_port = 80
# Start the server thread
_thread.start_new_thread(udp_server, (server_addr, server_port))
# Delay for a while to ensure that the server starts successfully
print('sleep 3s to ensure that the server starts successfully.')
utime.sleep(3)
# Start the client
udp_client(server_addr, server_port)
else:
print('[net] Network connection failed, stage={}, state={}'.format(stage, state))
运行结果如下:
[net] Network connection successful.
sleep 3s to ensure that the server starts successfully.
[server] socket object created.
[server] bind address: 10.110.90.159, 80
[client] socket object created.
[client] send data: b'1234567890'
[server] [client addr: ('10.104.189.115', 62104)] recv data: b'1234567890'
[client] [server addr: ('10.104.189.115', 80)] recv data: b'1234567890'
[client] -------------------------
[client] send data: b'1234567890'
[server] [client addr: ('10.104.189.115', 62104)] recv data: b'1234567890'
[client] [server addr: ('10.104.189.115', 80)] recv data: b'1234567890'
[client] -------------------------
...
多网卡,顾名思义,一台设备中有多个网卡。在进行网络通信时,如何选取指定的网卡呢?有两种方法:
socket.bind()
接口。
socket.bind()
仅用于将当前网络连接与指定的 IP 地址及端口号绑定,不区分客户端或服务器。- 端口号写 0 表示随机生成端口号,随机端口号在客户端编程中很常用。如果端口号非 0,即固定端口号,需要通过
socket.setsockopt(usocket.SOL_SOCKET, usocket.SO_REUSEADDR, 1)
设置端口重用,否则将会连接失败。
在实际应用场景中,有时会进行多路蜂窝数据连接,有时会 4G 网卡、外接的以太网卡或 Wi-Fi 网卡并存,此两种方法要如何实现呢?
前文提到,模组上电后会自动进行第 1 路的蜂窝数据连接,并且将该路网卡设置为默认网卡。
如果调用dataCall
相关接口进行了多路连接,则后面的连接可通过dataCall.getInfo()
接口获取模组蜂窝数据连接的 IP 地址。
目前暂未提供相关接口,将后来的蜂窝数据连接的网卡设置为默认网卡。
socket.bind()
接口绑定 IP 地址以太网卡提供ipconfig()
方法,用以获取包含 IP 地址等信息的网卡属性,返回值格式为:[(mac, hostname), (iptype, ip, subnet, gateway, primary_dns,secondary_dns)]
。
mac
:以太网mac地址。hostname
:网卡名称。iptype
:IP 协议类型。4 表示 IPv4,6 表示 IPv6。ip
:IP 地址。subnet
:子网掩码。gateway
:网关地址。primary_dns
:主 DNS 服务器地址。secondary_dns
:辅 DNS 服务器地址。获取以太网卡 IP 地址并且绑定的代码示例如下(点此在 github 中下载完整代码):
import ethernet
import usocket
# Create an Ethernet NIC
eth = ethernet.W5500(b'\x12\x34\x56\x78\x9a\xbc','','','',-1,38,36,37, 0)
print('W5500 ethernet nic created.')
# Enable DHCP
eth.dhcp()
print('DHCP enabled.')
# Enable NIC
eth.set_up()
print('Ethernet nic enabled.')
# After the NIC is registered, check the network configuration information
ip_conf = eth.ipconfig()
print('get ip_conf:', ip_conf)
def tcp_client(address, port):
# Create a socket object
sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP)
print('socket object created.')
# Bind the Ethernet NIC IP address after creating the socket object and before connecting to the server
local_address = ip_conf[1][1]
sock.bind((local_address, 0))
print('bind ethernet address: %s', local_address)
# Resolve the domain name
sockaddr=usocket.getaddrinfo(address, port)[0][-1]
print('DNS for %s: %s' % (address, sockaddr[0]))
# Connect to the TCP server
sock.connect(sockaddr)
print('tcp link established.')
# More code in TCP client samples above
以太网卡提供set_default_NIC(ip)
方法,用来将指定 IP 地址的网卡设置为默认网卡。
示例代码如下(点此在 github 中下载完整代码):
import ethernet
# Create an Ethernet NIC
eth = ethernet.W5500(b'\x12\x34\x56\x78\x9a\xbc','','','',-1,38,36,37, 0)
print('W5500 ethernet nic created.')
# Enable DHCP
eth.dhcp()
print('DHCP enabled.')
# After the NIC is registered, check the network configuration information
ip_conf = eth.ipconfig()
print('get ip_conf:', ip_conf)
# Set the Ethernet NIC as the default NIC
eth.set_default_NIC(ip_conf[1][1])
print('W5500 is set as default nic.')
# Enable the NIC
eth.set_up()
print('Ethernet nic enabled.')
- 点此查看以太网相关的接口用法。
- 关于以太网卡更多的编程应用指导,请查看相关文档。
socket.bind()
接口绑定 IP 地址Wi-Fi 网卡也提供了一个名为ipconfig()
的方法,但是该方法的返回值与以太网稍有不同,格式为(ip, subnet, gateway, mtu, primary_dns, secondary_dns)
。
Wi-Fi 网卡的ipconfig()
方法说明如下:
ip
:IP 地址。subnet
:子网掩码。gateway
:网关地址。mtu
:最大传输单元。primary_dns
:主 DNS 服务器地址。secondary_dns
:辅 DNS 服务器地址。获取 Wi-Fi 网卡 IP 地址并且绑定的代码示例如下(点此在 github 中下载完整代码):
from usr.WLAN import ESP8266
from machine import UART
import usocket
# Create a Wi-Fi NIC
wifi = ESP8266(UART.UART2, ESP8266.STA)
print('Wi-Fi nic created.')
# Configure the SSID and password, and connect to the router
ssid = 'ssid'
password = 'password'
wifi.station(ssid,password)
print('Wi-Fi connected:%s, %s.' % (ssid, password))
# Configure the DNS server address for the Wi-Fi NIC
wifi.set_dns('8.8.8.8', '114.114.114.114')
print('Wi-Fi DNS server configured.')
# Check the network configuration information
ip_conf = wifi.ipconfig()
print('get ip_conf:', ip_conf)
def tcp_client(address, port):
# Create a socket object
sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP)
print('Socket object created.')
# Before connecting to the server, bind the Wi-Fi NIC IP address
local_address = ip_conf[0]
sock.bind((local_address, 0))
print('Bind ethernet address: %s', local_address)
# Resolve the domain name
sockaddr=usocket.getaddrinfo(address, port)[0][-1]
print('DNS for %s: %s' % (address, sockaddr[0]))
# Connect to the TCP server
sock.connect(sockaddr)
print('TCP link established.')
# More code in TCP client samples above
Wi-Fi 网卡也提供了set_default_NIC(ip)
方法,用来将指定 IP 地址的网卡设置为默认网卡。
示例代码如下(点此在 github 中下载完整代码):
from usr.WLAN import ESP8266
from machine import UART
# Create a Wi-Fi NIC
wifi = ESP8266(UART.UART2, ESP8266.STA)
print('Wi-Fi NIC created.')
# Configure the SSID and password, and connect to the router
ssid = 'ssid'
password = 'password'
wifi.station(ssid,password)
print('Wi-Fi connected:%s, %s.' % (ssid, password))
# Configure the DNS server address for the Wi-Fi NIC
wifi.set_dns('8.8.8.8', '114.114.114.114')
print('Wi-Fi DNS server configured.')
# Check the network configuration information
ip_conf = wifi.ipconfig()
print('get ip_conf:', ip_conf)
# Set the Wi-Fi NIC as the default NIC
wifi.set_default_NIC(ip_conf[0])
print('Wi-Fi is set as default NIC.')
- 点此查看 Wi-Fi 网卡相关的接口用法。
- 关于 Wi-Fi 网卡更多的编程应用指导,请查看相关文档。
1. 为什么连接服务器会失败?
2. TCP 有自动重连功能吗?
底层没有自动重连,重连机制在应用层处理。
3. DNS 解析失败怎么排查原因?
检查 SIM 卡是否注网成功以及检查该地址的有效性再次进行尝试。
4. 为什么 4G 模块专网卡连接服务器失败?
5. 各系列模组最多可同时创建多少路 socket?
6. 4G 模块可以同时作为服务器和客户端使用吗?
7. 为什么我一包数据只有不到 50B,一天消耗的流量要远远大于实际传输值
如果使用的是 TCP 协议,需要三次握手四次挥手才算完成了一次数据交互,原始数据不多但是由于 TCP 协议决定的一包数据必须要加包头包尾帧校验等,所以实际消耗的流量不止 50B,部分运营商有限制每一包数据必须 1KB 起发,不足 1KB 也会加各种校验凑足 1KB。
8. 如果 KeepAlive 设置的时间长,会不会被基站断开?
会,一般建议使用 2 分钟,不建议超过 4 分钟,基站策略会关闭长时间没有数据传输的连接,太长时间可能会导致连接被基站关闭。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。