Python学习笔记--网络通信--socket

1.socket里面的,AF_INET和AF_UNIX有什么区别?

  • AF_INET用于真实的两台机器进行通信。
  • AF_UNIX用于本地自己跟自己通信。

 

 2.socket 通信示例?

  • 服务端:
  • 代码:
import socket

# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # AF_INET和AF_UNIX区别,AF_INET远程交互,AF_UNIX本地交互
    # SOCK_STREAM 流式协议 = TCP ; SOCK_DGRAM 数据报协议 = UDP

print(phone)   # <socket.socket fd=436, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0>

# 2.绑定电话卡
phone.bind(("127.0.0.1",8080))  # 绑定ip和端口 # 为了让客户端访问,这里一般固定后就不变了.

# 3.开机
phone.listen(5) # __backlog = 5 半连接池数量为5

# 4.接受链接信息
conn,client_addr = phone.accept()   # 返回两个值. conn是链接通路,是三次握手的成果;client_addr是客户端的地址.
print(conn)
#<socket.socket fd=356, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0,
# laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 1873)>
print(client_addr)
# ('127.0.0.1', 1873)

# 5.收发消息
data = conn.recv(1024) # 最大接收字节数 1024 ; 注意 这里的data是byte类型
print(data) # b'hello'
conn.send(data.upper()) # 回消息 ,这里为了演示,把data的大写返回去。

# 6.挂电话
conn.close()

# 7.关机
phone.close()
  • 代码:
import socket

# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # AF_INET和AF_UNIX区别,AF_INET远程交互,AF_UNIX本地交互
    # SOCK_STREAM 流式协议 = TCP ; SOCK_DGRAM 数据报协议 = UDP

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


# 3.发,收数据
phone.send("hello".encode("utf-8"))
# phone.send(b"hello") # 这样也可以

data = phone.recv(1024) # 接收数据 同样设置为1024
print(data.decode("utf-8")) # 返回的也是byte类型,需要解码


# 4.关机
phone.close()

 3.socket通信,升级--加上循环。

  • 服务端:
import socket

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

# 2.绑定电话卡
phone.bind(("127.0.0.1",8080))

# 3.开机
phone.listen(5)

# 4.接受链接信息
conn,client_addr = phone.accept()   # 返回两个值. conn是链接通路,是三次握手的成果;client_addr是客户端的地址.

# 5.收发消息
while True:
    data = conn.recv(1024) # 最大接收字节数 1024 ; 注意 这里的data是byte类型
    print(data) # b'hello'
    conn.send(data.upper()) # 回消息 ,这里为了演示,把data的大写返回去。

# 6.挂电话
conn.close()

# 7.关机
phone.close()
  • 客户端:
import socket

# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # AF_INET和AF_UNIX区别,AF_INET远程交互,AF_UNIX本地交互
    # SOCK_STREAM 流式协议 = TCP ; SOCK_DGRAM 数据报协议 = UDP

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


# 3.发,收数据
# 建立循环
while True:
    msg = input("请输入信息:")
    phone.send(msg.encode("utf-8"))

    data = phone.recv(1024) # 接收数据 同样设置为1024
    print(data.decode("utf-8")) # 返回的也是byte类型,需要解码


# 4.关机
phone.close()

4.socket通信--升级2--解决客户端发空报错。

  • 服务端代码不动:
import socket

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

# 2.绑定电话卡
phone.bind(("127.0.0.1",8080))

# 3.开机
phone.listen(5)

# 4.接受链接信息
conn,client_addr = phone.accept()   # 返回两个值. conn是链接通路,是三次握手的成果;client_addr是客户端的地址.

# 5.收发消息
while True:
    data = conn.recv(1024) # 最大接收字节数 1024 ; 注意 这里的data是byte类型
    print(data) # b'hello'
    conn.send(data.upper()) # 回消息 ,这里为了演示,把data的大写返回去。

# 6.挂电话
conn.close()

# 7.关机
phone.close()
import socket

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

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

