Day14 plus++ 网络编程socket对象
socket:基于TCP/IP与应用层之间的抽象层,也就是接口。
分为服务端与客户端,双方之间建立通信循环。原理类似打电话。
–传输层协议
UDP套接字:基于端口,和TCP不同,没有双向请求连接,所以只是传输,不需要管对方有没有接收到。因此整体效率高。
TCP/IP:传输层协议,需要3次请求连接,又名流式协议。
–应用层协议:
HTTP/FTP 或者是自定义协议。两层协议之间用socket去解释,因为socket对TCP等协议进行了封装,避免直接去学习TCP/IP,过于底层,应该应用程序开发的效率。
详细解释:https://blog.csdn.net/g863402758/article/details/79359075
服务端
import socket
#买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #stream是tcp
print(phone)
#绑定IP端口,绑定手机卡
phone.bind(('127.0.0.1',8080)) #服务端软件IP地址
#0-65535,其中0-1024给操作系统使用
#开机
phone.listen(5) #最大挂起的链接数
#等电话连接
print('starting........')
#res=phone.accept()
conn,client_addr=phone.accept()
print(client_addr)
#收发消息
while True:
data=conn.recv(1024) #调用操作系统,让操作系统接收数据并拷给服务端内存
#接收数据最大数是byte类型,中文会编码 #空字符无效
if not data:break #data不为空才输出,防止死循环
print('客户端数据:',data)
conn.send(data.upper()) #发送大写数据
#挂电话
conn.close()
#关机
phone.close()
客户端
import socket
#买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print(phone)
#拨号
phone.connect(('127.0.0.1',8080))
#发 收信息
while True:#通信循环
msg=input('>>: ').strip()
if not msg:continue #发送为空数据,就不继续;不为空一直发
phone.send(msg.encode('utf-8')) #应用程序把自己内存的数据传给操作系统
data=phone.recv(1024) #应用程序让操作系统去调用数据
#空字符输入无法输出,因为服务端接收空字符也无法收到
print(data.decode('utf-8'))
#挂电话
phone.close()
另外:
会有出现多个客户端发送数据的情形,此时无法使用并发操作时,就需要对服务端进行连接循环的修改。也就是完成前一个请求accept并完成通信循环后关闭,进行下一个客户端的accept。
服务端修改
#当有多个客户端请求服务时,如何保证服务端的while循环一直调用,不用并发,只能排队了。
import socket
server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #基于网络类型的TCP协议
print(server) #检查一下
server.bind(('127.0.0.1',8080)) #绑定IP地址;0-65535,其中0-1024给操作系统使用
server.listen(4) #最大挂起链接数,表示客户端最多挂4个
print('ready to start......')
while True:#连接循环
conn,client_addr=server.accept() #等客户端连接后建立连接
print(client_addr)
while True:#通信循环
try:
data=conn.recv(1024)
print('接收客户端传来的数据',data)
conn.send(data.upper())
except ConnectionResetError:
break
conn.close() #必须要等前一个完成通信循环关闭后才能accept后一个
phone.close()
#扩展:远程执行命令,得到结果并传递
----Windows下命令:
#dir:查看某一个文件夹下的子文件名与子文件夹名
#ipconfig:查看本地网卡IP信息
#tasklist:查看运行的进程
----Linux下命令:
#ls
#ifconfig
#ps aux
扩展后的服务端
----模拟ssh远程执行命令
import socket
import os
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #stream是tcp
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
print(phone)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
print('ready to start........')
while True: ##链接循环
conn,client_addr=phone.accept()
print(client_addr)
while True: ##通信循环
try:
#1.收到命令
cmd=conn.recv(1024)
if not cmd:break
print('客户端的数据',cmd)
#2.执行命令拿到结果
# res=os.system('ls /') #res不能拿到os.system的运行结果,只能看到是否运行成功的标志
# print('命令的结果是:',res) #error
obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
#字符串,命令解释器,把结果丢到原来管道,错误结果丢到另一个管道:启动一个程序去解析字符串,解析成命令执行
stdout=obj.stdout.read()
stderr=obj.stderr.read()
#看看输出结果是不是字节类型,是的话说明是要的结果//且只能取一次
#3.命令的结果返回给客户端
conn.send(stdout+stderr) #+号可以优化
except ConnectionResetError:
break
conn.close()
phone.close()
扩展后的客户端
import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
while True:
#1.发布命令
cmd=input('>> ').strip()
if not cmd:continue
phone.send(cmd.encode('utf-8'))
#2.接收数据
data=phone.recv(1024)
print(data.decode('utf-8'))
phone.close()
粘包现象
另外:注意到客户端接收数据,只能接收最多1024个字节的数据。在客户端发送数据,服务端接收并执行命令发送结果给客户端时,不知道结果字节数,如果超过了1024个字节,会堵在管道里。等到下一个命令发送,客户端收到的字节会继续接收上次没输出完成的字节。
----底层阐述
运行软件需要用到CPU,硬盘,内存
详细解释:https://www.cnblogs.com/zhangsanfeng/p/8891149.html
在客户端执行发送命令时,不是直接发送命令给服务端,而是通过调用操作系统去执行命令,客户端应用程序的内存与服务端应用程序的内存相互隔离。send只负责应用层,拷贝给操作系统内存;操作系统遵循传输层协议即TCP/IP协议。当把命令传输给服务端时,通过recv接收命令,recv可以分为2层。其一,数据到达操作系统内存;二,数据拷贝给应用程序。
不是一个send对应一个recv。
TCP中会运用的nagle算法:把时间间隔较短,数据量较小的数据打包发送,减少网络延迟,降低网络I/O带来的时间延迟。
socket层无法更改TCP协议中实现的算法,只能通过优化来解决。
如果recv一次接收的数据量小于传输过来的数据量,那么剩下的就会在第二次传输过来的时候接收,此时就会发生粘包。所以解决粘包最重要的做法是接收时知晓传输的数据本身大小。———计算出数据长度, 输出时用循环进行切割
此时需要引入struct模块。
举例如下:
import struct
res=struct.pack('i',1230)
"""
用法: pack(format, v1, v2, ...) -> bytes
返回一个字节对象,包含值V1,V2。format='i',表示转化为1个int,int类型占4个字节。
='ii',说明转化为2个int,占8个字节。
"""
print(res,len(res))
#client.recv(4)
obj=struct.unpack('i',res)
"""用法:转化为元组类型的数据"""
print(obj)
在服务端,命令结果返回给客户端时,传递报头,也就是数据长度给客户端。
----服务端
import socket
import struct
import os
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #stream是tcp
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
print(phone)
phone.bind(('127.0.0.1',8081))
phone.listen(5)
print('ready to start........')
while True: ##链接循环
conn,client_addr=phone.accept()
print(client_addr)
while True: ##通信循环
try:
#1.收到命令
cmd=conn.recv(8096)
if not cmd:break
print('客户端的数据',cmd)
#2.执行命令拿到结果
# res=os.system('ls /') #res不能拿到os.system的运行结果,只能看到是否运行成功的标志
# print('命令的结果是:',res) #error
obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
#字符串,命令解释器,把结果丢到原来管道,错误结果丢到另一个管道:启动一个程序去解析字符串,解析成命令执行
stdout=obj.stdout.read()
stderr=obj.stderr.read()
#看看输出结果是不是字节类型,是的话说明是要的结果//且只能取一次
#3.命令的结果返回给客户端
#--第一步,把数据长度发送给客户端(也就是发报头,固定长度)
print(len(stdout)+len(stderr)) #+号可以优化
total_size=len(stdout)+len(stderr)
header=struct.pack('i',total_size) #只能传输字节类型,需要转
#--第二步,把报头发送给客户端
conn.send(header)
#--第三步,再发送真实的数据
conn.send(stdout)
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
phone.close()
----客户端
import socket
import struct
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8081))
while True:
#1.发布命令
cmd=input('>> ').strip()
if not cmd:continue
phone.send(cmd.encode('utf-8'))
#2.拿到命令结果,并打印
#第一步,先拿到数据长度(收报头)
header=phone.recv(4)
#第二步,从报头中拿到关于命令结果的描述信息,得到传递的数据长度
total_size=struct.unpack('i',header)[0]
#第三部,接收真实的数据
recv_size=0
recv_data=b''
while recv_size<total_size:
res=phone.recv(1024) #只能接收不超过1024字节的结果
recv_data+=res
recv_size+=len(res)
print(recv_data.decode('utf-8'))
phone.close()