网络编程
S/C架构
Server 服务器 ---要一直运行,等待服务别人
Client 客户端 ---用的时候,才使用服务。
这里的客户端一般泛指客户端应用程序.exe,程序需要先安装后,才能运行在用户的电脑上,对电脑操作系统环境依赖较大。
B/S架构
Server 服务器
Broser 浏览器 ,其实也是一种Client客户端 ---浏览器上通过HTTP请求服务端相关的资源(网页资源),客户端Browser浏览器就能进行增删改
IP地址:
4个8为2进制 :00000000.00000000.00000000.00000000
0.0.0.0 -- 255.255.255.255
127.0.0.1 本地的回环地址(同一台电脑的两个程序通讯)
网关的概念:局域网中的机器想要访问局域网外的机器,需要通过网关
网段地址(局域网的网段):IP地址 与 子网掩码 按位与 ---> 192.168.1.1 和 255.255.255.0 -----网段:192.168.1.0
端口:找到对应的程序,
在计算机上,每个需要网络通讯的程序,都会开一个端口,在同一时间只会有一个程序占用一个端口。
端口的范围: 0-65535;一般情况下,使用8000之后的端口
ip --- 确定唯一一台机器
端口 --- 确定唯一的程序
ip + 端口 --- 找到唯一的一台机器上的唯一程序
tcp :是可靠的,面向连接的(只要连接上了就一直连接),传递信息是全双工的(双方都可以收发消息);如:打电话
server 服务端
import socket
sk = socket.socket() # 买手机
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 避免服务器重启的时候报 address already in use
# sk.bind(('ip', port))
sk.bind(('127.0.0.1', 8080)) # 绑定手机卡
sk.listen() # 监听 等着有人给我打电话
conn, addr = sk.accept() # 接收到别人的电话 connection 连接, address 地址
ret = conn.recv(1024) # 听别人说话
print(ret)
conn.send(b'hi) # 和别人说话, 必须传一个bytes类型
conn.close() # 挂电话
sk.close() # 关手机
------------------------------------------------------------
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
conn, adress = sk.accept()
while True:
ret = conn.recv(1024).decode('utf-8')
if ret == 'bye':
break
print(ret)
info = input('>>>')
conn.send(bytes(info, encoding='utf-8'))
conn.close()
sk.close()
client 客户端
import socket
sk = socket.socket() # 买手机
sk.connect(('127.0.0.1', 8080)) #搜索别人的号码
sk.send(b'hello')
ret = sk.recv(1024) # 接受1024字节
sk.close()
--------------------------------------------------------
import socket
sk = socket.socket()
sk.connect('127.0.0.1', 8080)
while True:
info = input('>>>')
sk.send(bytes(info, encoding='utf-8'))
ret = sk.recv(1024).decode('utf-8')
print(ret)
if ret == 'bye':
sk.send(b'bye')
break
sk.close()
udp:传输数据之前,源端和终端不建立连接,传输效率高;如:发短信
server 服务端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1', 8080))
msg, addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
sk.sendto(b'bye')
sk.close()
UDP的server 不需要进行监听也不需要建立连接
在启动服务之后只能被动的等待客户端发送消息过来
客户端发送消息的同时还会自带地址信息
消息回复的时候 不仅需要发送消息,还需要把自己的地址发送过去
-----------------------------------------------------
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind('127.0.0.1', 8080)
while True:
msg, addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
info = input('>>>').encode('utf-8')
sk.sendto(info, addr)
sk.close()
client 客户端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1', 8080)
sk.sendto(b'hello', ip_port)
ret, addr = sk.recvfrom(1024)
print(ret.decode('utf-8'))
sk.close()
------------------------------------------------------
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1', 8080)
while True:
info = input('二哥: ').encode('utf-8')
sk.sendto(info, ip_port)
msg, addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
sk.close()
黏包现象: 多个send小的数据连续一起,会缓存起来,发生黏包现象,是tcp协议内部的优化算法造成的
server 服务端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
conn, addr = sk.accept()
while True:
cmd = input('>>>')
if cmd == 'q':
conn.send(b'q')
break
conn.send(cmd.encode('gbk'))
res = conn.recv(1024).decode('gbk')
print(res)
conn.close()
sk.close()
client 端
import socket
import subprocess
sk = socket.socket()
sk.connect(('127.0.0.1', 8080))
while True:
cmd = sk.recv(1024).decode('gbk')
if cmd == 'q':
break
# PIPE 管道,跟队列类似,只能读一次,下次再读就没有了
res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
sk.send(res.stdout.read())
sk.send(res.stderr.read())
sk.close()
本质: 不知道到底要接受多大的数据
解决: 首先,发送一下这个数据到底有多大,再按照数据的长度接收数据
队列
import queue
q = queue.Queue() # 创建队列
q.put(1) # 在队列放入一个数
print(q.qsize()) 1 # 查看队列大小
q.get() # get走一个数据 ---相当于列表的pop
print(q.qsize()) 0
解决黏包问题
server 服务端
import socket sk = socket.socket() sk.bind(('127.0.0.1', 8080)) sk.listen() conn, addr = sk.accept() while True: cmd = input('>>>') if cmd == 'q': conn.send(b'q') break conn.send(cmd.encode('gbk'))
# 添加
----------------------------
num = conn.recv(1024).decode('utf-8')
conn.send('OK')
------------------------------- res = conn.recv(1024).decode('gbk') print(res) conn.close() sk.close()
client 客户端
import socket import subprocess sk = socket.socket() sk.connect(('127.0.0.1', 8080)) while True: cmd = sk.recv(1024).decode('gbk') if cmd == 'q': break # PIPE 管道,跟队列类似,只能读一次,下次再读就没有了 res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# 添加
-------------------------------------------
std_out = res.stdout.read()
std_err = res.stderr.read()
sk.send(str(len(std_out) + len(std_err)).encode('utf-8'))
sk.recv(1024)
------------------------------------------ sk.send(std_out) sk.send(std_err) sk.close()
为什么会出现黏包现象?
首先只有在TCP协议中才会出现黏包现象, 是因为TCP协议是面向流的协议,在发送的数据传输的过程中还有缓存机制来避免数据丢失,
因此在连续发送小数据的时候以及接收大小不符的时候都容易出现黏包现象,本质还是因为我们在接收数据的时候不知道发送数据的长短。
解决黏包问题
在传输大量数据之前首先告诉接收端要发送的数据大小,如果想更漂亮的解决问题,可以通过struct模块来指定协议
struct模块
方法:pack,unpack
模式:'i'
pack之后的长度:4个字节
unpack之后拿到的数据是一个元组:元组的第一个元素才是pack的值
实现一个大文件的上传或者下载
server 服务端
import socket
import struct
import json
buffer = 1024
sk = socket.socket()
sk.bind(('127.0.0.1', 8090))
sk.listen()
conn, addr = sk.accept()
# 接收pack_len
head_len = conn.recv(4)
# 解包得到head长度
head_len = struct.unpack('i', head_len)[0] #得到元组
# 接收head的bytes,解码得到head内容
json_head = conn.recv(head_len).decode('utf-8')
head = json.loads(json_head) # 字符串转成字典
# 获取文件大小
filesize = head['fielsize']
with open(head['filename'], 'wb') as f:
while filesize:
if filesize >= buffer:
content = conn.recv(buffer)
f.write(content)
filesize -= buffer
else:
content = conn.recv(filesize)
break
conn.close()
sk.close()
client 客户端
import socket
import so
import json
import struct
sk = socket.socket()
sk.connect(('127.0.0.1', 8090))
buffer = 1024
# 发送文件
head = {
'filepath': r'D:\视频\',
'filename': r'test.mp4',
'filesize': None
}
file_path = os.path.jion(head['filepath'], head['filename'])
filesize = os.path.getsize(file_path)
head['filesize'] = filesize
json_head = json.dumps(head) # 字典转字符串
bytes_head = json_head.encode('utf-8') # 字符串转bytes
# 计算head长度
head_len = len(bytes_head) # 报头长度
pack_len = struct.pack('i', head_len) # 4个字节, int类型使用’i‘
# 发送
sk.send(pack_len) # 先发报头长度
sk.send(bytes_head) # 再发送bytes类型报头
with open(file_path, 'rb') as f: # 二进制
while filesize:
if filesize >= buffer:
content = f.read(buffer) # 每次读出来的内容
sk.send(content)
filesize -= buffer
else:
content = f.read(filesize)
sk.send(content)
break
sk.close()