网络编程之tcp协议以及粘包问题

网络编程tcp协议与socket以及单例的补充

一、单例补充

  • 实现单列的几种方式
#方式一:classmethod

# class Singleton:
#
#     __instance = None
#
#     @classmethod
#     def singleton(cls):
#
#         if not cls.__instance:
#             cls.__instance = cls()
#
#         return cls.__instance
#
# obj1 = Singleton.singleton()
# obj2 = Singleton.singleton()
# print(obj1)
# print(obj2)
# <__main__.Singleton object at 0x000002127F230D08>
# <__main__.Singleton object at 0x000002127F230D08>


#方式二: __new__
# class Singleton:
#
#     __instance = None
#
#     def __new__(cls, *args, **kwargs):
#
#         if not cls.__instance:
#             cls.__instance = object.__new__(cls)
#
#         return cls.__instance
#
# obj1 = Singleton()
# obj2 = Singleton()
# print(obj1)
# print(obj2)
# <__main__.Singleton object at 0x00000257AAE10A88>
# <__main__.Singleton object at 0x00000257AAE10A88>


#方式三:装饰器
# def singleton(cls):  #cls---> Father
#
#     __instance = {}
#
#     def inner(*args, **kwargs):
#         if cls not in __instance:
#             obj = cls(*args, **kwargs)
#             __instance[cls] = obj
#
#
#         return __instance[cls]
#     return inner
#
# @singleton
# class Father:
#     pass
#
# print(Father())
# print(Father())
# <__main__.Father object at 0x000001F17EB21548>
# <__main__.Father object at 0x000001F17EB21548>



#方式四
先定义一个Singletoncls的py文件内容如下:
class Singletoncls:
    pass

obj = Singletoncls()
# from Singletoncls import obj
# print(obj)
# from Singletoncls import obj
# print(obj)
# from Singletoncls import obj
# print(obj)
# 
# 
# 

# 方式五:元类

二、tcp协议以及粘包问题

  • 理论知识
传输层:
    -tcp协议
    -udp协议
    端口(port):标识一台计算机上的某一个软件。
        -0-1024:禁止使用,因为是操作系统在用
        -8000---->以后接着用

    - 以下的一些软件的固定端口不要碰:
        django:8000
        mysql:3306
        redis:6379
        flask:5000
        tomcat:8080
        mongodb:27017
        ...
    要想要传输数据,必须建立双向通道


1、tcp协议:三次握手,四次挥手
    -tcp协议建立双向通道

    - 三次握手,键连接:
        1:客户端向服务端发送建立连接的请求
        2:服务端返回收到的请求信息给客户端,并且发送往客户端建立的请求
        3:客户端接收到服务端的请求,返回请求成功给服务端,完成双向连接


    - 反馈机制:
        客户端往服务端发送请求,服务端必须返回响应
        告诉客户端收到请求了,并且将服务端的数据一并返回给客户端
        c--->s:一次请求,必须有一次响应

        - 缺点:
            - 洪水攻击:
                指的是通过伪造大量的请求,往对方服务器发送请求
                导致对方服务器跟不上,以至于瘫痪。
                Linux系统有个参数可以限制

            - 半连接池listen:限制用户在同一时间段内的访问数量

    - 四次挥手,断开连接
        1:客户端向服务端发送断开连接的请求
        2:服务端返回收到请求的信息给客户端
        3:服务端确认所有的数据发送完成以后,再发送同意断开连接的请求给客户端
        4:客户端返回收到断开连接的请求,给服务端

2、socket套接字通信:
    - 什么是socket?
        socket是一个模块,又称套接字,用来封装 互联网协议(应用层以下的层)

    - 为什么要有socket?
        socket可以实现 互联网协议应用层已下的层的工作
        - 提高开发效率

    - 怎么使用socket?
        import socket
        写socket套接字:
            client
            sever

