• python的网络编程(socket)


    一、Socket介绍

    • Socket套接字,python中提供socket.py标准库,非常底层的接口库
    • Socket是一种通用的网络编程接口,和网络层次没有一一对应的关系

    1、Socket类型

    •  SOCK_STREAM :面向连接的流套接字,默认值,TCP协议
    •  SOCK_DGRAM : 五连接的数据报文套接字,UDP协议

    二、TCP编程

    • Socket编程,需要两端,一般来说需要一个服务端,一个客户端,服务端称为Sever,客户端称为lient

    1、TCP服务端

     服务器端编程步骤:

    •  创建Socket对象
    •  绑定IP地址Address和端口Port,bind()方法,IPv4地址为一个二元组('ip地址字符串',Port)
    •  开始监听,将在指定的IP的端口上监听,listen()方法
    •  获取用于传送数据的Socket对象:socket.accept() ->(socket object, address info)
    •  accept方法阻塞等待客户端建立连接,返回一个新的Socket对象和客户端地址的二元组
    •  地址是远程客户端的地址,IPv4中它是一个二元组(clientaddr,port)
    s = socket.socket()  #创建socket对象
        s.bind(('127.0.0.1',8888))  # 一个二元组
        s.listen()  #开始监听
    
        #开启一个连接
        s1, info = s.accept()  #阻塞直到客户端成功建立连接,返回一个socket对象和客户端地址
    
        #使用缓冲区获取数据
        data = s1.recv(1024)
        print(data, info)
        s1.send(b'magedu.com ack')

    2、 写一个群聊程序

    聊天工具是CS程序,C是每一个客户端,S是服务器端,服务器端应该具有的功能:

    •   启动服务,包括绑定地址和端口,监听
    •   建立连接,能和多个客户端建立连接,接收不同用户的信息
    •   分发,将接收的某个用户的信息转发到已连接的所有客户端
    •   停止服务,记录连接的客户端,服务器端应该对应一个类
    举例
        import logging
        import socket
        import threading
        import datetime
    
        FORAMT = '%(asctime)s %(thread)d %(message)s'
        logging.basicConfig(level=logging.INFO, format=FORAMT)
    
        class ChatSever:
            def __init__(self, ip='127.0.0.1', port=8888):  # 启动服务
                self.sock = socket.socket()
                self.addr = (ip, port)
                self.clients = {}  #客户端
                self.event = threading.Event()
    
            def start(self):  #启动监听
                self.sock.bind(self.addr)
                self.sock.listen()   # 监听
                #accept会阻塞主线程,所以要开一个新线程
                print('------------')
                threading.Thread(target=self.accept).start()
    
            def accept(self):  # 多人连接
                sock, client = self.sock.accept()  # 阻塞
                print(111,sock, client)
                self.clients[client] = sock  # 添加到客户端字典
                #准备接收数据,recv是阻塞的,开启新的线程
                threading.Thread(target=self.recv, args=(sock, client)).start()
    
            def recv(self, sock:socket, client):  # 接收客户端数据
                while not self.event.is_set():
                    data = sock.recv(1024)  #阻塞到数据到来
                    msg = "{:%Y/%m/%d %H:%M:%S} {} : {}
    {}
    ".format(datetime.datetime.now(), *client, data.decode())
                    print('msg')
                    logging.info(msg)
                    for s in self.clients.values():
                        s.send(msg.encode())
    
            def stop(self):  #停止服务
                for s in self.clients.values():
                    s.close()
                self.sock.close()
                self.event.set()
    
        cs = ChatSever()
        cs.start()
    
        e = threading.Event()
        while not e.wait(1):
            cmd = input('>>>').strip()
            if cmd == "quit":
                cs.stop()
                e.wait(3)
                break
    
    客户端主动断开带来的问题
        服务端知道自己何时断开,如果客户端断开,服务器不知道
        所以,好的做法是,客户端开发出特殊消息通知服务器端断开连接
        但是,如果客户端主动断开,服务端主动发送一个空消息,超时返回异常,捕获异常并清理连接
        即使为客户端提供了断开命令,也不能保证客户端会使用它断开连接,但是还是要增加这个退出功能
    为下面代码增加功能
        增加客户端退出命令
        增加多客户端支持
    
    
        import logging
        import socket
        import threading
        import datetime
    
        FORAMT = '%(asctime)s %(thread)d %(message)s'
        logging.basicConfig(level=logging.INFO, format=FORAMT)
    
        class ChatSever:
            def __init__(self, ip='127.0.0.1', port=8888):  # 启动服务
                self.sock = socket.socket()
                self.addr = (ip, port)
                self.clients = {}  #客户端
                self.event = threading.Event()
    
            def start(self):  #启动监听
                self.sock.bind(self.addr)
                self.sock.listen()   # 监听
                #accept会阻塞主线程,所以要开一个新线程
                print('------------')
                threading.Thread(target=self.accept).start()
    
            def accept(self):  # 多人连接
                sock, client = self.sock.accept()  # 阻塞
                print(111,sock, client)
                self.clients[client] = sock  # 添加到客户端字典
                #准备接收数据,recv是阻塞的,开启新的线程
                threading.Thread(target=self.recv, args=(sock, client)).start()
    
            def recv(self, sock:socket, client):  # 接收客户端数据
                while not self.event.is_set():
                    data = sock.recv(1024)  #阻塞到数据到来
                    msg = data.decode().strip()
                    #客户端退出命令
                    if msg == "quit":
                        self.clients.pop(client)
                        sock.close()
    
                        logging.info("{} quits".format(client))
                        break
                    msg = "{:%Y/%m/%d %H:%M:%S} {} : {}
    {}
    ".format(datetime.datetime.now(), *client, data.decode())
                    print('msg')
                    logging.info(msg)
                    for s in self.clients.values():
                        s.send(msg.encode())
    
            def stop(self):  #停止服务
                for s in self.clients.values():
                    s.close()
                self.sock.close()
                self.event.set()
    
        cs = ChatSever()
        cs.start()
    
        e = threading.Event()
        while not e.wait(1):
            cmd = input('>>>').strip()
            if cmd == "quit":
                cs.stop()
                e.wait(3)
                break

    3、其他方法

    •  socket.recv() : 获取数据,默认是阻塞的方式
    •  socket.recvfrom() : 获取数据,返回一个二元组
    •  socket.recv_info() : 获取到nbytes的数据后,存储到buffer中
    创建一个与该套接字相关连的文件对象
    使用makefile改写群聊类
    
    
    import logging
    import socket
    import threading
    import datetime
    
    FORAMT = '%(asctime)s %(thread)d %(message)s'
    logging.basicConfig(level=logging.INFO, format=FORAMT)
    
    class ChatSever:
        def __init__(self, ip='127.0.0.1', port=8888):  # 启动服务
            self.sock = socket.socket()
            self.addr = (ip, port)
            self.clients = {}  #客户端
            self.event = threading.Event()
    
        def start(self):  #启动监听
            self.sock.bind(self.addr)
            self.sock.listen()   # 监听
            #accept会阻塞主线程,所以要开一个新线程
            print('------------')
            threading.Thread(target=self.accept).start()
    
        def accept(self):  # 多人连接
            while not self.event.is_set():
                sock, client = self.sock.accept()  # 阻塞
                print(111,sock, client)
                f = sock.makefile(mode='rw')
                self.clients[client] = f  # 添加到客户端字典
                #准备接收数据,recv是阻塞的,开启新的线程
                threading.Thread(target=self.recv, args=(f, client), name='recv').start()
    
        def recv(self, f, client):  # 接收客户端数据
            while not self.event.is_set():
                try:
                    data = f.readline() # 阻塞到换行符
                except Exception as e:
                    logging.error(e)  #有任何异常保证退出
                    data = 'quit'
                msg = data.strip()
    
                #客户端退出命令
                if msg == "quit":
                    self.clients.pop(client)
                    f.close()
    
                    logging.info("{} quits".format(client))
                    break
                msg = "{:%Y/%m/%d %H:%M:%S} {} : {}
    {}
    ".format(datetime.datetime.now(), *client, data)
                print('msg')
                logging.info(msg)
                for s in self.clients.values():
                    s.wtitelines(msg)
                    s.flush()
    
        def stop(self):  #停止服务
            for s in self.clients.values():
                s.close()
            self.sock.close()
            self.event.wait(3)
            self.event.set()
    
    def show_thread(e:threading.Event):
        while not e.wait(3):
            logging.info(threading.enumerate())
    
    
    def main():
        e = threading.Event()
        cs = ChatSever()
    
        cs.start()
        threading.Thread(target=show_thread, args=(e,), name='showthread').start()
    
        while not e.wait(1):
            cmd = input('>>>').strip()
            if cmd == "quit":
                cs.stop()
                e.wait(3)
                break
    
    if __name__ == "__main__":
        main()
    
    
      Socket太底层了,实际开发中很少使用这么底层的接口
      
      


    三、TCP客户端

    •  客户端编程步骤
    •  创建Socket对象
    •  连接到远程服务端的ip和port,connect()
    •  传输数据:使用send,recv方法,接收数据
    •  关闭连接,释放资源
    import logging
    import socket
    import threading
    import datetime
    
    FORAMT = '%(asctime)s %(thread)d %(message)s'
    logging.basicConfig(level=logging.INFO, format=FORAMT)
    
    
    class ChatClient:
        def __init__(self, ip='127.0.0.1', port=8888):
            self.sock = socket.socket()
            self.addr = (ip, port)
    
            self.event = threading.Event()
            self.start()
    
        def start(self):  #启动对远端的连接
            self.sock.connect(self.addr)
    
            #准备接收数据,recv是阻塞的,开启新的线程
            threading.Thread(target=self._recv, name="recv").start()
    
        def _recv(self): #接收客户端数据
            while not self.event.is_set():
                try:
                    data = self.sock.recv(1024)  #阻塞
                except Exception as e:
                    logging.error(e)
                    break
    
                msg = "{:%Y/%m/%d %H:%M:%S} {} : {}
    {}
    ".format(datetime.datetime.now(), *client, data)
                logging.info(msg)
    
        def send(self, msg:str):
            data = "{}
    ".format(msg.strip()).encode()
            self.sock.send(data)
    
        def stop(self):
            logging.info("{} broken".format(self.addr))
            self.sock.close()
            self.event.wait(3)
            self.event.set()
            logging.info("client stops")
    
    def show_thread(e:threading.Event):
        while not e.wait(3):
            logging.info(threading.enumerate())
    def main():
        e = threading.Event()
        cc = ChatClient()
        while True:
            msg = input('>>>')
            if msg.strip() == "quit":
                cs.stop()
                break
            cc.send(msg)
    
    
    
    if __name__ == '__main__':
        main()

    四、Socketserver

    • socket编程过于底层,编程虽然有套路,但是想要写出健壮的代码还是比较困难的,所以很多语言都对socket底层API进行封装,Python的封装就是socketserver模块
    • 网络服务编程框架,便于企业级快速开发

    1、类的继承关系

    •  SocketSever简化了网络服务器的编写
    •  它有4个同步类:TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer
    •  2个Mixin类ForkingMixln和ThreadMixln类来支持异步

    2、编程接口

    •  socketserver.BaseServer(sever_address, RequestHandleClass)
    •  需要提供服务器绑定的地址信息,和用于处理请求的RequestHandlerClass类,RequestHandleClass类必须是BaseRequestHandler类的子类

    3、BaseRequestHandler类

    •  和用户连接的用户请求处理类,Server实例接收用户请求后,最后会实例化这个类;它被初始化时,送入3个构造参数:request,client_address,server
    •  以后可以在BaseRequestHandler类的实例上通过
    •  self.request是客户端的连接的socket对象
    •  self.server是TCPserver本身
    •  self.client_address是客户端地址
    •  它会一次调用3个函数,子类可以覆盖
    import threading
        import socketserver
    
        class MyHandler(socketserver.BaseRequestHandler):
            def handle(self):
                print(self.server, self.request, self.client_address)
                print('{} handler'.format(self.__class__))
                print(self.__class__)
                print(type(self).__dict__)
                print(self.__class__.__bases__[0].__dict__)
                print(threading.enumerate(), threading.current_thread())
    
        addr = ('127.0.0.1', 8888)
        server = socketserver.ThreadingTCPServer(addr, MyHandler)
        server.serve_forever()
            
        
        测试结果说明,handler方法和socket的accept对应,用户连接请求过来后,建立连接并生成一个
        socket对象保存在self.request中,客户端地址保存在self.client_address中,

    4、创建服务器需要几个步骤

    •  必须通过BaseRequestHandler类进行子类化并覆盖其他handle()方法来创建请求处理程序类,此方法将处理传入请求
    •  必须实例化一个服务器类,将它传递给服务器的地址和请求处理程序类
    •  然后调用服务器对象的handle_request()或server_forever()方法
    •  调用server_close()关闭套接字,shutdown()方法,等待停止server_forever()

    5、实现EchoServer

    • 顾名思义,Echo来什么消息回显什么消息,客户端发来什么信息,返回什么信息
    import threading
    import socketserver
    
    class EchoHandler(socketserver.BaseRequestHandler):
        def finish(self):
            super().finish()
            self.event.set()  # 清理工作
    
        def setup(self):
            super().setup()
            self.event = threading.Event()  # 初始化工作
    
        def handle(self):
            super().handle()
    
            while not self.event.is_set():
                data = self.request.recv(1024).decode()
                msg = "{} {}".format(self.client_address, data).encode()
                self.request.send(msg)
            print('End')
    
    addr = ('127.0.0.1', 8888)
    server = socketserver.ThreadingTCPServer(addr, EchoHandler)
    
    server_thread = threading.Thread(target=server.serve_forever, daemon=True)
    server_thread.start()
    
    try:
        while True:
            cmd = input('>>>')
            if cmd.strip() == 'quit':
                break
    except Exception as e:
        print(e)
    except KeyboardInterrupt:
        print('Exit')
    finally:
        server.shutdown()
        server.server_close()
    
        
  • 相关阅读:
    v:bind指令对于传boolean值的注意之处
    vue项目依赖的安装
    直接将文件存放到服务器tomcat中,就可以直接访问文件等
    什么情况下用vue.use方法
    创建Vue项目及其内容分析
    linux安装nginx以及如何启动,暂停,停止操作
    vue项目怎么搭建到云服务器上
    Nginx安装
    UNP学习笔记(第三十章 客户/服务器程序设计范式)
    UNP学习笔记(第二十六章 线程)
  • 原文地址:https://www.cnblogs.com/jiangzuofenghua/p/11453886.html
Copyright © 2020-2023  润新知