一、Socket抽象层(socket编程)
能够唯一表示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译成套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应层调用已实现进程在网络中通信
基于文件类型的套接字家族:AF_UNIX
基于网络类型的套接字家族:AF_INET
socket模块的基本使用
1.socket.socket() 产生socket对象
2.bind() 绑定地址
3.listen() 半连接池
4.accept() 等待客户端链接
5.send() 发送消息
6.recv() 接收消息
7.connect() 链接服务端
二、基于TCP协议的套接字(socket)编程
客户端和服务端应该是谁先发送消息:
客户端先发送消息,需要服务端先运行起来
"""服务端代码:"""
import socket
from socket import AF_INET
# socket.socket() # AF_INET, type=SOCK_STREAM: 基于TCP协议的
server=socket.socket(family=AF_INET, type=socket.SOCK_STREAM) # AF_INET, type=SOCK_DGRAM: 基于UDP协议的
# 绑定IP地址和port
server.bind(('127.0.0.1', 8000))
# 服务端做监听,也称为是半链接池(服务端能够同时等待客户端的数量)
server.listen(3)
print(123)
# 该接收客户端发来的消息
sock, addr = server.accept() # """我们知道代码运行到这里,就会卡主,等待客户端开发链接和发送消息"""
"""sock:当前链接的客户端的链接, addr:就是客户端的地址:ip,port"""
print(234)
# 想拿到客户端实际发过来的数据
data=sock.recv(1024) # 接收的数据类型是bytes类型,二进制的 1024 代表接收的最多的字节数
print("接收客户端的数据:",data)
# 要给客户端返回一个数据
sock.send(data.upper()) # 发送的数据是字节类型的
# 关闭链接
sock.close()
server.close()
"""客户端代码"""
import socket
from socket import AF_INET
client=socket.socket(family=AF_INET, type=socket.SOCK_STREAM)
# socket.socket()
client.connect(('127.0.0.1', 8000))
# 开始给服务端发送消息
client.send('hello'.encode('utf-8'))
# 接收服务端发来的消息
data=client.recv(1024)
print("服务端发来的消息:", data)
client.close()
三、加上通信循环
import socket
from socket import AF_INET
# socket.socket() # AF_INET, type=SOCK_STREAM: 基于TCP协议的
server=socket.socket(family=AF_INET, type=socket.SOCK_STREAM) # AF_INET, type=SOCK_DGRAM: 基于UDP协议的
# 绑定IP地址和port
server.bind(('127.0.0.1', 8000))
# 服务端做监听,也称为是半链接池(服务端能够同时等待客户端的数量)
server.listen(3)
while True:
# 该接收客户端发来的消息
sock, addr = server.accept() # """我们知道代码运行到这里,就会卡主,等待客户端开发链接和发送消息"""
"""sock:当前链接的客户端的链接, addr:就是客户端的地址:ip,port"""
while True:
# 想拿到客户端实际发过来的数据
try:
"""客户端发送过来的数据是个空,或者"""
data=sock.recv(1024) # 接收的数据类型是bytes类型,二进制的 1024 代表接收的最多的字节数
print("接收客户端的数据:",data)
# 要给客户端返回一个数据
sock.send(data.upper()) # 发送的数据是字节类型的
except Exception as e:
print(e)
break
# 关闭链接
sock.close()
server.close()
###########################客户端####################################
import socket
from socket import AF_INET
client=socket.socket(family=AF_INET, type=socket.SOCK_STREAM)
# socket.socket()
client.connect(('127.0.0.1', 8000))
while True:
send_data = input('请输入你要发送给服务端的数据:')
# 开始给服务端发送消息
client.send(send_data.encode('utf-8'))
# 接收服务端发来的消息
data=client.recv(1024)
print("服务端发来的消息:", data)
client.close()
四、基于UDP协议的套接字编程
服务端:
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议-》UDP
server.bind(('127.0.0.1', 8080))
while True:
# client_addr:客户端的地址:ip+port
data, client_addr = server.recvfrom(1024) # 接收客户端发来的消息,1024是字节数
print('===>', data, client_addr)
server.sendto(data.upper(), client_addr) # 给客户端发消息
server.close()
客户端:
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议-》UDP
while True:
msg = input('>>: ').strip() # msg=''
client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
data, server_addr = client.recvfrom(1024)
print(data)
client.close()
五、struct模块
我们可以借助一个模块,这个模块可以把要发送的数据长度转换成固定长度的字节。这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了。
struct模块可以将非固定长度的数字转为固定长度(len=4)的数字,语法结构为:struct.pack(‘i’, len(数据变量名)),其中’i’是固定的参数。打包的过程也称为’报头’。
import struct # 引入模块
info = b'hello my friend'
1.数据真实的长度(bytes)
# print(len(info)) # 15
2.# 将数据打包成固定的长度。i 是固定的打包模式
res = struct.pack('i',len(info))
3.# 打包之后的长度为(bytes) 也称为报头
print(len(res)) # 4
4.根据固定长度的报头解析出真实数据的长度
real_len = struct.unpack('i',res)
5.打印解析真实数据的长度
print(real_len) # (15,)
import struct # 引入模块
desc = b'hello my sister want to buy some fruit'
# 1. 数据真实的长度(bytes)
print(len(desc)) # 38
# 2. # 将数据打包成固定的长度。
res1 = struct.pack('i',len(desc))
# 3.打印打包之后的长度为(bytes)
print(len(res1)) # 4 报头
# 4.解析出真实数据的长度
real_desc = struct.unpack('i',res1)
print(real_desc) # (38,)