(1)基于TCP协议的简单套接字(打电话模型)

发布时间 2023-06-23 17:23:40作者: Chimengmeng

基于TCP协议的简单套接字(打电话模型)

【一】简单版1.0

服务端

# -*-coding: Utf-8 -*-
# @File : 服务端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/20
import socket

# 【1】.买手机
# socket.SOCK_STREAM :流式协议 ----> TCP协议 ----> 所有数据是一个整体
# socket.SOCK_DGRAM : 报协议  ----> 每一次数据都是单独一部分
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 【2】.绑定手机卡 ---> 192.168.1.50(本机IP地址)
# 0.0.0.0(任意IP地址) ---> 关联公网IP才会起作用  ----> 服务器用
# 127.0.0.1(本机固定IP地址) ---> 只有本机才能访问这个地址(测试用)
# 端口号:0-65535, 1024以前的都被系统保留使用
# phone.bind(('ip', port))
phone.bind(('127.0.0.1', 8080))

# 【3】.开机 -- 监听状态
# 5 :指的是半连接池的大小
phone.listen(5)
print(f'服务器启动,开始监听ip:>>>{"127.0.0.1"},port:>>>{8080}')
# (1)服务器启动,开始监听ip:>>>127.0.0.1,port:>>>8080

# 【4】.等待电话链接请求:拿到电话链接 conn
# 返回的是双向通路 --- 操作系统维持链接
# conn : 双向通路的链接
# client_addr : 客户端的iP和端口
conn, client_addr = phone.accept()
print('这是服务端的conn:>>>', conn)
# (3)这是服务端的conn:>>> <socket.socket fd=352, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 5249)>
print('这是服务端的client_addr:>>>', client_addr)
# (4)这是服务端的client_addr:>>> ('127.0.0.1', 5249)

# 【5】.接收消息
# 1024 :最大接受的数据量为1024 bytes类型,收到的是bytes类型
data = conn.recv(1024)
# 对接受的二进制数据进行解码
print('从客户端接受的数据:>>>>', data.decode('utf-8'))
# (5)从客户端接受的数据:>>>> is running for 发送信息
# 发消息 返回消息状态等信息
conn.send(data.upper())

# 【6】.关闭连接(必选的回收资源操作) conn
# 完成后断开连接
conn.close()

# 【7】.关机(可选操作)
phone.close()

客户端

# -*-coding: Utf-8 -*-
# @File : 客户端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/20
import socket

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

# 【2】.拨通服务端电话
# connect(('服务端ip', 服务端端口))
phone.connect(('127.0.0.1', 8080))
# (2)
# 【3】.通信
# send(二进制数据类型)
phone.send('is running for 发送信息'.encode('utf8'))

# 接受服务端返回的数据
data = phone.recv(1024)
# 打印返回的消息 解码
print(data.decode('utf8'))
# (6)IS RUNNING FOR 发送信息

# 【4】.关闭连接(必选的回收资源操作)
phone.close()

上面的问题就是信息只能传输一次

【二】升级版2.0

加上通信循环

【1】版本2.1 - 通信循环结束条件

服务端

# -*-coding: Utf-8 -*-
# @File : 服务端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/20
import socket

# 【1】.买手机
# socket.SOCK_STREAM :流式协议 ----> TCP协议 ----> 所有数据是一个整体
# socket.SOCK_DGRAM : 报协议  ----> 每一次数据都是单独一部分
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 【2】.绑定手机卡 ---> 192.168.1.50(本机IP地址)
# 0.0.0.0(任意IP地址) ---> 关联公网IP才会起作用  ----> 服务器用
# 127.0.0.1(本机固定IP地址) ---> 只有本机才能访问这个地址(测试用)
# 端口号:0-65535, 1024以前的都被系统保留使用
# phone.bind(('ip', port))
phone.bind(('127.0.0.1', 8080))

# 【3】.开机 -- 监听状态
# 5 :指的是半连接池的大小
phone.listen(5)
print(f'服务器启动,开始监听ip:>>>{"127.0.0.1"},port:>>>{8080}')
# (1)服务器启动,开始监听ip:>>>127.0.0.1,port:>>>8080

