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

前情提要

    一:套接字  socket() 

      1:三次握手

   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:四次挥手

    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'x80x8bx08x00' 4  字节
b'{x00x00x00' 4   字节
b'x05x94xce5' 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编码

客户端(自定制报头)
原文地址:https://www.cnblogs.com/baili-luoyun/p/10306355.html