总结day24 ---- socket ,struct 的学习

时间:2023-04-24 12:28:20

前情提要

    一:套接字  socket() 

      1:三次握手

总结day24 ---- socket ,struct  的学习

   1:客户端像服务端链接,   (第一次握手)

   2:服务端收到请求,告诉客户端服务端收到了内容    (第二次握手1)

   3:服务端像客户端连接,(第二次握手2)                             这俩可以合在一起

   4:客户端收到服务端请求,并告诉客户端服务端已经收到了内容 (第三次握手)

  

TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答SYN+ACK[1],并最终对对方的 SYN 执行 ACK 确认。这种建立连接的方法可以防止产生错误的连接。[1]
TCP三次握手的过程如下:
客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。
客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。
三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。 tcp的三次握手

      2:四次挥手

总结day24 ---- socket ,struct  的学习

    1:客户端告诉服务器说客户端要断开了,( 第一次挥手)

   2:服务端告诉客户端说服务端收到了断开信息(第二次挥手)  客户端和服务端通信关闭

   3:服务端像客户端通信,说服务端要断开了连接(第三次挥手)

   4:客户端收到通知后,告诉客户端已经收到了断开连接的信息. 服务端与客户端通信关闭

建立一个连接需要三次握手,而终止一个连接要经过四次握手,这是由TCP的半关闭(half-close)造成的。
(1) 某个应用进程首先调用close,称该端执行“主动关闭”(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕。
(2) 接收到这个FIN的对端执行 “被动关闭”(passive close),这个FIN由TCP确认。
注意:FIN的接收也作为一个文件结束符(end-of-file)传递给接收端应用进程,放在已排队等候该应用进程接收的任何其他数据之后,因为,FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。
(3) 一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。
(4) 接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。[1]
既然每个方向都需要一个FIN和一个ACK,因此通常需要4个分节。
注意:
(1) “通常”是指,某些情况下,步骤1的FIN随数据一起发送,另外,步骤2和步骤3发送的分节都出自执行被动关闭那一端,有可能被合并成一个分节。[2]
(2) 在步骤2与步骤3之间,从执行被动关闭一端到执行主动关闭一端流动数据是可能的,这称为“半关闭”(half-close)。
(3) 当一个Unix进程无论自愿地(调用exit或从main函数返回)还是非自愿地(收到一个终止本进程的信号)终止时,所有打开的描述符都被关闭,这也导致仍然打开的任何TCP连接上也发出一个FIN。
无论是客户还是服务器,任何一端都可以执行主动关闭。通常情况是,客户执行主动关闭,但是某些协议,例如,HTTP/1.0却由服务器执行主动关闭。[2] tcp的四次挥手

      3:基本模型

    二:简单的socket例子

服务端:

# sk =socket.socket()
# sk.bind(('127.0.0.1',8888)) #服务器建立ip 和端口
# sk.listen() # 创建监听
# conn,addr =sk.accept() #阻塞,直到有一个客户端来连接我,三次握手
# print(addr)
# while True:
# send_msg =input('msg: ')
# conn.send(send_msg.encode()) #转化成2进制
# msg =conn.recv(1024).decode() #最大接收1024,解码
# print(msg)
# conn.close()
# sk.close()
import socket
# sk =socket.socket() #实例化对象
# # sk.connect(('127.0.0.1',8888)) #选择要连接的服务器,端口
# # sk.send(b'12313123') #像服务器传输你要发送的东西
# # ret =sk.recv(1024) #设置接收,和发送的大小 字节
# # print(ret) #打印接收到的内容
# # sk.close() #关闭客户端链接

    三:带退出的socket例子

服务端:

# 带双方退出的版本
import time
sk =socket.socket()
sk.bind(('127.0.0.1',8887))
sk.listen() #建立监听
time1 =time.strftime("%Y-%m-%d %H:%M:%S")
while 1:
conn,addr =sk.accept() #建立阻塞
# conn.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#实现端口的复用 while 1:
ret = conn.recv(1024).decode()
print(ret)
if ret == 'Q':
break
else:
send_msg = input('msg :>>>'+time1).encode()
# time1 =
# conn.send(time1)
conn.send(send_msg)
if send_msg == 'Q':
break
conn.close()
sk.close
客户端:

import time
time1 =time.strftime("%Y-%m-%d %H:%M:%S") sk =socket.socket()
sk.connect(("127.0.0.1",8887))
while 1 :
send_msg = input('msg:>>>'+str(time1)).encode() sk.send(send_msg)
if send_msg == 'Q'.encode():
break else:
ret = sk.recv(1024).decode()
print(ret)
if ret == 'Q':
break sk.close()

    四:带时间的socket例子

 服务端:
# import time
# sk = socket.socket()
# sk.bind(('127.0.0.1',9000))
# sk.listen()
# while True:
# conn,addr = sk.accept()
# fmt = conn.recv(1024)
# str_time = time.strftime(fmt.decode())
# conn.send(str_time.encode())
# conn.close()
# sk.close()
客户端:
# import socket
# sk = socket.socket()
#
# sk.connect(('127.0.0.1',9000))
#
# sk.send(b'%m/%d %H:%M:%S')
# msg = sk.recv(1024).decode()
# print(msg)
# sk.close()

    五:粘包

服务端
import struct
import socket sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen() conn,addr = sk.accept()
send_msg = input('>>>').encode()
bytes_len = struct.pack('i',len(send_msg))
conn.send(bytes_len)
conn.send(send_msg) # 粘包现象
conn.send(b'world')
conn.close()
sk.close() # 1.发送端的粘包 合包机制 + 缓存区
# 2.接收端的粘包 延迟接受 + 缓存区
# 3.流式传输
# 电流 高低电压
# 所以我们说 tcp协议是无边界的流式传输
# 4.拆包机制 # 粘包现象
# 接收端不知道发送端给我发送了多长的数据
import struct
import socket sk = socket.socket()
sk.connect(('127.0.0.1',9000))
bytes_len = sk.recv(4)
msg_len = struct.unpack('i',bytes_len)[0]
msg = sk.recv(msg_len)
print(msg.decode())
msg2 = sk.recv(5)
print(msg2)
sk.close()

    六: 如何解决粘包 sturck包

服务端:
# import socket
# import struct
# sk =socket.socket()
# sk.bind(('127.0.0.1',8888))
# sk.listen()
# conn,addr =sk.accept() #创建阻塞
# send_msg =input('>>>').encode()
# bete_len =struct.pack('i',len(send_msg)) #文件长度标志
# conn.send(bete_len)
# conn.send(send_msg)
# conn.close()
# sk.close()
客户端
# import socket
# sk =socket.socket()
# sk.connect(('127.0.0.1',8888))
# ret =sk.recv(1024).decode()
# print(ret)
# sk.close()
# import struct
# import socket
# sk =socket.socket()
# sk.connect(('127.0.0.1',8888))
# bete_len =sk.recv(4) #只是读取前4个值
# # print(bete_len)
# msg_len =struct.unpack('i',bete_len)[0]
# msg =sk.recv(msg_len)
# print(msg.decode())
# # msg2 =sk.recv(5)
# sk.close()

    七 :struct 包的使用

      struct.pack('i',len(bytes))  #i 是固定的 len() 里面放 存的内容

        他会返回4个字节 , 客户端将这4个字节读取就可以得到真实字符串长度

import struct

ret = struct.pack('i',560000)
print(ret,len(ret))
ret1 = struct.pack('i',123)
print(ret1,len(ret1))
ret2 = struct.pack('i',902730757)
print(ret2,len(ret2)) res = struct.unpack('i',ret)
print(res[0])
res = struct.unpack('i',ret1)
print(res[0])
res = struct.unpack('i',ret2)
print(res[0]) >>>>>>>>>>>
b'\x80\x8b\x08\x00' 4 字节
b'{\x00\x00\x00' 4 字节
b'\x05\x94\xce5' 4  字节
560000
123
902730757

使用struct解决黏包

借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。

发送时 接收时
先发送struct转换好的数据长度4字节 先接受4个字节使用struct转换成数字来获取要接收的数据长度
再发送数据 再按照长度接收数据
import socket,struct,json
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加 phone.bind(('127.0.0.1',8080)) phone.listen(5) while True:
conn,addr=phone.accept()
while True:
cmd=conn.recv(1024)
if not cmd:break
print('cmd: %s' %cmd) res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
err=res.stderr.read()
print(err)
if err:
back_msg=err
else:
back_msg=res.stdout.read() conn.send(struct.pack('i',len(back_msg))) #先发back_msg的长度
conn.sendall(back_msg) #在发真实的内容 conn.close() 服务端(自定制报头)
#_*_coding:utf-8_*_
import socket,time,struct s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080)) while True:
msg=input('>>: ').strip()
if len(msg) == 0:continue
if msg == 'quit':break s.send(msg.encode('utf-8')) l=s.recv(4)
x=struct.unpack('i',l)[0]
print(type(x),x)
# print(struct.unpack('I',l))
r_s=0
data=b''
while r_s < x:
r_d=s.recv(1024)
data+=r_d
r_s+=len(r_d) # print(data.decode('utf-8'))
print(data.decode('gbk')) #windows默认gbk编码 客户端(自定制报头)