# 3.发,收数据
# 建立循环
while True:
    msg = input("请输入信息:")
    if len(msg) == 0:
        continue  # 跳过本次
    phone.send(msg.encode("utf-8"))
    # print("1111111111") 调试1
    data = phone.recv(1024)  # 接收数据  # 发空消息,会阻塞这里
    # print("222222222") 调试2
    print(data.decode("utf-8"))

# 4.关机
phone.close()

5.服务端,应该具备的能力是?

  • 一直提供服务
  • 并发的能力

 

6.socket通信--升级3--解决服务端一直接受“空”问题。

  • 服务端:
  • 增加长度判断
  • 代码:
import socket

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

# 2.绑定电话卡
phone.bind(("127.0.0.1",8080))

# 3.开机
phone.listen(5)

# 4.接受链接信息
conn,client_addr = phone.accept()   # 返回两个值. conn是链接通路,是三次握手的成果;client_addr是客户端的地址.

# 5.收发消息
while True:
    try:    # win 其他版本, 会报错
        data = conn.recv(1024)
        if len(data) == 0:  # linux win11 会收到空 b''
            break
        print(data) # b'hello'
        conn.send(data.upper())
    except Exception:
        break

# 6.挂电话
conn.close()

# 7.关机
phone.close()

 

 

 

  • 客户端:

import socket

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

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

# 3.发,收数据
# 建立循环
while True:
    msg = input("请输入信息:")
    if len(msg) == 0:
        continue  # 跳过本次
    phone.send(msg.encode("utf-8"))
    # print("1111111111") 调试1
    data = phone.recv(1024)  # 接收数据  # 发空消息,会阻塞这里
    # print("222222222") 调试2
    print(data.decode("utf-8"))

# 4.关机
phone.close()

7.socket通信--升级4--解决服务端只能提供一次服务。

  • 想让服务端一直提供服务。
  • 服务端:
  • 增加循环。
import socket

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

# 2.绑定电话卡
phone.bind(("127.0.0.1", 8080))

# 3.开机
phone.listen(5)

# 4.接受链接信息
# 循环执行
while True:
    conn, client_addr = phone.accept()

    # 5.收发消息
    while True:
        try:  # win 其他版本, 会报错
            data = conn.recv(1024)
            if len(data) == 0:  # linux win11 会收到空 b''
                break
            print(data)  # b'hello'
            conn.send(data.upper())
        except Exception:
            break

    # 6.挂电话
    conn.close()

# 7.关机
phone.close()
  • 客户端:
  • 代码:
import socket

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

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

# 3.发,收数据
# 建立循环
while True:
    msg = input("请输入信息:")
    if len(msg) == 0:
        continue  # 跳过本次
    phone.send(msg.encode("utf-8"))
    # print("1111111111") 调试1
    data = phone.recv(1024)  # 接收数据  # 发空消息,会阻塞这里
    # print("222222222") 调试2
    print(data.decode("utf-8"))

# 4.关机
phone.close()

8.如何写一个执行远程命令的C/S程序?

  • 服务端--调用subprocess来执行
import socket
import subprocess
# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2.绑定电话卡
phone.bind(("127.0.0.1", 8080))

# 3.开机
phone.listen(5)

# 4.接受链接信息
# 循环执行
while True:
    conn, client_addr = phone.accept()

    # 5.收发消息
    while True:
        try:  # win 其他版本, 会报错
            cmd = conn.recv(1024)
            if len(cmd) == 0:  # linux win11 会收到空 b''
                break
            cmd = cmd.decode("utf-8")   # 先解码
            # 再执行
            obj = subprocess.Popen(cmd,shell=True,
                                   stderr=subprocess.PIPE,
                                   stdout=subprocess.PIPE
                                   )
            res1 = obj.stdout.read()
            res2 = obj.stderr.read()
            conn.send(res1+res2)    # 把两个值都返回去
        except Exception:
            break

    # 6.挂电话
    conn.close()

# 7.关机
phone.close()
  • 客户端--注意解码方式(win用gbk,linux用utf-8)
import socket

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

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

# 3.发,收数据
# 建立循环
while True:
    msg = input("请输入信息:")
    if len(msg) == 0:
        continue  # 跳过本次
    phone.send(msg.encode("utf-8"))
    data = phone.recv(1024)  # 接收数据  # 发空消息,会阻塞这里
    print(data.decode("gbk"))   # 在win解码用gbk

