python-socket套接字_tcp-udp协议_循环收发消息_远程执行命令_黏包

socket 套接字编程

1. socket 的定义

网络基础 => socket

socket 是应用层与TCP/IP协议族通信的中间软件抽象层 , 它是一组接口. 在设计模式中 , socket 其实就是一个门面模式 , 把复杂的TCP/IP协议族隐藏在socket接口后面 , 对用户来说 , 一组简单的接口就是全部 , 让socket去组织数据 , 以符合指定的协议

2. 学习 socket 编程的目的

基于 socket 开发一个 C/S 或者 B/S 结构的软件

Client---------------网络---------------Server

Browser-----------网络---------------Server

3. 基于 socket 编程的方法

网络 : 底层的物理连接介质 + 互联网通信协议

网络存在的意义 : 通信

4. OSI 七层协议 / TCP/IP五层 / TCP/IP四层

1. 应用层 / 1. 应用层 / 1. 应用层

2. 表示层

3. 会话层

4. 传输层 / 2. 传输层 / 2. 传输层

5. 网络层 / 3. 网络层 / 3. 网络层

6. 数据链路层 / 4. 数据链路层 / 4. 网络接口层

7. 物理层 / 5. 物理层

socket 意义: 网络通信过程中 , 信息拼接的工具 (中文:套接字)

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

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

开发中 , 一个端口只对一个程序生效 , 在测试时 , 允许端口重复捆绑 (开发时删掉)

在 bind 方法之前加下面的代码 , 可以让一个端口重复使用

sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

1. TCP 协议

TCP (Transmission Control Protocol) 一种面向连接的 可靠的 传输层通信协议 (流式协议)

优点: 可靠 稳定 传输完整稳定 不限制数据大小

缺点: 慢 效率低 占用系统资源高 一发一收都需要对方确认

应用: Web 浏览器 电子邮件 文件传输 大量数据传输的场景

1. tcp socket 基本语法

套接字工作流程

1. 服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。

2. 在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。

3. 客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束

1. server 服务端
import socket

# 1. 创建一个 socket 对象
sk = socket.socket()
# 2. 绑定对应的 ip 和端口号 (让其他主机在网络中可以找得到)
# 127.0.0.1 代表本地 ip
sk.bind(("127.0.0.1",9000))
# 3. 开启监听
sk.listen()

# 4. 建立三次握手
conn,addr = sk.accept()

# 5. 处理收发数据的逻辑  recv 接收 send 发送
res = conn.recv(1024) # 一次最多接收 1024 字节
print(res.decode("utf-8"))

# 6. 四次挥手
conn.close()

# 7. 退还端口
sk.close()
2. client 客户端
import socket 

# 1. 创建一个 socket 对象
sk = socket.socket()
# 2. 与服务器建立连接
sk.connect(("127.0.0.1",9000))
# 3. 发送数据 (只能发送二进制的字节流)
"""send(字节流)"""
sk.send("北京昨天暴雨,如今不仅得有车,还得有船!".encode("utf-8"))
# 4. 关闭连接
sk.close()

2. tcp socket 循环收发消息

1. server 服务端
import socket

# 1. 创建 socket 对象
sk = socket.socket()
# 2. 绑定 ip 和端口号 (在网络中注册该主机做服务器)
sk.bind(("127.0.0.1",8080))
# 3. 开启监听
sk.listen()

# 4. 三次握手
conn,addr = sk.accept()

# 5. 处理收发数据的逻辑 recv 接收 send 发送
"""send(字节流)"""
"""
conn.send("我去北京先买船".encode("utf-8"))
"""

while True:
	# 4. 三次握手
	conn,addr = sk.accept()
	while True:
		res = conn.recv(1024)
		print(res.decode())
		strvar = input("请输入服务端要给客户端发送的内容")
		conn.send(strvar.encode())
		if strvar.upper() == "Q":
       	 	break

# 6. 四次挥手
conn.close()

# 7. 退还端口
sk.close()
2. client 客户端
import socket

# 1. 创建 socket 对象
sk = socket.socket()
# 2. 连接服务端
sk.connect(("127.0.0.1",8080))

# 3. 收发数据
"""
res = sk.recv(1024) # 一次最多接收1024个字节
print(res.decode())
"""

