python开发-网络编程

开发 python开发-网络编程

$fileName

IP地址

定位唯一设备

ipv4:点分十进制

ipv6:冒号十六进制

本地环回地址:127.0.0.1对应域名localhost

ping 公网ip

ping 局域网ip:能ping说明在一个局域网或者网段内

ping localhost:能ping说明本地物理网卡没有问题

端口

通信:通过ip找到对应设备,再通过端口号找到对应进程应用程序,确定数据是给哪个进程的

共65536个

知名端口号:0-1023固定分配给特定服务

21:FTP服务

25:SMTP(简单邮件传输协议)服务

80:HTTP服务

22:SSH服务

443:HTTPS服务

动态端口号:一般程序员开发应用程序使用端口号称为动态端口号,范围是从1024到65535。如果程序员开发的程序没有设置端口号,操作系统会在动态端口号这个范围内随机生成一个给开发的应用程序使用。
当运行一个程序默认会有一个端口号,当这个程序退出时,所占用的这个端口号就会被释放。

TCP

传输数据之前先要确定传输的协议

TCP 的英文全拼(Transmission Control Protocol)简称传输控制协议,它是一种面向连接的、可靠的、基于字节流的传输层通信协议。

TCP通信步骤:1.创建连接2.传输数据3.关闭连接

如:文件下载、上网等

TCP特点:

1.面向连接
通信双方必须先建立好连接才能进行数据的传输,数据传输完成后,双方必须断开此连接,以释放系统资源。
2.可靠传输
TCP采用发送应答机制
超时重传
错误校验(如延迟导致接收顺序错误,自动调整顺序)
流量控制和阻塞管理(网卡缓存区)

对应UDP(用户数据报协议):不建立连接、不可靠、不会管是否能够收到或者正确收到(上课用的广播、共屏)

TCP和UDP都是运输层的协议

socket

那么通信数据是如何完成传输的呢?socket套接字

进程通过网络与另一个机器上的进程通信、数据传输需要socket

只要跟网络相关的应用程序或者软件都使用到了socket。如qq、微信、浏览器、共屏软件等。

TCP网络应用程序

TCP网络应用程序:基于TCP协议、需要借助网络通信的程序

TCP网络应用程序开发分为:

  • TCP客户端程序开发

  • TCP服务端程序开发

主动发起建立连接请求的是客户端程序

等待接受连接请求的是服务端程序

d4fe-20260521-image-20260521001235238.png

网络程序通信的流程:
1.通过ip地址找到网络中的设备
2.通过端口号找到对应进程的端口
3.传输数据是还需要使用传输协议(tcp),保证数据的可靠性。

4.s0cket完成进程之间网络数的传输

tcp客户端程序开发

开发TCP客户端程序开发步骤回顾
1.创建客户端套接字对象
2.和服务端套接字建立连接
3.发送数据
4.接收数据
5.关闭客启端套接字

socket类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
导入socket模块
import socket
创建客户端socket对象
socket.socket(AddressFamily, Type)

参数说明:
AddressFamily表示IP地址类型,分为IPv4和IPv6
Type表示传输协议类型

方法说明:
connect(host,port)表示和服务端套接字建立连接
host是服务器ip地址
port是应用程序的端口号

send(data)表示发送数据,data是二进制数据
recv(buffersize)表示接收数据,buffersize是每次接收数据的长度

tcp客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if __name__ == '__main__':
import socket
# 1.创建客户端套接字对象
# socket.AF_INET表示ipv4,socket.SOCK_STREAM表示tcp协议
tcp_client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 2.和服务端套接字建立连接
tcp_client_socket.connect(("192.168.31.9",8080)) # 这里其实并没设置客户端端口,会默认随机分配一个动态端口号
# 3.发送数据到服务端
# 网络调试助手,windows端为gbk编码,linux为utf-8编码
send_content = "你好,我是tcp客户端小白!"
send_data = send_content.encode(encoding="gbk") # 需要使用二进制
tcp_client_socket.send(send_data)
# 4.接收服务端数据
# 1024:表示每次接收的最大字节数
recv_data = tcp_client_socket.recv(1024)
# 对二进制数据进行解码
recv_content = recv_data.decode(encoding="gbk")
print("接受的服务端数据为:", recv_data)
# 5.关闭客启端套接字
tcp_client_socket.close()

tcp服务端程序开发

