Python基础 3.3 网络编程

网络编程

3.3.1 几个概念性知识

1. ip地址

  • 标志网络中网络设备的地址,具有唯一性
  • 分为ipv4和ipv6俩种协议和表现形式
    • ipv4:点分十进制
    • ipv6:冒号分16进制
  • 局域网网段决定了网络设备的ip
  • 查看ip地址的命令
    • Linux/Mac:ifconfig(需要安装networktools)
    • Windows:ipconfig
  • ping可以测试设备之间的连通性,也可以测试设备是否连上网络

2. 端口

  • 传输数据的通道,每个端口有一个对应的端口号
  • 端口号有1-65535个。
  • 端口号是可以标志唯一的一个端口
  • 端口号有2种
    • 知名端口号:众所周知的端口号,这些端口固定分配给一些服务(0-1023)
    • 动态端口号:开发应用程序使用的端口号,如果开发的时候没有设置,操作系统会在动态端口的范围内随机生成一个给程序使用(1024-65535)
  • 当一个程序或者服务启用的时候会占用一个端口号,当结束的时候,会自动释放端口号(有时效性)

3. TCP

  • 数据传输不能乱传的,发送之前还需要选择一个对应的传输协议,保证程序之间按照指定的传输规则进行数据的通信。
  • TCP 的英文全拼(Transmission Control Protocol)简称传输控制协议,它是一种面向连接的、可靠的、基于字节流的传输层通信协议
  • 特点
    • 面向连接:通信双方必须先建立好连接才能传输数据,传输完必须断开连接来释放资源
    • 可靠传输:
      • 采用发送应答机制
      • 超时重传:接受方不答的时候会重复发包
      • 错误校验:接收方检验包发现包数据不等的时候会丢包等发送发重发
      • 流量控制和阻塞管理

拓展

实际上传输数据不仅只有tcp协议,还有udp,属于传输层协议,传输数据有2种模型。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tLMumk5w-1610959919617)(/Volumes/Data/SynologyDrive/MD/ProgramLanguage/Python/asset/20181017221803915.png)]

我们后面学习的http属于应用层协议。

tcp有个三次握手和四次挥手协议

4. socket

  • 是进程之间通信的一个工具,进程之间想要进行网络通信数据传输需要基于这个socket
  • socket接口唯一osi7层模型的应用层,是一组接口
  • socket编程指的是利用soket接口来实现自己的业务和协议
  • Socke接口属于软件抽象层,而sokcket编程却是标准的应用层开发

3.3.2 TCP网络应用程序开发

1. 客户端开发流程

  1. 创建套接字对象(socket)
  2. 和服务器端建立连接(connect())
  3. 发送数据(send())
  4. 接受数据(recv())
  5. 关闭连接(close())

2. 服务端开发流程

  1. 创建套接字对象(socket)
  2. 绑定端口(bind())
  3. 设置监听模式(listen())
  4. 等待客户建立连接(accept())
  5. 接受数据(recv())
  6. 发送数据(send())
  7. 关闭连接(close())

如果没有虚拟机或者服务器,可以在本地(127.0.0.1)建立连接,我们使用一个app叫网络调试助手来帮忙我们尝试开发。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uim7tMlz-1610959919623)(/Volumes/Data/SynologyDrive/MD/ProgramLanguage/Python/asset/Snipaste_2020-11-21_11-09-01.png)]

3. 客户端代码实现示例

import socket


if __name__ == '__main__':
    # socket早期是一个函数,后来转成了类,所以是小写,我们先创建一个客户端对象
    # socket.socket(IP地址类型,传输层协议类型)
    # socket.AF_INET 代表是 ipv4
    # SOCK_STREAM 代表使用tcp传输控制协议
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # socket类有个实例方法connect(('ip地址',端口号))参数是一个元祖
    tcp_client_socket.connect(("192.168.19.33", 8080))
    while True:
        send_msg = input("请输入要发送的信息:")
        # 如果打exit则退出死循环 执行关闭连接
        if send_msg == "exit":
            break
        # 传输数据要先转化成对应编码的二进制文件,Ubuntu那边网络调试助手编码格式固定为utf-8 我们就也这样使用
        send_data = send_msg.encode("utf-8")
        # 发送数据
        tcp_client_socket.send(send_data)
        # 接受数据,一次最大接受1024字节
        recv_data = tcp_client_socket.recv(1024)
        # 打印收到的数据
        print(recv_data)
        # 将收到的数据重新转码成可识别的字符串
        recv_content = recv_data.decode('utf-8')
        print("接受的数据为:", recv_content)
    tcp_client_socket.close()