while True:
    strvar = input("请输入要发送的内容: ")
    sk.send(strvar.encode())
    res = sk.recv(1024)
    if res == b'q' or res == b'Q':
        break
    print(res.decode())

# 4. 关闭连接
sk.close()

2. UDP 协议

UDP (User Datagram Protocol) 一种无连接的 , 不可靠的传输层通信协议

优点: 速度快 , 可以多人同时聊天 , 耗费资源少 , 不需要建立连接

缺点: 不稳定 , 不能保证每次数据都能接收到

应用: IP电话 , 实时视频会议 , 聊天软件 , 少量数据传输的场景

1. udp socket 基本语法

1. server 服务端
import socket

# 1. 创建 udp 对象
sk = socket.socket(type=socket.SOCK_DGRAM)
# 2. 绑定地址和端口号
sk.bind(("127.0.0.1",9000))
# 3. udp 服务器,在一开始只能接收数据
msg,cli_addr = sk.recvfrom(1024)

print(msg.decode())
print(cli_addr)

# 服务端给客户端发送数据
msg = "我是你老娘,赶紧回家吃饭"
sk.sendto(msg.encode(),cli_addr)

# 4. 关闭连接
sk.close()
2. client 客户端
import socket

# 1. 创建 udp 对象
sk = socket.socket(type=socket.SOCK_DGRAM)

# 2. 收发数据的逻辑

# 发送数据
msg = "你好,你是MM还是GG"
# sendto( 消息,(ip,端口号) )
sk.sendto(msg.encode("utf-8"),("127.0.0.1",9000))

# 接收数据
msg,server_addr = sk.recvfrom(1024)
print(msg.decode())
print(server_addr)

# 3. 关闭连接
sk.close()

2. udp socket 循环收发消息

1. server 服务端
import socket

# 1. 创建 udp 对象
sk = socket.socket(type=socket.SOCK_DGRAM)
# 2. 绑定地址和端口号
sk.bind(("127.0.0.1",9000))
# 3. udp 服务器,在一开始只能接收数据
while True:
    # 接收消息
    msg,cli_addr = sk.recvfrom(1024)
    print(msg.decode())
    message = input("服务端给客户端发送的消息是: ")
    # 发送数据
    sk.sendto(message.encode("utf-8") , cli_addr)
	

# 4. 关闭连接
sk.close()
2. client 客户端
import socket

# 1. 创建 udp 对象
sk = socket.socket(type=socket.SOCK_DGRAM)

# 2. 收发数据的逻辑
while True:
    # 发送数据
    message = input("客户端给服务端发送的消息是: ")
    sk.sendto(message.encode() , ("127.0.0.1",9000))
    
    # 接收数据
    msg,addr = sk.recvfrom(1024)
    print(msg.decode("utf-8"))

# 3. 关闭连接
sk.close()

3. 黏包

1. 黏包介绍

1. tcp 协议在发送数据时 , 会出现黏包现象

1. 数据黏包是因为在客户端/服务端都会有一个数据缓冲区 , 缓冲区用来临时保存数据

为了保证能够完整的接收到数据 , 因此缓冲区都会设置的比较大

2. 在收发数据频繁是 , 由于tcp 传输消息的无边界 , 不清楚应该截取多少长度

导致客户端/服务端 , 都有肯把多条数据当成是一条数据进行截取 , 造成黏包

2. 黏包出现的两种情况

现象一: 在发送端 , 由于两个数据短 , 发送的时间间隔较短 , 所以在发送端形成黏包

现象二: 在接收端 , 由于两个数据几乎同时被发送到对方的缓存中 , 所以在接收端形成黏包

​ **总结: **

发送端 包之间时间间隔短 或者 接收端 接收不及时 , 就会黏包

核心是因为 tcp 对数据无边界截取 , 不会按照发送的顺序判断

3. 黏包对比: tcp和udp

tcp 协议

缺点: 接收时数据之间无边界 , 有可能黏合几条数据成一条数据 , 造成黏包

优点: 不限制数据包的大小 , 稳定传输不丢包

udp 协议

优点: 接收时数据之间有边界 , 传输速度快 , 不黏包

