diff options
Diffstat (limited to 'server/src/tcp_server.py')
-rw-r--r-- | server/src/tcp_server.py | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/server/src/tcp_server.py b/server/src/tcp_server.py new file mode 100644 index 0000000..9aaf8fe --- /dev/null +++ b/server/src/tcp_server.py @@ -0,0 +1,254 @@ +"""TCP Server.""" + +import log +import select +import socket +import threading +import time + +from json_package import JSONPackage +from json_package import JSONPackageError +from users_text_manager import AUTHORITY +from users_text_manager import UserInfo + + +FREQUENCY = 8 + +TIMEOUT = 5 + + +class _JSON_TOKEN: # pylint:disable=W0232 + """Enumeration the Ttken strings for json object.""" + CURSORS = 'cursors' # other users' cursor position + ERROR = 'error' # error string + IDENTITY = 'identity' # identity of myself + INIT = 'init' # initialize connect flag + MODE = 'mode' # vim mode. + NICKNAME = 'nickname' # nick name of the user. + OTHERS = 'others' # other users info. + TEXT = 'text' # text content in the buffer + + +class TCPServer(threading.Thread): + """A thread to be the tcp server. + + Attributes: + _port: Port number. + _sock: Socket fd. + _users_text_manager: An instance of UsersTextManager. + _stop_flag: Flag for stopping. + _connection_handler_threads: List of connction handler threads. + """ + def __init__(self, port, users_text_manager): + """Constructor. + + Args: + port: Port number. + users_text_manager: An instance of UsersTextManager. + """ + super(TCPServer, self).__init__() + self._port = port + self._sock = None + self._users_text_manager = users_text_manager + self._stop_flag = False + self._connection_handler_threads = [] + + @property + def port(self): + """Gets the port of this server. None for unconnected case.""" + return self._port if self._sock else None + + def run(self): + """Runs the thread.""" + self._build() + self._accept() + + def stop(self): + """Stops the thread.""" + self._stop_flag = True + for thr in self._connection_handler_threads: + thr.join() + + def _build(self): + """Creates the socket.""" + timeout = 1 + while not self._stop_flag and not self._sock: + try: + self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._sock.bind(('', self._port)) + self._sock.listen(1024) + except socket.error as e: + self._sock = None + log.error(str(e) + '\n') + log.info('Try it %d second(s) later.\n' % timeout) + for _ in range(timeout * FREQUENCY): + if self._stop_flag: + break + time.sleep(float(1) / FREQUENCY) + timeout *= 2 + if self._sock: + log.info('Successfully built the tcp server.\n') + + def _accept(self): + """Accepts the connection and calls the handler.""" + while not self._stop_flag: + readable, _, _ = select.select([self._sock], [], [], + float(1) / FREQUENCY) + if readable: + sock, addr = self._sock.accept() + log.info('Client %r connect to server.\n' % str(addr)) + thr = _TCPConnectionHandler(sock, self._users_text_manager) + thr.start() + self._connection_handler_threads += [thr] + + +class TCPConnection(object): + """My custom tcp connection. + + Args: + _conn: The TCP-connection. + """ + def __init__(self, conn): + """Constructor. + + Args: + conn: TCP-connection. + """ + self._conn = conn + self._conn.settimeout(TIMEOUT) + + def send(self, data): + """Sends the data until timeout or the socket closed. + + Args: + data: Data to be sent. + """ + self._conn.sendall(data) + + def recv(self, nbyte): + """Receives the data until timeout or the socket closed. + + Args: + nbyte: Bytes of data to receive. + + Return: + Bytes of data. + """ + ret = b'' + while nbyte > 0: + recv = self._conn.recv(nbyte) + if not recv: + raise socket.error('Connection die.') + ret += recv + nbyte -= len(recv) + return ret + + def close(self): + """Closes the connection.""" + self._conn.close() + + +class _TCPConnectionHandler(threading.Thread): + """A thread to handle a connection. + + Attributes: + _sock: The connection socket. + _users_text_manager: An instance of UsersTextManager. + """ + def __init__(self, sock, users_text_manager): + """Constructor. + + Args: + sock: The connection socket. + users_text_manager: An instance of UsersTextManager. + """ + super(_TCPConnectionHandler, self).__init__() + self._sock = TCPConnection(sock) + self._users_text_manager = users_text_manager + + def run(self): + """Runs the thread.""" + try: + json_package = self._receive() + json_info = self._sanitize(json_package.content) + if json_info: + self._handle(json_info) + else: + self._send({_JSON_TOKEN.ERROR : 'Invalid client.'}) + except JSONPackageError as e: + log.error(str(e)) + except socket.error as e: + log.error(str(e)) + self._sock.close() + + def _sanitize(self, request): + """Sanitizes the request. + + Args: + request: The request package. + + Return: + Sanitized package. + """ + identity = request[_JSON_TOKEN.IDENTITY] + if identity not in self._users_text_manager.get_users_info(): + return None + if request[_JSON_TOKEN.INIT]: + self._users_text_manager.reset_user(identity) + request[_JSON_TOKEN.TEXT] = '' + for mark in request[_JSON_TOKEN.CURSORS]: + request[_JSON_TOKEN.CURSORS][mark] = 0 + else: + auth = self._users_text_manager.get_users_info()[identity].authority + if auth < AUTHORITY.READWRITE: + old_text = self._users_text_manager.get_user_text(identity) + request[_JSON_TOKEN.TEXT] = old_text + return request + + def _handle(self, request): + """Handles the request. + + Args: + request: The request package. + """ + identity = request[_JSON_TOKEN.IDENTITY] + text = request[_JSON_TOKEN.TEXT] + user_info = UserInfo() + user_info.mode = request[_JSON_TOKEN.MODE] + user_info.cursors = request[_JSON_TOKEN.CURSORS] + new_user_info, new_text = self._users_text_manager.update_user_text( + identity, user_info, text) + response = JSONPackage() + response.content = { + _JSON_TOKEN.TEXT : new_text, + _JSON_TOKEN.CURSORS : new_user_info.cursors, + _JSON_TOKEN.MODE : new_user_info.mode, + _JSON_TOKEN.OTHERS : [ + {_JSON_TOKEN.NICKNAME : other.nick_name, + _JSON_TOKEN.MODE : other.mode, + _JSON_TOKEN.CURSORS: other.cursors} + for iden, other in self._users_text_manager.get_users_info( + without=[identity], must_online=True).items()] + } + response.send_to(self._sock) + + + def _receive(self): + """Receive a request. + + Return: + The request package. + """ + request = JSONPackage() + request.recv_from(self._sock) + return request + + def _send(self, pkg): + """Sends a response. + + Args: + pkg: The package to be sent. + """ + response = JSONPackage() + response.content = pkg + response.send_to(self._sock) |