# 4.关机
phone.close()

# !!!注意,这里输入tasklist会出现不全。输入dir可以展示
  • 问题1,是输入tasklist会出现显示不全的现象。
  • 问题2,是输入tasklist后再输入dir会发现显示的上次剩余的内容。(这可以帮助理解【流协议】)
  • 我的解决:可以尝试recv(1024) -->recv(更大的数字)

 

 9.梳理--服务端

  • ①实例化服务
  • ②绑定ip+port
  • ③建立监听池
  • ④连接循环(接收连接)(类比,饭店迎宾)
  • ⑤通信循环(收发数据) (类比,服务员)
  • ⑥通信结束+连接结束。
  • ⑦补充通信连接的异常。

  • 示例代码(演示):
from socket import *

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

while True: # 链接循环
    conn, clint_addr = server.accept()
    print("客户端的地址")

while True: # 通信循环
    data = conn.recv(1024)  # 收
    conn.send(回复的数据)

# 关闭通信
conn.close()

# 整体关机
server.close()
  • 运行代码:
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()

    # 通信循环
    while True:
        try:
            data = conn.recv(1024)  # 收
            conn.send(数据)  # 发
        except Exception:
            break
    # 结束通信
    conn.close()

# 结束链接
server.close()
# 补充异常(为了简洁省略了win7 win10 的data为0的判断)

10.梳理--客户端

  • ①实例化客户端
  • ②连接远程ip+port
  • ③通信循环
  • ④补充异常输入为空的判断

 

  • 代码:
from socket import *

# 实例化客户端
client = socket(AF_INET, SOCK_STREAM)
# 连接远程的ip+port
client.connect(('127.0.0.1', 8080))
# 通信循环
while True:
    cmd = input('>>>').strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode('utf-8'))
    data = client.recv(1024)
    print(data.decode('utf-8'))

# 补充异常输入为空的判断: 在10,11行体现

11.梳理--服务器--增加执行命令功能

  • ①导入subprocess模块的Popen,PIPE
  • ②实例化Popen,shell打开,管道打开
  • ③从返回值里读取结果。

  • 代码

 

from socket import *
from subprocess import PIPE, Popen # *====增加执行命令的功能======*

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

# 链接循环(接收)
while True:
    conn, client_addr = server.accept()

    # 通信循环
    while True:
        try:
            data = conn.recv(1024)  # 收
            # *====增加执行命令的功能======*
            cmd = data.decode("utf-8")
            obj = Popen(cmd,
                        shell=True,
                        stdout=PIPE,
                        stderr=PIPE)
            res_out = obj.stdout.read()  # 读出管道的输出
            res_err = obj.stderr.read() # 读出管道的错误输出
            # 因为这里res_out是byte类型,所以可以直接发送
            # *==========================*
            conn.send(res_out+res_err)  # 发 byte类型
        except Exception:
            break
    # 结束通信
    conn.close()

# 结束链接
server.close()
# 补充异常(为了简洁省略了win7 win10 的为0的判断)
  • 客户端补充一行解码格式为gbk
from socket import *

# 实例化客户端
client = socket(AF_INET, SOCK_STREAM)
# 连接远程的ip+port
client.connect(('127.0.0.1', 8080))
# 通信循环
while True:
    cmd = input('>>>').strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode('utf-8'))
    data = client.recv(1024)
    print(data.decode('gbk')) # *===执行命令的机器是win,改编码为gbk======*

# 补充异常输入为空的判断: 在10,11行体现

 12.解决粘包--基础

  • 服务端:
from socket import *
from subprocess import PIPE, Popen  # *====增加执行命令的功能======*
import struct  # 导入把数据包变为固定长度的模块

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