缺点: 限制数据包的大小 (受带宽路由器等因素影响) , 传输不稳定 , 可能丢包

tcp和udp对于数据包来说都可以进行拆包和解包 , 理论上来讲 , 无论多大都能分次发送

但tcp一旦发送失败 , 对方无响应(对方无回执) , tcp可以选择再发 , 直到对应响应完毕为止

而udp一旦发送失败 , 是不会询问对方是否有响应的 , 如果数据量过大 , 易丢包

4. 解决黏包问题

自定义应用层协议

解决黏包场景

应用场景在实时通讯时 , 需要阅读此次发的消息是什么

不需要解决黏包场景

下载或者上传文件的时候 , 最后要把包都结合在一起 , 黏包无所谓

5. 模块 socketserver

网络协议的最底层就是 socket , 基于原有 socket 模块 , 又封装了一层 , 就是 socketserver

socketserver 为了实现 tcp 协议 , server端的并发

2. 黏包案例

1.1 server 服务端
import socket

sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(("127.0.0.1",9000))
sk.listen()

conn,addr = sk.accept()

# 处理收发数据的逻辑
# 先发送接下来要发送数据的大小
conn.send("5".encode())
# 发完长度之后,再发数据
conn.send("hello".encode())
conn.send(",world".encode())

conn.close()
sk.close()
1.2 client 客户端
import socket

sk = socket.socket()
sk.connect(("127.0.0.1",9000))

# 处理收发数据的逻辑
# 先接收接下来要发送数据的大小
res = sk.recv(1)
num = int(res.decode())
# 接收 num 这么多个字节数
res1 = sk.recv(num)
res2 = sk.recv(1024)
print(res1)
print(res2)

sk.close()
2.1 server 服务端
import socket

sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(("127.0.0.1",9000))
sk.listen()

conn,addr = sk.accept()

# 处理收发数据的逻辑
# 先发送接下来要发送数据的大小
conn.send("00000100".encode())
# 发完长度之后,再发数据
msg = "hello" * 20
conn.send(msg.encode())
conn.send(",world".encode())

conn.close()
sk.close()
2.2 client 客户端
import socket

sk = socket.socket()
sk.connect(("127.0.0.1",9000))

# 处理收发数据的逻辑
# 先接收接下来要发送数据的大小
res = sk.recv(8)
num = int(res.decode())
# 接收 num 这么多个字节数
res1 = sk.recv(num)
res2 = sk.recv(1024)
print(res1)
print(res2)

sk.close()

3. struct 模块

pack: 把任意长度数字转化成具有固定4个字节长度的字节流

unpack: 把4个字节值恢复成原来的数字 , 返回的最终是元组

import struct

# pack
# i => int 要转化的当前数据是整型
res = struct.pack("i",999999999)
res = struct.pack("i",1)
res = struct.pack("i",4399999)
# pack 的范围 -2147483648 ~ 2147483647 21个亿左右
res = struct.pack("i",2100000000)
print(res , len(res))

# unpack
# i => 把对应的数据转换成 int 整型
tup = struct.unpack("i",res)
print(tup) # (2100000000,)
print(tup[0])
1.1 server 服务端
import socket
import struct

sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(("127.0.0.1",9000))
sk.listen()

conn,addr = sk.accept()

# 处理收发数据的逻辑
strvar = input("请输入你要发送的数据")
msg = strvar.encode()
length = len(msg)
res = struct.pack("i",length)

# 第一次发送的是字节长度
conn.send(res)

# 第二次发送真实的数据
conn.send(msg)

# 第三次发送真实的数据
conn.send("世界真美好".encode())

conn.close()
sk.close()
1.2 client 客户端
import socket
import struct

sk = socket.socket()
sk.connect(("127.0.0.1",9000))

# 处理收发数据的逻辑

# 第一次接收的是字节长度
n = sk.recv(4)
tup = struct.unpack("i",n)
n = tup[0]

# 第二次接收真实的数据
res = sk.recv(n)
print(res.decode())

# 第三次接收真实的数据
res = sk.recv(1024)
print(res.decode())

sk.close()

练习

服务端套接字函数
s.bind()    绑定(主机,端口号)到套接字
s.listen()  开始TCP监听
s.accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来