3、粘包问题
    -1)问题:无法确认对方发送过来的数据的大小

    -2)问题:在发送数据间隔短并且数据量小的情况下,会将所有数据一次性发送

    解决:确认对方数据的大小

4、解决粘包问题(struct模块)
    -无论那一段发送数据
        -客户端
            -1)先制作报头,并发送(struct)
            -2)发送真实数据

        服务端
            -1)接收报头,并解包获取 真实的数据长度
            -2)根据真实数据长度 接收真实数据
            recv(真实数据长度)

网络编程之tcp协议以及粘包问题_第1张图片

三、socket套接字

  • socket套接字初级版本
  • 演示
-sever:
    
'''
先启动套接服务端
'''

import socket

#买手机
sever = socket.socket()

#绑定手机卡
sever.bind(
    ('127.0.0.1', 9876)
)

#半连接池
sever.listen(5)  #最多同时五个人坐椅子,实际上==6

print(
    '服务端正在运行...'
)

#等待电话接入
#coon:指的是服务端往客户端的管道
coon, addr = sever.accept()

#接听对方讲话的内容
#data客户端发送过来的消息
data = coon.recv(1024) #一次性可接受1024bytes的数据
print(data)

>>>服务端正在运行...
>>>b'hello'

#挂电话
coon.close()


-client:
'''
先启动服务端后再启动客户端
'''

import socket

#买手机
client = socket.socket()

#拨打电话
client.connect(
    ('127.0.0.1', 9876)
)

print('客户端正在运行...')

#必须发送bytes类型的数据
#开始讲话
client.send(b'hello')
# 或client.send('hello'.encode('utf_8'))
>>>客户端正在运行...
  • socket套接字升级版本
- sever

'''
注意:
    客户端法送一次,我服务端就得先接受一次,之后才可以再向客户端发送消息
'''

import socket

#买手机
sever = socket.socket()

#绑定手机卡
#里面绑定的是一个元祖
sever.bind(
    ('127.0.0.1', 9876)
)


#半连接池
sever.listen(5)

print('服务端正在等待服务...')

#等待电话接入
#coon:指的是服务端往客户端的管道

coon, addr = sever.accept()

#接听对方讲话的内容
data = coon.recv(1024)
print(data)

#服务端往客户端发送消息
coon.send(b'hi i am sever')

#挂电话
coon.close()

>>>服务端正在等待服务...
>>>b'hello i am client...'


- client
'''
启动服务端后再启动客户端
'''
import socket

#买手机
client = socket.socket()

#拨号
client.connect(
    ('127.0.0.1', 9876)
)

print('客户端正在发送请求...')

client.send(b'hello i am client...')

#接收服务端的请求
data = client.recv(1024)

print(data)

client.close()
>>>客户端正在发送请求...
>>>b'hi i am sever'
  • socket套接字高级版本
-sever

import socket

#买手机
sever = socket.socket()


#绑定手机卡
sever.bind(
    ('127.0.0.1', 9876)
)

#半连接池
sever.listen(5)

print('服务端正在运行...')

#等待电话接入
coon, addr = sever.accept()

while True:
    #接收对方讲话内容
    #data客户端发送过来的消息
    data = coon.recv(1024)
    if len(data) == 0:
        break
    if data.decode('utf-8') == 'q':
        break
    print(data.decode('utf-8'))

    send_data = input('服务端...')

    coon.send(send_data.encode('utf-8'))

#挂电话
coon.close()
服务端正在运行...
服务端...你好啊亚峰

-client

import socket

#买手机
client = socket.socket()

#拨打电话
client.connect(
    ('127.0.0.1', 9876)
)

print('客户端正在发送请求...')

while True:
    send_data = input('客户端>>>:').strip()
    client.send(send_data.encode('utf-8'))
    data = client.recv(1024)
    if data.decode('utf-8') == 'q':
        break
    if len(data) == 0:
        break

    print(data.decode('utf-8'))

