indexing description: "[ A TCP client socket that can be connected to a remote host. The remote host is identified by an EM_INET_SOCKET_ADDRESS which can be set through `set_hostname' and `set_port' or `set_address'. ]" date: "$Date$" revision: "$Revision$" class EM_TCP_CLIENT_SOCKET inherit EM_SOCKET rename is_open as is_connected, open as connect, close as disconnect, port as local_port, set_port as set_local_port export {ANY} local_port -- TODO: exported to ANY since local_port (was port) is in precondition of connect which is exported to ANY. check if refactoring necessary since the concept of local_port doesn't belong to a TCP client socket {NONE} set_local_port end EM_NETWORK_HELPER_FUNCTIONS -- export {NONE} all end NET2_FUNCTIONS_EXTERNAL export {NONE} all end STRING_HANDLER export {NONE} all end create make, make_from_address create {EM_SOCKETS} make_from_socket_id feature {NONE} -- Initialization make is -- Initialise socket with local address. require network_enabled: Network_subsystem.is_enabled do net2_socket_id := -1 local_port := -1 is_connected := False is_trying_to_connect := False create address.make_local_by_port (0) create connection_established_event create connection_closed_event create connection_failed_event create data_received_event end make_from_address (an_address: EM_INET_SOCKET_ADDRESS) is -- Initialise socket with `an_address'. require an_address_not_void: an_address /= Void do make address := an_address end make_from_socket_id (a_socket_id: like net2_socket_id) is -- Initialise socket with Net2 id `a_socket_id' in connected state. local ipaddress_struct_pointer: POINTER ipaddress_struct: IPADDRESS_STRUCT do make ipaddress_struct_pointer := net2_tcpget_peer_address_external (a_socket_id) if ipaddress_struct_pointer /= Default_pointer then create ipaddress_struct.make_shared (ipaddress_struct_pointer) create address.make_by_struct (ipaddress_struct) net2_socket_id := a_socket_id is_connected := True else Error_handler.raise_error (Error_handler.Em_error_this_should_never_happen, ["EM_TCP_CLIENT_SOCKET.make_from_socket_id"]) end ensure connected: is_connected net2_socket_id_set: net2_socket_id = a_socket_id end feature -- Access address: EM_INET_SOCKET_ADDRESS -- Server address feature -- Status report is_trying_to_connect: BOOLEAN -- Is socket trying to connect to server? feature -- Status setting connect is -- Connect to server. require else network_enabled: Network_subsystem.is_enabled not_connected: not is_connected local ewg_string: EWG_ZERO_TERMINATED_STRING a_transaction_id: INTEGER do if not is_trying_to_connect then if address.is_resolved then net2_tcpconnect_to_ip_threaded_external (address.ip_address_struct.item) else -- add a resolve transaction a_transaction_id := network_subsystem.sockets.next_transaction_id Network_subsystem.sockets.add_transaction (a_transaction_id, address) create ewg_string.make_unshared_from_string (address.hostname) net2_tcpconnect_to_threaded_external (ewg_string.item, address.port, address.ip_address_struct.item, a_transaction_id) end Network_subsystem.sockets.wait_for_connection (Current) is_trying_to_connect := True end ensure then trying_to_connect: is_trying_to_connect end disconnect is -- Disconnect from server. do if not is_trying_to_connect and is_connected then is_connected := False Network_subsystem.sockets.remove_socket (Current) net2_tcpclose_external (net2_socket_id) net2_socket_id := -1 end end feature -- Element change set_host_ip (an_ip: INTEGER) is -- Set host ip of `address'. require not_connected: not is_connected not_trying_to_connect: not is_trying_to_connect do create address.make_by_ip_port (an_ip, address.port) ensure host_ip_set: address.host_ip = an_ip end set_host_ip_as_string (an_ip_string: STRING) is -- Set host ip of `address'. -- `an_ip_string' has to be in the format 'xy.xy.xy.xy'. require not_connected: not is_connected not_trying_to_connect: not is_trying_to_connect an_ip_string_not_void: an_ip_string /= Void an_ip_string_valid: is_valid_ip_string (an_ip_string) do set_host_ip (convert_ip_string_to_ip (an_ip_string)) ensure host_ip_set: address.host_ip = convert_ip_string_to_ip (an_ip_string) end set_hostname (a_hostname: STRING) is -- Set hostname of `address'. require not_connected: not is_connected not_trying_to_connect: not is_trying_to_connect a_hostname_not_void: a_hostname /= Void a_hostname_not_empty: not a_hostname.is_empty do create address.make_by_hostname_port (a_hostname, address.port) ensure hostname_set: address.hostname.is_equal (a_hostname) address_unresolved: not address.is_resolved end set_port (a_port: INTEGER) is -- Set port of `address'. require not_connected: not is_connected not_trying_to_connect: not is_trying_to_connect a_port_in_range: 0 < a_port and a_port < 65535 do create address.make_by_hostname_port (address.hostname, a_port) ensure port_set: address.port = a_port end set_address (an_address: like address) is -- Set `address' to `an_address'. require not_connected: not is_connected not_trying_to_connect: not is_trying_to_connect an_address_not_void: an_address /= Void do address := an_address ensure address_set: address = an_address end feature -- Basic operations send_string (a_string: STRING) is -- Send `a_string'. require a_string_not_void: a_string /= Void connected: is_connected local ewg_string: EWG_ZERO_TERMINATED_STRING do create ewg_string.make_unshared_from_string (a_string) if net2_tcpsend_external (net2_socket_id, ewg_string.item, ewg_string.count) = -1 then Error_handler.raise_error (Error_handler.Em_error_tcp_send, [address.host_ip_port_as_string]) end end feature -- Events connection_established_event: EM_EVENT_CHANNEL [TUPLE []] -- Connected event connection_closed_event: EM_EVENT_CHANNEL [TUPLE []] -- Connection closed event connection_failed_event: EM_EVENT_CHANNEL [TUPLE []] -- Connection failed event data_received_event: EM_EVENT_CHANNEL [TUPLE [STRING]] -- Data received event feature {EM_SOCKETS} -- Implementation handle_data_received is -- Handle data received event. local length: INTEGER a_buffer: STRING tmp: ANY do from length := 1 until length = 0 loop create a_buffer.make (1024) tmp := a_buffer.to_c length := net2_tcpread_external (net2_socket_id, $tmp, a_buffer.capacity) if length > 0 then a_buffer.set_count (length) data_received_event.publish ([a_buffer]) end end end handle_connection_established (a_socket_id: INTEGER) is -- Handle connection established. do net2_socket_id := a_socket_id is_connected := True is_trying_to_connect := False connection_established_event.publish ([]) ensure socket_id_set: net2_socket_id = a_socket_id connected: is_connected end handle_connection_failed (an_error: INTEGER) is -- Handle connection failed. do is_trying_to_connect := False connection_failed_event.publish ([an_error]) end handle_connection_closed is -- Handle connection closed. do is_connected := False Network_subsystem.sockets.remove_socket (Current) net2_socket_id := -1 connection_closed_event.publish ([]) end invariant address_not_void: address /= Void connection_established_event_not_void: connection_established_event /= Void connection_closed_event_not_void: connection_closed_event /= Void connection_failed_event_not_void: connection_failed_event /= Void data_received_event_not_void: data_received_event /= Void socket_id_consistent: is_connected implies net2_socket_id >= 0 socket_id_consistent: not is_connected implies net2_socket_id < 0 end