执行尝试

请输入要发送的信息:hi
b'hi'
接受的数据为: hi
请输入要发送的信息:test 000001

b'hi'
接受的数据为: hi
请输入要发送的信息:hhhhhh
b'000005'
接受的数据为: 000005
请输入要发送的信息:exit

Process finished with exit code 0

4. 服务端代码实现示例

import socket

if __name__ == '__main__':
    # 同样的服务器端也是一样,先创建一个socket对象接口
    # 其中socket.AF_INET代表ipv4 socket.SOCK_STREAM代表使用TCP协议
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口号复用,让程序退出端口号立即释放
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 使用bind做一个端口绑定
    tcp_server_socket.bind(('', 9222))
    # 设置监听模式
    # 128:最大等待建立连接的个数, 提示: 目前是单任务的服务端,同一时刻只能服务与一个客户端,后续使用多任务能够让服务端同时服务与多个客户端,
    # 不需要让客户端进行等待建立连接
    # listen后的这个套接字只负责接收客户端连接请求,不能收发消息,收发消息使用返回的这个新套接字来完成
    tcp_server_socket.listen(128)
    # 等待客户端建立连接的请求, 只有客户端和服务端建立连接成功代码才会解阻塞,代码才能继续往下执行
    # 1. 专门和客户端通信的套接字: service_client_socket
    # 2. 客户端的ip地址和端口号: ip_port
    service_client_socket, ip_port = tcp_server_socket.accept()
    print("客户端的ip地址以及端口号为:", ip_port)
    while True:
        # 接受数据
        recv_data = service_client_socket.recv(1024)
        if recv_data:
            print(recv_data)
            recv_content = recv_data.decode('utf-8')
            print(recv_content)
            # 发送数据
            send_msg = 'This is server'
            service_client_socket.send(send_msg.encode('utf-8'))
        else:
            break
    # 如果接受数据为空,表示客户端已经断开连接
    service_client_socket.close()

注意点

  • 服务器端需要2个以上的套接字,一个用来被动监听客户端的连接,一个用来主动接受客户端的信息和发送信息
  • 只有客户端建立连接才会解阻塞
  • 服务器套接字一般不关闭做死循环处理,一般是服务器更新的时候会关闭
  • 如果没做端口复用,服务器端程序退出后端口不会立即退出,起因是tcp的四次挥手协议。会让服务器端等待一阵子再完全释放端口。
  • 端口复用需要3个参数
    • 参数1 需要复用的套接口
    • 参数2 端口复用
    • 参数3 是否开启端口复用 bool
  • 如果listen后的套接字提前关闭,会导致新的客户端无法创建连接,但是已经连接的客户端不会被破坏通信。
  • 如果客户端断开连接,接受信息会变成0,可以以此判断客户端是否断开链接来关闭套接字,客户端那边同理。

5. 案例:实现多线程的服务器端

我们之前设置的listen监听数约等于白设置,因为我们那段代码一次只能连接一个客户端,如果要连接多个客户端,我们必须实现多任务版本的,而因为传输数据是需要频繁实现io耗时,不适合多进程。故用多线程来实现。

具体实现步骤

  1. 编写一个TCP服务端程序,循环等待接受客户端的连接请求
  2. 当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞
  3. 把创建的子线程设置成为守护主线程,防止主线程无法退出。

以下是代码

import socket
import threading


def client_link(service_client_socket,ip_port):
    # 循环接受客户端数据
    while True:
        recv_data = service_client_socket.recv(1024)
        if recv_data:
            print(f"ip地址为{ip_port[0]}客户发来消息:", recv_data.decode('utf-8'))
            service_client_socket.send("请稍等".encode('utf-8'))
        else:
            # 如果接受数据为空,则确认客户端已经断开连接
            print("客户端已经断开连接,客户对应ip为:", ip_port)
            break
    service_client_socket.close()


