Esempio n. 1
0
def serve(POISON_PILL):

    sock_tcp = None

    rx_sockets = []
    tx_sockets = []
    err_sockets = []

    while True:

        # build a connection if we don't have one.  check none specifically.  don't make this a complex with
        # POISON_PILL -- it's possible we may need to have a listener for clients to drain their buffers
        if sock_tcp is None:
            sock_tcp = make_server_socket()

        try:
            # rlist - sockets to try reading; return from select is subset of actually readable (has data in buffer)
            # wlist - sockets to try writing to; return from select is subset of actually writable (outbound buffer available)
            # xlist - sockets to check for errors; return from select is subset of actually errored
            # select is blocking, so give it a timeout
            # "server" goes to rlist

            # socket to .connect() to clients gets appended into the wlist

            # see man select for more details
            try:
                rx_sockets, tx_sockets, err_sockets = select.select(SOCKET_CONNECTIONS, [], [], API.SOCKET_TIMEOUT_SECONDS)
            except KeyboardInterrupt:
                POISON_PILL = True

        except select.error as e:
            log.fatal("Could not select() on sockets.  Closing socket. %s " % e)
            # break here will jump to the close
            break

        # Please double-check the logic here
        # check for poison pill. bool is very slightly faster than constant int 1 comparison in py3.5
        if POISON_PILL:

            log.info("Received poison pill for shutdown.")
            # if we didn't have any clients between startup and shutdown, rx_sockets will be empty.
            if rx_sockets:
                for sock in rx_sockets:
                    try:
                        sock.shutdown(1)
                        # give it a little time for the socket to process the shutdown
                        time.sleep(.2)
                        sock.close()
                    except OSError:
                        # socket probably wasn't connected at .shutdown()
                        pass

                    SOCKET_CONNECTIONS.remove(sock)

            raise SystemExit('YAMS Socket Server exiting due to a poison pill.')



        # to respond to conversation initiated by a client (recv())
        for sock in rx_sockets:

            if sock == sock_tcp:
                # The server socket is responsible for accepting incoming connections and responding with its own client
                # connection socket.  We know we have a new client conn when data comes into our server socket buffer.

                # There shouldn't be an issue with this strategy for select/async with non-blocking sockets, but if so, this
                # would be where to dispatch a new thread/native thread
                socket_fd, address = sock_tcp.accept()
                SOCKET_CONNECTIONS.append(socket_fd)
                log.info("Client (%s, %s) connected" % address)
            else:
                # rx'd from a client

                try:
                    # message from the client here
                    rx_data = sock.recv(API.SOCKET_RX_BUFFER_BYTES)

                    # no exception, we have an rx_data
                    if rx_data:

                        # todo: process the data.  if way too large, disconnect client -- something is wrong..

                        # socket.send returns the number of bytes sent, so this should go to the metrics gather-er
                        # sendall returns None on success.  there is no way to determine how much data was sent on err, count bytes for tx size

                        # check for quit messages, evaluate based on the different yams event types

                        # socket.send returns the number of bytes sent, so this should go to the metrics gather-er
                        # sendall returns None on success.  there is no way to determine how much data was sent on err, count bytes for tx size

                        if not API.DEBUG:

                            # 1) Include zlib library and metadata about the request (+= zlib.ZLIB_VERSION)
                            # e.g. metadata: zlib=1.2.5
                            # 2) Checksum and store in checksum field (md5 or fletchers? fast, cheap - SSL does the security)
                            # 3) Compress (zlib.compress())
                            # 4) Encrypt

                            # with this order, if there's a corruption over the wire, zlib will see it as corrupted data
                            # then we'll have the checksum to compare the uncompressed payload against.

                            pass

                        # Else: Checksum, store checksum in message

                        sock.send(bytes_encoder("RECEIVED DATA: %s" % rx_data))


                # Probably errored on rx_data -- did something else deplete our buffer?
                # this next exception type is an educated guess -- couldn't get timing down for broken socket (e.g. ^c)
                except select.error as e:


                    # Probably fine that the connection is no longer there after initial connection.
                    # see man 2 accept
                    log.debug(str(e))
                    log.warn("Client socket <%s> is not connected." % sock)
                    # this could be harmless, but could be a sign of unintended clamping of client conns

                    # just close the conn and then remove it from our candidate FDs. don't bother with .shutdown(), but
                    # do close explicitly instead of assuming it will get GC'd
                    sock.close()
                    try:
                        log.info("Removing a socket due to a select error.")
                        SOCKET_CONNECTIONS.remove(sock)

                    except ValueError:
                        # sock wasn't in socket_connections. that's what we want, so that's fine.
                        continue



        # to initiate a conversation with a client (connect())
        for socket in tx_sockets:
            pass

        for socket in err_sockets:
            pass


        if API.DEBUG:
            log.debug("rlist: %s" % rx_sockets)
            log.debug("wlist: %s" % tx_sockets)
            log.debug("xlist: %s" % err_sockets)

        time.sleep(API.SOCKET_THREAD_YIELD_EPSILON)
        # poll for server shutdown on some interval too.  if we reach shutdown, tear down connections from clients

    # no longer True, exception raised, or we exited
    sock_tcp.close()
Esempio n. 2
0
    def close_socket():

        log.debug("Setting poison pill to shut down the socket.")
        tcpsocket.POISON_PILL = True
