有关socket以及粘包等

引子
什么是网络:一堆网络设备和一堆协议组成

协议:计算机届的英语、互联网协议价物理链接介质
应用层
传输层:TCP/UDP,基于端口通信
网络层
数据链路层:规定数据报的概念,基于mac广播的方式(找到同一子网的地址), 所以要IP(IP+子网掩码找到网络地址),跑以太网协议,mac地址固定,ip地址可变
物理层:单纯的二进制数,
一串二进制数字称为数据帧/数据报
两部分:报头:描述数据部分 固定长度的报头 数据部分
socket:应用层与TCP/IP协议通信的中间软件抽象层,它是一组接口
多道技术:多道程序
linux:一切皆文件
套接字: 基于网络通信的
套接字家族: 套接字的工作流程: 多网卡
套接字
基于文件类型的套接字家族

套接字家族名字:AF_UNIX

基于网络类型的套接字家族
套接字家族名字:AF_INET
服务端套接字函数
s.bind()绑定
s.listen() 开始TCP监听
s.accept()被动接受TCP客户的连接,等待连接的到来
客户端套接字函数
s.connect()主动初始化TCP服务器连接
s.connect_e()connect()函数的扩展版本,出错时返回错误码而不是抛出异常
公共用途的套接字函数

s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法

s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间

面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
模拟ssh远程执行命令
import socket
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)) #绑定到端口上,IP地址是以元祖的形式绑上去的,127是本机地址,也叫回环地址
phone.listen(5) #开机

print('starting...')
while True: #链接循环
    conn,client_addr=phone.accept() #等电话 (链接,客户的的ip和端口组成的元组)
    print('-------->',conn,client_addr)

    #收,发消息
    while True:#通信循环
        try:
            cmd=conn.recv(1024)
            if not cmd:break #针对linux
            # 执行cmd命令,拿到cmd的结果(结果应该是bytes类型)
            res = subprocess.Popen(cmd.decode('utf-8'), shell=True,

                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout=res.stdout.read()
            stderr=res.stderr.read()
            #发送命令结果
            conn.send(stdout+stderr)
        except Exception:
            break
    conn.close() #挂电话
phone.close() #关机
服务端

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
phone.connect(('127.0.0.1',8080)) #绑定手机卡
#发,收消息
while True:
    cmd=input('>>: ').strip()
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))
    cmd_res=phone.recv(1024)
    print(cmd_res.decode('gbk'))
    print(cmd_res)
phone.close()
客户端

粘包现象
TCP TCP是可靠的连接,是面向流的协议,基于数据流,所以收发消息都不能为空,
所以在客户端和服务端都需要加空消息的处理机制,数据不会丢失,如果上一次没有接收完就会
接着接收,会粘包,但是可靠
UDP 实时性高的用UDP,面向消息的协议
,无连接,高效率 UDP不会粘包,会丢失数据,不可靠
接受方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
两种情况下会发生粘包
1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
2.接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下
次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

拆包的发生情况
补充问题:send(字节流)和recv(1024)及sendall
recv里指定的1024意思是从缓存里一次拿出1024个字节的数据send的字节流是先放入己端缓存,
然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失

粘包问题演示
from socket import *
# import time
phone=socket(AF_INET,SOCK_STREAM) #买手机
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8082)) #绑定手机卡
phone.listen(5) #开机
conn,client_addr=phone.accept()


# data1=conn.recv(1024)
# print('data1:',data1)
# data2=conn.recv(1024)
# print('data2:',data2)
data1=conn.recv(10)
print('data1:',data1)
data2=conn.recv(4)
print('data2:',data2)
# data3=conn.recv(1024)
# print('data3:',data3)
服务端

from socket import *
import time
phone=socket(AF_INET,SOCK_STREAM) #买手机
phone.connect(('127.0.0.1',8082)) #绑定手机卡


# phone.send('hello'.encode('utf-8'))
# time.sleep(5)
# phone.send('world'.encode('utf-8'))
phone.send('helloworld'.encode('utf-8'))
time.sleep(5)
phone.send('egon'.encode('utf-8'))
客户端
解决粘包问题
服务端
import subprocess
import struct
import json
from socket import *
server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8086))
# print(server)
server.listen(5)
while True:
    conn,addr=server.accept()
    # print(conn)
    print(addr)
    while True:
        try:
            cmd=conn.recv(8096)
            if not cmd:break #针对linux

            #执行命令
            cmd=cmd.decode('utf-8')
            #调用模块,执行命令,并且收集命令的执行结果,而不是打印
            obj = subprocess.Popen(cmd, shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   )
            stdout=obj.stdout.read()
            stderr=obj.stderr.read()


            # 1:先制作报头,报头里放:数据大小, md5, 文件
            header_dic = {
                'total_size':len(stdout)+len(stderr),
                'md5': 'xxxxxxxxxxxxxxxxxxx',
                'filename': 'xxxxx',
                'xxxxx':'123123'
            }
            header_json = json.dumps(header_dic)
            header_bytes = header_json.encode('utf-8')
            header_size = struct.pack('i', len(header_bytes))

            # 2: 先发报头的长度
            conn.send(header_size)

            # 3:先发报头
            conn.send(header_bytes)

            # 4:再发送真实数据
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:
            break
    conn.close()

server.close()


客户端
import struct
import json
from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8086))

while True:
    cmd=input('>>: ').strip()
    if not cmd:continue
    client.send(cmd.encode('utf-8'))

    # 1:先收报头长度
    obj = client.recv(4)
    header_size = struct.unpack('i', obj)[0]

    # 2:先收报头,解出报头内容
    header_bytes = client.recv(header_size)
    header_json = header_bytes.decode('utf-8')
    header_dic = json.loads(header_json)

    print(header_dic)
    total_size = header_dic['total_size']

    # 3:循环收完整数据
    recv_size=0
    res=b''
    while recv_size < total_size:
        recv_data=client.recv(1024)
        res+=recv_data
        recv_size+=len(recv_data)
    print(res.decode('gbk'))

client.close()

 
 
原文地址:https://www.cnblogs.com/1996-11-01-614lb/p/7418584.html