if __name__ == '__main__':
    # 先创造服务器套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设定端口复用,如果客户断开连接则释放端口
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 设置固定端口
    tcp_server_socket.bind(("", 9222))
    # 设置监听连接数
    tcp_server_socket.listen(128)
    while True:
        service_client_socket, ip_port = tcp_server_socket.accept()
        print("客户端连接上了,客户的ip地址和端口为:", ip_port)
        # 每当连接上一个新客户端,就给新客户端创建一个新线程
        new_thread = threading.Thread(target=client_link, args=(service_client_socket, ip_port))
        # 设置守护线程
        new_thread.setDaemon(True)
        # 设置启动该子线程
        new_thread.start()

我们执行代码

可以同时在虚拟机里多开几个网络调试助手

客户端连接上了,客户的ip地址和端口为: ('192.168.19.33', 53310)
客户端连接上了,客户的ip地址和端口为: ('192.168.19.33', 53312)
ip地址为('192.168.19.33', 53310)客户发来消息: kki
ip地址为('192.168.19.33', 53312)客户发来消息: kkp
ip地址为('192.168.19.33', 53312)客户发来消息: kkp
ip地址为('192.168.19.33', 53310)客户发来消息: kki
客户端已经断开连接,客户对应ip为: ('192.168.19.33', 53312)
客户端已经断开连接,客户对应ip为: ('192.168.19.33', 53310)

注意点

6. 案例:点对点机器人

实现局域网内的点对点聊天机器人程序。
使用TCP协议编写 socket 程序,分别实现消息的发送端和接收端
服务端记录客户端发送的消息,并进行随机回复
当客户端发送Bye时结束聊天

服务端

import socket
import threading
import random

bot_reply_list = ['Hello', 'Hi', "How Are You", "Sorry"]


def chat_save(data, ip_port):
    with open(f'{ip_port}.txt', 'a', encoding='utf-8')as f:
        f.write(data)


def client_chat(service_tcp_socket, ip_port):
    while True:
        recv_data = service_tcp_socket.recv(1024)
        if recv_data:
            recv_msg = recv_data.decode('utf-8')
            chat_save(recv_msg, ip_port)
            print(f"用户{ip_port}的发言已记录")
            if recv_msg == 'bye':
                break
            send_data = bot_reply_list[random.randint(0, len(bot_reply_list)-1)].encode('utf-8')
            service_tcp_socket.send(send_data)
        else:
            break
    print(f"与用户{ip_port}断开连接")
    service_tcp_socket.close()


def main():
    # 创建一个监听用的套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 实现端口复用
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 设置固定端口
    tcp_server_socket.bind(("", 9222))
    # 设置监听连接数
    tcp_server_socket.listen(128)
    while True:
        # 确认握手建立连接
        service_tcp_socket, ip_port = tcp_server_socket.accept()
        print(f"{ip_port}用户与您创立了连接")
        # 为此连接创建一个新的线程
        new_chat_thread = threading.Thread(target=client_chat, args=(service_tcp_socket, ip_port))
        new_chat_thread.setDaemon(True)
        new_chat_thread.start()


if __name__ == '__main__':
    main()

客户端

import socket


if __name__ == '__main__':
    # socket早期是一个函数,后来转成了类,所以是小写,我们先创建一个客户端对象
    # socket.socket(IP地址类型,传输层协议类型)
    # socket.AF_INET 代表是 ipv4
    # SOCK_STREAM 代表使用tcp传输控制协议
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # socket类有个实例方法connect(('ip地址',端口号))参数是一个元祖
    tcp_client_socket.connect(("127.0.0.1", 9222))
    print("您已经连接上机器人")
    while True:
        send_msg = input("请输入要发送的信息:")
        # 如果打exit则退出死循环 执行关闭连接
        if send_msg == "exit":
            break
        # 传输数据要先转化成对应编码的二进制文件
        send_data = send_msg.encode("utf-8")
        # 发送数据
        tcp_client_socket.send(send_data)
        # 接受数据,每次最大接受1024字节
        recv_data = tcp_client_socket.recv(1024)
        if recv_data:
            # 打印收到的数据
            print(recv_data)
            # 将收到的数据重新转码成可识别的字符串
            recv_content = recv_data.decode('utf-8')
            print("机器人:", recv_content)
        else:
            break
    tcp_client_socket.close()

7. 理解 socket.send() sokcket.recv()

  • socket.send() -> 缓冲区 -> 网卡 -> 网络
  • 网络 -> 网卡 -> 缓冲区 -> socket.recv(1024)

实际发送和接受操作的都是缓存区,所以不会说过多的就读取不到了。但是缓存区也有限制,如果存放的内容超出缓冲区会会异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值