zoukankan      html  css  js  c++  java
  • 总结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编码
    
    客户端(自定制报头)
  • 相关阅读:
    输入一个nxn矩阵各元素的值,球出两条对角线元素之和
    打印杨辉三角
    编写一个函数,实现两个字符串的连接功能
    字符串置换。将字符串s中的出现的字符s1用字符s2置换
    有一行文字,要求删去其中某个字符
    自定义函数delstr()的功能是删去字符串s1中所有的"*"
    用微软的kestrel在Linux上利用Apache架设Asp.Net Core环境
    2012年8月14日 星期二 equals()方法 (冲突备份)
    jquery 操作DOM 案例
    FileUpload 控件上传图片和文件
  • 原文地址:https://www.cnblogs.com/baili-luoyun/p/10306355.html
Copyright © 2011-2022 走看看