目录
scoket套接字
socket工作流程
TCP服务端
TCP客户端
基于TCP 的SOCKET服务端与客户端
基础版本
客户端
加入连接循环
加入通信循环
支持并发的TCP服务端
常见问题:
半连接池
粘包问题
TCP协议的特点
解决粘包问题
UDP协议
服务端不需要考虑客户端是否异常退出
scoket套接字
基于文件类型的套接字家族名字:AF_UNIX
基于网络类型的套接字家族名字:AF_IN
socket(简称 套接字) 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的
例如我们每天浏览网页、QQ 聊天、收发 email 等等
1.socket介绍
1.什么是socket?
socket套接字,是进程通信的一种实现方式
2.主要特点
主要针对不同主机之间的通信。即网络进程的通信,
几乎所有的网络都是通et流程,实现网络通信
socket工作流程
TCP服务端
首先拿到一个socket对象,socket对象与IP+PORT绑定才能进行端口监听.
然后socket对象监听端口是否有请求,同时调用accept方法,一直阻塞等待客户端的链接;
如果有客户端的请求发来,那么服务端接收到请求之后调用read方法读取数据;
之后再处理请求,写回数据使用write方法(python中是send方法)
当需要断开连接时服务端发送请求,客户端读取之后在调用close方法断开请求,
服务端read收到断开请求,服务端再调用close方法即可.
TCP客户端
首先拿到一个socket对象,调用connect方法,传入IP与PORT,发送建立链接的请求,
之后就可以进行数据交互, 如果需要结束链接,直接调用close方法即可.
基于TCP 的SOCKET服务端与客户端
基础版本
TCP是基于链接的, 必须先启动服务端,然后再启动客户端去链接服务器
import socket
import time
server = socket.socket()
# 注意: 传入的参数必须是一个元组或者列表
# ip '127.0.0.1'是本地回环地址,只能自己玩
# 如果是别的IP地址,可以在同一个局域网里一起使用
server.bind(('127.0.0.1',8080))
# 监听 半连接池为5,相当于队列中最多有5个, 超出5个就会报错
# 同时智能服务一个人
server.listen(5)
# 等待客户端的链接
# socket 是连接对象, address是客户端地址
# 以后这个服务端和客户端使用sock这个连接对象
sock,address = server.accept()
# 接收客户端发了的数据
# 传入的参数为一次接受的字节
data = sock.recv(1024)
print(f'收到了来自{address}的数据{data.decode("utf8")}')
# 服务端给客户端发送消息
data_send = f'收到了你发送的{data}'.encode('utf8')
# 注意必须传送二进制格式数据
sock.send(data_send)
time.sleep(2)
# 关闭连接对象,此时服务器没有关闭
sock.close()
# 关闭服务端
server.close()
客户端
import socket
client = socket.socket()
# 链接服务端的地址加端口
client.connect(('127.0.0.1,8080))
# 连上以后就可以发送消息了
client.send(b'good evening')
# 收到服务端返回的消息
data = client.recv(1024)
print(data.decode('utf8'))
client.close()
加入连接循环
只加入连接循环, 此时服务端会一直接收消息看相当于死循环, 服务器不会自动断开
服务端
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8008))
server.listen(5)
print('等待客户端的链接')
# 等待客户端的链接
# 链接循环
while True:
sock, addr = server.accept()
# 接受客户端发了的数据
data = sock.recv(1024)
print(f'接收到来自{addr}的数据--{data.decode("utf-8")}')
# 服务端给客户端发送消息
data_send = f'收到了你发送的 {data}'.encode('utf-8')
# 注意:必须传送二进制的格式
sock.send(data_send)
# 关闭连接对象,此时服务端并没有关闭!!!
sock.close()
# 关闭服务端
server.close()
客户端
import socket
client = socket.socket()
# 连接服务端的地址加端口
client.connect(('127.0.0.1', 8008))
# 连上以后就可以发送消息了
client.send(b'good evening')
# 收到服务端返回的消息
data = client.recv(1024)
print(data.decode('utf-8'))
client.close()
加入通信循环
加入通信循环,此时客户端可以多次发送数据
服务端
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8008))
server.listen(5)
print('等待客户端的链接')
while True:
sock, addr = server.accept()
print(f'客户端{addr}已连接')
# 等待接受客户端发的数据
# 如果客户端没法,就会一直等待下去
while True:
try:
# 同一个客户端,不停地发送
data = sock.recv(1024)
# 当客户端主动断开后,客户端会发送一个空的数据过来
# 此时服务端必须对这种情况进行处理,否则会出现死循环
if len(data) == 0:
print(f'客户端{addr}已断开', '\n')
break
print(f'接收到来自{addr}的数据--{data.decode("utf-8")}')
data_send = f'收到了你发送的 {data}'.encode('utf-8')
sock.send(data_send)
except Exception as e:
print(e)
break
sock.close()
# 关闭服务端
server.close()
客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8008))
# 连上以后就可以发送消息了
while True:
data = input('输入发送给服务端的消息(q to quit):')
if data == 'q': break
client.send(data.encode("utf-8"))
data = client.recv(1024)
print(data.decode('utf-8'))
client.close()
支持并发的TCP服务端
在这里是开启多进程完成的并发,可以换成多线程, 只要将Process类换成Thread类
import socket
from multiprocessing import Process
def talk(sock,addr):
print('客户端连接成功', addr)
while True:
try:
data = sock.recv(1024)
if len(data) == 0:
print(f'客户端{addr}已断开', '\n')
break
print(f'接收到来自{addr}的数据--{data.decode("utf-8")}')
data_send = f'收到了你发送的 {data}'.encode('utf-8')
sock.send(data_send)
except Exception as e:
print(e)
break
sock.close()
if __name__ == '__main__':
server = socket.socket()
server.bind(('127.0.0.1', 81))
server.listen(5)
while True:
sock,addr = server.accept()
print(f'客户端{addr}已连接')
# 连接上一个客户端之后开启一个进程,对该客户端进行服务
p = Process(target=talk, args=(sock, addr))
p.start()
server.close()
常见问题:
1.发送消息不能为空 >>>> 统计长度饼判断即可
2. 反复重启服务端可能会报错 >>> address in use
这个错在苹果本上会比较频繁出现,windows频率较少
from socket import SOL_SOCKET,SO_REUSEADDR
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 在bind前加
3. 连接循环
如果是Windows电脑 客户端异常推出之后服务端会直接报错
处理方式 >>> 异常处理
如果是mac或者linux服务端会接收到一个空消息
处理方式 >>> len判断
客户端如果异常断开,服务端代码应该重新回到accept等待新的客人
目前的服务端只能实现一次服务一个人 不能做到同时服务多人, [并发状态下可以实现]
4. 加入通信循环之后,想要同一个py文件启动多个客户端, pycharm会出现以下提示,不允许启动多个
解决办法
半连接池
listen(5)
# py文件默认同一时间只能运行一次
# 半连接池
# 设置最大等待人数 >>> 节省资源 提高CPU利用率
粘包问题
data1 = conn.recv(1024)
print(data1)
data2 = conn.recv(1024)
print(data2)
data3 = conn.recv(1024)
print(data3)
client.send(b'hello')
client.send(b'jason')
client.send(b'kevin')
"""
三次打印的结果
b'hellojasonkevin'
b''
b''
"""
TCP协议的特点
会将数据量比较小 并且时间间隔比较短的数据整合到一起发送
并且还会受制于recv()内数字的大小,此现象我们称之为''六十协议''
>>> 问题产生的原因是因为recv括号内我们不知道即将要接收的数据到底有多大, 如果每次接受的收据我们都能够精准的知道他的大小, 那么肯定不会有粘包问题出现<<<<
如果我们能知道即将要接收的数据量大小那么粘包问题就解决了~
解决粘包问题
# struct模块
import struct
data1 = 'hello world!'
print(len(data1)) # 12
res1 = struct.pack('i', len(data1)) # 第一个参数是格式 写i就可以了
print(len(res1)) # 4
ret1 = struct.unpack('i', res1)
print(ret1) # (12,)
data2 = 'hello baby baby baby baby baby baby baby baby'
print(len(data2)) # 45
res2 = struct.pack('i', len(data2))
print(len(res2)) # 4
ret2 = struct.unpack('i', res2)
print(ret2) # (45,)
pack可以讲任意长度的数字打包成固定长度
unpack可以将固定长度的数字解包成打包之前的数据真实长度
>>> 将真实数据打包成固定长度的包
>>> 将固定长度的包发送给对方
>>> 对方接收到包之后再解包获取真实数据长度
>>> 接受真是长度的数据
<<<<<<< 1.先接收固定长度的报头
2.再根据报头解压出真实长度
3.根据真实长度接收即可<<<<<<<<<<<<<<
struct模块针对数据量特别大的数字没有办法打包
UDP协议
服务端
# 服务端
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8080))
msg, address = server.recvfrom(1024)
print('msg>>>:%s' % msg.decode('utf8'))
print('address>>>:',address)
server.sendto('我是服务端 你好啊'.encode('utf8'), address)
客户端
# 客户端
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1', 8080)
client.sendto('我是客户端 想我了没'.encode('utf8'), server_address)
msg, address = client.recvfrom(1024)
print('msg>>>:%s' % msg.decode('utf8'))
print('address>>>:',address)
服务端不需要考虑客户端是否异常退出
UDP不存在粘包问题, UDP多用于短消息交互,如QQ