客户端套接字函数
s.connect()     主动初始化TCP服务器连接
s.connect_ex()  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()        创建一个与该套接字相关的文件

1. tcp 套接字通信

1.1 server 服务端
import socket

# 1. 买手机
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # SOCK_STREAM => TCP协议

# 2. 插手机卡
phone.bind(("127.0.0.1",8080)) # 本地回环

# 3. 开机
phone.listen(5)
print("starting %s:%s" % ("127.0.0.1",8080))

# 4. 等电话连接
conn,client_addr = phone.accept()

# 5. 收/发消息
data = conn.recv(1024) # 最大接收的字节个数
print("收到的客户端数据: ",data.decode('utf-8'))
conn.send(data.upper())

# 6. 关闭
conn.close() 
phone.close()
1.2 client 客户端
import socket

# 1. 买手机
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # SOCK_STREAM => TCP协议

# 2. 拨电话
phone.connect(("127.0.0.1",8080))

# 3. 发收消息
phone.send("hello".encode("utf-8"))
data = phone.recv(1024)
print("服务端返回的数据: ",data.decode("utf-8"))

# 4. 关闭
phone.close()
2.1 server 服务端
# 加上循环
"""
服务端应该满足的特性:
	1. 一直对外提供服务
	2. 并发的提供服务
"""
import socket

# 1. 买手机
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # SOCK_STREAM => TCP协议

# 2. 插手机卡
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

phone.bind(("127.0.0.1",8080)) # 本地回环

# 3. 开机
phone.listen(5)
print("starting %s:%s" % ("127.0.0.1",8080))

# 4. 等电话连接 => 连接循环
while True:
    conn,client_addr = phone.accept()
    print(client_addr)
    # 5. 收/发消息 => 通信循环
    while True:
        try:
            data = conn.recv(1024) # 最大接收的字节个数 1024
            if len(data) == 0: # 针对linux系统
                break
            print("收到的客户端数据: ",data.decode("utf-8"))
            conn.send(data.upper())
        except Exception: # 针对 windows 系统
            break
            
    # 6. 关闭
    conn.close()
phone.close()    
2.2 client 客户端
import socket

# 1. 买手机
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # SOCK_STREAM => TCP协议

# 2. 拨电话
phone.connect(("127.0.0.1",8080))

# 3. 发/收消息 => 通信循环
while True:
    msg = input(">>: ").strip()
    phone.send(msg.encode("utf-8"))
    data = phone.recv(1024)
    print("服务端返回的数据: ",data.decode("utf-8"))
    
# 4. 关闭
phone.close()
3.1 server 服务端
# 远程执行命令

import socket
import subprocess

# 1. 买手机
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

# 2. 插手机卡
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

phone.bind(("127.0.0.1",8080))

# 3. 开机
phone.listen(5)
print("starting %s:%s" % ("127.0.0.1",8080))

# 4. 等电话连接 => 连接循环
while True:
    conn,client_addr = phone.accept()
    print(client_addr)
    # 5. 收/发数据 => 通信循环
    while True:
        try:
            cmd = conn.recv(1024)
            if len(cmd) == 0:
                break
                
            obj = subprocess.Popen(cmd.decode('utf-8'),
                                  shell = True,
                                  stdout = subprocess.PIPE,
                                  stderr = subprocess.PIPE 
                                  )    
            res = obj.stdout.read() + obj.stderr.read()
            print(res)
            conn.send(res)
        except Exception:
            break
    
    # 6. 关闭
    conn.close()
phone.close()      
3.2 client 客户端
import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.connect(("127.0.0.1",8080))

while True:
    cmd = input("[root@localhost]# ").strip()
    phone.send(cmd.encode('utf-8'))
    data = phone.recv(1024)
    print(data.decode('gbk'))
    
phone.close()    
4.1 server 服务端
import subprocess
import struct
from socket import *

