目录
socket
为了保证数据的完整性和可靠性我们使用 tcp 传输协议进行数据的传输,udp是不管有没有建立连接成功都会进行发送,如飞Q,邮件,为了能够找到对应设备我们需要使用 ip 地址,为了区别某个端口的应用程序接收数据我们需要使用端口号,那么通信数据是如何完成传输的呢?
使用 socket 来完成
负责进程之间的网络数据传输,只要跟网络相关的应用程序或者软件都使用到了 socket
TCP 网络应用程序开发分为:
- TCP 客户端程序开发
- TCP 服务端程序开发
客户端:
步骤说明:
- 创建客户端套接字对象
- 和服务端套接字建立连接
- 发送数据
- 接收数据
- 关闭客户端套接字
服务器
步骤说明:
- 创建服务端端套接字对象
- 绑定端口号
- 设置监听
- 等待接受客户端的连接请求
- 接收数据
- 发送数据
- 关闭套接字
导入 socket 模块 import socket
创建客户端 socket 对象
socket.socket(AddressFamily, Type)
参数说明:
- AddressFamily 表示IP地址类型, 分为TPv4和IPv6
- Type 表示传输协议类型
方法说明:
- connect((host, port)) 表示和服务端套接字建立连接, host是服务器ip地址,port是应用程序的端口号
- send(data) 表示发送数据,data是二进制数据
- recv(buffersize) 表示接收数据, buffersize是每次接收数据的长度
代码分析:
socket是遵循tcp/ip协议的,创建socket对象传入两参数,
并与服务器去建立连接 传入服务器ip port 元组
发送二进制数据,并编码
接收二进制数据并解码,
最后关闭socket,
一句话:socket对象与服务器连接后发送数据 或接收数据
import socket
if __name__=='__main__':
# 创建socket对象,并传入tcp/ip,
# socket.AF_INET ipv4
# socket.SOCK_STREAM tcp
tcp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_client_socket.connect(('192.168.0.112',8989))
# 发送二进制数据
tcp_client_socket.send('你好'.encode('utf8'))
# 接收二进制数据并解码
res=tcp_client_socket.recv(1024).decode('utf8')
print('收到服务器的消息:',res)
tcp_client_socket.close()
创建服务端 socket 对象
- bind((host, port)) 表示绑定端口号, host 是 ip 地址,port 是端口号,ip 地址一般不指定,表示本机的任何一个ip地址都可以。
- listen (backlog) 表示设置监听,backlog参数表示最大等待建立连接的个数。
- accept() 表示等待接受客户端的连接请求
- send(data) 表示发送数据,data 是二进制数据
- recv(buffersize) 表示接收数据, buffersize 是每次接收数据的长度
代码分析创建socket对象,
程序结束立即释放端口号
绑定端口号设置监听客户端连接的个数
等待客户端的连接,之后接收客户端的请求
import socket
if __name__=='__main__':
tcp_server_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 程序退出端口号立即释放
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
# 绑定端口号
tcp_server_socket.bind(('',8989))
# 监听最多128个连接数
tcp_server_socket.listen(128)
# 等待客户端的连接请求,只有连接后程序才会往下执行
# 建立连接后获取的2个参数 1和客户端通信的套接字,2客户端端口号
# 建立连接后TCP 服务器端程序会产生一个新的套接字,收发客户端消息使用该套接字
service_client_socket,ip_port=tcp_server_socket.accept()
print(ip_port)
# 接收客户端消息
recv_data=service_client_socket.recv(1024)
#服务端可以通过返回数据的长度来判断客户端是否已经下线
recv_data_length=len(recv_data)
recv_content=recv_data.decode('utf8')
print(recv_content)
send_data = "ok, 问题正在处理中...".encode("utf8")
# 发送数据给客户端
service_client_socket.send(send_data)
# 关闭服务与客户端的套接字, 终止和客户端通信的服务
service_client_socket.close()
tcp_server_socket.close()
说明:
当客户端和服务端建立连接后,服务端程序退出后端口号不会立即释放,需要等待大概1-2分钟。
解决办法有两种:
- 更换服务端端口号
- 设置端口号复用(推荐大家使用),也就是说让服务端程序退出后端口号立即释放。
设置端口号复用的代码如下:
# 参数1: 表示当前套接字
# 参数2: 设置端口号复用选项
# 参数3: 设置端口号复用选项对应的值
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
多线程服务器端
import socket
import threading
import time
def handle_client_request(service_client_socket, ip_port):
# 循环处理请求
while True:
time.sleep(5)
data=service_client_socket.recv(1024)
# 若存在数据,即消息长度不是0 则打印 长度为0则客户端下线
if data:
print('客户端发来的消息',data.decode('utf8'),'端口号',ip_port)
# 特别注意:客户端服务端一旦连接,双方的收发必须一一对应,有发就有收,毕竟是基于TCP协议的,
# 不然就成UDP协议的了
service_client_socket.send("ok,问题正在处理中...".encode("utf8"))
else:
print('客户端下线了')
break
service_client_socket.close()
if __name__=='__main__':
tcp_server_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
tcp_server_socket.bind(('',8989))
tcp_server_socket.listen(128)
# 准备工作做完后循环执行
while True:
# 等待接收客户端的连接请求
service_client_socket,ip_port=tcp_server_socket.accept()
# 一旦走到这说明建立连接成功,连接后开启子线程完成多任务
#传入处理客户端请求的新socket和端口号
sub_t=threading.Thread(target=handle_client_request,args=(service_client_socket,ip_port),daemon=True)
# 此时需要开启守护主线程,因为建立连接后才执行到子线程处理,此时若再子线程工作的时候客户端断开连接了
# 主线程无法正常断开连接
#分析:子线程执行需要5秒 再5秒内被客户端强制退出 导致子线程未执行完主线程就挂掉了
#Error 10054] 远程主机强迫关闭了一个现有的连接,要求子线程跟随主线程的生命周期
sub_t.start()
# 这里不用关闭服务器socket 让其一直可以接收客户端发消息
socket之send和recv原理剖析
1. 认识TCP socket的发送和接收缓冲区
当创建一个TCP socket对象的时候会有一个发送缓冲区和一个接收缓冲区,这个发送和接收缓冲区指的就是内存中的一片空间。
2. send原理剖析
send是不是直接把数据发给服务端?
不是,要想发数据,必须得通过网卡发送数据,应用程序是无法直接通过网卡发送数据的,它需要调用操作系统接口,也就是说,应用程序把发送的数据先写入到发送缓冲区(内存中的一片空间),再由操作系统控制网卡把发送缓冲区的数据发送给服务端网卡 。
3. recv原理剖析
recv是不是直接从客户端接收数据?
不是,应用软件是无法直接通过网卡接收数据的,它需要调用操作系统接口,由操作系统通过网卡接收数据,把接收的数据写入到接收缓冲区(内存中的一片空间),应用程序再从接收缓存区获取客户端发送的数据。
4. send和recv原理剖析图
说明:
- 发送数据是发送到发送缓冲区
- 接收数据是从接收缓冲区 获取