indexing description: "Timing and buffering extensions for a socket" license: "MIT license (see ../../license.txt)" author: "Beat Strasser " date: "$Date$" revision: "$Revision$" class P2P_SOCKET_EXTENSIONS inherit DT_SHARED_SYSTEM_CLOCK export {NONE} all end THREAD_CONTROL export {NONE} all end EXCEPTIONS export {NONE} all end P2P_EXCEPTION_LOG create make, make_non_blocking, empty_buffer feature {NONE} -- Initialization make (network_socket: like socket; max_wait: INTEGER) is -- Create enhanced socket with a timeout `max_wait' in seconds require Socket_existent: network_socket /= Void Timeout_valid: max_wait >= 0 do socket := network_socket set_timeout (max_wait) empty_buffer end make_non_blocking (network_socket: like socket; max_wait: INTEGER) is -- Create enhanced socket with a timeout `max_wait' in seconds and enable non-blocking socket mode require Socket_existent: network_socket /= Void and network_socket.exists Timeout_valid: max_wait >= 0 do make (network_socket, max_wait) socket.set_non_blocking -- lets do the wait/sleep process here end feature -- Access socket: NETWORK_SOCKET buffer: STRING_8 buffer_length: INTEGER is -- Current buffer length do Result := buffer.count end feature -- Status report last_string: STRING_8 last_integer_8: INTEGER_8 last_integer_16: INTEGER_16 last_integer_32: INTEGER_32 last_integer_64: INTEGER_64 has_socket_error: BOOLEAN is -- Does socket indicate some errors? do -- TODO change to non blocking as soon as eiffel net supports non blocking calls socket.set_blocking Result := c_get_sock_opt_int (socket.descriptor, level_sol_socket, soerror) /= 0 socket.set_non_blocking end feature -- Status setting set_timeout (a_timeout: INTEGER) is -- Set new timeout in seconds require Timeout_valid: a_timeout >= 0 do socket.set_timeout (a_timeout) ensure Timeout_set: socket.timeout = a_timeout end set_checking_restraint (a_checker: like checker) is -- Set a checking restraint query which returns true when reading should be immediately stopped require Checker_valid: a_checker /= Void do checker := a_checker end feature -- Read read_chars (a_number: INTEGER) is -- Read exactly `a_number' chars (waiting on each interrupt max `timeout' seconds) and sets `last_string', -- or throw exception when timed out require Number_valid: a_number >= 0 Socket_non_blocking: socket /= Void and not socket.is_blocking local result_string: STRING do from create result_string.make_empty until result_string.count = a_number loop read_chars_once (a_number - result_string.count) result_string.append (last_string) end last_string := result_string ensure Result_set: last_string /= Void and last_string.count = a_number end read_chars_once (max_count: INTEGER) is -- Read maximal `max_count' chars (waiting maximal `timeout' seconds) and sets `last_string', -- or throw exception when timed out require Number_valid: max_count >= 0 Socket_non_blocking: socket /= Void and not socket.is_blocking local wait: BOOLEAN do if socket.exists and (not wait or ready_for_reading_or_failure) then -- TODO change to non blocking as soon as eiffel net supports non blocking calls socket.set_blocking socket.read_stream (max_count) socket.set_non_blocking if socket.bytes_read <= 0 then raise_retrieval_exception (exception_name_nothing_read) else last_string := socket.last_string.as_string_8 end else raise_retrieval_exception (exception_name_nothing_read) end rescue if not wait and exception /= retrieve_exception then wait := True log_exceptions retry end end read_byte_1 is -- Read byte (8-bit integer) (waiting maximal `timeout' seconds) and sets last_integer_8, -- or throw exception when timed out require Socket_non_blocking: socket /= Void and not socket.is_blocking do read_bytes (1) last_integer_8 := integer_buffer.read_integer_8_be (0) end read_byte_2 is -- Read two bytes (16-bit integer), MSB first (Big-Endian), (waiting maximal `timeout' seconds) and sets last_integer_16, -- or throw exception when timed out require Socket_non_blocking: socket /= Void and not socket.is_blocking do read_bytes (2) last_integer_16 := integer_buffer.read_integer_16_be (0) end read_byte_4 is -- Read four bytes (32-bit integer), MSB first (Big-Endian), (waiting maximal `timeout' seconds) and sets last_integer_32, -- or throw exception when timed out require Socket_non_blocking: socket /= Void and not socket.is_blocking do read_bytes (4) last_integer_32 := integer_buffer.read_integer_32_be (0) end read_byte_8 is -- Read eight bytes (64-bit integer), MSB first (Big-Endian), (waiting maximal `timeout' seconds) and sets last_integer_64, -- or throw exception when timed out require Socket_non_blocking: socket /= Void and not socket.is_blocking do read_bytes (8) last_integer_64 := integer_buffer.read_integer_64_be (0) end feature -- Write to buffer empty_buffer, create_buffer is -- Empty buffer do create buffer.make_empty create integer_buffer.make (integer_buffer_capacity) ensure Storage_set: buffer /= Void end write_chars (s: STRING) is -- Write string to buffer require String_valid: s /= Void do buffer.append (s) end write_byte_1 (i: INTEGER_8) is -- Write byte (8-bit integer) to buffer do buffer.append_character (i.to_character_8) end write_byte_2 (i: INTEGER_16) is -- Write two bytes (16-bit integer) to buffer, MSB first (Big-Endian) local p: MANAGED_POINTER pos: INTEGER do create p.make (2) p.put_integer_16_be (i, 0) from pos := 0 until pos > 1 loop buffer.append_character (p.read_character (pos)) pos := pos + 1 end end write_byte_4 (i: INTEGER_32) is -- Write four bytes (32-bit integer) to buffer, MSB first (Big-Endian) local p: MANAGED_POINTER pos: INTEGER do create p.make (4) p.put_integer_32_be (i, 0) from pos := 0 until pos > 3 loop buffer.append_character (p.read_character (pos)) pos := pos + 1 end end write_byte_8 (i: INTEGER_64) is -- Write eight bytes (64-bit integer) to buffer, MSB first (Big-Endian) local p: MANAGED_POINTER pos: INTEGER do create p.make (8) p.put_integer_64_be (i, 0) from pos := 0 until pos > 7 loop buffer.append_character (p.read_character (pos)) pos := pos + 1 end end feature -- Basic operations connect_successful: BOOLEAN is -- Is connect successful within timeout? require Socket_valid: socket /= Void local failed: BOOLEAN old_timeout: INTEGER old_is_blocking: BOOLEAN do if not failed then if socket.connect_in_progress then if socket.ready_for_writing and not has_socket_error then old_timeout := socket.timeout old_is_blocking := socket.is_blocking -- mark the socket as open socket.create_from_descriptor (socket.descriptor) socket.set_timeout (old_timeout) if old_is_blocking then socket.set_blocking else socket.set_non_blocking end else socket.cleanup end end Result := socket.exists and socket.is_open_read and socket.is_open_write and not call_checker end rescue failed := True log_exceptions retry end send_buffer is -- Send and empty buffer require Socket_valid: socket /= Void and socket.is_writable do if buffer.count > 0 then if socket.exists then -- TODO: remove the blocking as soon as Eiffel Net supports non blocking send socket.set_blocking socket.put_string (buffer) socket.set_non_blocking empty_buffer else raise (socket_closed_message) end end end wait_for_retry (exit_time: DT_TIME): DT_TIME is -- Set exit_time to current time plus `timeout' seconds, if `exit_time' is Void. -- Wait a little and return `exit_time' if time isn't reached yet. do if socket.timeout /= 0 then if exit_time = Void then -- set time when we time out Result := system_clock.time_now Result.add_seconds (socket.timeout) else Result := exit_time end if system_clock.time_now < Result then -- sleep a little and retry sleep (read_wait_interval) else Result := Void end end end ready_for_reading_or_failure: BOOLEAN is -- Wait until data available (True) or timeout/connection closed (False) local max_timeout: INTEGER step: INTEGER do max_timeout := socket.timeout set_timeout (check_restraints_interval) from until Result or step >= max_timeout or call_checker or not socket.exists loop Result := socket.ready_for_reading step := step + check_restraints_interval end set_timeout (max_timeout) Result := Result and socket.exists end feature {NONE} -- Implementation Read_wait_interval: INTEGER is 10000000 -- 10ms Check_restraints_interval: INTEGER is 2 -- 2s Exception_name_nothing_read: STRING is "Too few bytes read" Socket_closed_message: STRING is "Socket closed" checker: FUNCTION [ANY, TUPLE, BOOLEAN] call_checker: BOOLEAN is -- Returns `checker's result or False do if checker /= Void then checker.call ([]) Result := checker.last_result end end integer_buffer: MANAGED_POINTER Integer_buffer_capacity: INTEGER is 8 read_bytes (a_number: INTEGER) is -- Read exactly `a_number' bytes into `integer_buffer' (waiting maximal `timeout' seconds), -- or throw exception when timed out require Socket_non_blocking: socket /= Void and not socket.is_blocking Number_valid: a_number = 1 or a_number = 2 or a_number = 4 or a_number = 8 local wait: BOOLEAN pos, last_read: INTEGER do from until pos = a_number or not socket.exists or (wait and not ready_for_reading_or_failure) loop -- TODO change to non blocking as soon as eiffel net supports non blocking calls socket.set_blocking last_read := c_read_stream (socket.descriptor, a_number - pos, integer_buffer.item + pos) socket.set_non_blocking if last_read < 1 then raise_retrieval_exception (exception_name_nothing_read) else pos := pos + last_read wait := True end end if pos < a_number then raise_retrieval_exception (exception_name_nothing_read) end rescue if not wait and exception /= retrieve_exception then wait := True log_exceptions retry end end feature {NONE} -- Externals c_get_sock_opt_int (fd, level, opt: INTEGER): INTEGER is -- C routine to get socket options of integer type external "C" end level_sol_socket: INTEGER is -- SOL_SOCKET level of options external "C" end soerror: INTEGER is -- SO_ERROR option name external "C" end c_read_stream (fd: INTEGER_32; l: INTEGER_32; buf: POINTER): INTEGER_32 -- External routine to read a `l' number of characters -- into buffer `buf' from socket `fd' external "C blocking" end end