client.close()
>>>客户端正在发送请求...
>>>客户端>>>:你好啊热巴
>>>好啊亚峰
  • socket套接字终级版本
- sever


import socket

#买手机
sever = socket.socket()

#绑定手机卡
sever.bind(
    ('127.0.0.1', 9876)
)

#半连接池
sever.listen(5)

print('服务端正在服务...')

#循环实现可接受多个用户访问
while True:

    coon, addr = sever.accept()
    print(addr)

    #循环实现通信
    while True:
        try:
            #监听代码是否有异常出现
            #接听对方讲话的内容
            #data客户端发送过来的消息
            data = coon.recv(1024)

            if len(data) == 0:
                break

            if data.decode('utf-8') =='q':
                break

            print(data.decode('utf-8'))

            send_data = input('服务端>>>...')

            #服务端向客户端发送消息
            coon.send(send_data.encode('utf-8'))

        except Exception as e:
            print(e)
            break

    #挂电话
    coon.close()

>>>
服务端正在服务...
('127.0.0.1', 52467)
我想找迪丽热巴
服务端>>>...你好啊亚峰,我是热巴,有什么能帮你吗
热巴youare beautiful
服务端>>>...谢谢亚峰


-client

import socket

#买手机

client = socket.socket()

#拨打号码
client.connect(
    ('127.0.0.1', 9876)
)

print('客户端正在发送请求...')

while True:
    send_data = input('客户端>>>:')
    client.send(send_data.encode('utf-8'))
    data = client.recv(1024)

    if data.decode('utf-8') == 'q':
        break

    if len(data) == 0:
        break

    print(data.decode('utf-8'))
>>>
client.close()
客户端正在发送请求...
客户端>>>:我想找热巴
你好啊亚峰,我是热巴,有什么能帮你吗
客户端>>>:热巴you are beautiful
谢谢亚峰
客户端>>>:

四、粘包问题

  • 粘包问题的出现以及几种情况
  • 第一个问题
-sever
#问题一不知道数据的具体长度
# import socket
#
# import subprocess
#
# #买手机
# sever = socket.socket()
#
# #绑定电话卡
# sever.bind(
#     ('127.0.0.1', 9867)
# )
#
# #半整数池
# sever.listen(5)
#
# while True:
#     coon, addr = sever.accept()
#     print(addr)
#
#     while True:
#         try:
#             #recv从内存中获取数据
#             cmd = coon.recv(1024)
#
#             if len(cmd) == 0:
#                 continue
#             cmd = cmd.decode('utf-8')
#             if cmd == 'q':
#                 break
#
#             #调用subprocess连接终端,对终端进行操作,并获取操作后正确或错误的结果
#             obj = subprocess.Popen(
#                 cmd, shell=True, stdout=subprocess.PIPE,
#                 stderr=subprocess.PIPE
#             )
#             #结果交给result变量名
#             result = obj.stdout.read() + obj.stderr.read()
#             print(len(result))
#
#             #windows默认是gbk
#             print(result.decode('gbk'))
#
#             #将结果返回给客户端
#             coon.send(result)
#         except Exception as e:
#             print(e)
#             break
#
#     coon.close()


-client
# import socket
#
# client = socket.socket()
#
# client.connect(
#     ('127.0.0.1', 9867)
# )
#
# while True:
#
#     cmd = input('客户端输入的内容:')
#
#     client.send(cmd.encode('utf-8'))
#
#     data = client.recv(19190)
#
#     print(len(data))
#
#     print(data.decode('gbk'))
  • 第二种问题
-sever

#问题二:当发送多次传入的数据长度却不是很长的时候,服务端多次接收后面接收的没内容

import socket

sever = socket.socket()

sever.bind(
    ('127.0.0.1', 9000)
)

sever.listen(5)

coon, addr = sever.accept()

data = coon.recv(10)
print(data)

data = coon.recv(1024)
print(data)

