网络编程-socket
网络开发的框架
- C/S
- B/S 统一了程序的入口 (浏览器)
osi七层模型
TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。
UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
tcp协议的socket进行通信
- 对于TCP协议的socket server来说
server端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字
sk.listen() #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024) #接收客户端信息
print(ret) #打印客户端信息
conn.send(b'hi') #向客户端发送信息
conn.close() #关闭客户端套接字
sk.close() #关闭服务器套接字(可选)
client端
import socket
sk = socket.socket() # 创建客户套接字
sk.connect(('127.0.0.1',8898)) # 尝试连接服务器
sk.send(b'hello!')
ret = sk.recv(1024) # 对话(发送/接收)
print(ret)
sk.close() # 关闭客户套接字
- 不能同时接受多个client端的连接
server端 client端
import socket import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket() #实例化一个对象(买手机)
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)#在bind前加(复用端口,测试用)
sk.bind(('ip地址',端口号)) #(买卡)
sk.listen(数字(1024)) #(开机)以上为启动一个socket服务
conn,addr = sk.accept() # 三次握手 sk.connect((服务端ip,服务端口号))
conn.send(bytes类型的内容) #发送消息 sk.recv(数字)
msg = conn.recv(数字) #接受消息 sk.send(bytes的消息)
...打印 操作
conn.close() #断开与client端的链接(4次挥手) sk.close()
sk.close() #关闭服务器端服务
# server服务器端
import socket
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',10000))
sk.listen()
while True:
try:
conn,addr = sk.accept()
while True:
msg = input(':')
conn.send(msg.encode('utf-8'))
if msg == 'exit':break
msg = conn.recv(1024)
if msg == b'exit':break
print(msg.decode('utf-8'))
conn.close()
except UnicodeDecodeError:
pass
sk.close()
# client端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',10000))
while True:
mag = sk.recv(1024)
if mag == b'exit':break
print(mag.decode('utf-8'))
mag = input(':')
sk.send(mag.encode('utf-8'))
if mag == 'exit':break
sk.close()
文件的一次性传送
#client端
import time
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',10000))
sk.send('文件名'.encode('utf-8'))
time.sleep(0.1)
with open('文件绝对路径','rb') as f:
sk.send(f.read())
sk.close()
#server服务器端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9999))
sk.listen()
conn,addr = sk.accept()
filename = conn.recv(1024)
with open(filename,'wb') as f:
while True:
try:
f1 = conn.recv(1024)
f.write(f1)
except Exception:
break
conn.close()
sk.close()
- 文件的分次传送
# 服务器端
import socket
def sky(ip,port):
i = 0
count = 1
sk = socket.socket()
sk.bind((ip,port))
sk.listen()
conn,addr = sk.accept()
filename = conn.recv(1024)
butter = int(conn.recv(1024))
with open(filename,'wb') as f:
while count:
try:
count = conn.recv(butter)
f.write(count)
i += 1
print('\r%s' % i,end = '')
except Exception:
break
conn.close()
sk.close()
sky('127.0.0.1',9999)
# client 端
import os
import time
import socket
def use(filename,ip,port,butter = 1024):
sk = socket.socket()
sk.connect((ip,port))
sk.send(os.path.basename(filename).encode('utf-8'))
time.sleep(0.01)
sk.send(str(butter).encode('utf-8'))
time.sleep(0.01)
file_size = os.path.getsize(filename)
with open(filename,'rb') as f:
while file_size:
count = f.read(butter)
sk.send(count)
time.sleep(0.0000001)
file_size -= len(count)
sk.close()
use(r'D:\BaiduNetdiskDownload\day17\项目架构讲解.mp4','127.0.0.1',9999,102400)
udp协议的socket进行通信
- 基本结构
server端
import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM) #创建一个服务器的套接字
udp_sk.bind(('127.0.0.1',9000)) #绑定服务器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr) # 对话(接收与发送)
udp_sk.close() # 关闭服务器套接字
client端
import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.bind(('ip地址',端口号))
对方发给你的消息,对方的地址 = udp_sk.recvfrom(接受的字节数)
sk.sendto(b'要发给对方的消息',对方的地址)
sk.close()
- low版
# 服务端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9999))
mag,addr = sk.recvfrom(1024)
print(mag.decode('utf-8'),end = '')
print(addr)
sk.sendto('你好'.encode('utf-8'),addr)
sk.close()
# 客户端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.sendto('你好'.encode('utf-8'),('127.0.0.1',9999))
mag,addr = sk.recvfrom(1024)
print(mag.decode('utf-8'),end='')
print(addr)
sk.close()
- 还是low版
# 服务端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9999))
while True:
mag,addr = sk.recvfrom(1024)
print(mag.decode('utf-8'))
mag = input('>>:')
sk.sendto(mag.encode('utf-8'),addr)
sk.close()
# 客户端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
while True:
mag = input('>>:')
sk.sendto(mag.encode('utf-8'),('127.0.0.1',9999))
mag,addr = sk.recvfrom(1024)
print(mag.decode('utf-8'))
sk.close()
- 粘包现象 -->优化方法(Nagle算法)
- 只有TCP有粘包现象,UDP永远不会粘包
由于流式传输的特点 产生了数据连续发送的粘包现象
在一个conn建立起来的连接上传输的多条数据是没有边界的
数据的发送和接收实际上不是在执行send/recv的时候就立刻被发送或者接收
而是需要经过操作系统内核
tcp: 面向流的通信是无消息保护边界的,对于空消息:tcp是基于数据流的,于是收发的消息不能为空
Nagle算法 能够将发送间隔时间得很近的短数据合成一个包发送到接收端
拆包机制 ,当要发送的数据超过了网络上能传输的最大长度,就会被tcp协议强制拆包
udp:是无连接的,面向消息,基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y)
如果是短数据 :只需要告诉对方边界就可以了
如果是长数据 :不仅要告诉对方边界,还要保证对面完整的接受了
-
udp协议中是不会发生粘包现象的
适合短数据的发送
不建议你发送过长的数据
会增大你数据丢失的几率 -
会发生黏包的两种情况
- 1,发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
# 服务端
import socket
ip_port=('127.0.0.1',8081)
sk = socket.socket()
sk.bind(ip_port)
sk.listen()
conn,addr = sk.accept()
data1=conn.recv(10)
data2=conn.recv(10)
print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))
conn.close()
# 客户端
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8081)
sk = socket.socket()
sk.connect(ip_port)
sk.send('hello'.encode('utf-8'))
sk.send('egg'.encode('utf-8'))
- 2 接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
# 客户端
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)
s.send('hello egg'.encode('utf-8'))
# 服务端
from socket import *
ip_port=('127.0.0.1',8080)
tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5) #最多接受5个客户端的链接
conn,addr=tcp_socket_server.accept()
data1=conn.recv(2) #一次没有收完整
data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的
print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))
conn.close()
- 实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
- 使用struct解决黏包
- 该模块可以把一个类型,如数字,转成固定长度的bytes
- 借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。
struct.pack('i',1111111111111)
struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
i int 型
l long 型
f folat型
- struct实现结构
import struct
ret = struct.pack('i',1023800976)
print(ret,len(ret))
num = struct.unpack('i',ret)
print(num[0])# num是一个元祖,应该取缔一个元素
- 实现基本结构
# 客户端
import socket
import struct
sk = socket.socket()
sk.connect(('127.0.0.1',9999))
mag = '借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。'.encode('utf-8')
num = struct.pack('i',len(mag))
sk.send(num)
sk.send(mag)
sk.close()
# 服务端
import socket
import struct
sk = socket.socket()
sk.bind(('127.0.0.1',9999))
sk.listen(3)
conn,addr = sk.accept()
num = conn.recv(4)
num = struct.unpack('i',num)[0]
mag = conn.recv(num).decode('utf-8')
print(mag)
conn.close()
sk.close()
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据
s.sendall() 发送TCP数据
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式 ,默认True(阻塞) 设置成False就不阻塞了,没人连就会报错(用try-excpet解决)
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
验证客户端链接的合法性
# 服务端
import os
import hmac
import socket
def auth(conn):
msg = os.urandom(32) # 生成一个随机的32位字节字符串
conn.send(msg) # # 发送到client端
result = hmac.new(secret_key, msg) # 处理这个随机字符串,得到一个结果
client_digest = conn.recv(1024) # 接收client端处理的结果
if result.hexdigest() == client_digest.decode('utf-8'):
print('是合法的连接') # 对比成功可以继续通信
return True
else:
print('不合法的连接') # 不成功 close
return False
secret_key = b'alex_sb'
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()
conn,addr = sk.accept()
if auth(conn):
print(conn.recv(1024))
# 正常的和client端进行沟通了
conn.close()
else:
conn.close()
sk.close()
# 客户端
import hmac
import socket
def auth(sk):
msg = sk.recv(32)
result = hmac.new(key, msg)
res = result.hexdigest()
sk.send(res.encode('utf-8'))
key = b'alex_s'
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
auth(sk)
sk.send(b'upload')
# 进行其他正常的和server端的沟通
sk.close()
- socketserver模块
import socketserver
# tcp协议的server端就不需要导入socket,socketserver中导入了socket模块
class Myserver(socketserver.BaseRequestHandler):
def handle(self):
conn = self.request #self.data = self.request.recv(1024).strip()
while True:
conn.send(b'hello')
print(conn.recv(1024))
server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),Myserver)
server.serve_forever()
# 这个类不用实例化也不用调用类中的handle方法