zoukankan      html  css  js  c++  java
  • Python中socket编程

    1、Socket介绍:

    Python中提供socket.py标准库,非常底层的接口库。

    Socket是一种通用的网络编程接口,和网络层次没有一一对应关系。

    跨操作系统的。紧密结合tcp和udp来使用的。

    接口简单,但是背后的原理不简单,ip加tcp,通过插两端。通过socket通道;连接程序。

    建立关联。

    apc库。

    加端口是因为应用程序太多了。绑定ip地址,作为假端口。

    端口是由谁管理的

    一般都是tcp和udp编程。Socket基本的,chatserver。

    协议族:AF 表示address family ,用于socket()的第一个参数。

    名称

    含义

    AF_INET

    Ipv4

    AF_INET6

    Ipv6

    AF_UNIX

    Unix domain socket,windows没有

    第三个本机使用效率不错的,通用的话就是应该进一步考虑了。

    Socket类型:

    名称

    含义

    Sock_STREAM

    面向连接的流套接字,默认值,tcp协议

    SOCK_DGRAM

    无连接的数据报文套接字,UDP协议

    2、tcp编程

    Tcp编程是IO密集型。多线程处理的问题。

    Server端:1、要有socket接口。2、找ip地址和端口绑定。3、监听端口。4、accept,接受socket,创建小的socket端。直接和应用程序连接在一起的。5、读取用户数据。6、写,发送数据。7、数据完成后断开。

    Client端:1、要有socket端,主动连接别人。2、connect建立连接,有socket,和端口和ip。

    3、写,发送数据。4、读取服务器端数据。5、数据完成后关闭了。

    服务器端没有响应了,tcp协议管理。

    Socket会占用描述符,每一个都会创建一个文件描述符。客户端看到只有的是一个。

    Import socket

    Server = socket.socket()   socket接口。

    Server.bind(ipaddr) 

    Server.bind((‘0.0.0.0’,9999))绑定

    Server.listen()监听

    S2,iP2 = server.accept()

    S1.recv(1024)缓冲区大小。

    S1.send(b’ack’)

    decode()解码
    encode()编码

    创建socket对象,

    一个ip和一个端口只能被一个程序使用。端口只能进行一次监听,绑定,再次监听或者绑定的话就会报错。

    使用完毕后必须进行关闭。

    应用:

    简单的实现:解决中文的情况,编解码的时候全部注明统一编码和解码。

    import socket

    server =socket.socket()
    server.bind(('127.0.0.1',99))
    server.listen(99)

    s1,ip = server.accept()
    print(s1)
    print(ip)

    while True:
        data = s1.recv(1024)
        print(data)
        s1.send('ack{}'.format(data.decode('gbk')).encode('gbk'))


    s1.close()

    server.close()
     
     
     
     
     

    <socket.socket fd=192, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 99), raddr=('127.0.0.1', 50149)>

    ('127.0.0.1', 50149)

    b'xd6xd0xb9xfa'

    客服端和服务器端建立间接,需要建立一条socket通道,每次建立连接,listen的端口都会和客户端建立的新的端口,因为连接端口就会阻塞,所以需要建立新的端口。隐士的还是看到连接的端口还是原来的socket。

    应用:写一个群聊程序

    1)第一步:import threading
    import logging
    import socket

    FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
    logging.basicConfig(format=FORMAT,level=logging.INFO)


    class ChatServer:
        def __init__(self,ip='127.0.0.1',port=999):
            self.addr = (ip , port)
            self.socket = socket.socket()

        def start(self):
            self.socket.bind(self.addr)
            self.socket.listen()

            threading.Thread(target=self.accept,name='accept').start()

        def accept(self):
            while True:
                s,ip = self.socket.accept()
                logging.info(s)
                logging.info(ip)
                threading.Thread(target=self.connt,name='connt',args=(s,)).start()

        def connt(self,sockets):
            while True:
                data = sockets.recv(1024)
                logging.info(data)
                sockets.send('ack-{}'.format(data.decode()).encode())


        def stop(self):
            self.socket.close()

    cs = ChatServer()
    cs.start()

    2)第二步

    把所有的客户端的ip和端口保留在一个容器里面,一个客户端发送消息到服务器端,服务器端,进行消息的转发等。

    import threading
    import logging
    import socket

    FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
    logging.basicConfig(format=FORMAT,level=logging.INFO)


    class ChatServer:
        def __init__(self,ip='127.0.0.1',port=999):
            self.addr = (ip , port)
            self.socket = socket.socket()
            self.cliens = {}

        def start(self):
            self.socket.bind(self.addr)
            self.socket.listen()

            threading.Thread(target=self.accept,name='accept').start()

        def accept(self):
            while True:
                s,ip = self.socket.accept()
                logging.info(s)
                logging.info(ip)
                self.cliens[ip] = s
                threading.Thread(target=self.connt,name='connt',args=(s,)).start()

        def connt(self,sockets):
            while True:
                data = sockets.recv(1024)
                logging.info(data)
                sockets.send('ack-{}'.format(data.decode('gbk')).encode('gbk'))
                for s in self.cliens.values():
                    s.send('ack1-{}'.format(data.decode('gbk')).encode('gbk'))


        def stop(self):
            for s in self.cliens.values():
                    s.close()

            self.socket.close()

    cs = ChatServer()
    cs.start()

    其他方法:

    名称

    含义

    Socket.recv(bufsize[,flags])

    获取数据,默认阻塞的方式

    Socket.recvfrom(bufsize[,flags])

    获取数据,返回一个二元组(bytes,address)

    Socket.recv_into(buffer[,nbytes[,flags]])

    获取nbytes的数据后,存储到buffer中,如果nbytes没有指定或0,将buffer大小的数据存入buffer中,返回接受的字节数

    Socket.recvfrom_into(buffer[,nbytes[,flags]])

    获取数据,返回一个二元组(bytes,address)到buffer中

    Socket.send(bytes[,flags])

    TCP 发送数据

    Socket.sendall(bytes[,flags])

    TCP发送全部数据,成功返回None

    Socket.sendto(string[,flag],address)

    UDP发送数据

    Socket.sendfile(file,offset=0,count=None)

    发送一个文件直到EOF,使用高性能的os.sendfile机制,返回发送的字节数,如果win下不支持sendfile,或者不是普通文件,使用send()发送文件,offset告诉其实位置,3.5版本开始。

    Makefile

    Socket.makefile(mode=’r’,buffering=None,*,encoding=None,errors=None,newline=None)创建一个与该套接字相关连的文件对象,将recv方法看做读方法,将send方法看做是写方法。

    异常不捕获会导致当前线程异常退出,不捕获直接到最外层,也就是主线程。

    3、客户端tcp编程

    import socket

    raddr = ('127.0.0.1',999)
    client = socket.socket()
    client.connect(raddr)

    while True:
        data =  client.recv(1024)
        print(data)
        if data.strip() == b'quit':
            break
        client.send(b'ack')
    client.close()
    import threading
    import socket
    import logging
    import datetime
    FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
    logging.basicConfig(format=FORMAT,level=logging.INFO)


    class ChatClient:
        def __init__(self,ip='127.0.0.1',port=8080):
            self.clients = socket.socket()
            self.raddr = (ip,port)
            self.event = threading.Event()


        def start(self):
            self.clients.connect(self.raddr)
            self.send('I am ok')

            threading.Thread(target=self.recive,name='receive').start()

        def recive(self):
            while not self.event.is_set():
                data = self.clients.recv(1024)
                # logging.info(data)
                if data.strip() == b'quit':
                    break
                message = '{:%Y/%m/%d %H:%M:%S}{}:{} {} '.format(datetime.datetime.now(),*self.raddr,data.strip())
                logging.info(message)
        def send(self,message:str):
            data = '{} '.format(message.strip()).encode()
            self.clients.send(data)

        def stop(self):
            self.event.set()
            self.clients.close()

    def main():
        cc = ChatClient()
        cc.start()

        while True:
            cmd = input('>>>>')
            if cmd.strip() == 'quit':
                cc.stop()
                break
            cc.send(cmd)
            logging.info(threading.enumerate())

    if __name__ == '__main__':
        main()

    4、udp编程

    同一个协议下绑定同一个端口,才会有端口冲突。Udp不会真的连接。

    Import socket

    Server=socket.socket(type=)

    Server.bind(laddr)绑定本地自己用的。

    Server.recv(1024)

    Data ,raddr = Server.recvfrom(1024)

    Server.sendto(b’back’,raddr);后面的ip可以是不存在的,都会发送出去的。

    Server.connect(raddr)后面才可以使用send。一般都是客户端向服务端连接用的。

    服务器端代码:

    import socket
    import threading
    import logging

    FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
    logging.basicConfig(format=FORMAT,level=logging.INFO)

    class ChatServer:

        def __init__(self,ip='127.0.0.1',port=9999):
            self.addr = (ip,port)
            self.sockets = socket.socket(type=socket.SOCK_DGRAM)
            self.event = threading.Event()

        def start(self):
            self.sockets.bind(self.addr)
            threading.Thread(target=self.recv,name='recv').start()

        def recv(self):
            while not self.event.is_set():
                data,laddr = self.sockets.recvfrom(1024)
                logging.info(data)
                logging.info(laddr)

                msg = 'ack.{}from{}{}'.format(data.decode(),*laddr)
                masg1 = msg.encode()
                logging.info(msg)
                self.sockets.sendto(masg1,laddr)

        def stop(self):
            self.sockets.close()
            self.event.set()


    def main():
        cs = ChatServer()
        cs.start()


        while True:
            cmd = input('>>>>')
            if cmd.strip() == 'quit':
                cs.stop()
                break
            logging.info(threading.enumerate())

    if __name__ == '__main__':
        main()

    客户端代码:

    import threading
    import socket
    import logging
    import datetime

    FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
    logging.basicConfig(format=FORMAT,level=logging.INFO)


    class ChatUdpClient:
        def __init__(self,ip='127.0.0.1',port=8080):
            self.addr = (ip,port)
            self.cucsocket = socket.socket(type=socket.SOCK_DGRAM)
            self.event = threading.Event()

        def start(self):
            self.cucsocket.connect(self.addr)
            threading.Thread(target=self.recive,name='recive').start()

        def recive(self):
            while not self.event.is_set():
                data,raddr =self.cucsocket.recvfrom(1024)
                logging.info(data)
                logging.info(raddr)

                message = '{}from{}{}'.format(data.decode(),*raddr)

        def send(self,message:str):
            self.cucsocket.sendto(message.encode(), self.addr)

        def stop(self):
            self.cucsocket.close()
            self.event.set()

    def main():
        cuc = ChatUdpClient()
        cuc.start()


        while True:
            cmd = input('>>>')
            if cmd.strip() == 'quit':
                cuc.stop()
                break
            cuc.send(cmd)
            logging.info(threading.enumerate())

    if __name__ == '__main__':
        main()

    ack机制和心跳heartbeat。

    心跳机制:

    1)一般来说客户端定时发往服务器端,服务器端并不需要ack回复客户端,只是需要记录客户端活着就可以了。(严格考虑时间的问题)

    2)服务器端定时发往客户端,一般需要客户端ack响应来表示活着,如果没有收到ack的客户端,服务端移除其信息,这种实现复杂,用的较少。

    3)也可以是双向都发心跳包的,用的情况下较少。

    为True的时候就不进入循环了。

    心跳包客户端代码:

    import threading
    import socket
    import logging
    import datetime

    FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
    logging.basicConfig(format=FORMAT,level=logging.INFO)


    class ChatUdpClient:
        def __init__(self,ip='127.0.0.1',port=8080):
            self.addr = (ip,port)
            self.cucsocket = socket.socket(type=socket.SOCK_DGRAM)
            self.event = threading.Event()

        def start(self):
            self.cucsocket.connect(self.addr)
            threading.Thread(target=self.sen_hb,name='hb').start()
            threading.Thread(target=self.recive,name='recive').start()

        def recive(self):
            while not self.event.is_set():
                data,raddr =self.cucsocket.recvfrom(1024)
                logging.info(data)
                logging.info(raddr)

                message = '{}from{}{}'.format(data.decode(),*raddr)

        def send(self,message:str):
            self.cucsocket.sendto(message.encode(), self.addr)

        def sen_hb(self):
            self.send('hb')

        def stop(self):
            self.cucsocket.close()
            self.event.set()

    def main():
        cuc = ChatUdpClient()
        cuc.start()


        while True:
            cmd = input('>>>')
            if cmd.strip() == 'quit':
                cuc.stop()
                break
            cuc.send(cmd)
            logging.info(threading.enumerate())

    if __name__ == '__main__':
        main()

    心跳包服务器端代码:

    import threading
    import socket
    import logging
    import datetime

    FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
    logging.basicConfig(format=FORMAT,level=logging.INFO)


    class ChatUdpServer:
        def __init__(self,ip='127.0.0.1',port=8080,interval=10):
            self.addr = (ip,port)
            self.udpsocket = socket.socket(type=socket.SOCK_DGRAM)
            self.event = threading.Event()
            self.interval = interval
            self.clients = {}

        def start(self):
            self.udpsocket.bind(self.addr)
            threading.Thread(target=self.revice,name='recive').start()

        def revice(self):
            while not self.event.is_set():
                lset = set()
                data,raddr = self.udpsocket.recvfrom(1024)
                logging.info(data)
                logging.info(raddr)

                current = datetime.datetime.now().timestamp()
                if data.strip() == b'hb':
                    self.clients[raddr]=current
                    continue
                elif data.strip() == b'quit':  #有可能发出来的数据不在clients。
                    self.clients.pop(raddr,None)
                    logging.info('{}leaving'.format(raddr))
                    continue
                self.clients[raddr] = current
               
                message = '{}form{}{}'.format(data.decode(),*raddr)
               
               
                for c ,stamp in self.clients.items():
                    if current - stamp >self.interval:
                        lset.add(c)
                    else:
                        self.udpsocket.sendto(message.encode(), raddr)
               
                for c in lset:
                    self.clients.pop(c)


        def stop(self):
            self.event.set()
            self.udpsocket.close()

    def main():

        cus = ChatUdpServer()
        cus.start()

        while True:
            cmd = input('>>>>')
            if cmd == 'quit':
                cus.stop()
                break
            logging.info(threading.enumerate())



    if __name__ == '__main__':
        main()

    正在迭代字典的时候不能进行pop。。

    将其添加到set中,之后再进行pop。

    Udp协议应用:

    是无连接协议,基于以下假设:网络足够好 消息不会丢包,包不会乱序。

    但是,即使在局域网,也不能保证不丢包,而且包到达不一定有序。

    应用场景在视频、音频传输,一般来说,丢些包,问题不大,最多丢图像,听不清话语,可以重新发话语来解决,海量采集数据,例如传感器发来的数据,丢十几,几百条没有太大问题,DNS协议,数据内容小,一个包能 查询到结果,不存在乱序,丢包,重新请求解析。

    Udp性能优于tcp,但是可靠性场所适用于tcp。

    Udp广域网。

    二、Socketserver

    Socket编程过于底层,Python中对api进行封装的就是socketserver模块,是网络编程框架,便于企业快速开发;

    类的继承关系

    Socketserver简化了网络服务器的编写。

    有四个同步类:TCPserver,UDPserver,Unixstreamserver,Unixdatagramserver

    2个Mixin类:ForkingMixin和threadingMixin类,用来支持异步

    Class forKingUDPserver(forKingMixin,UDPserver):pass

    Class forKingTCPserver(forKingMixin,TCPPserver):pass

    Class ThreadingUDPserver(ThreadingMixin,UDPserver):pass

    Class ThreadingTCPserver(ThreadingMixin,TCPserver):pass

    class BaseServer:
            def __init__(self, server_address, RequestHandlerClass):
             """Constructor.  May be extended, do not override."""
            
    self.server_address = server_address
             self.RequestHandlerClass = RequestHandlerClass
             self.__is_shut_down = threading.Event()
             self.__shutdown_request = False
            def finish_request(self, request, client_address):
             """Finish one request by instantiating RequestHandlerClass."""
            
    self.RequestHandlerClass(request, client_address, self)

    baserequesthandler类:

    他是和用户连接的用户请求处理类的基类,定义为baserequesthandler(request,client_address,server)

    服务器端server实例接受用户的请求偶,最后会实例化这个类。

    被初始化以后,送入三个构造参数,request,client_address,server 本身。

    以后就可以在baserequesthandler类的实例上使用以下属性:

    Self.request是和客户端的连接的socket对象,

    Self.server是TCPserver本身

    Self.client_address是客户端地址。

    这个类在初始化的过程中,会依次调用3个方法,子类可以覆盖这些方法

    class BaseRequestHandler:

        """Base class for request handler classes.

        This class is instantiated for each request to be handled.  The
        constructor sets the instance variables request, client_address
        and server, and then calls the handle() method.  To implement a
        specific service, all you need to do is to derive a class which
        defines a handle() method.

        The handle() method can find the request as self.request, the
        client address as self.client_address, and the server (in case it
        needs access to per-server information) as self.server.  Since a
        separate instance is created for each request, the handle() method
        can define other arbitrary instance variables.

        """

       
    def __init__(self, request, client_address, server):
            self.request = request
            self.client_address = client_address
            self.server = server
            self.setup()
            try:
                self.handle()
            finally:
                self.finish()

        def setup(self):
            pass

        def handle(self):
            pass

        def finish(self):
            pass
    import threading
    import socketserver


    class Myhandler(socketserver.BaseRequestHandler):
        def handle(self):
            print('--------')
            print(self.server)
            print(self.request)
            print(self.client_address)
            print(self.__dict__)
            print(self.server.__dict__)

            print(threading.enumerate())
            print(threading.current_thread())

    addr = ('127.0.0.1',8080)
    server = socketserver.ThreadingTCPServer(addr,Myhandler)

    server.serve_forever()
    import threading
    import socketserver
    import logging

    FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
    logging.basicConfig(format=FORMAT,level=logging.INFO)

    class Myhandler(socketserver.BaseRequestHandler):
        def handle(self):
            print('--------')
            print(self.server)
            print(self.request)
            print(self.client_address)
            print(self.__dict__)
            print(self.server.__dict__)

            print(threading.enumerate())
            print(threading.current_thread())
            for i in range(2):
                data = self.request.recv(1024)
                logging.info(data)


    addr = ('127.0.0.1',8080)
    server = socketserver.ThreadingTCPServer(addr,Myhandler)

    server.serve_forever()

    类命名:

    编程接口:

    Socketserver.baseserver(server_address,RequestHandlerclass)

    需要提供服务器绑定的地址信息,和用于请求处理请求的requesthandlerclass类。

    Requesthandlerclass类必须是baserequesthandler类的子类,在baseserver中代码如下:

    创建、传端口、handler。

    ThreadingTCPServer。多线程,异步的,同时处理多个连接,

    TCPServer  TCP的,串行的。同步的,一个处理完毕后,才能处理下一个。只有主线程。

    创建服务器需要几个步骤:

    1)从baseRequestHandler类派生出子类,并覆盖其handler()方法来创建请求处理程序类,此方法将处理传入处理。

    2)实例化一个服务器类,传参服务器的地址和请求处理类。

    3)调用服务器实例的handle_request()或server_forever()方法。

    4)调用server_close()关闭套接字。

    实现echoserver

    import threading
    import socketserver
    import logging
    import sys


    FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
    logging.basicConfig(format=FORMAT,level=logging.INFO)


    class EchoHandler(socketserver.BaseRequestHandler):
        clients = {}

        def setup(self):
            self.event = threading.Event()
            self.clients[self.client_address] = self.request


        def finish(self):
            self.event.set()

        def handle(self):
            while not self.event.is_set():
                data = self.request.recv(1024)
                logging.info(data)
                if data == b'' or data =='quit':
                    break
                msg = '{}'.format(data.decode())
                for c in self.clients.values():
                    c.send(msg.encode())

    addr = ('127.0.0.1',8080)
    server = socketserver.ThreadingTCPServer(addr,EchoHandler)

    # server.serve_forever()
    t = threading.Thread(target=server.serve_forever,name='encho')
    t.start()

    if __name__ == '__main__':

        try:
            while True:
                cmd = input('>>>')
                if cmd.strip() == 'quit':
                    server.server_close()
                    break
                logging.info(threading.enumerate())
        except Exception as e:
            logging.info(e)

        finally:
            print('exit')
            sys.exit(0)

    解决客户端主动断开连接服务器端报错的方式:客户端主动断开,会导致recv方法会立即返回一个空bytes,并没有同事抛出异常,当循环到recv这一句的时候就会抛出异常,所以,可以通过判断data数据是否为空客户端是否断开。

    总结:

    为每一个连接提供requesthandlerclass类实例,依次调用setup、handler、finish方法,且使用了try..finally结构,保证finish方法一定被调用、这些方法一次执行完毕,如果想维持这个连接和客户端通信,就需要在handler函数中循环。

    所持socketserver模块提供不同的类,但是编程接口一样的,即使是多进程、多线程的类也是一样,大大减少了编程的难度。

  • 相关阅读:
    Largest Rectangle in Histogram
    Vertica环境安装R-Lang包提示缺少libgfortran.so.1
    Sybase数据库收集表及其索引的统计信息
    Linux 内存管理
    ORA-01439: 要更改数据类型, 则要修改的列必须为空
    MySQL ibdata1文件迁移
    exp/imp 参数说明,中英对照
    expdp/impdp 参数说明,中英对照
    Oracle ASM diskgroup在主机重启后启动失败
    Linux NFS 服务部署
  • 原文地址:https://www.cnblogs.com/wangchunli-blogs/p/9949903.html
Copyright © 2011-2022 走看看