标签:rev 效率 发送数据 gbk stdout 优化算法 定义类 调用 web
TCP:传输控制协议(使用情况多于udp)
稳定:保证数据一定能收到
相对UDP会慢一点
web服务器一般都使用TCP(银行转账,稳定比快要重要)
TCP通信模型:
在通信之前,必须先等待建立链接
TCP的三次握手:
第一次握手:建立连接时,客户端发送SYN(请求同步)包到服务器,并进入SYN_SENT(请求连接)状态,等待服务器确认
第二次握手:服务器收到syn包,必须确认客户的SYN(x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV(SYN派遣)状态
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。客户端与服务器才正式开始传送数据
理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去
TCP服务器:
在tcp传输过程中,如果有一方收到了对方的数据,一定会发送一个ACK确认包给发送方
TCP的四次挥手:
断开一个TCP连接则需要“四次挥手”
第一次挥手:主动关闭方调用close,会发送一个长度为0的数据包以及FIN(结束标志)用来关闭主动方到被动关闭方的数据传送,
告诉被动关闭方:我已经不会再给你发数据了,但是,此时主动关闭方还可以接受数据
第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1
第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,
我的数据也发送完了,不会再给你发数据了
第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手
长连接:三次握手四次挥手之间分多次传递完所有数据(优酷看视频、在线游戏),长时间占用某个套接字
短连接:三次握手四次挥手之间传递少部分数据,多次握手挥手才传递完所有数据(浏览器),短时间占用
tcp服务器流程如下:1. socket创建?个套接字
2. bind绑定ip和port
3. listen设置最大连接数,收到连接请求后,这些请求需要排队,如果队列满,就拒绝请求
4. accept等待客户端的链接、接收连接请求
5. recv/send接收发送数据
TCP服务端:
from socket import *
#创建套接字对象,参数使用TCP协议:
s = socket(AF_INET,SOCK_STREAM)
#绑定一个IP地址和端口号:
s.bind(("127.0.0.1",8881))
#设置监听状态和最大连接数:
s.listen(5)
如果有新的客户端来链接服务器, 那么就产??个新的套接字
# newS?来为这个客户端服务(10086小妹)
# addr就可以省下来等待其他新客户端的链接
newS,addr = s.accept()
#发送数据:
newS.send("nihao".encode("utf-8"))
#接收1024个字节:
data = newS.recv(1024)
print(data)
TCP客户端:
from socket import *
s = socket(AF_INET,SOCK_STREAM)
s.connect(("127.0.0.1",8881))
data = s.recv(1024)
print(data)
s.send(b"123")
s.close()
单进程服务器(每次只能服务一个客户端):
from socket import *
serSocket = socket(AF_INET, SOCK_STREAM)
localAddr = (‘‘,7788)
serSocket.bind(localAddr)
serSocket.listen(5)
while True:
print("主进程等待新客户端")
newSocket,destAddr = serSocket.accept()
print("主进程接下来负责处理",str(destAddr))
try:
while True:
recvData = newSocket.recv(1024)
if len(recvData)>0: #如果收到的客户端数据长度为0,代表客户端已经调用close()下线
print("接收到", str(destAddr),recvData)
else:
print("%s-客户端已关闭" %str(destAddr))
break
finally:
newSocket.close()
serSocket.close()
并发服务器:
serSocket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
重新设置套接字选项,重复使用绑定的信息
# 当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,
你的程序就要用到SO_REUSEADDR选项。
创建多进程服务器:
from socket import *
from multiprocessing import Process
import time
def func(new):
while True:
new.send(b"nihao")
data = new.recv(1024)
time.sleep(1)
print(data)
if data.decode() == "886":
new.close()
break
if __name__ == "__main__":
s = socket(AF_INET,SOCK_STREAM)
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(("192.168.19.138",7789))
s.listen(5)
try:
while True:
time.sleep(1)
print(‘-----主进程, , 等待新客户端的到来------’)
new,addr = s.accept()
print(‘-----主进程, , 接下来创建?个新的进程负责数据处理’)
p = Process(target=func,args=(new,))
p.start()
except ConnectionResetError:
print("非法中断")
#当为所有的客户端服务完之后再进?关闭,表示不再接收新的客户端的链接
s.close()
创建多进程客户端:
from socket import *
s = socket(AF_INET,SOCK_STREAM)
s.connect(("192.168.19.138",7789))
while True:
data = s.recv(1024)
print(data)
s.send(b"123")
s.close()
多线程服务器(耗费的资源比多进程小一些):
from socket import *
from threading import Thread
from time import sleep
# 处理客户端的请求并执?事情
def dealWithClient(newSocket,destAddr):
while True:
recvData = newSocket.recv(1024)
if len(recvData)>0:
print(‘recv[%s]:%s‘%(str(destAddr), recvData))
else:
print(‘[%s]客户端已经关闭‘%str(destAddr))
break
newSocket.close()
def main():
serSocket = socket(AF_INET, SOCK_STREAM)
serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1)
localAddr = (‘‘, 7788)
serSocket.bind(localAddr)
serSocket.listen(5)
try:
while True:
print(‘-----主进程, , 等待新客户端的到来------’)
newSocket,destAddr = serSocket.accept()
print(‘主进程接下来创建?个新的线程负责处理 ‘, str(destAddr)))
client = Thread(target=dealWithClient, args=(newSocket,destAddr))
client.start()
#因为线程中共享这个套接字, 如果关闭了会导致这个套接字不可?,
#但是此时在线程中这个套接字可能还在收数据, 因此不能关闭
#newSocket.close()
finally:
serSocket.close()
if __name__ == ‘__main__’:
main()
socketserver:
可以使用socketserver来创建socket用来简化并发服务器
socketserver可以实现和多个客户端通信(实现并发处理多个客户端请求的Socket服务端)
它是在socket的基础上进行了一层封装,也就是说底层还是调用的socket
服务器接受客户端连接请求——》实例化一个请求处理程序——》根据服务器类和请求处理程序类,调用处理方法。
例如:
基本请求程序类(BaseRequestHandler)调用方法 handle 。此方法通过属性 self.request 来访问客户端套接字
多线程的并发服务器旗舰版:
import socketserver
import time
#创建一个请求处理类,继承 BaseRequestHandler 并且重写父类中的 handle()
class Mysock(socketserver.BaseRequestHandler):
#在handle()中处理和客户端所有的交互,建立链接时会自动执行handle方法
def handle(self):
while True:
re = self.request
re.send(b"abc")
data = re.recv(1024)
time.sleep(1)
print(data)
#对socketserver.ThreadingTCPServer 类实例化对象,将ip地址,端口号以及自己定义的类名传入,并返回一个对象
sock = socketserver.ThreadingTCPServer(("192.168.19.138",8181),Mysock)
#对象执行serve_forever方法,开启服务端(handle_request()只处理一个请求)
sock.serve_forever()
#处理多个请求,永远执行
多线程的并发客户端旗舰版:
from socket import *
import time
s = socket(AF_INET,SOCK_STREAM)
s.connect(("192.168.19.138",8181))
while True:
data = s.recv(1024)
time.sleep(1)
print(data)
s.send(b"123")
s.close()
socketserver服务端自定义类实现通信循环服务端:
import socketserver
# 自定义类来实现通信循环
class MyServer(socketserver.BaseRequestHandler):
# 必须写入handle方法,建立链接时会自动执行handle方法
def handle(self):
while True:
data = self.request.recv(1024)
# handle 方法通过属性 self.request 来访问客户端套接字
print(‘->client:‘, data)
self.request.send(data.upper())
socketserver.TCPServer.allow_reuse_address = True
server = socketserver.ThreadingTCPServer((‘127.0.0.1‘, 8080), MyServer)
server.serve_forever()
socketserver客户端自定义类实现通信循环客户端:
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((‘127.0.0.1‘, 8080))
while True:
client.send(‘hello‘.encode(‘utf-8‘))
data = client.recv(1024)
print(data)
远程执行命令subprocess:
Python可以使用subprocess下的Popen类中的封装的方法来执行命令
构造方法 popen() 创建popen类的实例化对象
obj = Subprocess.Popen(data,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
data 命令内容
shell = True 命令解释器,相当于调用cmd 执行指定的命令
stdout 正确结果丢到管道中
stderr 错了丢到另一个管道中
PIPE 将结果转移到当前进程
stdout.read() 可以获取命令执行的结果
指定结果后会将执行结果封装到指定的对象中
然后通过对象.stdout.read()获取执行命令的结果,如果不定义stdout会将结果进行标准输出
print(obj.stdout.read().decode(‘gbk‘)) # 正确命令
print(obj.stderr.read().decode(‘gbk‘)) #错误命令
远程执行命令服务端subprocess:
import subprocess
from socket import *
s = socket(AF_INET,SOCK_STREAM)
s.bind(("192.168.19.138",8878))
s.listen(5)
while True:
ns,addr = s.accept()
while True:
try:
data = ns.recv(1024)
sub = subprocess.Popen(data.decode,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE )
a = sub.stdout.read().decode("gbk")
b = sub.stderr.read().decode("gbk")
ns.send((a + b).encode("utf-8"))
except ConnectionResetError:
print("服务结束")
break
ns.close()
s.close()
远程执行命令客户端subprocess:
客户端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect((‘127.0.0.1‘, 8080))
while 1:
cmd = input(‘>>>‘)
if cmd == "zaijian":
break
phone.send(cmd.encode(‘utf-8‘))
from_server_data = phone.recv(1024)
print(from_server_data.decode(‘gbk‘))
phone.close()
#提出沾包问题
解决沾包问题:
TCP协议是面向流的协议,容易出现粘包问题
不管是recv还是send都不是直接接收对方的数据(不是一个send一定对应一个recv),而是操作自己的缓
存区(产生沾包的根本原因)
例如基于tcp的套接字客户端往服务端上传数据,发送时数据内容是按照一段一段的字节流发送的,
在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,
不能一次提取任意字节的数据,这一点和TCP是很不同的
只有TCP有粘包现象,UDP永远不会粘包
粘包不一定会发生
如果发生了:1.可能是在客户端已经粘了
2.客户端没有粘,可能是在服务端粘了
客户端粘包:
发送端需要等缓冲区满才发送出去,造成粘包
(发送数据时间间隔很短,数据量很小,TCP优化算法会当做一个包发出去,产生粘包)
服务端粘包
接收方没能及时接收缓冲区的包(或没有接收完),造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,
服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
粘包服务端:
from socket import *
import time
s = socket(AF_INET,SOCK_STREAM)
s.bind(("192.168.19.138",8881))
s.listen(5)
newS,addr = s.accept()
time.sleep(1)
data = newS.recv(1024)
data1 = newS.recv(1024)
print("1",data.decode())
print("2",data1.decode())
粘包客户端:
from socket import *
import time
s = socket(AF_INET,SOCK_STREAM)
s.connect(("192.168.19.138",8881))
s.send("hello".encode("utf-8"))
s.send("world".encode("utf-8"))
s.close()
不合适的解决方案:
send时加上时间间隔,虽然可以解决,但是会影响效率。不可取。
标签:rev 效率 发送数据 gbk stdout 优化算法 定义类 调用 web
原文地址:https://www.cnblogs.com/zhang-da/p/11908436.html