# 【4】.等待电话链接请求:拿到电话链接 conn
# 返回的是双向通路 --- 操作系统维持链接
# conn : 双向通路的链接
# client_addr : 客户端的iP和端口
conn, client_addr = phone.accept()
print('这是服务端的conn:>>>', conn)
# (3)这是服务端的conn:>>> <socket.socket fd=352, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 5249)>
print('这是服务端的client_addr:>>>', client_addr)
# (4)这是服务端的client_addr:>>> ('127.0.0.1', 5249)

while True:
    # 【5】.接收消息
    # 1024 :最大接受的数据量为1024 bytes类型,收到的是bytes类型
    data = conn.recv(1024)

    if data.decode('utf-8') == 'q':
        break

    # 对接受的二进制数据进行解码
    print('从客户端接受的数据:>>>>', data.decode('utf-8'))
    # (5)从客户端接受的数据:>>>> is running for 发送信息
    # 发消息 返回消息状态等信息
    conn.send(data.upper())

# 【6】.关闭连接(必选的回收资源操作) conn
# 完成后断开连接
conn.close()

# 【7】.关机(可选操作)
phone.close()

客户端

# -*-coding: Utf-8 -*-
# @File : 客户端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/20
import socket

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

# 【2】.拨通服务端电话
# connect(('服务端ip', 服务端端口))
phone.connect(('127.0.0.1', 8080))
# (2)

while True:

    # 【3】.通信
    # send(二进制数据类型)
    msg = input('请输入需要发送的消息:>>>>').strip()

    phone.send(f'{msg}'.encode('utf8'))

    # 加入结束条件强制结束通信
    if msg == 'q':
        break

    # 接受服务端返回的数据
    data = phone.recv(1024)
    # 打印返回的消息 解码
    print(data.decode('utf8'))
    # (6)IS RUNNING FOR 发送信息

# 【4】.关闭连接(必选的回收资源操作)
phone.close()

输入的信息为空时,会阻塞通信

阻塞在服务端的反馈信息位置

客户端的接受信息不能为空

【2】版本2.2 - 用户输入信息为空时阻塞

客户端

# -*-coding: Utf-8 -*-
# @File : 客户端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/20
import socket

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

# 【2】.拨通服务端电话
# connect(('服务端ip', 服务端端口))
phone.connect(('127.0.0.1', 8080))
# (2)

while True:

    # 【3】.通信
    # send(二进制数据类型)
    msg = input('请输入需要发送的消息:>>>>').strip()

    phone.send(f'{msg}'.encode('utf8'))

    if len(msg) == 0: continue
    # 加入结束条件强制结束通信
    if msg == 'q':
        break

    # 接受服务端返回的数据
    data = phone.recv(1024)
    # 打印返回的消息 解码
    print(data.decode('utf8'))
    # (6)IS RUNNING FOR 发送信息

# 【4】.关闭连接(必选的回收资源操作)
phone.close()

服务端

# -*-coding: Utf-8 -*-
# @File : 服务端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/20
import socket

# 【1】.买手机
# socket.SOCK_STREAM :流式协议 ----> TCP协议 ----> 所有数据是一个整体
# socket.SOCK_DGRAM : 报协议  ----> 每一次数据都是单独一部分
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 【2】.绑定手机卡 ---> 192.168.1.50(本机IP地址)
# 0.0.0.0(任意IP地址) ---> 关联公网IP才会起作用  ----> 服务器用
# 127.0.0.1(本机固定IP地址) ---> 只有本机才能访问这个地址(测试用)
# 端口号:0-65535, 1024以前的都被系统保留使用
# phone.bind(('ip', port))
phone.bind(('127.0.0.1', 8080))

# 【3】.开机 -- 监听状态
# 5 :指的是半连接池的大小
phone.listen(5)
print(f'服务器启动,开始监听ip:>>>{"127.0.0.1"},port:>>>{8080}')
# (1)服务器启动,开始监听ip:>>>127.0.0.1,port:>>>8080

