网络编程bs和cs框架
如果有些公司开发出来的软件没人要安装app,就不需要开发出来app了,只开发出来一个网站,用浏览器浏览就行了。
软件:
- 客户端:CS架构,Client --> Server(单机的没有server,不需要网络)
- 浏览器:BS架构,Browser --> Server
如何实现相互通信:
- 相互通信本质发送01010101
- 交换机作用
- 通过ipconfig查看自己的内网IP
- 公网IP,付费
python中socket模块帮助完成网络通信,下面编写一小段代码介绍一下,python中是如何网络通信的。
用此可以验证用户名和密码是否正确
- 先执行server.py,等待有人连接服务端
- 再执行范冰冰.py,范冰冰连接到服务端,发送给服务端内容为'你好,我是范冰冰'
- 服务端接收范冰冰的消息后打印内容
- 服务端发给范冰冰内容为stop
- 范冰冰断开和服务端的连接
- 服务端关闭服务
socket注意事项:退出的时候,服务器和客户端都要退出,否则可能出现错误。
#文件名:server.py import socket # 创建socket对象 server = socket.socket() # 绑定IP和端口,只有服务端才绑定IP和端口 server.bind(('192.168.0.105',8000)) # 后面最多有5个请求 排队等候 listen需要的参数(backlog:积压的工作) server.listen(5) #这个客户端连接之后,后面还可以有5个客户端排队,再有一个客户端连接的时候,就无法排队了;所以在第7位的时候才报错。 # 等待客户端来连接,从这里一启动,程序不会结束,accept会阻塞住,一直等待客户端来连接,一连接就立刻往下执行; # 如果没人来连接就傻傻地等待。 # server.accept()是个元组,这个元组永远有两个元素, # 一个是连接对象(比如两个地址之间要有一条路,通过这条路来传输东西),通过这个对象传输信息, # 另一个是客户端的地址信息 conn,addr = server.accept() #**************************在此阻塞了,只要有人来连接,程序就往下走,开始通信。 # conn.recv()通过那条路去获取信息,参数bufsize设置为1024的话,一次最多获取1024字节 # 1024表示:服务端通过马路获取数据时,一次性最多拿取1024字节 # 如果客户端一直不通过client.send()发送消息,server一直在此等候消息 data = conn.recv(1024) # 待会范冰冰会过来连接发数据-->'你好' print(data.decode('utf-8')) # 网络传输时都是通过bytes(字节)传输的, # 服务端通过连接对象(马路)给客户端回复了一个消息 conn.send(b'stop') # 范冰冰给服务器发送一个'你好',服务器回复一个‘stop’ # conn.close() 把马路用炸弹炸开,断开连接 conn.close() # 服务断开,不给任何人提供服务了 server.close()
# 文件名:范冰冰.py import socket client=socket.socket() # 有一个人来连接我的服务器,相当于有个人向服务器修了一条马路 # 这里也存在阻塞,如果服务端没有开启,范冰冰就连接不上,一直在这等待,直到连接成功才会继续往下走。 client.connect(('192.168.0.105',8000)) client.send('你好,我是范冰冰'.encode('utf-8')) # 范冰冰等待服务端的回复消息 data=client.recv(1024) print(data.decode('utf-8')) # 范冰冰收到消息后,拿了一个炸药包把自己通向服务端的路炸掉了,关闭自己 client.close()
python网络编程之socket模块2(开发一个聊天工具)
# 服务端.py import socket server=socket.socket() server.bind(('192.168.0.105',8001)) server.listen(5) # 服务端一启动就就永远不会关闭了 while True: print('server is working.') conn, addr = server.accept() # 创建与客户端的连接 路 while True: try: data = conn.recv(1024) if data.decode('utf-8') == 'exit': print('关闭和范冰冰的连接') break print(data.decode('utf-8')) response = input('>>>') conn.send(response.encode('utf-8')) except Exception as e: print('连接主动断开') break conn.close()
# 客户端.py import socket client = socket.socket() client.connect(('192.168.0.105',8001)) while True: data = input('>>>') if data=='exit': client.send(data.encode('utf-8')) break client.send(data.encode('utf-8')) receive=client.recv(1024) print(receive.decode('utf-8')) client.close()
python网络编程之socket模块3 作业
# 服务端.py import socket server=socket.socket() server.bind(('192.168.0.105',8001)) server.listen(5) while True: conn, addr = server.accept() print('有人连接进来了') while 1: try: user=conn.recv(1024) psw=conn.recv(1024) with open('user_info.txt', 'r', encoding='utf-8') as fh: for i in fh: if user.decode('utf-8') ==i.split('/')[0].strip() and psw.decode('utf-8') == i.split('/')[1].strip(): conn.send('恭喜,连接成功'.encode('utf-8')) print('客户端连接成功了,已经退出') break#跳出for循环 else: conn.send('用户名或者密码错误,请重新输入'.encode('utf-8')) continue break # 跳出while 1循环 except Exception as e: print('连接异常退出') break
# 客户端.py import socket client=socket.socket() client.connect(('192.168.0.105',8001)) while True: user=input('输入用户名>>>') pwd=input('输入密码>>>') client.send(user.encode('utf-8')) client.send(pwd.encode('utf-8')) response=client.recv(1024) print(response.decode('utf-8')) if response.decode('utf-8')=='恭喜,连接成功': break
模拟ssh,用struct模块解决黏包问题
# server.py import subprocess import socket import struct server = socket.socket() server.bind(('192.168.0.105',8003)) server.listen(5) while True: conn,attr = server.accept() print('有客户端连接进来了...') while True: try: cmd = conn.recv(1024).decode('utf-8') print(cmd) response = subprocess.Popen(cmd, shell=True, stdout = subprocess.PIPE, stderr = subprocess.PIPE) strout_result=response.stdout.read()#response.stdout.read()读出来之后就不能再读了,再读就读出来空了,stderr也一样 strout_result_length=len(strout_result) # 命令结果的字节长度 if strout_result: res=struct.pack('i',strout_result_length)# 返回固定4个长度的bytes conn.send(res) # #发送4个字节的结果长度 conn.send(strout_result)# 发送结果,这样两个连续发送回造成黏包 else: stderr_result = response.stderr.read() strerr_result_length = len(stderr_result) res = struct.pack('i', strerr_result_length) conn.send(res) # 发送4个字节的结果长度 conn.send(stderr_result) # 发送结果,这样两个连续发送回造成黏包 except Exception as e: print('客户端断开连接') break
# client.py import socket import struct client=socket.socket() client.connect(('192.168.0.105',8003)) while True: cmd=input('输入命令>>>') if cmd == 'exit': break elif cmd == '': print('不能输入空,请重新输入') continue client.send(cmd.encode('utf-8')) recv_data=b'' recv_data_length=0 header_pack = client.recv(4)# 第一次只接收4个bytes长度,这个是返回结果的长度 result_length = struct.unpack('i',header_pack)[0]#unpack得到的是个元组,所以用[0]获取内容 while recv_data_length<result_length: #当接收的内容小于client.recv(4)这个数字时循环 response=client.recv(1024) recv_data+=response recv_data_length+=len(response) print(recv_data.decode('gbk'))
# 返回值 Windows IP 配置 以太网适配器 以太网 2: 媒体状态 . . . . . . . . . . . . : 媒体已断开连接 连接特定的 DNS 后缀 . . . . . . . : 以太网适配器 以太网: 媒体状态 . . . . . . . . . . . . : 媒体已断开连接 连接特定的 DNS 后缀 . . . . . . . : ................. ................. .................
黏包原因:
情况一 发送方的缓存机制:
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
情况二 接收方的缓存机制:
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
总结
解决黏包问题:
#为什么会出现黏包问题?
首先只有在TCP协议中才会出现黏包现象
是因为TCP协议是面向流的协议
在发送的数据传输的过程中还有缓存机制来避免数据丢失
因此在连续发送小数据的时候,以及接收大小不符的时候容易出现黏包现象
本质还是因为我们在接收数据的时候不知道发送的数据的长度
#解决黏包问题:
本质解决问题:在传输大量数据之前先告诉接收端要发送的数据大小
如果想更好的解决问题,可以通过struct模块来定制协议
# struct模块
方法:pack unpack
模式:‘i’i
pack之后的长度:4个字节
unpack之后拿到的数据是一个元组:元组的第一个元素才是pack的值
黏包现象只发生在tcp协议中:
1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
黏包的解决方案:
解决方案一
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
存在的问题: 程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗
优化解决方案
刚刚的方法,问题在于我们我们在发送
我们可以借助一个模块,这个模块可以把要发送的数据长度转换成固定长度的字节。这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了。
struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes
>>> struct.pack('i',1111111111111) struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
应用程序不能直接操控硬件,必须借助操作系统。
文件上传 FTP功能
# server.py import hashlib import subprocess import socket import struct import json server = socket.socket() server.bind(('192.168.0.105',8005)) server.listen(5) while True: conn,attr = server.accept() print('有客户端连接进来了...') while True: try: file_info_json_pack_length=conn.recv(4) file_info_json_unpack_length=struct.unpack('i',file_info_json_pack_length)[0] recv_file_info=conn.recv(file_info_json_unpack_length) file_info=json.loads(recv_file_info.decode('utf-8')) file_name=file_info.get('file_name') file_size = file_info.get('file_size') action = file_info.get('action') all_recv_length=0 print(all_recv_length) print(file_size) md5=hashlib.md5() while all_recv_length<file_size: data= conn.recv(1024) md5.update(data) with open(file_name, 'ab') as f: f.write(data) all_recv_length+=len(data) print('文件总长为%s已经接收长度%s'%(file_size,all_recv_length)) conn.send(md5.hexdigest().encode('utf-8')) except Exception as e: print('客户端断开连接:%s',e) break
# client.py import json import os import socket import struct import hashlib client=socket.socket() client.connect(('192.168.0.105',8005)) while True: cmd=input('>>>') action,file_name=cmd.strip().split(' ')# 执行的命令,和文件名 print(action,file_name) if not os.path.exists(file_name): print('你输入的文件不存在,请重新输入') continue file_size=os.path.getsize(file_name)# 文件大小 print('file_size',file_size) if action == 'put': print('文件%s正在传送...'%(file_name)) file_info={'action':action, 'file_name':file_name, 'file_size':file_size } file_info_json=json.dumps(file_info,ensure_ascii=False) # file_info_json字符串形式 file_info_json_length= len(file_info_json.encode('utf-8'))# file_info_json字符串形式的长度的bytes类型 file_info_json_pack = struct.pack('i',file_info_json_length) # 4位 client.send(file_info_json_pack) # 传送4位 client.send(file_info_json.encode('utf-8'))# 传送file_info的字节长度 client_md5 = hashlib.md5() with open('photo.jpg','rb') as f:# 传送文件 for i in f: client_md5.update(i) client.send(i) server_md5=client.recv(1024) if server_md5.decode('utf-8')==client_md5.hexdigest(): print('文件上传成功') else: print('文件上传失败')