网络编程 TCP协议:三次握手,四次回收,反馈机制 socket套接字通信 粘包问题与解决方法

Posted ludingchao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程 TCP协议:三次握手,四次回收,反馈机制 socket套接字通信 粘包问题与解决方法相关的知识,希望对你有一定的参考价值。

TCP协议:三次握手,四次挥手

  TCP协议建立双向通道。

 

三次握手, 建连接:

技术图片

 

 

 

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

第一客戶向服务端发送请求,请求建立连接
服务端同客户端的请求,并同时向客户端发送建立
连接的请求,最后客户端同意后建立 双向连接。
C ----> S
C <---- S

 

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

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

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

 

四次挥手, 断开连接:

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

技术图片

 

 

 

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

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

- 怎么使用socket?
  import socket
  写socket套接字:
    Client
    Server

socket初级版

注意:server和client的发送接收命令必须一一对应,recv接收命令必须收到内容能会运行

           windows系统下,即使发送消息为空,也有数据头,接收消息不为空;而linux下,接收消息则会为空

‘‘‘
先启动套接字服务端   server代码
‘‘‘
import socket

# 买手机
server = socket.socket()

# 绑定手机卡
server.bind(
    # 相当于手机号码
    # 127.0.0.1 == localhost 本机回环地址
    # 单机测试下: 127.0.0.1
    (127.0.0.1, 9527)
)

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

print(
    server is running...
)

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

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

# 挂电话
conn.close()
‘‘‘
启动服务端后再启动客户端   Client代码
‘‘‘
import socket

# 买手机
client = socket.socket()

# 拨号
client.connect(
    # 客户端的ip与port必须与服务端一致
    (127.0.0.1, 9527)
)

print(
    client is running...
)

# 必须发送bytes类型的数据
# client.send(‘hello‘.encode(‘utf-8‘))
# 讲话给对方听
client.send(bhello)

 

2)socket升级版

‘‘‘
注意:
    客户端先一次发送,服务端得先一次接受,再发送消息。
‘‘‘
import socket

# 买手机
server = socket.socket()

# 绑定手机卡
server.bind(
    # 相当于手机号码
    # 127.0.0.1 == localhost 本机回环地址
    # 单机测试下: 127.0.0.1
    (127.0.0.1, 9527)
)

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

print(server is running...)

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

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

# 服务端往客户端发送消息
conn.send(bhi im server)


# 挂电话
conn.close()
‘‘‘
启动服务端后再启动客户端
‘‘‘
import socket

# 买手机
client = socket.socket()

# 拨号
client.connect(
    # 客户端的ip与port必须与服务端一致
    (127.0.0.1, 9527)
)

print(client is running...)

# 必须发送bytes类型的数据
# client.send(‘hello‘.encode(‘utf-8‘))
# 讲话给对方听
client.send(bhello im client...)
data = client.recv(1024)
print(data)

client.close()

 

 3)socket升级版

‘‘‘
注意:
    客户端先一次发送,服务端得先一次接受,再发送消息。 server
‘‘‘
import socket
# 买手机
server = socket.socket()

# 绑定手机卡
server.bind(
    # 相当于手机号码
    # 127.0.0.1 == localhost 本机回环地址
    # 单机测试下: 127.0.0.1
    (127.0.0.1, 9527)
)
# 半连接池
server.listen(5)  # 最多5个人坐椅子, 实际上==6
print(server is running...)
# 等待电话接入
# conn: 指的是服务端往客户端的管道
conn, addr = server.accept()
while True:
    # 接听对方讲话的内容
    # data客户端发送过来的消息
    data = conn.recv(1024)  # 可接受一次1024bytes的数据
    if len(data) == 0:    # 没有意义,windows系统下不可能为0
        break
    if data.decode(utf-8) == q:
        break
    print(data)
    send_data = input(服务端>>>:)
    # 服务端往客户端发送消息
    conn.send(send_data.encode(utf-8))
# 挂电话
conn.close()
‘‘‘
启动服务端后再启动客户端  client
‘‘‘
import socket

# 买手机
client = socket.socket()

# 拨号
client.connect(
    # 客户端的ip与port必须与服务端一致
    (127.0.0.1, 9527)
)

print(client is running...)

# 必须发送bytes类型的数据
# 讲话给对方听
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)

client.close()

 

4)socket最终版

‘‘‘
注意:
    客户端先一次发送,服务端得先一次接受,再发送消息。  server
‘‘‘
import socket
# 买手机
server = socket.socket()

# 绑定手机卡
server.bind(
    # 相当于手机号码
    # 127.0.0.1 == localhost 本机回环地址
    # 单机测试下: 127.0.0.1
    (127.0.0.1, 9527)

    # 局域网内测试
    # (‘192.168.12.202‘, 9527)

)
# 半连接池
server.listen(5)  # 最多5个人坐椅子, 实际上==6
print(server is running...)