# 【4】.等待电话链接请求:拿到电话链接 conn
# 返回的是双向通路 --- 操作系统维持链接
# conn : 双向通路的链接
# client_addr : 客户端的iP和端口
conn, client_addr = phone.accept()
print('这是服务端的conn:>>>', conn)
# (3)这是服务端的conn:>>> <socket.socket fd=352, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 5249)>
print('这是服务端的client_addr:>>>', client_addr)
# (4)这是服务端的client_addr:>>> ('127.0.0.1', 5249)

while True:
    # 【5】.接收消息
    # 1024 :最大接受的数据量为1024 bytes类型,收到的是bytes类型
    data = conn.recv(1024)

    if data.decode('utf-8') == 'q':
        break

    # 对接受的二进制数据进行解码
    print('从客户端接受的数据:>>>>', data.decode('utf-8'))
    # (5)从客户端接受的数据:>>>> is running for 发送信息
    # 发消息 返回消息状态等信息
    conn.send(data.upper())

# 【6】.关闭连接(必选的回收资源操作) conn
# 完成后断开连接
conn.close()

# 【7】.关机(可选操作)
phone.close()

客户端强制终止程序时,服务端会产生一系列问题

【3】版本2.3 - 用户信息为空时检测异常

  • 客户端
# -*-coding: Utf-8 -*-
# @File : 客户端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/20
import socket

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

# 【2】.拨通服务端电话
# connect(('服务端ip', 服务端端口))
phone.connect(('127.0.0.1', 8080))
# (2)

while True:

    # 【3】.通信
    # send(二进制数据类型)
    msg = input('请输入需要发送的消息:>>>>').strip()

    phone.send(f'{msg}'.encode('utf8'))

    if len(msg) == 0: continue
    # 加入结束条件强制结束通信
    if msg == 'q':
        break

    # 接受服务端返回的数据
    data = phone.recv(1024)
    # 打印返回的消息 解码
    print(data.decode('utf8'))
    # (6)IS RUNNING FOR 发送信息

# 【4】.关闭连接(必选的回收资源操作)
phone.close()
  • 服务端
# -*-coding: Utf-8 -*-
# @File : 服务端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/20
import socket

# 【1】.买手机
# socket.SOCK_STREAM :流式协议 ----> TCP协议 ----> 所有数据是一个整体
# socket.SOCK_DGRAM : 报协议  ----> 每一次数据都是单独一部分
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 【2】.绑定手机卡 ---> 192.168.1.50(本机IP地址)
# 0.0.0.0(任意IP地址) ---> 关联公网IP才会起作用  ----> 服务器用
# 127.0.0.1(本机固定IP地址) ---> 只有本机才能访问这个地址(测试用)
# 端口号:0-65535, 1024以前的都被系统保留使用
# phone.bind(('ip', port))
phone.bind(('127.0.0.1', 8080))

# 【3】.开机 -- 监听状态
# 5 :指的是半连接池的大小
phone.listen(5)
print(f'服务器启动,开始监听ip:>>>{"127.0.0.1"},port:>>>{8080}')
# (1)服务器启动,开始监听ip:>>>127.0.0.1,port:>>>8080

# 【4】.等待电话链接请求:拿到电话链接 conn
# 返回的是双向通路 --- 操作系统维持链接
# conn : 双向通路的链接
# client_addr : 客户端的iP和端口
conn, client_addr = phone.accept()
print('这是服务端的conn:>>>', conn)
# (3)这是服务端的conn:>>> <socket.socket fd=352, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 5249)>
print('这是服务端的client_addr:>>>', client_addr)
# (4)这是服务端的client_addr:>>> ('127.0.0.1', 5249)