data = coon.recv(1024)
print(data)
>>>b'hellohello'
>>>b'hello'
>>>b''


- client
#问题二
import socket

client = socket.socket()

client.connect(
    ('127.0.0.1', 9000)
)

client.send(b'hello')
client.send(b'hello')
client.send(b'hello')

五、解决粘包问题

  • 演示
- sever

import socket
import subprocess
import struct

sever = socket.socket()

sever.bind(
    ('127.0.0.1', 9000)
)

sever.listen(5)

while True:
    coon, addr = sever.accept()
    print(addr)

    while True:
        try:

            #获取客户端传过来的报头
            header = coon.recv(4)

            #解包获取真实的数据长度
            data_len = struct.unpack('i', header)[0]

            #准备接收真实数据
            cmd = coon.recv(data_len)

            if len(cmd) == 0:
                continue

            cmd = cmd.decode('utf-8')

            if cmd == 'q':
                break

            #调用subprocess连接终端,对终端进行操作,并获取操作后的正确或错误的结果
            obj = subprocess.Popen(
                cmd, shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )

            #结果交给result变量名
            result = obj.stdout.read() + obj.stderr.read()

            print('发送给客户端的真实长度', len(result))

            #将结果返回给客户端,做一个报头,返回给客户端
            header = struct.pack('i', len(result))
            print(len(header))
            coon.send(header)
            coon.send(result)

        except Exception as e:
            print(e)
            break

    coon.close()
    
    
- client


import struct
import socket

client = socket.socket()

client.connect(
    ('127.0.0.1', 9000)
)

while True:

    cmd = input('客户端输入的内容:')

    cmd_bytes = cmd.encode('utf-8')

    #做一个报头
    header = struct.pack('i', len(cmd_bytes))

    print(len(header))

    client.send(header)

    #待服务器确认长度以后,再发送真实数据长度
    client.send(cmd_bytes)

    #接收服务端返回的报头
    s_header = client.recv(4)

    #解包,接收服务端返回的真实数据
    data_len = struct.unpack('i', s_header)[0]

    result = client.recv(data_len)

    print('接收服务器返回的真实数据长度', len(result))
    print(result.decode('gbk'))
  • 演示二
-sever

import socket
import struct
import json

sever = socket.socket()

sever.bind(
    ('127.0.0.1', 9000)
)

sever.listen(5)

while True:

    coon, addr = sever.accept()
    print(addr)

    while True:
        try:
            #获取客户端传来的报头
            header = coon.recv(4)

            #解包,获取真实的数据长度
            json_len = struct.unpack('i', header)[0]

            #接收json(dictionary)的真实长度
            json_bytes_data = coon.recv(json_len)

            #反序列化将bytes类型数据变成json数据
            json_data = json_bytes_data.decode('utf-8')

            back_dic = json.loads(json_data)
            print(back_dic)
            print(back_dic.get('movie_len'))

        except Exception as e:
            print(e)
            break

    coon.close()
    
>>>('127.0.0.1', 53414)
>>>{'movie_name': '色戒', 'movie_len': 100000}
>>>100000


-client
import struct
import socket
import json

client = socket.socket()

client.connect(
    ('127.0.0.1', 9000)
)

while True:

    movie_name = input('请输入上传电影的名字:').strip()

    #伪装电影真实数据
    movie_len = 100000

    send_dic = {
        'movie_name': movie_name,
        'movie_len': movie_len
    }

    #序列化
    json = json.dumps(send_dic)
    # print(json)
    # print(json.encode('utf-8'))
    # print(len(json.encode('utf-8')))

    json_bytes = json.encode('utf-8')

    #做一个报头
    header = struct.pack('i', len(json_bytes))

    #发送报头
    client.send(header)

    #发送真实数据
    client.send(json_bytes)

>>>请输入上传电影的名字:色戒

你可能感兴趣的:(网络编程之tcp协议以及粘包问题)