# 链接循环(接收)
while True:
    conn, client_addr = server.accept()
    print("对方的ip地址为:", client_addr)

    # 通信循环
    while True:
        try:
            data = conn.recv(1024)  # 收
            if len(data) == 0:
                continue
            # *====增加执行命令的功能======*
            cmd = data.decode("utf-8")
            obj = Popen(cmd,
                        shell=True,
                        stdout=PIPE,
                        stderr=PIPE)
            res_out = obj.stdout.read()  # 读出管道的输出
            res_err = obj.stderr.read()  # 读出管道的错误输出
            # 因为这里res_out是byte类型,所以可以直接发送
            # *==========================*
            res_size = len(res_out) + len(res_err)
            print("长度为:", res_size)
            # ①先发数据头,包含数据长度
            header = struct.pack("i", res_size)
            conn.send(header)
            # ②再发数据
            conn.send(res_out)  # 发 byte类型
            conn.send(res_err)

        except Exception:
            break
    # 结束通信
    conn.close()

# 结束链接
server.close()
# 补充异常(为了简洁省略了win7 win10 的为0的判断)
  • 客户端:
from socket import *
import struct
# 实例化客户端
client = socket(AF_INET, SOCK_STREAM)
# 连接远程的ip+port
client.connect(('127.0.0.1', 8080))
# 通信循环
while True:
    cmd = input('>>>').strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode('utf-8'))
    # *===增加数据头的接收======*
    header = client.recv(4)
    total_size = struct.unpack("i",header)[0]
    print("客户端接收到的header为:",total_size) #(437,),这里要取[0]
    print("="*50)

    # *===增加数据长度的判断======*
    # total_size = 437  # 假设总大小为total_size
    recv_size = 0
    res = b''
    while recv_size < total_size:
        data = client.recv(1024)
        recv_size += len(data)
        res += data # 累加到res
    res = res.decode("gbk")   # 再统一解码gbk
    print(res)
    # *=======================*


# 补充异常输入为空的判断: 在10,11行体现

 13.解决粘包--升级(终极版)

  • 服务端:
from socket import *
from subprocess import PIPE, Popen  # *====增加执行命令的功能======*
import struct  # 导入把数据包变为固定长度的模块
import json

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))  # 根据需要修改ip
server.listen(5)

# 链接循环(接收)
while True:
    conn, client_addr = server.accept()
    print("对方的ip地址为:", client_addr)

    # 通信循环
    while True:
        try:
            data = conn.recv(1024)  # 收
            if len(data) == 0:
                continue
            # *====增加执行命令的功能======*
            cmd = data.decode("utf-8")
            obj = Popen(cmd,
                        shell=True,
                        stdout=PIPE,
                        stderr=PIPE)
            res_out = obj.stdout.read()  # 读出管道的输出
            res_err = obj.stderr.read()  # 读出管道的错误输出
            # print(res_out.decode("gbk"))
            # *==========================*
            # 计算总长度
            total_size = len(res_out) + len(res_err)
            # ①先构建header_dic
            header_dic = {
                "filename": "a.txt",
                "total_size": total_size,
                "md5": "123456789"
            }
            header_json = json.dumps(header_dic)  # 转换为json
            header_byte = header_json.encode("utf-8")   # 转换为byte
            header_len = len(header_byte)   # 求出长度
            header_pack = struct.pack("i", header_len)  # 压成包,发送
            # ②先发4字节
            conn.send(header_pack)
            # ③再发送header_byte
            conn.send(header_byte)
            # ④最后发送真正的数据
            conn.send(res_out)
            conn.send(res_err)

        except Exception:
            break
    # 结束通信
    conn.close()

# 结束链接
server.close()
# 补充异常(为了简洁省略了win7 win10 的为0的判断)
  • 客户端
import json
from socket import *
import struct
# 实例化客户端
client = socket(AF_INET, SOCK_STREAM)
# 连接远程的ip+port
client.connect(('127.0.0.1', 8080))# 这里根据需要修改
# 通信循环
while True:
    cmd = input('>>>').strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode('utf-8'))
    # *===增加数据头的接收======*
    # ①接收对面的4字节
    header = client.recv(4)
    # ②解包
    header_size = struct.unpack("i",header)[0]  #
    # ③接收header_byte
    header_byte = client.recv(header_size)
    # ④解码为json
    header_json = header_byte.decode("utf-8")
    # ⑤转换为python的字典
    header_dic = json.loads(header_json)
    # ⑥拿到字典里的total_size
    total_size = header_dic["total_size"]

    # ⑦循环拿取数据
    recv_size = 0
    res = b''
    while recv_size < total_size:
        data = client.recv(1024)
        recv_size += len(data)
        res += data # 累加到res
    res = res.decode("gbk")   # 再统一解码gbk,linux就用utf-8
    print(res)
    # *=======================*

 

 

 14.后台运行?

  • python3 xxx.py &

