python学习笔记-(8)网络编程
2020-10-08 本文已影响0人
MR_詹
IP
- IP地址的作用是标识网络中唯一的一台设备
- IP地址的表现形式分为:IPv4 和 IPv6
- 查看网卡信息:ifconfig
- 检查网络:ping
(ping www.baidu.com检查是否能上公网
ping 当前局域网的ip地址 检查是否在同一个局域网内
ping 127.0.0.1 检查本地网卡是否正常
)
Socket
scoket简称套接字,是进程之间通信一个工具,`进程之间想要进行网络通信需要基于这个socket`
Socket的发送send和接收recv原理
当创建一个TCP socket对象的时候会有一个`发送缓冲区`和一个`接收缓冲区`,
这个发送和接收缓冲区指的就是`内存中的一片内存`
`send原理`:
想要发送数据,必须得通过网卡发送数据,应用程序是无法直接通过网卡发送数据的,
它需要调用操作系统接口,也就是说,应用程序把发送的数据先写入到发送缓冲区(内存中的一片空间),
再由操作系统控制网卡把发送缓冲区的数据发送到服务端网卡
`recv原理`:
应用软件是无法直接通过网卡接收数据的,它需要调用操作系统接口,由操作系统通过网卡接收数据,
把接收的数据写入到接收缓冲区(内存中的一片空间),应用程序再从接收缓冲区获取客户端发送的数据
总结:
不管是recv和send都不是直接接收对方的数据和发送数据到对方,发送数据会写入到发送缓存区,
接收收据是从接收缓冲来读取,`发送数据和接收数据最终是有操作系统控制网卡来完成`
image.png
端口
* `通过ip地址找到对应的设备,通过端口号找到对应的端口,然后通过端口把数据传输给应用程序`
* 端口的作用是给运行的应用成提供传输数据的 通道
* 端口号的作用是用来区分和管理不同端口的,通过端口号能找到唯一个的一个端口
* 端口号的分类
1、`知名端口号`
指众所周知的端口号,范围从0到1023
这些端口一般固定分配给一些服务,比如21端口分配给FTP服务,25端口分配给SMTP服务,80端口分配给HTTP服务
2、`动态端口号`
一般程序员开发应用使用端口号称为动态端口号,范围从1024到65535
* 如果程序员开发的程序没有设置端口号,操作系统会在动态端口号这个范围内随机生成一个给开发的应用程序使用
当运行一个程序默认会有一个端口号,当这个程序退出时,所占用的这个端口号就会被释放
* 当运行一个程序默认会有一个端口号,当这个程序退出时,所占用的这个端口号就会释放
TCP
数据在发送之前还需要选择一个对应的传输协议,保证程序之间按照指定的传输规则进行数据的通信
TCP简称传输控制协议,它是一种面向连接的、可靠的、基于字节流的传输层通信协议。
TCP通信步骤:
1、创建步骤
2、传输数据
3、关闭连接
TCP特点:
1、`面向连接`
通信双方必须先建立好连接才能进行数据的传输,数据传输完成后,双方必须断开此连接,以释放系统资源。
2、`可靠传输`
* TCP采用发送应答机制
* 超时重传
* 错误校验
* 流量控制和阻塞管理
Socket
`进程之间网络数据的传输`可以通过socket来完成,socket就是进程间网络数据通信的工具
开发流程
TCP网络应用程序开发分为客户端程序开发和服务端程序开发
主动发起建立连接请求的客户端程序
等待接受连接请求的服务端程序
TCP开发流程
TCP客户端开发
# 导入socket模块
import socket
if __name__ == '__main__':
# 1. 创建TCP客户端套接字
# AF_INET:ipv4地址类型
# SOCK_STREAM:tcp传输协议类型
tcp_client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 2. 建立连接
# 第一个是:服务端ip地址
# 第二个是:应用程序端口号
tcp_client_socket.connect(('192.168.22.78',9090))
# 3. 发送数据
# windows里面的网络调试小助手使用的是gbk编码
# linux里面的网络调试小助手使用的是utf-8
send_content = '你好,我是客户端'
send_data = send_content.encode('utf-8')
tcp_client_socket.send(send_data)
# 4. 接受收据
# 1024: 表示每次接收的最大字节数
recv_data = tcp_client_socket.recv(1024)
# 对二进制数据进行编码
recv_content = recv_data.decode('utf-8')
print(f'接收到服务器返回的数据:{recv_content}')
# 5. 关闭套接字
tcp_client_socket.close()
TCP 服务端开发
import socket
if __name__ == '__main__':
# 1.创建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_client,ip_port = tcp_server_socket.accept()
# 代码执行到此说明客户端和服务端建立连接成功
print('客户端的ip地址和端口号为:',ip_port)
# 5.接收客户端的链接请求
# 收发消息都使用返回的这个新套接字
recv_data = new_client.recv(1024)
recv_content = recv_data.decode('utf-8')
print('接收客户端的数据为:',recv_content)
# 6.发送数据到客户端
send_content = '问题正在处理中...'
send_data = send_content.encode('utf-8')
new_client.send(send_data)
# 关闭服务端与客户端套接字,表示和客户端终止通信
new_client.close()
# 7.关闭套接字
tcp_server_socket.close()
问题
当客户端和服务端建立连接后,服务端程序退出端口号不会立即释放,需要等待大概1~2分钟。
解决的办法:
1、更换服务器端口号
2、设置端口号复用(推荐大家使用),也就是说让服务端程序退出后端口号立即释放。
# 设置端口号复用的代码如下:
# 设置端口号复用,表示:服务端程序退出端口号立即释放
# 1.SOL_SOCKET:表示当前套接字
# 2.SO_REUSEADDR:表示复用端口号的选项
# 3.True:确定复用
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
完整代码
import socket
if __name__ == '__main__':
# 1.创建tcp服务端套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 设置端口号复用,表示:服务端程序退出端口号立即释放
# 1.SOL_SOCKET:表示当前套接字
# 2.SO_REUSEADDR:表示复用端口号的选项
# 3.True:确定复用
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_client,ip_port = tcp_server_socket.accept()
# 代码执行到此说明客户端和服务端建立连接成功
print('客户端的ip地址和端口号为:',ip_port)
# 5.接收客户端的链接请求
# 收发消息都使用返回的这个新套接字
recv_data = new_client.recv(1024)
recv_content = recv_data.decode('utf-8')
print('接收客户端的数据为:',recv_content)
# 6.发送数据到客户端
send_content = '问题正在处理中...'
send_data = send_content.encode('utf-8')
new_client.send(send_data)
# 关闭服务端与客户端套接字,表示和客户端终止通信
new_client.close()
# 7.关闭套接字
tcp_server_socket.close()
注意点
- 当TCP客户端程序想要和TCP服务端程序进行通信的时候必须要先建立连接
- TCP客户端程序一般不需要绑定端口号,因为客户端是主动发起建立连接的
-
TCP服务端程序必须绑定端口号
,否则客户端找不到这个TCP服务端程序 - listen后的套接字是被动套接字,
只负责接收新的客户端的连接请求
,不能收发消息 - 当TCP客户端程序和TCP服务端程序连接成功后,TCP服务端程序会产生一个
新的套接字
,收发客户端消息使用该套接字
关闭accept返回的套接字意味着和这个客户端已经通信完毕
-
关闭listen后的套接字意味着服务端的套接字关闭了
,会导致新的客户端不能连接服务端
,但是之前已经接成功的客户端还能正常通信
-
当客户端的套接字调用close后,服务端的recv会解阻塞,返回的数据长度是0
,服务端可以通过返回数据的长度来判断客户端是否已经下线,反之服务端关闭套接字,客户端的recv也会解阻塞,返回的长短也是0
tcp服务端服务于多个客户端
import socket
import threading
# 处理客户端请求
def handle_client_request(ip_port,new_client):
# 5.接收客户端的链接请求
# 收发消息都使用返回的这个新套接字
# 添加while,是为了可以循环接收客户端的消息
while True:
recv_data = new_client.recv(1024)
if recv_data:
print('接收到的数据长度是:',len(recv_data))
recv_content = recv_data.decode('utf-8')
print('接收客户端的数据为:', recv_content,ip_port)
# 6.发送数据到客户端
send_content = '问题正在处理中...'
send_data = send_content.encode('utf-8')
new_client.send(send_data)
else:
print('客户端下线了:',ip_port)
break
# 关闭服务端与客户端套接字,表示和客户端终止通信
new_client.close()
if __name__ == '__main__':
# 1.创建tcp服务端套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 设置端口号复用,表示:服务端程序退出端口号立即释放
# 1.SOL_SOCKET:表示当前套接字
# 2.SO_REUSEADDR:表示复用端口号的选项
# 3.True:确定复用
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_client,ip_port = tcp_server_socket.accept()
# 当客户端和服务端建立连接成功,创建子线程,让子线程专门负责接收客户端的消息
sub_thread = threading.Thread(target=handle_client_request,args=(ip_port,new_client))
# 启动子线程,执行对应的任务
sub_thread.start()
# 7.关闭套接字
tcp_server_socket.close()