while True:
    # 【5】.接收消息
    # 1024 :最大接受的数据量为1024 bytes类型,收到的是bytes类型
    try:
        data = conn.recv(1024)

        if len(data) == 0:
            # 在 unix 系统里,一旦data收到的内容为空
            # 就意味着一种异常行为:客户端非法断开了链接
            break

        if data.decode('utf-8') == 'q':
            break

        # 对接受的二进制数据进行解码
        print('从客户端接受的数据:>>>>', data.decode('utf-8'))
        # (5)从客户端接受的数据:>>>> is running for 发送信息
        # 发消息 返回消息状态等信息
        conn.send(data.upper())
    except Exception as e:
        break

# 【6】.关闭连接(必选的回收资源操作) conn
# 完成后断开连接
conn.close()

# 【7】.关机(可选操作)
phone.close()

【三】迭代版3.0

  • 服务端应该满足的特点
    • 服务端一直提供服务
      • 在建立连接与结束链接之间再加上一层 循环
    • 服务端并发提供服务

【1】3.1 - 链接循环(循环建立链接)

  • 客户端
# -*-coding: Utf-8 -*-
# @File : 客户端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/20
import socket

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

# 【2】.拨通服务端电话
# connect(('服务端ip', 服务端端口))
phone.connect(('127.0.0.1', 8080))
# (2)

while True:

    # 【3】.通信
    # send(二进制数据类型)
    msg = input('请输入需要发送的消息:>>>>').strip()

    phone.send(f'{msg}'.encode('utf8'))

    if len(msg) == 0: continue
    # 加入结束条件强制结束通信
    if msg == 'q':
        break

    # 接受服务端返回的数据
    data = phone.recv(1024)
    # 打印返回的消息 解码
    print(data.decode('utf8'))
    # (6)IS RUNNING FOR 发送信息

# 【4】.关闭连接(必选的回收资源操作)
phone.close()
  • 服务端
# -*-coding: Utf-8 -*-
# @File : 服务端 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/20
import socket

# 【1】.买手机
# socket.SOCK_STREAM :流式协议 ----> TCP协议 ----> 所有数据是一个整体
# socket.SOCK_DGRAM : 报协议  ----> 每一次数据都是单独一部分
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 【2】.绑定手机卡 ---> 192.168.1.50(本机IP地址)
# 0.0.0.0(任意IP地址) ---> 关联公网IP才会起作用  ----> 服务器用
# 127.0.0.1(本机固定IP地址) ---> 只有本机才能访问这个地址(测试用)
# 端口号:0-65535, 1024以前的都被系统保留使用
# phone.bind(('ip', port))
phone.bind(('127.0.0.1', 8080))

# 【3】.开机 -- 监听状态
# 5 :指的是半连接池的大小
phone.listen(5)
print(f'服务器启动,开始监听ip:>>>{"127.0.0.1"},port:>>>{8080}')
# (1)服务器启动,开始监听ip:>>>127.0.0.1,port:>>>8080

# 【4】.等待电话链接请求:拿到电话链接 conn
#  ---- 加上链接循环 ----> 循环建链接
while True:
    # 返回的是双向通路 --- 操作系统维持链接
    # conn : 双向通路的链接
    # client_addr : 客户端的iP和端口
    conn, client_addr = phone.accept()
    # (3)这是服务端的conn:>>> <socket.socket fd=352, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 5249)>
    print('这是服务端的client_addr:>>>', client_addr)
    # (4)这是服务端的client_addr:>>> ('127.0.0.1', 5249)

    while True:
        # 【5】.接收消息
        # 1024 :最大接受的数据量为1024 bytes类型,收到的是bytes类型
        try:
            data = conn.recv(1024)

            if len(data) == 0:
                # 在 unix 系统里,一旦data收到的内容为空
                # 就意味着一种异常行为:客户端非法断开了链接
                break

            if data.decode('utf-8') == 'q':
                break

            # 对接受的二进制数据进行解码
            print('从客户端接受的数据:>>>>', data.decode('utf-8'))
            # (5)从客户端接受的数据:>>>> is running for 发送信息
            # 发消息 返回消息状态等信息
            conn.send(data.upper())
        except Exception as e:
            break

    # 【6】.关闭连接(必选的回收资源操作) conn
    # 完成后断开连接
    conn.close()

# 【7】.关机(可选操作)
phone.close()