socket套接字,粘包问题

发布于:2023-01-21 ⋅ 阅读:(630) ⋅ 点赞:(0)

目录

scoket套接字

 socket工作流程

TCP服务端

TCP客户端

基于TCP 的SOCKET服务端与客户端

基础版本

客户端

加入连接循环

加入通信循环

支持并发的TCP服务端

常见问题:

半连接池

粘包问题

TCP协议的特点

解决粘包问题

UDP协议

服务端不需要考虑客户端是否异常退出



scoket套接字

基于文件类型的套接字家族名字:AF_UNIX

基于网络类型的套接字家族名字:AF_IN

socket(简称 套接字) 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的
例如我们每天浏览网页、QQ 聊天、收发 email 等等

1.socket介绍
1.什么是socket?
socket套接字,是进程通信的一种实现方式
2.主要特点
主要针对不同主机之间的通信。即网络进程的通信,
几乎所有的网络都是通et流程,实现网络通信

在这里插入图片描述

 socket工作流程

在这里插入图片描述


TCP服务端

首先拿到一个socket对象,socket对象与IP+PORT绑定才能进行端口监听.

然后socket对象监听端口是否有请求,同时调用accept方法,一直阻塞等待客户端的链接;

如果有客户端的请求发来,那么服务端接收到请求之后调用read方法读取数据;

之后再处理请求,写回数据使用write方法(python中是send方法)

当需要断开连接时服务端发送请求,客户端读取之后在调用close方法断开请求,

服务端read收到断开请求,服务端再调用close方法即可.

TCP客户端

首先拿到一个socket对象,调用connect方法,传入IP与PORT,发送建立链接的请求, 

之后就可以进行数据交互, 如果需要结束链接,直接调用close方法即可.

基于TCP 的SOCKET服务端与客户端

基础版本

TCP是基于链接的, 必须先启动服务端,然后再启动客户端去链接服务器

import socket
import time

server = socket.socket()

# 注意: 传入的参数必须是一个元组或者列表
# ip '127.0.0.1'是本地回环地址,只能自己玩
# 如果是别的IP地址,可以在同一个局域网里一起使用
server.bind(('127.0.0.1',8080))


# 监听 半连接池为5,相当于队列中最多有5个, 超出5个就会报错
# 同时智能服务一个人
server.listen(5)


# 等待客户端的链接
# socket 是连接对象, address是客户端地址
# 以后这个服务端和客户端使用sock这个连接对象
sock,address = server.accept()



# 接收客户端发了的数据
# 传入的参数为一次接受的字节
data = sock.recv(1024)

print(f'收到了来自{address}的数据{data.decode("utf8")}')


# 服务端给客户端发送消息
data_send = f'收到了你发送的{data}'.encode('utf8')
# 注意必须传送二进制格式数据
sock.send(data_send)

time.sleep(2)

# 关闭连接对象,此时服务器没有关闭
sock.close()

# 关闭服务端
server.close()


客户端

import socket


client = socket.socket()


