socket套接字与socket模块
socket套接字:
Socket套接字是进程通讯的一种方式,即调用这个网络库的一些API函数实现分布在不同主机的相关进程之间的数据交换。
'''
socket套接字是一门实现不同计算机之间数据交互的技术。它的底层逻辑相当复杂,即用代码实现操作OSI七层架构。
由于C/S架构的编写都需要经过OSI七层架构固定的模式,所以socket套接字也有固定的模块,这些都是最底层封装好之后的功能。
'''
socket模块:
socket模块就是pycharm当中提供的一种内置模块,这个模块的功能就是socket套接字的功能。
'''
socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
'''
socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
socket模块的基本使用
C/S架构的软件无论是在编写还是运行,都应该先考虑服务端 。在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器(Client/Server,
C/S)模式,即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务。
- 客户与服务器进程的作用是非对称的,因此代码不同。
- 服务器进程一般是先启动的。只要系统运行,该服务进程一直存在,直到正常或强迫终止。
服务端:
import socket
# 括号内不写参数默认就是基于网络的遵循TCP协议的套接字
server = socket.socket() # 买手机
# 服务端应该具备固定的地址和端口号
server.bind(('127.0.0.1', 8080)) # 插电话卡
"""
127.0.0.1是计算机的本地回环地址,只有当前计算机本身可以访问
"""
# 半连接池
server.listen(5) # 开机 半连接池,相当于排号容量
# listen和accept对应TCP三次握手服务端的两个状态
sock, addr = server.accept() # 等待并接听电话,没有人来就原地等待(程序阻塞)
print(addr) # 客户端的地址
data = sock.recv(1024) # 听别人说话
# recv和send接收和发送的都是bytes类型的数据
print(data.decode('utf8'))
sock.send('你好啊'.encode('utf8')) # 回复别人说的话
sock.close() # 挂电话
server.close() # 关机
客户端:
import socket
client = socket.socket() # 产生一个socket对象
client.connect(('127.0.0.1', 8080)) # 根据服务端的地址链接
client.send(b'hello sweet heart!!!') # 给服务端发送消息
data = client.recv(1024) # 接收服务端回复的消息
print(data.decode('utf8')) # bys类型
client.close() # 关闭客户端
note:服务端与客户端首次交互,一边是recv,那么另一边必须是send;两边不能相同,处于同一模式下(’说‘或’听‘),就会卡在这一阶段,无法进行下一步程序。
通信循环代码优化
利用input获取用户输入,解决通信循环的问题,将数据交互的代码循环起来。
# 服务端
import socket
# 括号内不写参数默认就是基于网络的遵循TCP协议的套接字
server = socket.socket() # 买手机
# 服务端应该具备固定的地址和端口号
server.bind(('127.0.0.1', 8080)) # 插电话卡
"""
127.0.0.1是计算机的本地回环地址,只有当前计算机本身可以访问
"""
# 半连接池
server.listen(5) # 开机 半连接池,相当于排号容量
# listen和accept对应TCP三次握手服务端的两个状态
sock, addr = server.accept() # 等待并接听电话,没有人来就原地等待(程序阻塞)
print(addr) # 客户端的地址
while True:
data = sock.recv(1024) # 听别人说话
print(data.decode('utf8'))
msg = input('请回复消息>>>:').strip()
sock.send(msg.encode('utf8')) # 回复别人说的话
# 客户端
import socket
client = socket.socket() # 产生一个socket对象
client.connect(('127.0.0.1', 8080)) # 根据服务端的地址链接
while True:
msg = input('请输入你需要发送的消息>>>:').strip()
client.send(msg.encode('utf8')) # 给服务端发送消息
data = client.recv(1024) # 接收服务端回复的消息
print(data.decode('utf8'))
细节代码优化与链接循环
1.发送消息不能为空
len()统计长度并判断即可
2.反复重启服务端可能会报错>>>:address in use
这个错在苹果电脑报的频繁 windows频率较少
from socket import SOL_SOCKET,SO_REUSEADDR
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 在bind前加
3.链接循环
客户端如果异常断开,服务端代码应该重新回到accept等待新的客人
"""
如果是windows 客户端异常退出之后服务端会直接报错
处理方式>>>异常处理
如果是mac或linux 服务端会接收到一个空消息
处理方式>>>len判断
"""
# 目前我们的服务端只能实现一次服务一个人,不能做到同事服务多个,学了并发编程才可以实现
半连接池
listen(5)
'''
半连接就是说服务端在响应客户端的请求后会进入等待的状态,会等待客户端发送ack信息,这个状态就是半连接。
半连接池其实就相当于一个容器,系统会自动将半连接放入这个容器当中,节省资源,提高效率。
'''
'''
半连接池可以设置上限状态
'''
粘包问题
粘包指的是数据与数据之间没有明确的分界线,导致不能正确读取数据。(只有TCP有粘包现象,UDP永远不会黏包)
TCP协议会将数据量比较小并且时间间隔比较短的数据整合到一起发送;并且还会受制于recv括号内的数字大小。
TCP在两种情况下会发生粘包问题:
1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据很小,会合到一起,产生粘包)
2.接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务器下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
粘包问题处理
粘包问题产生的原因其实是因为recv括号内我们不知道即将要接收的数据到底多大;如果每次接收的数据我们都能够精确的知道它的大小,那么肯定不会出现黏包。
方向:精准获取数据的大小!!!
思路:
1.先将真实数据打包成固定长度的包
2.将固定长度的包先发给对方
3.对方接收到包之后再解包获取真实数据长度
4.接收真实数据长度
使用struct模块:
'''
struct模块可以将任意大小的数子转成固定长度的bytes;该数据可以是文本、字符串等诸多类型
'''
'''
pack可以将任意长度的数字打包成固定长度
unpack可以将固定长度的数字解包成打包之前数据真实的长度
'''
struct模块只能转换数字,不能转换其他数据类型
'''