# 循环实现可接受多个用户访问
while True:
    # 等待电话接入 ---> 接入客户端
    # conn: 指的是服务端往客户端的管道
    conn, addr = server.accept()
    print(addr)

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

            if len(data) == 0:
                break
            if data.decode(utf-8) == q:
                break
            print(data.decode(utf-8))
            send_data = input(服务端>>>:)
            # 服务端往客户端发送消息
            conn.send(send_data.encode(utf-8))

        # 捕获异常信息,并打印  # e: 报错信息
        except Exception as e:
            print(e)
            break

    # 挂电话
    conn.close()
‘‘‘
启动服务端后再启动客户端  client
‘‘‘
import socket

# 买手机
client = socket.socket()

# 拨号
client.connect(
    # 客户端的ip与port必须与服务端一致
    (127.0.0.1, 9527)
)

print(client is running...)

# 必须发送bytes类型的数据
# 讲话给对方听
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()

 

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

# server
import socket
import subprocess

server = socket.socket()

server.bind(
    (127.0.0.1, 9000)
)

server.listen(5)

while True:
    conn, addr = server.accept()
    print(addr)

    while True:
        try:
            # recv从内存中获取数据
            cmd = conn.recv(10)

            if len(cmd) == 0:
                continue
            cmd = cmd.decode(utf-8)  # dir
            if cmd == q:
                break

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

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

            # 将结果返回给客户端
            conn.send(result)

        except Exception as e:
            print(e)
            break

    conn.close()
#client
import socket

client = socket.socket()

client.connect(
    (127.0.0.1, 9000)
)

while True:

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

    client.send(cmd.encode(utf-8))

    data = client.recv(19190)  # 无法估计大小
    print(len(data))
    print(data.decode(gbk))

 

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

# server
import socket
server = socket.socket()

server.bind(
    (127.0.0.1, 9000)
)

server.listen(5)

conn, addr = server.accept()

data = conn.recv(1024)
print(data)  # b‘hellohellohello‘

data = conn.recv(1024)
print(data)  # b‘‘

data = conn.recv(1024)
print(data)  # b‘‘
# client
import socket

client = socket.socket()

client.connect(
    (127.0.0.1, 9000)
)

client.send(bhello)
client.send(bhello)
client.send(bhello)

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

 

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

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

‘‘‘
1.struct是什么?
    是一个python内置的模块, 它可以将固定长度的数据,打包成固定格式的长度。

    # 模式
    i: 4

    # 其他模式


2.作用:
    可以将真实数据,做成一个固定长度多的报头,客户端发送给服务端,服务端可以接收报头,
    然后对报头进行解包,获取真实数据的长度,进行接收即可。
‘‘‘

import struct

data = b11111111111111
print(len(data))
# 打包制作报头
header = struct.pack(i, len(data))
print(header)
print(len(header))


# 解包获取真实数据长度 ---> 得到一个元组,元组中第一个值是真实数据的长度
res = struct.unpack(i, header)[0]
print(res)

 

 案例一:

# server
import socket
import subprocess
import struct

server = socket.socket()

server.bind(
    (127.0.0.1, 9000)
)

server.listen(5)

while True:
    conn, addr = server.accept()
    print(addr)

    while True:
        try:
            # 获取客户端传过来的报头
            header = conn.recv(4)
            # 解包获取真实数据长度
            data_len = struct.unpack(i, header)[0]

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

            if len(cmd) == 0:
                continue

            cmd = cmd.decode(utf-8)  # dir
            if cmd == q:
                break

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

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

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

            # gbk
            # print(result.decode(‘gbk‘))

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

        except Exception as e:
            print(e)
            break

    conn.close()
#client
import socket
import struct

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)

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

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

    print(接收服务端返回的真实数据长度, len(result))
    print(result.decode(gbk))

案例二:

#server
import socket
import json
import struct

server = socket.socket()

server.bind(
    (127.0.0.1, 9000)
)

server.listen(5)

while True:
    conn, addr = server.accept()
    print(addr)

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

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

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

            # 将bytes类型数据转为json数据
            json_data = json_bytes_data.decode(utf-8)

            # 反序列化  json ---> dict
            back_dic = json.loads(json_data)
            print(back_dic)
            print(back_dic.get(movie_len))

            # 准备接收真实数据
            # movie_data = conn.recv(back_dic.get(‘movie_len‘))
            # print(movie_data)

        except Exception as e:
            print(e)
            break


    conn.close()
#client
import socket
import struct
import json

client = socket.socket()

client.connect(
    (127.0.0.1, 9000)
)

while True:

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

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

    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协议:三次握手,四次回收,反馈机制 socket套接字通信 粘包问题与解决方法的主要内容,如果未能解决你的问题,请参考以下文章

网络 之 三次握手&四次挥手 介绍

TCP 协议(包含三次握手,四次挥手)

TCP 协议(包含三次握手,四次挥手)

TCP 协议(包含三次握手,四次挥手)

TCP协议三次握手/四次挥手

[ 网络协议篇 ] TCP三次握手四次挥手深度解析