1
2
3
4
5
6
7
8
开发TCP服务端程序开发步骤回顾
1.创建服务端端套接字对象
2.绑定端口号
3.设置监听
4.等待接受客户端的连接请求
5.按收数据
6.发送数据
7.关闭套接字

socket类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
导入socket 模块
import socket
创建服务端socket对象
socket.socket(AddressFamily, Type)
参数说明:
AddressFamily表示IP地址类型,分为IPv4和IPv6
Type表示传输协议类型

方法说明:
bind(host,port))表示绑定端口号,host是ip地址
port 是端口号,ip地址一般不指定,表示本机的任何一个ip地址都可以(有的机器有多个网卡,不指定ip表示通过任一个ip都可以)
listen(backlog)表示设置监听,backlog参数表示最大等待建立连接的个数(目前是单任务,一个客户端连接,其它的全部排队等待)
accept()表示等待接受客户端的连接请求
send(data)表示发送数据,data是二进制数据
recv(buffersize)表示接收数据,buffersize是每次接收数据的长度

a301-20260521-image-20260521211342931.png

tcp服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
if __name__ == '__main__':
import socket
# 1.创建服务端套接字对象
# socket.AF_INET表示ipv4,socket.SOCK_STREAM表示tcp协议
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 2.绑定端口号
# 第一个参数表示ip地址,一般不用指定,表示本机的任何一个ip即可
# 第二个参数表示端口号
tcp_server_socket.bind(("",9090)) # 客户端这里不绑定端口,因为没有意义,没人主动连接
# 3.设置监听
# 128:表示最大等待建立速接的个数
tcp_server_socket.listen(128)
# 4.等待接受客户端的连接请求
# 注意点:每次当客户瑞和服务搞建立连接成功职会返回一个新的套接字
# tcp_server_socket只负责等待接收客户端的连接请求,收发消息不使用该套接字
new_server_socket,ip_port = tcp_server_socket.accept() # 阻塞操作,等待客户端连接,返回元组(套接字,(ip,端口))
# 代码执行到此·说明客户福和服务端建立造接或功
print("来自:",ip_port)
recv_data = new_server_socket.recv(1024) # 阻塞
# 对二进制数据进行解码变成字符串
recv_content = recv_data.decode(encoding="utf-8")
print("接受的客户端数据为:", recv_content)
# 6.发送数据
send_content = "问题正在处理..."
send_data = send_content.encode(encoding="utf-8")
new_server_socket.send(send_data)
# 关团服务与客户瑞套接字·表示和客户端终止通信
# 7.关闭服务端套接字,表示服务端以后不再等待接受客户端的连接请求
new_server_socket.close()
tcp_server_socket.close()

端口号复用

当客户端和服务端建立连接后,服务端程序退出后端口号不会立即释放,需要等待大概1-2分钟。

解决办法有两种:
1.更换服务端端口号(极不推荐)
2.设置端口号复用(推荐大家使用)·也就是说让服务端程序退出后端口号立即释放

设置端口号复用的代码如下:

1
2
3
4
#参数1:表示当前套接字
#参数2:设置端口号复用选项
#参数3:设置端口号复用选项对应的值
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
if __name__ == '__main__':
import socket
# 1.创建服务端套接字对象
# socket.AF_INET表示ipv4,socket.SOCK_STREAM表示tcp协议
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 设置端口号复用,表示意思:服务端程序退出端口号立即释放
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
# 2.绑定端口号
# 第一个参数表示ip地址,一般不用指定,表示本机的任何一个ip即可
# 第二个参数表示端口号
tcp_server_socket.bind(("",9090)) # 客户端这里不绑定端口,因为没有意义,没人主动连接
# 3.设置监听
# 128:表示最大等待建立速接的个数
tcp_server_socket.listen(128)
# 4.等待接受客户端的连接请求
# 注意点:每次当客户瑞和服务搞建立连接成功职会返回一个新的套接字
# tcp_server_socket只负责等待接收客户端的连接请求,收发消息不使用该套接字
new_server_socket,ip_port = tcp_server_socket.accept() # 阻塞操作,等待客户端连接,返回元组(套接字,(ip,端口))
# 代码执行到此·说明客户福和服务端建立造接或功
print("来自:",ip_port)
recv_data = new_server_socket.recv(1024) # 阻塞
# 对二进制数据进行解码变成字符串
recv_content = recv_data.decode(encoding="utf-8")
print("接受的客户端数据为:", recv_content)
# 6.发送数据
send_content = "问题正在处理..."
send_data = send_content.encode(encoding="utf-8")
new_server_socket.send(send_data)
# 关团服务与客户瑞套接字·表示和客户端终止通信
# 7.关闭服务端套接字,表示服务端以后不再等待接受客户端的连接请求
new_server_socket.close()
tcp_server_socket.close()