# 链接服务端的地址加端口
client.connect(('127.0.0.1,8080))


# 连上以后就可以发送消息了
client.send(b'good evening')


# 收到服务端返回的消息
data = client.recv(1024)
print(data.decode('utf8'))


client.close()

加入连接循环

只加入连接循环, 此时服务端会一直接收消息看相当于死循环, 服务器不会自动断开

服务端




import socket

server = socket.socket()

server.bind(('127.0.0.1', 8008))

server.listen(5)
print('等待客户端的链接')

# 等待客户端的链接
# 链接循环
while True:
    sock, addr = server.accept()


    # 接受客户端发了的数据
    data = sock.recv(1024)

    print(f'接收到来自{addr}的数据--{data.decode("utf-8")}')


    # 服务端给客户端发送消息
    data_send = f'收到了你发送的 {data}'.encode('utf-8')
    # 注意:必须传送二进制的格式
    sock.send(data_send)


    # 关闭连接对象,此时服务端并没有关闭!!!
    sock.close()

    # 关闭服务端
    server.close()

客户端


import socket

client = socket.socket()

# 连接服务端的地址加端口
client.connect(('127.0.0.1', 8008))

# 连上以后就可以发送消息了
client.send(b'good evening')

# 收到服务端返回的消息
data = client.recv(1024)
print(data.decode('utf-8'))

client.close()

加入通信循环

加入通信循环,此时客户端可以多次发送数据

服务端



import socket

server = socket.socket()
server.bind(('127.0.0.1', 8008))
server.listen(5)
print('等待客户端的链接')

while True:
    sock, addr = server.accept()
    print(f'客户端{addr}已连接')
    # 等待接受客户端发的数据
    # 如果客户端没法,就会一直等待下去
    while True:
        try:
            # 同一个客户端,不停地发送
            data = sock.recv(1024)
            # 当客户端主动断开后,客户端会发送一个空的数据过来
            # 此时服务端必须对这种情况进行处理,否则会出现死循环
            if len(data) == 0:
                print(f'客户端{addr}已断开', '\n')
                break

            print(f'接收到来自{addr}的数据--{data.decode("utf-8")}')
            data_send = f'收到了你发送的 {data}'.encode('utf-8')
            sock.send(data_send)
        except Exception as e:
            print(e)
            break

    sock.close()

    # 关闭服务端
    server.close()

客户端


import socket

client = socket.socket()
client.connect(('127.0.0.1', 8008))
# 连上以后就可以发送消息了

while True:
    data = input('输入发送给服务端的消息(q to quit):')
    if data == 'q': break
    client.send(data.encode("utf-8"))

    data = client.recv(1024)
    print(data.decode('utf-8'))

    client.close()


支持并发的TCP服务端

在这里是开启多进程完成的并发,可以换成多线程, 只要将Process类换成Thread类

import socket
from multiprocessing import Process

def talk(sock,addr):
    print('客户端连接成功', addr)
    while True:
        try:
            data = sock.recv(1024)
            if len(data) == 0:
                print(f'客户端{addr}已断开', '\n')
                break

            print(f'接收到来自{addr}的数据--{data.decode("utf-8")}')
            data_send = f'收到了你发送的 {data}'.encode('utf-8')
            sock.send(data_send)
        except Exception as e:
            print(e)
            break
    sock.close()


if __name__ == '__main__':
    server = socket.socket()
    server.bind(('127.0.0.1', 81))
    server.listen(5)
    while True:
        sock,addr = server.accept()
        print(f'客户端{addr}已连接')
        # 连接上一个客户端之后开启一个进程,对该客户端进行服务
        p = Process(target=talk, args=(sock, addr))
        p.start()

        server.close()

常见问题:

1.发送消息不能为空 >>>> 统计长度饼判断即可

2. 反复重启服务端可能会报错 >>> address in use

        这个错在苹果本上会比较频繁出现,windows频率较少

from socket import SOL_SOCKET,SO_REUSEADDR
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 在bind前加

3. 连接循环

如果是Windows电脑 客户端异常推出之后服务端会直接报错

处理方式 >>> 异常处理

如果是mac或者linux服务端会接收到一个空消息

处理方式 >>> len判断

客户端如果异常断开,服务端代码应该重新回到accept等待新的客人

目前的服务端只能实现一次服务一个人 不能做到同时服务多人, [并发状态下可以实现]

4. 加入通信循环之后,想要同一个py文件启动多个客户端, pycharm会出现以下提示,不允许启动多个

在这里插入图片描述

 解决办法

 

半连接池

listen(5)

# py文件默认同一时间只能运行一次 

# 半连接池
# 设置最大等待人数 >>> 节省资源 提高CPU利用率

粘包问题

data1 = conn.recv(1024)
print(data1)
data2 = conn.recv(1024)
print(data2)
data3 = conn.recv(1024)
print(data3)

client.send(b'hello')
client.send(b'jason')
client.send(b'kevin')
"""
三次打印的结果
  b'hellojasonkevin'
  b''
  b''
"""

TCP协议的特点

会将数据量比较小 并且时间间隔比较短的数据整合到一起发送

并且还会受制于recv()内数字的大小,此现象我们称之为''六十协议''

>>> 问题产生的原因是因为recv括号内我们不知道即将要接收的数据到底有多大, 如果每次接受的收据我们都能够精准的知道他的大小, 那么肯定不会有粘包问题出现<<<<

如果我们能知道即将要接收的数据量大小那么粘包问题就解决了~

解决粘包问题


  
# struct模块
	import struct

  data1 = 'hello world!'
  print(len(data1))  # 12
  res1 = struct.pack('i', len(data1))  # 第一个参数是格式 写i就可以了
  print(len(res1))  # 4
  ret1 = struct.unpack('i', res1)
  print(ret1)  # (12,)


  data2 = 'hello baby baby baby baby baby baby baby baby'
  print(len(data2))  # 45
  res2 = struct.pack('i', len(data2))
  print(len(res2))  # 4
  ret2 = struct.unpack('i', res2)
  print(ret2)  # (45,)



pack可以讲任意长度的数字打包成固定长度

unpack可以将固定长度的数字解包成打包之前的数据真实长度

>>> 将真实数据打包成固定长度的包

>>> 将固定长度的包发送给对方

>>> 对方接收到包之后再解包获取真实数据长度

>>> 接受真是长度的数据

<<<<<<< 1.先接收固定长度的报头 
        2.再根据报头解压出真实长度 
        3.根据真实长度接收即可<<<<<<<<<<<<<<

struct模块针对数据量特别大的数字没有办法打包

UDP协议

服务端

# 服务端
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8080))
msg, address = server.recvfrom(1024)
print('msg>>>:%s' % msg.decode('utf8'))
print('address>>>:',address)
server.sendto('我是服务端 你好啊'.encode('utf8'), address)

客户端

# 客户端
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1', 8080)
client.sendto('我是客户端 想我了没'.encode('utf8'), server_address)
msg, address = client.recvfrom(1024)
print('msg>>>:%s' % msg.decode('utf8'))
print('address>>>:',address)

服务端不需要考虑客户端是否异常退出

UDP不存在粘包问题, UDP多用于短消息交互,如QQ


网站公告

今日签到

点亮在社区的每一天
去签到