zoukankan      html  css  js  c++  java
  • 02_套接字编程(socket抽象层)

    1.套接字概述

        1.套接概述: 套接是进行网络通信的一种手段(socket)

        2.套接字分类:
            流式套接字(SOCK_STREAM): 传输层基于tcp协议进行通信
            数据报套接字(SOCK_DEGAM): 传输层基于udp协议进行通信
            原始套接字(SOCK_RAW): 访问底层协议的套接字

        3.TCP与UDP通讯模型流程图: https://www.processon.com/view/link/5ef43bfd1e0853263742690b

        4.套接字属性和方法

    import socket
    
    s = socket.socket()  # 默认会创建流式套接字
    
    # 功能: 获取套接字的描述符
    # 描述符: 每一个IO操作系统都会分配一个不同的整数与之对应,该整数极为此IO操作的描述符
    s.fileno()  # 12
    print(s.fileon())  # 12
    
    # 获取套接字类型
    s.type  # <SocketKind.SOCK_STREAM: 1>
    print(s.type)  # SocketKind.SOCK_STREAM
    
    # 获取套接字绑定的地址
    s.getsockname()  # ('0.0.0.0', 0)
    s.bind(("127.0.0.1", 7890))
    print(s.getsockname())  # ('127.0.0.1', 7890)
    
    # 使用accept生成的套接字调用,获取该套接字对应的客户端的地址,在一个服务器有多个客户端连接时常会用到这个方法
    s.listen(128)
    conn, addr = s.accept()  # 阻塞时需要找一个用户连接
    conn.getpeername()  # ('127.0.0.1', 53519)
    print(conn.getpeername())  # ('127.0.0.1', 53519)
    
    # s.setsockopt(level, optname, value) 设置套接字选项
    # 参数 level: 定义的选项类型,常用选项(IPPROTO_TCP,IPPROTO_IP,SOL_SOCKET)
    # 参数 optname: 根据level选项确定的子选项
    # 参数 value: 根据子选项设置的值
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 设置端口重用
    s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)  # 设置套接字允许接收广播
    
    # s.getsockopt(level, optname) 获取套接字选项
    # 参数 level: 定义的选项类型,常用选项(IPPROTO_TCP,IPPROTO_IP,SOL_SOCKET)
    # 参数 optname: 根据level选项确定的子选项
    # 返回值: 返回根据子选项设置的值
    s.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)  # 1
    
    # 面向锁的套接字方法
    s.setblocking()  # 设置套接字阻塞与非阻塞模式,参数默认为True表示阻塞
    s.settimeout()  # 设置阻塞套接字操作的超时时间
    s.gettimeout()  # 获取阻塞套接字操作的超时时间
    
    # 面向文件的套接字函数
    s.fileno()  # 套接字的文件描述符
    s.makefile()  # 创建一个与该套接字相关的文件

    2.TCP流式套接字

    1.TCP服务端流程

            1.创建套接字
                sockfd = socket(socket_family=AF_INET, socket_type=SOCK_STERAM, proto=0)  # 创建套接字并返回套接字对象
                参数:
                    socket_family: 选择地址族种类AF_INET(UNIX)
                    socket_type: 套接字类型 流式(SOCK_STREAM),数据报(SOCK_DGRAM)
                    proto: 子协议类型,tcp和udp都没有用到自协议因此默认为0

            2.绑定IP和端口号
                sockfd.bind(("", 7890))  # 绑定IP和端口号
                参数: 类型为元组('127.0.0.1', 7890)  # 第一项是字符串形式的IP,第二项是端口号

            3.让套接字具有监听功能
                sockfd.listen(n)  # 使套接字变为监听套接字,同时创建监听队列
                参数: n监听队列大小,一般设置为128

            4.等待客户端连接
                new_socket, client_addr = socket.accept()  # 阻塞等待客户端连接
                返回值: 类型为元祖(new_socket, client_addr)
                    第一项: 返回一个新的套接字用来和客户端通信
                    第二项: 返回连接的客户端的地址

            5.消息的收发
                接收: new_socket.recv(buffer)  # 接收消息
                    参数: 一次接收消息的大小, 即一次收多少字节
                    返回值: 接收到的内容, 类型为字节
                发送: new_socket.send(data)  # 发送消息,当没有接收端的时候send操作会导致管道破裂(broken pipe)
                    参数: 发送的内容(bytes), 类型为字节
                    返回值: 发送了多少个字节

            6.关闭套接字
                new_socket.close()  # 关闭为客户端服务的套接字
                sockfd.close()  # 关闭监听套接字

    2.TCP客户端流程

            1.创建流式套接字: sockfd = socket(socket_family=AF_INET, socket_type=SOCK_STREAM, proto=0)

            2.发起连接请求
            sockfd.connect(('127.0.0.1', 7890))  # 发起连接请求
            参数(元组): 第一项是服务器的IP,第二项是服务器的PORT

            3.收发消息: sockfd.recv() sockfd.send()

            4.关闭套接字: sockfd.close()

    3.TCP服务端-通讯示例

    import socket
    
    
    def main():
        # 创建数据流套接字
        tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置端口重用
        tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定端口
        tcp_server_socket.bind(("", 7890))
        # 让默认的套接字由主动变为被动监听 listen
        tcp_server_socket.listen(128)
    
        # 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
        while True:
            print("等待一个新的客户端的到来...")
            new_client_socket, client_addr = tcp_server_socket.accept()
            print("客户端' %s '已经到来" % str(client_addr))
            # 循环目的: 为同一个客服端服务多次
            while True:
                # 接收客户端发送过来的请求
                recv_data = new_client_socket.recv(1024)
    
                # recv解堵塞的两种方式: 1.客户端发送过来数据,2.客户端调用了close
                if recv_data:
                    print("客户端' %s '发送过来的请求是: %s" % (str(client_addr), recv_data.decode("utf-8")))
                    # 回送数据给客户端表示响应客户端的请求
                    send_data = "----ok----Request accepted"
                    new_client_socket.send(send_data.encode("utf-8"))
                else:
                    break
    
            # 关闭accept返回的套接字
            new_client_socket.close()
            print("已经为 %s 客户端已经服务完毕" % str(client_addr))
        # 关闭监听套接字
        tcp_server_socket.close()
    
    
    if __name__ == "__main__":
        main()

    4.TCP客户端-通讯示例

    import socket
    
    
    def main():
        # 创建数据流套接字
        tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 连接服务器
        server_ip = input("请输入要连接的服务器的ip:")
        serve_port = int(input("请输入要连接的服务器的port:"))
        server_addr = (server_ip, serve_port)
        tcp_client_socket.connect(server_addr)  # 连接失败会报错
        # s = tcp_client_socket.connect_ex(server_addr)  # 有返回值,如果连接失败不会报错,会返回错误的编码
        # 发送数据
        send_data = input("请输入要发生的数据:")
        tcp_client_socket.send(send_data.encode("utf-8"))
        # 接收服务器发送过来的数据
        recv_data = tcp_client_socket.recv(1024)
        print("接收到的数据为:%s" % recv_data.decode("utf-8"))
        # 关闭套接字
        tcp_client_socket.close()
    
    
    if __name__ == "__main__":
        main()

    5.TCP服务端-文件下载示例

    import socket
    
    
    def send_file_client(nwe_client_socket, client_addr):
        # 1.接收客户端发送过来的需要下载的文件名
        file_name = nwe_client_socket.recv(1024).decode("utf-8")
        print("客户端 %s 要下载的文件是:%s" % (str(client_addr), file_name))
        # 2.打开文件读取数据
        file_content = None
        try:
            f = open(file_name, "rb")
            file_content = f.read()
            f.close()
        except Exception as ret:
            print("没有要下载的文件%s" % file_name)
        # 3.发送文件的数据给客户端
        if file_content:
            nwe_client_socket.send(file_content)
    
    
    def main():
        # 创建套接字
        tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置端口重用
        tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定本地信息
        local_addr = ("", 7890)
        tcp_server_socket.bind(local_addr)
        # 开启监听
        tcp_server_socket.listen(128)
        while True:
            # 等待用户连接
            nwe_client_socket, client_addr = tcp_server_socket.accept()
            # 调用发送文件函数,完成为客户端服务
            send_file_client(nwe_client_socket, client_addr)
    
            # 关闭套接字
            nwe_client_socket.close()
        # 关闭监听套接字
        tcp_server_socket.close()
    
    
    if __name__ == "__main__":
        main()

    6.TCP客户端-文件下载示例

    import socket
    
    
    def main():
        # 创建套接字
        tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 连接服务器
        dest_ip = input("请输入下载服务器的ip:")
        dest_port = int(input("请输入下载服务器的port:"))
        dest_addr = (dest_ip, dest_port)
        tcp_client_socket.connect(dest_addr)
        # 获取要下载的文件名
        download_file_name = input("请输入要下载的文件名:")
        # 发送要下载的文件名
        tcp_client_socket.send(download_file_name.encode("utf-8"))
        # 接收文件数据
        recv_data = tcp_client_socket.recv(1024)  # 一次接收1k数据
        # 打开文件写入数据
        if recv_data:
            with open("[new]" + download_file_name, "wb") as f:
                f.write(recv_data)
        # 关闭套接字
        tcp_client_socket.close()
    
    
    if __name__ == "__main__":
        main()

    7.TCP粘包现象

            1.发送接收缓冲区
                发送和接收消息均放到缓存区再进行处理
                当recv接收消息一次接收不完的时候下次会继续接收,当recv阻塞时,如果客户端断开则recv立即返回空字符串

            2.TCP粘包概述:
                1.TCP中数据以数据流的方式发送接收,每次发送的数据间没有边界,在接收时可能造成数据的粘连即为粘包
                2.合包机制造成数据混乱: nagle算法将多次连续发送且间隔较小的数据进行打包成一个数据传输
                3.拆包机制造成数据混乱: 在发送端因为受到数据链路层网卡的MTU限制,会将大的超过MTU限制的数据进行拆分成多个小的数据包进行传输
                    当传输到目标主机的操作系统层时,会将多个小的数据包合并成原本的数据包

            3.粘包如何处理方案:
                1.每次发送消息和结束位置加标志,每次收到消息按结束标准切割
                2.发送的消息添加结构描述,例如加一个包头,包头里记录 name:大小, password: 大小
                3.当连续发送的时每次发送有一个短暂延迟sleep(0.1)

            4.tcp粘包参考链接: https://www.cnblogs.com/LY-C/p/9120992.html

    8.TCP服务端-粘包现象验证

    import socket
    
    
    def main():
        # 创建数据流套接字
        tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 绑定端口
        tcp_server_socket.bind(("", 7890))
        # 让默认的套接字由主动变为被动监听 listen
        tcp_server_socket.listen(128)
        # 等待客户端连接
        new_client_socket, client_addr = tcp_server_socket.accept()
        print("客户%s端已连接" % str(client_addr))
    
        # 循环目的: 为同一个客服端服务多次
        while True:
            # 接收客户端发送过来的请求
            recv_data = new_client_socket.recv(1024)
            # recv解堵塞的两种方式: 1.客户端发送过来数据,2.客户端调用了close
            if not recv_data:
                break
    
            # 客户端是先遍历["Coco", "1355656****", "Coco@xxx.com", "xx省xx市xx区xxx"]列表,再分了4次发送用户信息的数据
            # 但是此时服务端收到的数据却是连续的: Coco1355656****Coco@xxx.comxx省xx市xx区xxx
            print("客户端' %s '发送过来的请求是: %s" % (str(client_addr), recv_data.decode("utf-8")))
            # 回送数据给客户端表示响应客户端的请求
            send_data = "----ok----Request accepted"
            new_client_socket.send(send_data.encode("utf-8"))
    
        # 关闭accept返回的套接字
        new_client_socket.close()
        print("已经为 %s 客户端已经服务完毕" % str(client_addr))
        # 关闭监听套接字
        tcp_server_socket.close()
    
    
    if __name__ == "__main__":
        main()

    9.TCP客户端-粘包现象验证

    import socket
    import time
    
    
    def main():
        # 创建数据流套接字
        tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 连接服务器
        server_addr = ("", 7890)
        tcp_client_socket.connect(server_addr)
        # 发送数据
        user_info = ["Coco", "1355656****", "Coco@xxx.com", "xx省xx市xx区xxx"]
        for i in user_info:
            tcp_client_socket.send(str(i).encode("utf-8"))
            # 发送时添加延时可以解决粘包现象
            # time.sleep(0.1)
        # 接收服务器发送过来的数据
        recv_data = tcp_client_socket.recv(1024)
        print("接收到的数据为:%s" % recv_data.decode("utf-8"))
        # 关闭套接字
        tcp_client_socket.close()
    
    
    if __name__ == "__main__":
        main()

    10.TCP服务端-大文件上传

    import socket
    import json
    import struct
    
    
    def main():
        # 创建数据流套接字
        tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置端口重用
        tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定端口
        tcp_server_socket.bind(("", 7890))
        # 让默认的套接字由主动变为被动监听 listen
        tcp_server_socket.listen(128)
    
        # 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
        while True:
            print("等待一个新的客户端的到来...")
            new_client_socket, client_addr = tcp_server_socket.accept()
            print("客户端' %s '已经到来" % str(client_addr))
    
            # 接收客户端发送过来的请求
            b_len_dic = new_client_socket.recv(4)
    
            print(b_len_dic)
            len_dic = struct.unpack("i", b_len_dic)[0]  # unpack得到一个元祖,取下标0的位置获取到int类型的字典长度
            recv_data = new_client_socket.recv(len_dic).decode("utf-8")
    
            # new_client_socket.send(b"OK")  # 向客户端发送文件准备就绪标识,同时也避免接收数据过快产生粘包
    
            recv_dic = json.loads(recv_data)
            if recv_dic["opt"] == "upload":
                filename = "副本" + recv_dic["filename"]
                with open(filename, "ab") as f:
                    while recv_dic["filesize"]:
                        content = new_client_socket.recv(1024)
                        f.write(content)
                        recv_dic["filesize"] -= len(content)
            elif recv_dic["opt"] == "download":
                pass
    
            # 关闭accept返回的套接字
            new_client_socket.close()
            print("已经为 %s 客户端已经服务完毕" % str(client_addr))
        # 关闭监听套接字
        tcp_server_socket.close()
    
    
    if __name__ == "__main__":
        main()

    11.TCP客户端-大文件上传

    import socket
    import os
    import json
    import struct
    
    
    def main():
        # 创建数据流套接字
        tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 连接服务器
        server_ip = input("请输入要连接的服务器的ip:")
        serve_port = int(input("请输入要连接的服务器的port:"))
        server_addr = (server_ip, serve_port)
        tcp_client_socket.connect(server_addr)  # 连接失败会报错
        # s = tcp_client_socket.connect_ex(server_addr)  # 有返回值,如果连接失败不会报错,会返回错误的编码
    
        menu = {"1": "upload", "2": "download"}
        for i in menu:
            print(i, menu[i])
        num = input("请输入功能选项: ")
        if num == "1":
            send_dic = {"opt": menu.get(num), "filename": None, "filesize": None}
            file_path = input("请输入一个绝对路径: ")  # 要上传文件的绝对路径
            filename = os.path.basename(file_path)  # 获取要上传文件的文件名
            filesize = os.path.getsize(file_path)  # 获取文件大小
    
            send_dic["filename"] = filename
            send_dic["filesize"] = filesize
            send_data = json.dumps(send_dic)
    
            len_dic = len(send_data)  # 获取字典长度,是一个int数据类型,可能是30,也可能是120
            b_len_dic = struct.pack("i", len_dic)  # 加字典长度打包成一个4位的bytes类型
            # 将bytes类型的字典长度 + bytes类型的字典内容,一起发送给服务器
            tcp_client_socket.send(b_len_dic + send_data.encode("utf-8"))
    
            # tcp_client_socket.recv(1024)  # 1.向服务器确认是否可以上传文件;2.避免与下列代码的send过快造成粘包
    
            with open(file_path, "rb") as f:
                while filesize:
                    content = f.read(1024)
                    tcp_client_socket.send(content)
                    filesize -= len(content)
    
            # 发送数据
            tcp_client_socket.send(send_data.encode("utf-8"))
        elif num == "2":
            pass
    
        # 关闭套接字
        tcp_client_socket.close()
    
    
    if __name__ == "__main__":
        main()

    12.TCP服务端-身份加密验证

    import socket
    import os
    import hmac
    
    
    def main():
        # 创建数据流套接字
        tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置端口重用
        tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定端口
        tcp_server_socket.bind(("", 7890))
        # 让默认的套接字由主动变为被动监听 listen
        tcp_server_socket.listen(128)
    
        # 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
        while True:
            print("等待一个新的客户端的到来...")
            new_client_socket, client_addr = tcp_server_socket.accept()
            print("客户端' %s '已经到来" % str(client_addr))
    
            sor = b"hmy"  # 服务器加盐
            r_str = os.urandom(16)  # 随机一个16位长度的bytes类型数据
            new_client_socket.send(r_str)
    
            # md5加密
            md5_obj = hmac.new(sor, r_str)
            result = md5_obj.digest()
    
            # 接收客户端发送过来的密文与服务器的密文对比
            recv_msg = new_client_socket.recv(1024)
            if recv_msg == result:
                new_client_socket.send(b"success")
            else:
                new_client_socket.send(b"failed")
    
            # 关闭accept返回的套接字
            new_client_socket.close()
            print("已经为 %s 客户端已经服务完毕" % str(client_addr))
        # 关闭监听套接字
        tcp_server_socket.close()
    
    
    if __name__ == "__main__":
        main()

    13.TCP客户端-身份加密验证

    import socket
    import hmac
    
    
    def main():
        # 创建数据流套接字
        tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 连接服务器
        server_ip = input("请输入要连接的服务器的ip:")
        serve_port = int(input("请输入要连接的服务器的port:"))
        server_addr = (server_ip, serve_port)
        tcp_client_socket.connect(server_addr)  # 连接失败会报错
        # s = tcp_client_socket.connect_ex(server_addr)  # 有返回值,如果连接失败不会报错,会返回错误的编码
    
        sor = b"hmy"  # 客户端加盐
        r_str = tcp_client_socket.recv(1024)
    
        # md5加密
        md5_obj = hmac.new(sor, r_str)
        result = md5_obj.digest()
        # 向服务器发送验证密文
        tcp_client_socket.send(result)
    
        # 接收验证结果
        recv_msg = tcp_client_socket.recv(1024)
        print(recv_msg)
    
        # 关闭套接字
        tcp_client_socket.close()
    
    
    if __name__ == "__main__":
        main()

    14.TCP服务端-切换目录

    import socket
    import os
    
    
    def send_data(new_client_socket, path):
        """你给我一个目录,我把目录发给client"""
        lis_dir = os.listdir(path)
        str_dir = '--'.join(lis_dir)
        new_client_socket.send(str_dir.encode('utf-8'))
    
    
    def main():
        # 创建数据流套接字
        tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 设置端口重用
        tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定端口
        tcp_server_socket.bind(("", 7890))
        # 让默认的套接字由主动变为被动监听 listen
        tcp_server_socket.listen(128)
    
        # 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
        while True:
            print("等待一个新的客户端的到来...")
            new_client_socket, client_addr = tcp_server_socket.accept()
            print("客户端' %s '已经到来" % str(client_addr))
    
            abs_path = new_client_socket.recv(1024).decode('utf-8')  # 获取用户输入的绝对路径
            current_dir = abs_path + '/'  # 以下再处理,都要根据当前路径去处理,无论是返回上一层,还是进入下一层
            send_data(new_client_socket, current_dir)  # 把用户输入的路径下的所有文件及文件夹返回给客户端
    
            while True:
                # 测试输入: /Users/tangxuecheng
                cmd = new_client_socket.recv(1024).decode('utf-8')
                if cmd == '..':
                    current_dir = current_dir.split('/')[:-2]
                    current_dir = '/'.join(current_dir) + '/'
                    # if 如果当前是根目录:
                    #     就返回给客户端告诉说没有上一层了
                    send_data(new_client_socket, current_dir)
                else:
                    filename = cmd.split(' ')[1]  # 获取用户输入的文件名字
                    current_dir += filename + '/'  # 将文件名字添加到当前路径下,组成一个完整的新路径
                    if os.path.isdir(current_dir):  # 如果客户输入的文件名字是一个文件夹
                        send_data(new_client_socket, current_dir)
                    else:  # 如果不是一个文件夹
                        new_client_socket.send(b'not a folder')
    
            # 关闭accept返回的套接字
            new_client_socket.close()
            print("已经为 %s 客户端已经服务完毕" % str(client_addr))
        # 关闭监听套接字
        tcp_server_socket.close()
    
    
    if __name__ == "__main__":
        main()

    15.TCP客户端-切换目录

    import socket
    
    
    def main():
        # 创建数据流套接字
        tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 连接服务器
        server_ip = input("请输入要连接的服务器的ip:")
        serve_port = int(input("请输入要连接的服务器的port:"))
        server_addr = (server_ip, serve_port)
        tcp_client_socket.connect(server_addr)  # 连接失败会报错
        # s = tcp_client_socket.connect_ex(server_addr)  # 有返回值,如果连接失败不会报错,会返回错误的编码
    
        # 测试输入: /Users/tangxuecheng
        abs_path = input('请输入您的根目录:')
        tcp_client_socket.send(abs_path.encode('utf-8'))
        current_dir = tcp_client_socket.recv(1024).decode('utf-8')
        print(current_dir.split('--'))
    
        while True:
            cmd = input('请输入>>>')
            # cd + 文件夹      ..
            if cmd == '..':
                tcp_client_socket.send(cmd.encode('utf-8'))
                current_dir = tcp_client_socket.recv(1024).decode('utf-8')
                print(current_dir.split('--'))
            if cmd == 'cd':
                filename = input('请输入一个文件夹名:')
                tcp_client_socket.send((cmd + ' ' + filename).encode('utf-8'))
                current_dir = tcp_client_socket.recv(1024).decode('utf-8')
                print(current_dir.split('--'))
    
        # 关闭套接字
        tcp_client_socket.close()
    
    
    if __name__ == "__main__":
        main()

    16.TCP应用-web静态服务器

    # html文件夹网盘链接: https://pan.baidu.com/s/1wbZ92KLstff3GurLV_C0lg  密码: hp9o
    # 下载完命令行解压后放到程序的同级路径下,解压命令: tar -zxvf 06_html.tar.gz -C ./
    import socket
    
    
    def client_handle(client_socket):
        # 接收客户端发送过来的请求数据
        request = client_socket.recv(2048)
        request_handles = request.splitlines()
        for line in request_handles:
            print(line)
        try:
            f = open("./html/index.html", "r")
        except IOError:
            # 找不到文件时返回404错误
            response = "HTTP/1.1 404 not found
    "
            response += "
    "
            response += "sorry file not found"
        else:
            # 找到文件时读取文件内容
            response = "HTTP/1.1 200 OK
    "
            response += "
    "
            for line in f:
                response += line
        finally:
            # 向浏览器返回信息
            client_socket.send(response.encode())
            # 关闭客户端套接字
            client_socket.close()
    
    
    def main():
        # 1.创建套接字
        tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 2.设置端口重用
        tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 4.允许局域网内用户访问
        local_addr = ("0.0.0.0", 7890)
        tcp_server_socket.bind(local_addr)
        # 5.监听
        tcp_server_socket.listen(128)
        # 5.循环服务
        while True:
            # 客户端连接
            client_socket, client_addr = tcp_server_socket.accept()
            # 为客户端服务
            client_handle(client_socket)
    
        # 6.关闭监听套接字
        tcp_server_socket.close()
    
    
    if __name__ == "__main__":
        main()

    3.UDP数据报套接字

    1.UDP套接字使用流程

            1.创建数据报套接字
                sockfd = socket(AF_INET,SOCK_DGRAM)
                参数: AF_INET表示ipv4,SOCK_DGRAM表示数据报套接字

            2.绑定服务端地址: sockfd.bind("127.0.0.1", 7890)

            3.收发消息
                接收消息(recvfrom)
                    data, addr = sockfd.recvfrom(buffersize) # 一次接收一个数据包,如果是数据包一次没有接收完则会丢失没有接收的内容
                    参数: 每次最多接收消息的大小(字节)
                    返回值:
                        data: 接收到的消息
                        addr: 消息发送者的地址
                发送消息(sendto)
                    sockfd.sendto(data, addr) # 发送消息
                    参数:
                        data:要发送的消息
                        addr: 发送给某个主机的地址
                    返回值: 发送消息的字节数

            4.关闭套接字: sockfd.close()

    2.UDP应用-发送消息

    import socket
    
    
    def main():
        # 创建一个udp套接字
        udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 指定接消息收方的地址
        dest_addr = ("", 7788)
    
        while True:
            # 从键盘上获取数据
            send_data = input("请输入要发送的数据:")
            # 如果输入的数据是exit则退出程序
            if send_data.lower() == "exit":
                break
            # 使用套接字收发数据
            # udp_socket.sendto(b"hello world!", ("169.254.119.158", 8080))
            udp_socket.sendto(send_data.encode("utf-8"), dest_addr)
    
        # 关闭套接字
        udp_socket.close()
    
    
    if __name__ == "__main__":
        main()

    3.UDP应用-接收消息

    import socket
    
    
    def main():
        # 1.创建套接字
        udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 2.绑定本地信息
        local_addr = ("", 7788)
        udp_socket.bind(local_addr)
        # 3.接收数据
        while True:
            recv_data = udp_socket.recvfrom(1024)  # 1024表示本次接收的最大字节数
            # 4.打印接收的数据
            # recv_data这个变量中存储的是一个元祖(接收到的数据, (发送方的ip, port))
            recv_msg = recv_data[0]  # 第1个元素是对方发送的数据,类型为字节
            recv_addr = recv_data[1]  # 第2个元素是对方的ip和端口,类型为元祖
            print(type(recv_msg), type(recv_addr))  # <class 'bytes'> <class 'tuple'>
    
            # 功能扩展: 含有敏感词汇消息在输出时进行颜色标识
            color_dic = {"用户名": "33[32m", "密码": "33[33m", "验证": "33[0;32;40m"}
            msg = recv_msg.decode("utf-8")
            for i in color_dic:
                if i in msg:
                    color = color_dic[i]
                    print("%s%s:%s33[0m" % (color, str(recv_addr), msg))
                    break
            else:
                print("%s:%s" % (str(recv_addr), msg))  # ('127.0.0.1', 61804):Tom你好吗?
        # 5.关闭套接字
        udp_socket.close()
    
    
    if __name__ == "__main__":
        main()

    4.UDP应用-实现广播的设置

            1.将广播接收端套和发送端的接字属性设置为允许接收广播
            2.将广播发送端的地址设置为发送给局域网所有终端: ("192.168.0.255", 7890) 或 ("", 7890)
            3.广播风暴: 在一个网络中大量发送广播会占用大量带宽,解决方案是组播

    5.UDP应用-广播接收端

    # broadcast_recv.py
    import socket
    
    
    def main():
        # 1.创建套接字
        udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 2.设置套接字允许接收广播
        udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        # 3.设置端口重用
        udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 4.固定接收端的端口号
        local_addr = ("", 7890)
        udp_socket.bind(local_addr)
        # 5.接收数据
        while True:
            try:
                recv_msg, recv_addr = udp_socket.recvfrom(1024)  # 1024表示本次接收的最大字节数
                # 6.打印接收的数据
                # print(type(recv_msg), type(recv_addr))  # <class 'bytes'> <class 'tuple'>
                print("从广播{}获取消息: {}".format(str(recv_addr), recv_msg.decode("utf-8")))
    
                # 7.回送消息
                udp_socket.sendto(b"ok", recv_addr)
            except (KeyboardInterrupt, SyntaxError):  # control + c 终止程序或者语法错误时捕获异常
                raise
            except Exception as e:
                print(e)
    
        # 8.关闭套接字
        udp_socket.close()
    
    
    if __name__ == "__main__":
        main()

    6.UDP应用-广播发送端

    # broadcast_send.py
    import socket
    import time
    
    
    def main():
        # 1.创建一个udp套接字
        udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 2.设置套接字允许接收广播
        udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        # 3.设置发送广播的地址
        # dest_addr = ("192.168.0.255", 7890)  # 这种写法也可以
        dest_addr = ("<broadcast>", 7890)  # 将消息发送给局域网所有终端
    
        while True:
            time.sleep(1)  # 每隔1秒发送一次广播
            # 使用套接字收发数据
            udp_socket.sendto("开始广播了...".encode("utf-8"), dest_addr)
            recv_msg, recv_addr = udp_socket.recvfrom(1024)  # 1024表示本次接收的最大字节数
            print("从接收端{}获取消息: {}".format(str(recv_addr), recv_msg.decode("utf-8")))
    
        # 关闭套接字
        udp_socket.close()
    
    
    if __name__ == "__main__":
        main()

    7.函数实现-UDP聊天器

    import socket
    
    
    def send_msg(udp_socket):
        """获取键盘数据,并将其发送给对方"""
        # 1. 从键盘输入数据
        msg = input("
    请输入要发送的数据:")
        # 2. 输入对方的ip地址
        dest_ip = input("
    请输入对方的ip地址:")
        # 3. 输入对方的port
        dest_port = int(input("
    请输入对方的port:"))
        # 4. 发送数据
        udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))
    
    
    def recv_msg(udp_socket):
        """接收数据并显示"""
        # 1. 接收数据
        recv_msg = udp_socket.recvfrom(1024)
        # recv_msg, recv_ip = udp_socket.recvfrom(1024)
        # 2. 解码
        recv_ip = recv_msg[1]
        recv_msg = recv_msg[0].decode("utf-8")
        # 3. 显示接收到的数据
        print(">>>%s:%s" % (str(recv_ip), recv_msg))
    
    
    def main():
        # 1. 创建套接字
        udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 2. 绑定本地信息
        udp_socket.bind(("", 7788))
        while True:
            # 3. 选择功能
            print("="*30)
            print("1:发送消息")
            print("2:接收消息")
            print("0:退出系统")
            print("="*30)
            op_num = input("请输入要操作的功能序号:")
    
            # 4. 根据选择调用相应的函数
            if op_num == "1":
                send_msg(udp_socket)
            elif op_num == "2":
                recv_msg(udp_socket)
            elif op_num == "0":
                break
            else:
                print("输入有误,请重新输入...")
        # 5.关闭套接字
        udp_socket.close()
    
    
    if __name__ == "__main__":
        main()

    8.对象实现-UDP聊天器

    import socket
    
    
    class UdpSocket:
    
        def __init__(self):
            # 1. 创建套接字
            self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            # 2. 绑定本地信息
            self.udp_socket.bind(("", 7788))
    
        # 发送消息
        def send_msg(self):
            """获取键盘数据,并将其发送给对方"""
            # 1. 从键盘输入数据
            msg = input("
    请输入要发送的数据:")
            # 2. 输入对方的ip地址
            dest_ip = input("
    请输入对方的ip地址:")
            # 3. 输入对方的port
            dest_port = int(input("
    请输入对方的port:"))
            # 4. 发送数据
            self.udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))
    
        # 接收消息
        def recv_msg(self):
            """接收数据并显示"""
            # 1. 接收数据
            recv_msg = self.udp_socket.recvfrom(1024)
            # recv_msg, recv_ip = self.udp_socket.recvfrom(1024)
            # 2. 解码
            recv_ip = recv_msg[1]
            recv_msg = recv_msg[0].decode("utf-8")
            # 3. 显示接收到的数据
            print(">>>%s:%s" % (str(recv_ip), recv_msg))
    
        # 功能选择
        def start_menu(self):
            while True:
                print("="*30)
                print("1:发送消息")
                print("2:接收消息")
                print("0:退出系统")
                print("="*30)
                op_num = input("请输入要操作的功能序号:")
    
                # 4. 根据选择调用相应的函数
                if op_num == "1":
                    self.send_msg()
                elif op_num == "2":
                    self.recv_msg()
                elif op_num == "0":
                    break
                else:
                    print("输入有误,请重新输入...")
    
        # 关闭套接字
        def __del__(self):
            print("%s对象已回收" % self)
            self.udp_socket.close()
    
    
    def main():
        # 实例化一个udp聊天器对象
        udp_chat = UdpSocket()
        # 启动聊天器
        udp_chat.start_menu()
    
    
    if __name__ == "__main__":
        main()

    9.自定义socket类

    import socket
    
    
    class MySocket(socket.socket):
        """自定义MySocket类继承自socket模块中的socket"""
    
        def __init__(self, encoding="utf-8"):
            # 调用父类中的初始化方法
            super().__init__(type=socket.SOCK_DGRAM)
            # 自定义默认的编码格式
            self.encoding = encoding
    
        def my_sendto(self, send_msg, send_addr):
            return self.sendto(send_msg.encode(self.encoding), send_addr)
    
        def my_recvfrom(self, num):
            recv_msg, recv_addr = self.recvfrom(num)
            return recv_msg.decode(self.encoding), recv_addr

    10.UDP收发数据包

            1.UDP不会发生粘包现象

            2.UDP收发数据包大小取舍
                udp协议本身对一次收发消息数据大小限制是: 65535 - ip包头(20) - udp包头(8) = 65507
                在数据链路层网卡的MTU一般为1500的限制,所以在链路层收发数据包大小限制为: 1500 - ip包头(20) - udp包头(8) = 1472

            3.UDP收发数据包大小结论
                接收数据包: recvfrom(num)  # num < 65507
                发送数据包: sendto(num)
                    num > 65507  # 报错
                    1472 < num < 65507  # 在数据链路层拆包发送,然而udp本身就是不可靠协议
                        # 所以一旦拆包后,造成的多个小数据包在网络传输中,如果丢失任意一个,那么此次数据传输失败
                    num < 1472  # 是比较理想的状态

    4.本地套接字

    1.Linux下常用文件辨识(ls -lh 第一个用来辨识文件类型)

            d: 文件夹

            -: 普通文件

            l: 链接文件

            s: 套接字文件,是一个虚拟文件且大小在显示上永远为0,存在的意义是在Linux/Unix下提供本地进程间通信的一种方式

            p: 管道文件

    2.UNIX本地套接字使用流程

            服务端流程
                1.创建本地套接字: unix_socket = socket(AF_UNIX, SOCK_STREAM)
                2.绑定套接字文件: unix_socket.bind("./unix_socket_test")
                3.监听: unix_socket.listen(128)
                4.等待客户端连接: new_client_socket, client_addr = unix_socket.accept()
                4.接收连接: recv_data = unix_socket.recv(1024)
                5.收发消息: new_client_socket.send()
                6.关闭客户端套接字: new_client_socket.close()
                8.关闭本地套接字: unix_socket.close()

            客户端流程
                1.创建本地套接字: unix_client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                2.连接本地套接字文件: unix_client_socket.connect(unix_server_address)
                3.发送消息: unix_client_socket.send()
                4.接收消息: recv_data = unix_client_socket.recv(1024)
                5.关闭套接字: unix_client_socket.close()

    3.UNIX应用-客户端

    import socket
    import sys
    import traceback
    
    
    def main():
        try:
            # 创建unix本地套接字
            unix_client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            # 文件已经被服务端创建,且和服务端使用的是同一个socket文件
            unix_server_address = "./unix_socket_test"
            # 连接本地套接字文件
            unix_client_socket.connect(unix_server_address)
        except socket.error:
            traceback.print_exc()
            sys.exit(1)
        while True:
            # 发送数据
            send_data = input("请输入要发生的数据:")
            if send_data:
                unix_client_socket.sendall(send_data.encode("utf-8"))
                # 接收服务器发送过来的数据
                recv_data = unix_client_socket.recv(1024)
                print("接收到的数据为:%s" % recv_data.decode("utf-8"))
            else:
                break
        # 关闭套接字
        unix_client_socket.close()
    
    
    if __name__ == "__main__":
        main()

    4.UNIX应用-服务端

    import socket
    import os
    
    
    def main():
        # 确定那个文件作为通讯文件
        unix_server_address = "./unix_socket_test"
        # 判断通讯文件是否存在,如果存在则删除文件
        if os.path.exists(unix_server_address):
            os.unlink(unix_server_address)
    
        # 创建一个unix本地套接字
        unix_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        # 绑定本地套接字文件
        unix_socket.bind(unix_server_address)
        # 监听
        unix_socket.listen(128)
    
        # 循环目的: 多次调用accept等待客户端连接,为多个客服端服务
        while True:
            print("等待一个新的客户端的到来...")
            new_client_socket, client_addr = unix_socket.accept()
            print("客户端' %s '已经到来" % str(client_addr))
            # 循环目的: 为同一个客服端服务多次
            while True:
                # 接收客户端发送过来的请求
                recv_data = new_client_socket.recv(1024)
    
                # recv解堵塞的两种方式: 1.客户端发送过来数据,2.客户端调用了close
                if recv_data:
                    print("客户端' %s '发送过来的请求是: %s" % (str(client_addr), recv_data.decode("utf-8")))
                    # 回送数据给客户端表示响应客户端的请求
                    send_data = "----ok----Request accepted"
                    new_client_socket.send(send_data.encode("utf-8"))
                else:
                    break
            # 5.关闭客户端套接字
            new_client_socket.close()
    
        # 关闭本地套接字
        unix_socket.close()
    
    
    if __name__ == "__main__":
        main()

    5.TCP和UDP区别

            1.TCP传输数据使用字节流方式传输,UDP是数据包

            2.TCP会产生粘包现象,UDP不会

            3.TCP对网络条件要求高,UDP不需要

            4.TCP编程可以保证传输的可靠性,UDP不保证

            5.TCP使用listen和accept,UDP不需要

            6.收发消息
                TCP使用recv send sendall # sendall用法和send一样,只是返回值不同,成功返回None, 失败则产生异常
                UDP使用recvfrom sendto

  • 相关阅读:
    数据库主键策略
    经历alidns在国外的严重延时
    无题 MVC
    Jquery操作select,左右移动,双击移动 取到所有option的值
    Computop支付网关(一) credit Card
    [转载] 测试的道理
    微信砍价活动总结
    transform,变换
    transition,过渡效果
    文字居底方法
  • 原文地址:https://www.cnblogs.com/tangxuecheng/p/13597035.html
Copyright © 2011-2022 走看看