TCP网络应用程序的注意点

1.当TCP客户端程序想要和TCP服务端程序进行通信的时候必须要先建立连接

2.TCP客户端程序一般不需要绑定端口号,因为客户端是主动发起建立连接的

3.TCP服务端程序必须绑定端口号,否则客户端找不到这个TCP服务端程序。

4.listen后的套接字是被动套接字,只负责接收新的客户端的连接请求,不能收发消息。

5.当TCP客户端程序和TCP服务端程序连接成功后TCP服务器端程序会产生一个新的套接字,收发客户端消息使用该套接字。
6.关闭accept返回的套接字意味着和这个客户端已经通信完毕。

7.关闭listen后的套接字意味着服务端的套接字关闭了,会导致新的客户端不能连接服务端,但是之前已经接成功的客户端还能正常通信
8.当客户端的套接字调用close后,服务器端的recv 会解阻塞,返回的数据长度为0,服务端可以通过返回数据的长度来判断客户端是否已经下线,反之服务端关闭套接字,客户端的recv 也会解阻塞,返回的数据长度也为0

tcp服务端服务多个客户端

目前只能循环排队服务客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
if __name__ == '__main__':
import socket
# 1.创建服务端套接字对象
# socket.AF_INET表示ipv4,socket.SOCK_STREAM表示tcp协议
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 设置端口号复用,表示意思:服务端程序退出端口号立即释放
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
# 2.绑定端口号
# 第一个参数表示ip地址,一般不用指定,表示本机的任何一个ip即可
# 第二个参数表示端口号
tcp_server_socket.bind(("",9090)) # 客户端这里不绑定端口,因为没有意义,没人主动连接
# 3.设置监听
# 128:表示最大等待建立速接的个数
tcp_server_socket.listen(128)
# 4.等待接受客户端的连接请求
# 注意点:每次当客户瑞和服务搞建立连接成功职会返回一个新的套接字
# tcp_server_socket只负责等待接收客户端的连接请求,收发消息不使用该套接字
while True:
new_server_socket,ip_port = tcp_server_socket.accept() # 阻塞操作,等待客户端连接,返回元组(套接字,(ip,端口))
# 代码执行到此·说明客户福和服务端建立造接或功
print("来自:",ip_port)
recv_data = new_server_socket.recv(1024) # 阻塞
# 对二进制数据进行解码变成字符串
recv_content = recv_data.decode(encoding="utf-8")
print("接受的客户端数据为:", recv_content)
# 6.发送数据
send_content = "问题正在处理..."
send_data = send_content.encode(encoding="utf-8")
new_server_socket.send(send_data)
# 关团服务与客户瑞套接字·表示和客户端终止通信
# 7.关闭服务端套接字,表示服务端以后不再等待接受客户端的连接请求
new_server_socket.close()
tcp_server_socket.close()

目前可以并行处理客户端连接,但是服务客户端时不能多轮处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import threading

def process(new_server_socket,ip_port):
# 代码执行到此·说明客户福和服务端建立造接或功
print("来自:",ip_port)
recv_data = new_server_socket.recv(1024) # 阻塞
# 对二进制数据进行解码变成字符串
recv_content = recv_data.decode(encoding="utf-8")
print("接受的客户端数据为:", recv_content)
# 6.发送数据
send_content = "问题正在处理..."
send_data = send_content.encode(encoding="utf-8")
new_server_socket.send(send_data)
# 关团服务与客户瑞套接字·表示和客户端终止通信
# 7.关闭服务端套接字,表示服务端以后不再等待接受客户端的连接请求
new_server_socket.close()