server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
    conn,client_addr = server.accept()
    print(conn)
    print(client_addr)
    
    while True:
        try:
            cmd = conn.recv(1024)
            obj = subprocess.Popen(cmd.decode('utf-8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE
                                  )
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            total_size = len(stdout) + len(stderr)
            
            # 先发送数据的长度
            conn.send(struct.pack('i',total_size))
            # 再发送真正的数据
            conn.send(stdout)
            conn.send(stderr)
        except Exception:
            break
    conn.close()
    
server.close() 
4.2 client 客户端
import struct
from socket import *

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

while True:
    cmd = input(">>: ").strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode('utf-8'))
    
    # 先收数据的长度
    n = 0
    header = b''
    while n < 4:
        data = client.recv(1)
        header += data
        n += len(data)
        
    total_size = struct.unpack('i',header)[0]
    
    # 收真正的数据
    recv_size = 0
    res = b''
    while recv_size < total_size:
        data = client.recv(1024)
        res += data
        recv_size += len(data)
        
    print(res.decode('gbk'))
    
client.close()    
5.1 server 服务端
# 定制复杂的报头 
# 版本一
import subprocess
import os
import struct
from socket import *

server = socket(AF_INET,SOCK_STREAM)
server.bind(("127.0.0.1",8080))
server.listen(5)
while True:
    conn,client_addr = server.accept()
    print(conn)
    print(client_addr)
    
    while True:
        try:
            msg = conn.recv(1024).decode('utf-8')
            cmd,file_path = msg.split()
            if cmd == "get":
                # 先发送报头
                total_size = os.path.getsize(file_path)
                conn.send(struct.pack('q',total_size))
                # 再发送文件
                with open(r'%s' %file_path,mode='rb') as f:
                    for line in f:
                        conn.send(line)
        except Exception:
            break
    conn.close()

server.close()    
5.2 client 客户端
import struct
from socket import *

client = socket(AF_INET,SOCK_STREAM)
client.connect(("127.0.0.1",8080))

while True:
    cmd = input('>>: ').strip()  # get 文件路径
    if len(cmd) == 0:
        continue
    client.send(cmd.encode('utf-8'))  
    
    # 先收数据的长度
    n = 0
    header = b''
    while n < 8:
        data = client.recv(1)
        header += data
        n += len(data)
        
    total_size = struct.unpack('q',header)[0]
    print(total_size)
    # 收真正的数据
    recv_size = 0
    with open('aaa.jpg',mode='wb') as f:
        while recv_size < total_size:
            data = client.recv(1024)
            f.write(data)
            recv_size += len(data)
            
client.close()            
6.1 server 服务端
# 定制复杂的报头 
# 版本二
import os 
import json
import struct
import subprocess
from socket import *

server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn,client_addr = server.accept()
    print(conn)
    print(client_addr)
    
    while True:
        try:
            msg = conn.recv(1024).decode('utf-8')
            cmd,file_path = msg.split()
            if cmd == 'get':
                # 一. 制作报头
                header_dic = {
                    "total_size":os.path.getsize(file_path)
                    "file_name":os.path.basename(file_path)
                    "md5":"4681351846813151879441351"
                }
                header_json = json.dumps(header_dic)
                header_json_bytes = header_json.encode('utf-8')
                
                # 二. 发送数据
                # 1. 先发送报头的长度
                header_size = len(header_json_bytes)
                conn.send(struct.pack('i',header_size))
                # 2. 再发送报头
                conn.send(header_json_bytes)
                # 3. 最后发送真实的数据
                with open(r'%s' %file_path,mode='rb') as f:
                    for line in f:
                        conn.send(line)
        except Exception:
            break
    conn.close()
    
server.close()    
6.2 client 客户端
import json
import struct
from socket import *

client = socket(AF_INET,SOCK_STREAM)
client.connect(("127.0.0.1",8080))

while True:
    cmd = input(">>: ").strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode('utf-8'))
    
    # 1. 先接收报头的长度
    res = client.recv(4)
    header_size = struct.unpack('i',res)[0]
    # 2. 再接收报头
    header_json_bytes = client.recv(header_size)
    header_json = header_json_bytes.decode('utf-8')
    header_dic = json.loads(header_json)
    print(header_dic)
    # 3. 最后接收真实的数据
    total_size = header_dic['total_size']
    file_name = header_dic['file_name']
    recv_size = 0
    with open(r'%s' %file_name,mode='wb') as f:
        while recv_size < total_size:
            data = client.recv(1024)
            f.write(data)
            recv_size += len(data)
            
client.close()            

你可能感兴趣的:(python,互联网通信)