一 . 软件
客户端: CS架构 , client --> server
浏览器: BS架构 , browser --> server
二 . socket模块
socket通常也称作"套接字" , 用于描述IP地址和端口 , 是一个通信链的句柄 , 应用程序通常通过"套接字"向网络发出请求或者应答网络请求.
socket起源于Unix , 而Unix/Linux基本哲学之一就是"一切皆文件" , 对于文件用[打开] [读写] [关闭] 模式来操作 . socket就是该模式的一个实现 , socket即是一种特殊的文件 , 一些socket函数就是对其进行的操作
socket和file的区别:
-- file模块是针对某个指定文件进行[打开] [读写] [关闭]
-- socket模块是针对 服务器端 和 客户端 Socket 进行[打开] [读写] [关闭]
import socket # 创建服务端socket对象 server = socket.socket() # 绑定IP和端口 server.bind(('192.168.13.155',8000)) # 后边可以等5个人 server.listen(5) print('服务端准备开始接收客户端的连接') # 等待客户端来连接,如果没人来就傻傻的等待。 # conn是客户端和服务端连接的对象(伞),服务端以后要通过该对象进行收发数据。 # addr是客户端的地址信息。 # #### 阻塞,只有有客户端进行连接,则获取客户端连接然后开始进行通信。 conn,addr = server.accept() print('已经有人连接上了,客户端信息:',conn,addr) # 通过对象去获取(王晓东通过伞给我发送的消息) # 1024表示:服务端通过(伞)获取数据时,一次性最多拿1024字节。 data = conn.recv(1024) print('已经有人发来消息了',data) # 服务端通过连接对象(伞)给客户端回复了一个消息。 conn.send(b'stop') # 与客户端断开连接(放开那把伞) conn.close() # 关闭服务端的服务 server.close()
服务端和客户端的操作:
import socket server = socket.socket() server.bind(("IP地址",端口)) server.listen(服务次数) while 1: conn,addr = server.accept() # 等待与客户端连接 while 1: data = conn.recv(最大字节数 = 1024) if data == b"exit": break result = data + b"任意" conn.send(result) # 返回给客户端的内容 conn.close()
import socket sk = socket.socket() sk.connect("服务端IP",服务端端口) while 1: name = input(">>>") sk.send(name.encode("utf-8")) # 传给服务端必须是字节 if name == "exit": break result = sk.recv(1024) # 服务端回复给客户端的也是字节 print(result.decode("utf-8")) sk.close()
总结:
python3 : send / recv 都是字节
python2 : send / recv 都是字符串
服务端 :
accept : 阻塞 , 等待客户端来连接
recv : 阻塞 , 等待客户端发来数据
客户端 :
connect : 阻塞 ,一直在连接 , 直到连接成功才往下运行其他代码
recv : 阻塞 , 等待服务端发来数据
三 . Tcp协议和Udp协议
Tcp : 可靠的 , 面向连接的协议 , 传输效率低全双工通信 , 面向字节流 . 使用Tcp的应用 : Web浏览器 , 电子邮件 , 文件传输程序.
Udp : 不可靠的 , 无连接的服务 , 传输效率高 , 一对一 , 一对多 , 多对一 , 多对多 , 面向报文 , 尽最大努力服务 , 无阻塞控制 . 使用UDP的应用 : 域名系统(DNS) , 视频流 , IP语音(Volp) .
四 . 套接字初使用
1. 基于TCP协议的socket
tcp是基于链接的 , 必须先启动服务端 , 然后再启动客户端去链接服务端 .
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() # 关闭客户套接字
问题 : 在重启服务端时可能会遇到
解决方法:
#加入一条socket配置,重用ip和端口 import socket from socket import SOL_SOCKET,SO_REUSEADDR sk = socket.socket() sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加 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() #关闭服务器套接字(可选)
2 . 基于UDP协议的socket
udp是无链接的 , 启动服务之后可以直接接受消息 , 不需要提前建立链接.
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)
QQ聊天:
#_*_coding:utf-8_*_ import socket ip_port=('127.0.0.1',8081) udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) udp_server_sock.bind(ip_port) while True: qq_msg,addr=udp_server_sock.recvfrom(1024) print('来自[%s:%s]的一条消息: 33[1;44m%s 33[0m' %(addr[0],addr[1],qq_msg.decode('utf-8'))) back_msg=input('回复消息: ').strip() udp_server_sock.sendto(back_msg.encode('utf-8'),addr) server
#_*_coding:utf-8_*_ import socket ip_port=('127.0.0.1',8081) udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) udp_server_sock.bind(ip_port) while True: qq_msg,addr=udp_server_sock.recvfrom(1024) print('来自[%s:%s]的一条消息: 33[1;44m%s 33[0m' %(addr[0],addr[1],qq_msg.decode('utf-8'))) back_msg=input('回复消息: ').strip() udp_server_sock.sendto(back_msg.encode('utf-8'),addr) server
时间服务器:
# _*_coding:utf-8_*_ from socket import * from time import strftime ip_port = ('127.0.0.1', 9000) bufsize = 1024 tcp_server = socket(AF_INET, SOCK_DGRAM) tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) tcp_server.bind(ip_port) while True: msg, addr = tcp_server.recvfrom(bufsize) print('===>', msg) if not msg: time_fmt = '%Y-%m-%d %X' else: time_fmt = msg.decode('utf-8') back_msg = strftime(time_fmt) tcp_server.sendto(back_msg.encode('utf-8'), addr) tcp_server.close() server
#_*_coding:utf-8_*_ from socket import * ip_port=('127.0.0.1',9000) bufsize=1024 tcp_client=socket(AF_INET,SOCK_DGRAM) while True: msg=input('请输入时间格式(例%Y %m %d)>>: ').strip() tcp_client.sendto(msg.encode('utf-8'),ip_port) data=tcp_client.recv(bufsize) client
五 . 黏包
同时执行多条命令之后 , 得到的结果很可能只有一部分 , 在执行其他命令的时候又接收到之前执行的另外一部分结果 , 这种现象就是黏包 .
注意 : 只有TCP有黏包现象 , UDP永远不会黏包
TCP是面向连接的 , 面向流的 , 提供高可靠性服务 .
收发两端都要有一一承兑的socket . 因此 , 发送端为了将多个发往接收端的包 , 更有效的发到对方 , 使用了优化方法 , 将多次间隔较小且数据量小的数据 , 合并成一个大的数据块 , 然后进行封包 .
1. 发生黏包的两种情况
情况一 : 发送方的缓存机制
发送端需要等缓冲区满才发送出去 , 造成黏包(发送数据时间间隔很短 , 数据量很小 , 会合到一起 , 产生黏包)
#_*_coding: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) conn,addr=tcp_socket_server.accept() data1=conn.recv(10) data2=conn.recv(10) print('----->',data1.decode('utf-8')) print('----->',data2.decode('utf-8')) conn.close() 服务端
#_*_coding:utf-8_*_ 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'.encode('utf-8')) s.send('egg'.encode('utf-8')) 客户端
情况二 : 接收方的缓存机制
接受方不及时接受缓冲区的包 , 造成多个包接受(客户端发送了一段数据 , 服务端只收了一小部分 , 服务端下次再收的时候还是从缓冲区拿上次遗留的数据 , 产生黏包)
#_*_coding: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) 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()
#_*_coding:utf-8_*_ 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'))
总结 :
黏包现象只发生在tcp协议中 :
1. 从表面上看 , 黏包问题主要是发送方和接受方的缓存机制 , tcp协议面向流通信的特点
2. 实际上 , 主要还是因为接收方不知道消息之间的界限 , 不知道一次性提取多少字节的数据所造成的
六 . 黏包的解决的方案
1. struct模块
import struct res=struct.pack("i","") print(res) print(len(res)) obj=struct.unpack("i",res) print(obj[0]) """ 结果: b'xb3xb5Vx07' 4 123123123 """
该模型可以把一个类型转换成固定长度的bytes(固定长度4)
2. subprocess模块
import subprocess res=subprocess.Popen("dir", shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) print(res.stdout.read().decode("gbk")) """ 结果: 驱动器 C 中的卷是 Windows 卷的序列号是 A786-D135 C:UsersAdministratorPycharmProjectsuntitled1基础28day 的目录 2018/09/04 15:50 <DIR> . 2018/09/04 15:50 <DIR> .. 2018/09/04 15:50 218 1.py 2018/09/04 14:53 0 __init__.py 2 个文件 218 字节 2 个目录 215,588,208,640 可用字节 """
3. 使用struct模块解决黏包
借助struct模块 , 我们可以知道长度数字可以转换成一个标准大小的4字节数字 . 因袭可以利用这个特点来预先发送长度 .
发送时 | 接收时 |
先发送struct转换好的数据长度4字节 | 先接受4字节使用struct转换成数字来获取要接收的数据长度 |
再发送数据 | 再按照长度接受数据 |
import socket import subprocess server = socket.socket() server.bind(('127.0.0.1',8008)) server.listen(5) while True: print("server is working.....") conn,addr = server.accept() # 字节类型 while True: # 针对window系统 try: cmd = conn.recv(1024).decode("utf8") # 阻塞 if cmd == b'exit': break res=subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, ) # print("stdout",res.stdout.read()) # print("stderr",res.stderr.read().decode("gbk")) out=res.stdout.read() err=res.stderr.read() print("out响应长度",len(out)) print("err响应长度",len(err)) if err: import struct header_pack = struct.pack("i", len(err)) conn.send(header_pack) conn.send(err) else: #构建报头 import struct header_pack=struct.pack("i",len(out)) print("header_pack",header_pack) # # 发送报头 conn.send(str(len(out)).encode("utf8")) # 发送数据 conn.send(out) except Exception as e: break conn.close()
import socket import struct sk = socket.socket() sk.connect(('127.0.0.1',8008)) while 1: cmd = input("请输入命令:") sk.send(cmd.encode('utf-8')) # 字节 if cmd=="": continue if cmd == 'exit': break header_pack=sk.recv(4) data_length=struct.unpack("i",header_pack)[0] print("data_length",data_length) data_length=int(sk.recv(1024).decode("utf8")) print("data_length",data_length) recv_data_length=0 recv_data=b"" while recv_data_length<data_length: data=sk.recv(1024) recv_data_length+=len(data) recv_data+=data print(recv_data.decode("gbk")) sk.close()
4. 解决黏包的两种方法
方法一:
import socket import json sock = socket.socket() sock.bind(("121.12.11.11",5555)) sock.listen(5) while 1: print("server is working....") conn,addr = sock.accept() while 1: data = conn.recv(1024).decode("utf-8") file_info = josn.loads(data) # 文件信息 action = file_info.get("action") filename = file_info.get("filename") filesize = file_info.get("filesize") connsend(b"succes") # 接收文件数据 with open("put/" +filename , "wb") as f: recv_data_length = 0 while recv_data_lengh < filesize: data = conn.recv(1024) recv_data_length += len(data) f.write(data) print("文件总大小: %s , 已接收 %s" %(filesize,recv_data_length)) print("接收成功!") break sock.close()
import socket import os import json sock=socket.socket() sock.connect(("121.12.11.11",5555)) while 1: cmd = ("请输入命令:") action,filename = cmd.strip().split(" ") file_info = {"action":action,"filename":filename,"filesize":filesize} file_info_josn = josn.dumps(file_info).encode("utf-8") sock.send(file_info_josn) # 确认服务端接收到了文件信息 code = sock.recv(1024).decode("utf-8") if code == "succes": # 发送文件数据 with open("filename","rb") as f: for line in f: sock.send(line) else: print("服务器异常!") break sock.close()
方法二:
import struct import socket import josn import hashlib sock = socket.socket() sock.bind(("121.21.12.11",5555)) sock.listen(5) while 1: print("server is working...") conn,addr = sock.accept() while 1: # 接收josn的打包长度 file_info_length_pack = conn.recv(4) file_info_length = struct.unpack("i",file_info_length_pack)[0] # 接收josn字符串 file_info_josn = conn.recv(file_info_length) file_info = josn.loads(file_info_josn) action=file_info.get("action") filename=file_info.get("filename") filesize=file_info.get("filesize") # 循环接收文件 md5 = hashlib.md5() with open("put/" + filename,"wb") as f: recv_data_length = 0 while recv_data_length < filesize: data = conn.recv(1024) recv_data_length += len(data) f.write() # md5摘要 md5.update(data) print("文件总大小:%s,已成功接收%s" %(filesize,recv_data_length)) print("接收成功!") conn.send(b"ok") md5_val = md5.hexdigest() client_md5 = conn.recv(1024).decode("utf-8") if md5_val == client_md5: conn.send(b"203") else: conn.send(b"204") break sock.close()
import socket import os import json import struct import hashlib sock=socket.socket() sock.connect(("121.21.12.11",5555)) while 1: cmd = input("请输入命令:") action , filename = cmd.strip().split(" ") filesize = os.patn.getsize(filename) file_info = {"action":action,"filename":filename,"filesize":filesize} file_info_josn = josn.dumps(file_info).encode("utf-8") res = struct.pack("i",len(file_info_josn)) # 发送file_info_josn 的打包长度 sock.send(res) # 发送 file_info_josn字节串 sock.send(file_info_josn) md5 = hashlib.md5() # 发送文件数据 with open(filename,"rb") as f: for line in f: sock.send(line) md5.update(line) data = sock.recv(1024) md5_val = md5.hexdigest() sock.send(md5_val.encode("utf-8")) is_val = sock.recv(1024).decode("utf-8") if is_val == "203": print("文件上传成功!") else: print("文件上传失败!") break sock.close()
七 . 并发编程(socketserver模块)
import socketserver class Myserver(socketserver.BaseRequestHandler): def handle(self): # 字节类型 while 1: # 针对window系统 try: print("等待信息") data = self.request.recv(1024) # 阻塞 # 针对linux if len(data) == 0: break if data == b'exit': break response = data + b'SB' self.request.send(response) except Exception as e: break self.request.close() # 1 创建socket对象 2 self.socket.bind() 3 self.socket.listen(5) server=socketserver.ForkingUDPServer(("127.0.0.1",8899),Myserver) server.serve_forever()
import socket sk = socket.socket() sk.connect(('127.0.0.1',8899)) while 1: name = input(">>>>:") sk.send(name.encode('utf-8')) # 字节 response = sk.recv(1024) # 字节 print(response.decode('utf-8'))