15.查看端口状态

  • netstat - an | grep 8080

 

16.什么是粘包问题? 

  • 主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
  • 简单来讲就是【界限不清楚】

 

 17.为何tcp是可靠传输,udp是不可靠传输?

  • tcp会发送一个ack=1来确认消息。而udp不会。
  • 所以,基于这一点,我们认为tcp是可靠的。
  • 也就是说,这里的可靠与不可靠的界限是,是否有确认消息的过程。

 

 18.什么是并发,什么是并行?

  • 4个服务员,并行数最大是4个,并发N个

 

19.什么时候用到struct模块?

  • 数据需要压缩为定长的时候,用到struct模块
  • 代码:
import struct

# 压缩数据
res = struct.pack("i",1234567)
print(res)  # b'\x87\xd6\x12\x00'

# 解压数据
data = struct.unpack("i",res)
print(data) # (1234567,)

 

 

 20.UDP实现方式?示例代码。

  • 服务端:
from socket import *

server = socket(AF_INET,SOCK_DGRAM)
server.bind(('127.0.0.1',9999))

# 通信
while True:
    res = server.recvfrom(1024)
    print(res)  # 结果是  (b'123', ('127.0.0.1', 62180))
  • 客户端
from socket import *

client = socket(AF_INET,SOCK_DGRAM)


# 通信
while True:
    msg  = input(">>>") # 输入 123 测试一下
    client.sendto(msg.encode("utf-8"),('127.0.0.1',9999))

 

21.UDP协议--如何实现收发消息?

  • 服务端
from socket import *

server = socket(AF_INET,SOCK_DGRAM)
server.bind(('127.0.0.1',9999))

# 通信
while True:
    res,addr = server.recvfrom(1024)
    print("接受到的消息为:",res)
    # 回消息
    server.sendto(res.upper(),addr)
  • 客户端
from socket import *

client = socket(AF_INET, SOCK_DGRAM)

# 通信
while True:
    msg = input(">>>")  # 输入 123 测试一下
    client.sendto(msg.encode("utf-8"), ('127.0.0.1', 9999))
    res, addr = client.recvfrom(1024)
    print("客户端收到消息:", res.decode("utf-8"))

22.UDP需要处理空数据吗?

  • 不需要。
  • UDP传输数据,是以数据报的形式传递。
  • 数据报的意思是,每次传输数据都会带上自己的IP信息。
  • 也就是说,每次数据内容可以是空,但数据头永远有信息。
  • 所以,也就是说,不用特殊处理空数据的情况。

 

 

23. socektserver模块实现并发?

  • 服务端--改造
import socketserver  # 导入socketserver


# 第一步:处理通信循环
class MyRequestHandler(socketserver.BaseRequestHandler):
    def handle(self) -> None:
        # 下面代码借用原来的,把conn改成self.request
        # socketserver把conn封装了成了对象
        while True:
            try:
                data = self.request.recv(1024)  # 改
                if len(data) == 0:
                    break
                print(data)
                self.request.send(data.upper())  # 改
            except Exception:
                break
        self.request.close()  # 改


# 第二步:处理连接循环
# IO密集型,推荐用线程(后续会讲为什么)
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyRequestHandler, bind_and_activate=True)
# bind_and_activate 意思是绑定并监听
server.serve_forever()  # 一直提供服务
  • 客户端--不用动
import socket

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

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

# 3.发,收数据
# 建立循环
while True:
    msg = input("请输入信息:")
    if len(msg) == 0:
        continue  # 跳过本次
    phone.send(msg.encode("utf-8"))
    data = phone.recv(1024)  # 接收数据  # 发空消息,会阻塞这里
    print(data.decode("utf-8"))

# 4.关机
phone.close()
  • 效果: