indexing description: "Server thread waiting for incoming connections" license: "MIT license (see ../../../license.txt)" author: "Beat Strasser " date: "$Date$" revision: "$Revision$" class P2P_TCP_SERVER inherit THREAD P2P_RANDOM_SHARED P2P_EXCEPTION_LOG EXCEPTIONS export {NONE} all end create make feature {NONE} -- Initialization make (a_transport: like transport; an_interface_address: STRING; a_port_range_start, a_port_range_end: like port) is -- Create server require Transport_valid: a_transport /= Void Interface_valid: an_interface_address /= Void Ports_valid: a_port_range_start > 0 and a_port_range_start <= a_port_range_end local failed: BOOLEAN do if not failed then transport := a_transport -- create new server socket transport.logger.debugging ("tcp_server: Creating server socket, port: " + port.out + ", group id: " + transport.peer_group.id.out) create_socket (an_interface_address, a_port_range_start, a_port_range_end) if socket.is_open_read then -- listen socket.listen (connections_queue_limit) -- ready for connections is_running := True create socket_lock.make transport.logger.debugging ("tcp_server: Listening now, ready for connections, group id: " + transport.peer_group.id.out) else port := 0 transport.logger.error ("tcp_server: Can't bind to any port in the range: " + a_port_range_start.out + "-" + a_port_range_end.out + ", group id: " + transport.peer_group.id.out) end end rescue failed := True transport.logger.fatal ("tcp_server: Initialization exception, trace:%N" + exception_trace) retry end feature -- Access port: INTEGER -- Server port feature -- Status report is_running: BOOLEAN -- Is server currently running? feature -- Basic operations shutdown is -- Close server socket and initiate stopping of thread do transport.logger.debugging ("tcp_server: Requesting shutdown, group id: " + transport.peer_group.id.out) socket_lock.lock is_running := False socket.cleanup socket_lock.unlock ensure Not_running: not is_running rescue socket_lock.unlock end feature {NONE} -- Implementation transport: P2P_TCP_TRANSPORT socket: NETWORK_STREAM_SOCKET socket_lock: MUTEX Connections_queue_limit: INTEGER is 5 execute is -- New thread: listen on socket and accept connections local failed: BOOLEAN do if not failed and is_running then connection_loop end transport.logger.debugging ("tcp_server: Shutting down, group id: " + transport.peer_group.id.out) -- cleanup socket if socket_lock.is_set then socket_lock.lock if is_running then socket.cleanup is_running := False end socket_lock.unlock socket_lock.destroy end -- finish thread join_all -- wait for all children threads transport.logger.debugging ("tcp_server: Terminating server thread, group id: " + transport.peer_group.id.out) rescue failed := True log_exceptions if is_running then transport.logger.fatal ("tcp_server: Loop exception, trace:%N" + exception_trace) end retry end create_socket (an_interface_address: STRING; a_port_range_start, a_port_range_end: INTEGER) is -- Create socket listening on `an_interface_address' with a port in the given range require Interface_valid: an_interface_address /= Void Ports_valid: a_port_range_start > 0 and a_port_range_start <= a_port_range_end local port_list: DS_ARRAYED_LIST [NATURAL_16] a_port: NATURAL_16 h_address: HOST_ADDRESS address: NETWORK_SOCKET_ADDRESS rand_pos: INTEGER do -- create port range list create port_list.make (a_port_range_end - a_port_range_start + 1) from a_port := a_port_range_start.as_natural_16 until a_port > a_port_range_end.as_natural_16 or a_port < a_port_range_start loop port_list.put_last (a_port) a_port := a_port + 1 end -- now create socket create socket.make -- listen on given interface create h_address.make_from_ip_number (an_interface_address) create address.make address.set_host_address (h_address) socket.set_address (address) -- now try to bind a random port from range until we succeed or the range is exhausted from port_list.finish until socket.is_open_read or port_list.before loop -- swap with random port rand_pos := (rand.item \\ port_list.index) + 1 rand.forth if rand_pos /= port_list.index then port_list.swap (port_list.index, rand_pos) end port := port_list.item_for_iteration address.set_port (port) socket.bind port_list.back end ensure Socket_existent: socket /= Void end connection_loop is -- Wait for connections and accept them local connsock: like socket do from until not is_running or socket.is_closed loop -- wait for next connection transport.logger.debugging ("tcp_server: Waiting for connection, group id: " + transport.peer_group.id.out) socket.accept -- new connection accepted connsock ?= socket.accepted -- check socket for a valid connection if is_running and connsock /= Void and connsock.exists then transport.logger.debugging ("tcp_server: Accepted new connection, remote address: " + connsock.peer_address.host_address.host_address + ":" + connsock.peer_address.port.out + ", group id: " + transport.peer_group.id.out) transport.handle_incoming_connection (connsock) end end end end