• Python复习笔记(六)网络编程(udp/tcp)


    一、网络-udp(用户数据报协议)

    • 用户数据报协议

    • 类似写信,不安全,数据有可能丢

    1.1 ip地址

    注意:

    IP地址127.0.0.1 ~ 127.255.255.255 用于回路测试

    私有ip地址,不在公网中使用


    1.2 端口(重点)

    • 端口是通过端口号来标记的,端口号只有整数,范围是从0~65535(2^16)
    • 知名端口:(0~1023)(>1024的随便用)
      • 80端口 分配给 HTTP 服务
      • 21端口 分配给 ftp 服务
    • 动态端口(1024~65535)

    1.3 socket简介

    1.31 不同电脑上进程之间如何通信

    • 利用 ip地址、协议、端口 就可以标识网络的进程了。
    • 进程间通信:运行的程序之间的数据共享。

    1.32 创建socket

    在Python中 使用socket 模块的函数 socket就可以完成:、

    import socket
    
    # AddressFamily: ipv4/ipv6; Type: udp/tcp
    socket.socket(AddressFamily, Type)

    说明:

    函数 socket.socket 创建一个 socket,该函数带有两个参数:

    • Address Family:可以选择 AF_INET用于Internet 进程间通信) 或者 AF_UNIX(用于同一台机器进程间通信),实际工作常用 AF_INET

    • Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于TCP协议或者 SOCK_DGRAM(数据报套接字,主要用于UDP协议

    创建一个 tcp socket (tcp套接字)

    import socket
    
    # AddressFamily: ipv4/ipv6; Type: udp/tcp
    
    # 创建tcp的套接字
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # ... 这里是使用套接字的功能 (省略)
    
    # 不用的时候,关闭套接字
    s.close()

    创建一个 udp socket (udp 套接字)

    import socket
    
    # AddressFamily: ipv4/ipv6; Type: udp/tcp
    
    # 创建tcp的套接字
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    # ... 这里是使用套接字的功能 (省略)
    
    # 不用的时候,关闭套接字
    s.close()

    说明

    • 套接字使用流程
      1. 创建套接字
      2. 使用套接字收/发数据
      3. 关闭套接字

    1.4 udp网络程序-发送、接收数据

    1.41 udp网络程序-发送程序

    import socket
    
    # AddressFamily: ipv4/ipv6; Type: udp/tcp
    
    # 1. 创建udp的套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    # 2. 准备接收方地址
    # '192.168.43.74' 表示目标ip地址
    # 8080表示目的端口
    dest_addr = ('192.168.43.74', 8080) # 注意 是元组,ip是字符串,端口是数字
    
    # 3. 从键盘获取数据
    send_data = input("请输入要发送的数据:")
    
    
    # 4. 发送数据到指定的电脑上的指定程序中
    udp_socket.sendto(send_data.encode('utf-8'), dest_addr)
    
    # 不用的时候,关闭套接字
    udp_socket.close()

    注意:可以用NetAssist测试

    1.42 udp网络程序—接收程序 (绑定实例)

    # -*- coding: utf-8 -*-
    """
    Created on Fri Mar  1 22:25:32 2019
    
    @author: Douzi
    """
    
    import socket
    
    
    def main():
        # AddressFamily: ipv4/ipv6; Type: udp/tcp
        
        # 1. 创建udp的套接字
        udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        
        # 2. 绑定一个本地信息 '192.168.43.74'
        local_addr = ("",  7788)    # 注意 是元组,ip是字符串,端口是数字
        udp_socket.bind(local_addr) # 必须绑定自己电脑的ip以及port,其他的不行   
        
        # 3. 等待接收对方发送的数据
        recv_data = udp_socket.recvfrom(1024) #1024表示本次接收的最大字节数
        # recv_data这个变量中存储的是一个元组(接收到的数据, (发送方ip, port))
        recv_msg = recv_data[0]
        send_addr = recv_data[1]
        
        # 4. 显示接收到的数据------------
        print(recv_data)
        print(recv_msg.decode("gbk"))
        print(send_addr)
        
        # 关闭套接字
        udp_socket.close()
    
    if __name__=="__main__":
        main()

    1.43 聊天程序

    # -*- coding: utf-8 -*-
    """
    Created on Sat Mar  2 20:54:38 2019
    
    @author: Douzi
    """
    
    import socket
    
    def send_msg(udp_socket):
        """发送消息"""    
        # 获取内容
        dest_ip = input("输入ip: ")
        dest_port = int(input("输入对方port:"))
        send_data = input("输入发送数据:")
        udp_socket.sendto(send_data.encode("utf-8"), (dest_ip, dest_port))
    
    
    def recv_msg(udp_socket):
        recv_data = udp_socket.recvfrom(1024)
        print("%s:%s " % (str(recv_data[1]), recv_data[0].decode("utf-8")))        
    
    
    def main():
        # 创建套接字    
        udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
        # 绑定信息
        udp_socket.bind(("", 10000)) # '192.168.43.74'
        
        while True:
    
            print("0----发送消息")        
            print("1----接收消息")
            print("2----退出")
            op = input("输入功能: ")                
            
            if op == "0":
                # 发送
                send_msg(udp_socket)
            elif op == "1":
                # 接收并显示
                recv_msg(udp_socket)
            else:
                return
    
    if __name__=="__main__":
        main()
           

    二、TCP

    2.1 简介(传输控制协议)

    • TCP协议,传输控制协议(Transmission Control Protocol)
    • 类似打电话
    • TCP通信需要经过 创建连接、数据传送、终止连接 三个步骤。

    2.2 特点(安全复杂)

    1. 面向连接

    2. 可靠传输

    • TCP采用发送应答机制
    • 超时重传

      • 发送端发出一个报文段之后,就 启动定时器,如果在定时时间没有收到应答就重新发送这个报文段

      • TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的 按序接收,然后 接收端实体 对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的 往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失,将会进行重传。

    • 错误校验

      • TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。
    • 流量控制和阻塞管理

      • 流量控制用来避免主机发送得快而使接收方来不及完全收下

     TCP和UDP的不同点

    • 面向连接(确认有创建三方交握,连接已创建才做传输)

    • 有序数据传输

    • 重发丢失的数据包

    • 舍弃重复的数据包

    • 无差错的数据传输

    • 阻塞/流量控制


    2.3 tcp客户端(重点)

    2.31 tcp客户端构建流程

    示例代码:

    # -*- coding: utf-8 -*-
    """
    Created on Sun Mar  3 14:14:03 2019
    
    @author: Douzi
    """
    
    import socket
    
    def main():
        # 1.创建socket
        tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        
        # 2.链接服务器
        # server_ip = input("输入服务器ip:")
        # server_port = int(input("输入服务器port:"))
        # tcp_client_socket.connect((server_ip, server_port))
        tcp_client_socket.connect(("192.168.43.74", 8080))
        
        # 提示用户输入数据
        send_data = input("输入数据: ")
        
        tcp_client_socket.send(send_data.encode("gbk"))
        
        # 接收对方发送过来的数据,最大接收1024个字节
        recvData = tcp_client_socket.recv(1024)
        
        print("接收到的数据: ", recvData.decode("gbk"))
        
        # 关闭套接字
        tcp_client_socket.close()
    
    if __name__=="__main__":
        main()

    2.4 tcp服务器(重点)

    流程如下:

    1. socket创建一个套接字

    2. bind绑定ip和port

    3. listen使套接字变为 可以被动链接(监听套接字 负责 等待有新的客户端进行链接)

    4. accept等待客户端的链接 (accept 产生新的套接字 用来为客户端服务)

    5. recv/send接收发送数据

    一个简单的tcp服务器如下:

    # -*- coding: utf-8 -*-
    """
    Created on Sun Mar  3 14:55:42 2019
    
    @author: Douzi
    """
    
    import socket
    
    def main():
        # 创建套接字
        tcp_socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
        
        # 绑定本地信息 bind
        tcp_socket_server.bind(("192.168.43.74", 6677))
        # 让默认的套接字由主动变为被动 listen,这样就可以接收别人的链接了
        tcp_socket_server.listen(128)    
        # 等待客户端的链接accept    
        # client_socket用来为这个客户端服务
        # tcp_socket_server就可以省下来专门等待其他新客户端的链接
        client_socket, clientAddr = tcp_socket_server.accept()
        
        # 接收对方发送过来的数据
        recv_data = client_socket.recv(1024) # 接收1024个字节
        print('接收到的数据: ', recv_data.decode('gbk'))
        
        # 发送一些数据到客户端
        client_socket.send("Douzi is cute".encode('gbk'))
        
        # 关闭套接字
        client_socket.close()
    
    if __name__=="__main__":
        main()  

    为多个客户端服务

    # -*- coding: utf-8 -*-
    """
    Created on Sun Mar  3 14:55:42 2019
    
    @author: Douzi
    """
    
    import socket
    
    def main():
        # 创建套接字(买个手机)
        tcp_socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
        
        # 绑定本地信息 bind (插入手机卡)
        tcp_socket_server.bind(("192.168.43.74", 6677))
        # (将手机设置为响铃)让默认的套接字由主动变为被动 listen,这样就可以接收别人的链接了
        tcp_socket_server.listen(128)    
    
        while True:
            print("等待一个新的客户端的到来....")
            # 等待客户端的链接accept (等待别人的电话)
            # client_socket用来为这个客户端服务
            # tcp_socket_server就可以省下来专门等待其他新客户端的链接
            client_socket, client_addr = tcp_socket_server.accept()
            
            print(client_addr)
            
            # 为同一个客户端服务多次
            while True:
                # 接收对方发送过来的数据
                recv_data = client_socket.recv(1024) # 接收1024个字节
                print('接收到的数据: ', recv_data.decode("gbk"))
                
                # 如果recv 解堵塞,那么有2种方式:
                # 1. 客户端发送过来数据
                # 2. 客户端调用close导致了 这里recv解堵塞
                if recv_data:
                    # 回送一部分数据到客户端
                    client_socket.send("Douzi is cute".encode('gbk'))
                else:
                    break
                
            # 关闭套接字
            client_socket.close()
            
            print("已经服务完毕....")
        
        tcp_socket_server.close()
    
    if __name__=="__main__":
        main()


    2.5 案例:文件下载器

    文件下载的服务器

    # -*- coding: utf-8 -*-
    """
    Created on Sun Mar  3 17:46:37 2019
    
    @author: Douzi
    """
    
    import socket
    import sys
    
    def get_file_content(file_name):
        """获取文件内容"""
        try:
            with open(file_name, "rb") as f:
                content = f.read()
            return content
        except:
            print("没有下载的文件: %s" % file_name)
    
    def main():
        
        if len(sys.argv) != 2:
            print("请按如下方式运行: python3 xxx.py 7890")
            return 
        else:
            # 运行方式
            port = int(sys.argv[1])
    
        # 创建socket
        tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        
        # 本地信息, port: 7890
        address = ("192.168.43.74", port)
        # 绑定本地信息
        tcp_server_socket.bind(address)
        # 将主动套接字变为被动套接字
        tcp_server_socket.listen(128)
        
        
        while True:
            # 等待客户端的链接,即为这个客户端发送文件
            client_socket, client_addr = tcp_server_socket.accept()
            # 接收对方发送过来的数据
            recv_data = client_socket.recv(1024)
            file_name = recv_data.decode("utf-8")
            print("客户端需要下载的文件名为:%s" % file_name)
            
            file_content = get_file_content(file_name)    
            # 发送文件的数据给客户端
            # 因为获取打开文件时是以rb方式打开,所以file_content中的数据已经是二进制的格式
            # 所以不需要encode
            if file_content:
                client_socket.send(file_content)
            # 关闭这个套接字
            client_socket.close()
            isOK = input("继续吗?Y/N")
            if isOK == "Y":
                break
            
        # 关闭监听套接字
        tcp_server_socket.close()        
            
        
    if __name__=="__main__":
        main()
    

    文件下载的客户端

    # -*- coding: utf-8 -*-
    """
    Created on Sun Mar  3 19:34:20 2019
    
    @author: Douzi
    """
    
    import socket
    
    def main():
        
        # 创建socket
        tcp_socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        
        # 目的信息
        server_ip = "192.168.43.74" # 服务器的ip
        server_port = 7891          # 服务器的port
        
        # 链接服务器
        tcp_socket_client.connect((server_ip, server_port))
        
        # 输入需要下载的文件名
        file_name = input("输入要下载的文件名: ")
        
        # 发送文件下载请求
        tcp_socket_client.send(file_name.encode("utf-8"))
        
        # 接收对方发送过来的数据,最大接收1024个字节 (1k)
        # 1024--1k, 1024*1024--1M,1024*1024*1024--1G
        recv_data = tcp_socket_client.recv(1024*1024)
        
        # print('接收到的数据', recv_data.decode('utf-8'))
        # 如果接收到数据再创建文件,否则不创建
        if recv_data:
            with open("[接收]"+file_name, "wb") as f:
                f.write(recv_data)
        
        # 关闭套接字
        tcp_socket_client.close()            
                
    
    if __name__=="__main__":
        main()
                


    2.6 tcp注意点

    • tcp服务器 一般需要绑定,否则客户端找不到这个服务器

    • tcp客户端 一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip,port等信息就好,本地客户端可随意

    • tcp服务器通过listen可以将socket创建出来的主动套接字变成被动的,这是做tcp服务器必须要做的

    • 当客户端需要链接服务时,服务器端会有1个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务

    • listen后的套接字是被动套接字,用来接收 新的客户端 的链接请求,而accept返回的新套接字,是标记这个新客户端的。

    • 关闭 listen 后的套接字 意味着 被动套接字被关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信

    • 关闭 accept 返回的套接字意味着这个客户端已经服务完毕。

    • 当客户端的套接字 调用close后,服务器端会recv解阻塞,并且返回的长度为0,因此服务器可以通过返回数据的长度来区别客户端是否已经下线。

    udp tcp:client tcp:server
    socket         socket    socket
    bind connect bind()
    sendto/recvfrom send/recv listen(128)
    close close accept: client_socket, client_addr
        recv/send
        close

     

     


    三. tcp的3次握手、4次挥手(重点 !)

    • syn:标记请求的第一次
    • ack:应答

    3.1 3次握手

    业务开始(客户端先请求)

    3.2 4次挥手

    业务结束后,关闭(客户端先关)

    服务器先关闭,需要等待2-5分钟

    设置当前服务器先close 即服务器4次挥手后资源能够立即是否,这样就保证了。下次运行程序时,可以立即执行

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

     

  • 相关阅读:
    Activiti Model Editor组件
    Activiti 5.17 实体对象与类和数据库表的映射
    工作流入门链接
    揭秘jbpm流程引擎内核设计思想及构架
    比较Activiti中三种不同的表单及其应用
    Activiti源码分析
    Spring Security教程(5)---- 国际化配置及UserCache
    spring DelegatingFilterProxy管理过滤器
    Apache 处理svg工具包Apache(tm) Batik SVG Toolkit
    Binary Tree Right Side View
  • 原文地址:https://www.cnblogs.com/douzujun/p/10425156.html
Copyright © 2020-2023  润新知