if __name__ == '__main__':
import socket
# 1.创建服务端套接字对象
# socket.AF_INET表示ipv4,socket.SOCK_STREAM表示tcp协议
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 设置端口号复用,表示意思:服务端程序退出端口号立即释放
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
# 2.绑定端口号
# 第一个参数表示ip地址,一般不用指定,表示本机的任何一个ip即可
# 第二个参数表示端口号
tcp_server_socket.bind(("",9090)) # 客户端这里不绑定端口,因为没有意义,没人主动连接
# 3.设置监听
# 128:表示最大等待建立速接的个数
tcp_server_socket.listen(128)
# 4.等待接受客户端的连接请求
# 注意点:每次当客户瑞和服务搞建立连接成功职会返回一个新的套接字
# tcp_server_socket只负责等待接收客户端的连接请求,收发消息不使用该套接字
while True:
new_server_socket,ip_port = tcp_server_socket.accept() # 阻塞操作,等待客户端连接,返回元组(套接字,(ip,端口))
args = {"new_server_socket":new_server_socket,"ip_port":ip_port}
t = threading.Thread(target=process,kwargs=args)
t.daemon = True
t.start()

tcp_server_socket.close()

最终程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import threading

def process(new_server_socket,ip_port):
# 代码执行到此·说明客户福和服务端建立造接或功
print("来自:",ip_port)
# 循环接受客户端消息
while True:
recv_data = new_server_socket.recv(1024) # 阻塞
if len(recv_data) == 0:
print(ip_port,"下线了!")
break
# 对二进制数据进行解码变成字符串
recv_content = recv_data.decode(encoding="utf-8")
print("接受的客户端数据为:", recv_content)
# 6.发送数据
send_content = "问题正在处理..."
send_data = send_content.encode(encoding="utf-8")
new_server_socket.send(send_data)
# 关团服务与客户瑞套接字·表示和客户端终止通信
# 7.关闭服务端套接字,表示服务端以后不再等待接受客户端的连接请求
new_server_socket.close()



if __name__ == '__main__':
import socket
# 1.创建服务端套接字对象
# socket.AF_INET表示ipv4,socket.SOCK_STREAM表示tcp协议
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 设置端口号复用,表示意思:服务端程序退出端口号立即释放
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
# 2.绑定端口号
# 第一个参数表示ip地址,一般不用指定,表示本机的任何一个ip即可
# 第二个参数表示端口号
tcp_server_socket.bind(("",9090)) # 客户端这里不绑定端口,因为没有意义,没人主动连接
# 3.设置监听
# 128:表示最大等待建立速接的个数
tcp_server_socket.listen(128)
# 4.等待接受客户端的连接请求
# 注意点:每次当客户瑞和服务搞建立连接成功职会返回一个新的套接字
# tcp_server_socket只负责等待接收客户端的连接请求,收发消息不使用该套接字
while True:
new_server_socket,ip_port = tcp_server_socket.accept() # 阻塞操作,等待客户端连接,返回元组(套接字,(ip,端口))
args = {"new_server_socket":new_server_socket,"ip_port":ip_port}
t = threading.Thread(target=process,kwargs=args)
# 设置守护主线程,主线程退出子线程直接销毁 t.setDaemon(True)
t.daemon = True
t.start()

# 服务端需要一直运行,这段代码可以不需要了
tcp_server_socket.close()

socket之send和recv的原理剖析

认识TCP socket的发送和接收缓冲区

当创建一个TCP socket对象的时候会有一个发送缓冲区和一个接收缓冲区,这个发送和接收缓冲区指的就是内存中的一片空间。

send原理剖析

send是不是直接把数据发给服务端?
不是,要想发数据,必须得通过网卡发送数据,应用程序是无法直接通过网卡发送数据的。它需要调用操作系统接口。也就是说,应用程序把发送的数据先写入到发送缓冲区(内存中的一片空间)。再由操作系统控制网卡把发送缓冲区的数据发送给服务端网卡

recv原理剖析

recv是不是直接从客户端接收数据?
不是,应用软件是无法直接通过网卡接收数据的,它需要调用操作系统接口,由操作系统通过网卡接收数据,把接收的数据写入到接收缓冲区(内存中的一片空间),应用程序再从接收缓存区获取客户端发送的数据

send和recv原理剖析

image-20260522134817535

说明:

  • 发送数据是发送到发送缓冲区
  • 接收数据是从接收缓冲区获取

小结

不管是recv还是send都不是直接接收到对方的数据和发送数据到对方,发送数据会写入到发送缓冲区,接收数据是从接收缓冲区来读取,发送数据和接收数据最终是由操作系统控制网卡来完成。