Esempio n. 3
0
    def close_socket():

        log.debug("Setting poison pill to shut down the socket.")
        tcpsocket.POISON_PILL = True
Esempio n. 4
0
def serve(POISON_PILL):

    sock_tcp = None

    rx_sockets = []
    tx_sockets = []
    err_sockets = []

    while True:

        # build a connection if we don't have one.  check none specifically.  don't make this a complex with
        # POISON_PILL -- it's possible we may need to have a listener for clients to drain their buffers
        if sock_tcp is None:
            sock_tcp = make_server_socket()

        try:
            # rlist - sockets to try reading; return from select is subset of actually readable (has data in buffer)
            # wlist - sockets to try writing to; return from select is subset of actually writable (outbound buffer available)
            # xlist - sockets to check for errors; return from select is subset of actually errored
            # select is blocking, so give it a timeout
            # "server" goes to rlist

            # socket to .connect() to clients gets appended into the wlist

            # see man select for more details
            try:
                rx_sockets, tx_sockets, err_sockets = select.select(
                    SOCKET_CONNECTIONS, [], [], API.SOCKET_TIMEOUT_SECONDS)
            except KeyboardInterrupt:
                POISON_PILL = True

        except select.error as e:
            log.fatal("Could not select() on sockets.  Closing socket. %s " %
                      e)
            # break here will jump to the close
            break

        # Please double-check the logic here
        # check for poison pill. bool is very slightly faster than constant int 1 comparison in py3.5
        if POISON_PILL:

            log.info("Received poison pill for shutdown.")
            # if we didn't have any clients between startup and shutdown, rx_sockets will be empty.
            if rx_sockets:
                for sock in rx_sockets:
                    try:
                        sock.shutdown(1)
                        # give it a little time for the socket to process the shutdown
                        time.sleep(.2)
                        sock.close()
                    except OSError:
                        # socket probably wasn't connected at .shutdown()
                        pass

                    SOCKET_CONNECTIONS.remove(sock)

            raise SystemExit(
                'YAMS Socket Server exiting due to a poison pill.')

        # to respond to conversation initiated by a client (recv())
        for sock in rx_sockets:

            if sock == sock_tcp:
                # The server socket is responsible for accepting incoming connections and responding with its own client
                # connection socket.  We know we have a new client conn when data comes into our server socket buffer.

                # There shouldn't be an issue with this strategy for select/async with non-blocking sockets, but if so, this
                # would be where to dispatch a new thread/native thread
                socket_fd, address = sock_tcp.accept()
                SOCKET_CONNECTIONS.append(socket_fd)
                log.info("Client (%s, %s) connected" % address)
            else:
                # rx'd from a client

                try:
                    # message from the client here
                    rx_data = sock.recv(API.SOCKET_RX_BUFFER_BYTES)

                    # no exception, we have an rx_data
                    if rx_data:

                        # todo: process the data.  if way too large, disconnect client -- something is wrong..

                        # socket.send returns the number of bytes sent, so this should go to the metrics gather-er
                        # sendall returns None on success.  there is no way to determine how much data was sent on err, count bytes for tx size

                        # check for quit messages, evaluate based on the different yams event types

                        # socket.send returns the number of bytes sent, so this should go to the metrics gather-er
                        # sendall returns None on success.  there is no way to determine how much data was sent on err, count bytes for tx size

                        if not API.DEBUG:

                            # 1) Include zlib library and metadata about the request (+= zlib.ZLIB_VERSION)
                            # e.g. metadata: zlib=1.2.5
                            # 2) Checksum and store in checksum field (md5 or fletchers? fast, cheap - SSL does the security)
                            # 3) Compress (zlib.compress())
                            # 4) Encrypt

                            # with this order, if there's a corruption over the wire, zlib will see it as corrupted data
                            # then we'll have the checksum to compare the uncompressed payload against.

                            pass

                        # Else: Checksum, store checksum in message

                        sock.send(bytes_encoder("RECEIVED DATA: %s" % rx_data))

                # Probably errored on rx_data -- did something else deplete our buffer?
                # this next exception type is an educated guess -- couldn't get timing down for broken socket (e.g. ^c)
                except select.error as e:

                    # Probably fine that the connection is no longer there after initial connection.
                    # see man 2 accept
                    log.debug(str(e))
                    log.warn("Client socket <%s> is not connected." % sock)
                    # this could be harmless, but could be a sign of unintended clamping of client conns

                    # just close the conn and then remove it from our candidate FDs. don't bother with .shutdown(), but
                    # do close explicitly instead of assuming it will get GC'd
                    sock.close()
                    try:
                        log.info("Removing a socket due to a select error.")
                        SOCKET_CONNECTIONS.remove(sock)

                    except ValueError:
                        # sock wasn't in socket_connections. that's what we want, so that's fine.
                        continue

        # to initiate a conversation with a client (connect())
        for socket in tx_sockets:
            pass

        for socket in err_sockets:
            pass

        if API.DEBUG:
            log.debug("rlist: %s" % rx_sockets)
            log.debug("wlist: %s" % tx_sockets)
            log.debug("xlist: %s" % err_sockets)

        time.sleep(API.SOCKET_THREAD_YIELD_EPSILON)
        # poll for server shutdown on some interval too.  if we reach shutdown, tear down connections from clients

    # no longer True, exception raised, or we exited
    sock_tcp.close()