Skip to content

Commit e1728ad

Browse files
authored
Docs: fix the command dealing function and updating TCP_Server_APIs.rst (#39)
* Docs: updating TCP_Server_APIs.rst * Docs: updating TCP_Server_APIs.rst while the handling info APIs part has been done. * core-code: updating TCP_Server_APIs.rst * Docs: updating TCP_Server_APIs.rst * core-code: fix the errors of the command dealing output result sending * core-code: fix the error of the command dealing functions (undone) * core-code: fix the dealing command functions * core-code: fix the command dealing functions
1 parent 932a759 commit e1728ad

3 files changed

Lines changed: 214 additions & 27 deletions

File tree

docs/Network_APIs/TCP_Server_APIs.rst

Lines changed: 178 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,18 +170,29 @@ the arguments which has been defined in the
170170
change are ``port_add_step``, ``port_range_num``
171171
and ``is_hand_alloc_port``.*
172172

173-
- receiving raw bytes from the socket using ``recieve_message``
173+
- receiving raw bytes from the socket using `recieve_message`
174174
- buffering incoming data until newline-terminated messages are complete
175175
- splitting and processing each message line-by-line
176-
- routing special commands beginning with ``/`` to ``handle_command``
176+
- routing special commands beginning with ``/`` to `handle_command`
177177

178-
*Note: The ``handle_command`` function is defined as:*
178+
The `handle_command` function is defined as:
179179

180180
.. code-block:: python
181181
182182
def handle_command(self: Self, client_socket: Any, client_address: Any, command: Any) -> None:
183183
...
184184
185+
In `handle_command`, there are variable conditional
186+
branches to handle built-in commands like ``/help``,
187+
``/time``, ``/clients``, ``/quit``, and some file
188+
transfer commands. If you already added more commands
189+
by the command extension API, the function will
190+
determine if the inputed message matched the extension
191+
commands.
192+
193+
*Note: For more details of the command extension API,
194+
and the built-in commands, please visit ...*
195+
185196
- logging normal chat messages and acknowledging receipt
186197
- removing the client from ``self.clients`` and closing the socket when the client disconnects or an error occurs
187198

@@ -190,11 +201,12 @@ and ``is_hand_alloc_port``.*
190201
def recieve_message(self: Self, client_socket: Any, msg_length: Int) -> Any:
191202
...
192203
193-
`recieve_message` is a thin wrapper around socket receive operations.
194-
It reads up to ``msg_length`` bytes from the given
195-
``client_socket`` and returns the raw byte payload.
196-
Message decoding and newline message framing are handled
197-
by the caller.
204+
`recieve_message` is a thin wrapper around socket
205+
receive operations. It reads up to ``msg_length``
206+
bytes from the given ``client_socket`` and
207+
returns the raw byte payload. Message decoding
208+
and newline message framing are handled by the
209+
caller.
198210

199211
.. code-block:: python
200212
@@ -214,4 +226,161 @@ These methods form the server's client I/O loop
214226
and ensure reliable message exchange for connected
215227
TCP clients.
216228

217-
TCP Server
229+
TCP Server command API
230+
----------------------
231+
232+
The server supports several built-in commands
233+
and a command extension API. The main entry
234+
point is the `handle_command` method, which
235+
is invoked for any message starting with ``/``.
236+
237+
We support two solutions for command handling,
238+
one is input a command in the console, and
239+
the other is recieving a command from other
240+
clients, and you can also call the functions
241+
which are defined for the commands in the code.
242+
243+
*Note: You can select the solution which you
244+
want to use by changing the args of the
245+
`TCP_Server_Base` class, the arg is
246+
``is_input_command_in_console``. ``True`` is
247+
allow the server to input the command in
248+
console, while ``False`` is don't allow.*
249+
250+
Built-in client commands include:
251+
252+
- ``/help``: returns the available command list and usage hints.
253+
- ``/time``: returns the current server time.
254+
- ``/clients``: returns the list of connected client IDs.
255+
- ``/quit``: returns a goodbye message and disconnects the client.
256+
- ``/file <file_path> <client_id>``: starts a file transfer request from client to server.
257+
- ``/file_folder <folder_path> <client_id>``: starts a folder transfer request from client to server.
258+
- ``/server_file_transfer_port <port> <client_id>``: internal protocol message used to coordinate file transfer ports.
259+
260+
In `handle_command`, the server will first
261+
check if the command matches any built-in commands.
262+
If it dose, the `handle_command` will call the
263+
functions which are defined for the commands.
264+
265+
If a command is not recognized by the built-in handler,
266+
`handle_command` will check if it matches any
267+
registered custom commands from the command extension
268+
API. If the command is already registered, the
269+
`handle_command` will call the function defined
270+
for that command. The command extension API is
271+
defined as:
272+
273+
.. code-block:: python
274+
275+
def register_command(
276+
self: Self, command_name: Any, handler: Any,
277+
where_to_run: Any, run_in_thread: Any=False) -> bool: ...
278+
279+
The args of the `register_command` function
280+
are as follows:
281+
282+
- ``command_name``: The name of the command to register.
283+
284+
*Note: The ``command_name`` should start with a slash
285+
(e.g., ``/my_command``) to be recognized as a command.*
286+
287+
- ``handler``: The function to call when the command is received.
288+
- ``where_to_run``: Specifies where the command should be executed.
289+
- ``run_in_thread``: A boolean indicating whether to run the command in a separate thread.
290+
291+
This extension API allows server-side and
292+
console-side custom commands to be registered
293+
dynamically. Valid values for ``where_to_run``
294+
are ``"server"`` and ``"client"``. The ``"server"``
295+
means the command will be handled when a client
296+
sends the command, while the ``"client"`` means
297+
the command will be handled when the server input
298+
the command in console.
299+
300+
The ways to run the command will be different
301+
according to the args ``run_in_thread``. If
302+
``run_in_thread`` is ``True``, the command handler
303+
will be executed in a separate thread from the
304+
server's thread pool. If ``run_in_thread`` is
305+
``False``, the command handler will be executed
306+
synchronously in the main server thread.
307+
308+
TCP Server console commands
309+
---------------------------
310+
311+
The server console input thread accepts administrative commands when
312+
``is_input_command_in_console`` is ``True``. Supported console commands include:
313+
314+
- ``/stop``: stops the server and closes all active connections.
315+
- ``/status``: prints the current connection count and running state.
316+
- ``/clients``: prints the connected clients and their connection times.
317+
- ``/send_msg <message...> <client_id1> <client_id2> ...``: sends one or more messages to specific clients.
318+
- ``/file <file_path> <client_id>``: sends a file from the server to a specific client.
319+
- ``/file_folder <folder_path> <client_id>``: sends a folder from the server to a specific client.
320+
- ``/multiple_file_multiple_client <file1> <file2> ... <client1> <client2> ...``: sends multiple files to multiple clients.
321+
- ``/diff_multiple_file_diff_multiple_client <file1> <file2> ... <client1> <client2> ...``: sends different file lists to different clients.
322+
- ``/help``: prints a help summary of console commands.
323+
324+
These console commands make it easy to manage the active server and perform
325+
server-initiated file transfers without modifying the code.
326+
327+
TCP Server file transfer API
328+
----------------------------
329+
330+
The TCP server contains a file transfer subsystem that supports both client-to-server
331+
and server-to-client transfers.
332+
333+
Client-to-server transfer flow:
334+
335+
1. The client sends ``/file`` or ``/file_folder`` to request a transfer.
336+
2. ``handle_command`` starts a dedicated file-server thread using
337+
``file_transfer_server_recv_server_start_thread``.
338+
3. The server allocates an ephemeral transfer port with ``palloc`` and sends
339+
``/server_file_transfer_port <port> <client_id>`` back to the client.
340+
4. The client connects to that transfer port and sends file metadata, including
341+
length-prefixed filename and file size.
342+
5. The server receives the file and writes it under ``received_files``.
343+
344+
Server-to-client transfer flow:
345+
346+
- ``file_transfer_server_recv_client_start`` is used to initiate outgoing
347+
transfers from server to a connected client.
348+
- The server sends transfer commands to the client socket and waits for the
349+
client to establish the file transfer connection.
350+
- Folder transfers are performed recursively, with each file transfer respecting
351+
``self.max_file_transfer_thread_num`` and the configured semaphore limit.
352+
353+
Common file transfer helper methods include:
354+
355+
- ``file_transfer_server_recv_server_start``: receives file data from a client.
356+
- ``file_transfer_server_recv_client_start``: sends a file or folder to a client.
357+
- ``file_transfer_mode``: performs the low-level client-side transfer handshake.
358+
- ``file_transfer_mode_recv``: performs the low-level receive-side transfer handshake.
359+
360+
Port allocation API
361+
-------------------
362+
363+
When ``is_hand_alloc_port`` is ``True``, the server uses manual port allocation
364+
and lock files to avoid conflicts across multiple server instances. The relevant
365+
methods are:
366+
367+
- ``alloc_port``: allocate a port range for the server.
368+
- ``free_port``: release the allocated port range when the server stops.
369+
- ``hand_alloc_port`` and ``hand_free_port``: internal helpers used by the manual allocation flow.
370+
371+
This mode is useful when the server must reserve a controlled range of ports
372+
for client-file transfers or when multiple server processes share the same host.
373+
374+
TCP Server helper APIs
375+
----------------------
376+
377+
The following helper methods are also available on ``TCP_Server_Base``:
378+
379+
- ``broadcast(self, message, exclude_client=None)``: broadcast a message to all connected clients.
380+
- ``send_msg_to_specific_client(self, message)``: send a message to one or more specific clients by address.
381+
- ``submit_task(self, func, *args, **kwargs)``: submit work to the server's internal thread pool.
382+
- ``create_temporary_server(self, handler, port=None, max_connections=1)``: start a temporary TCP server for short-lived tasks.
383+
- ``create_temporary_client(self, server_host, server_port, bind_port=None, on_data=None)``: start a temporary client that receives data asynchronously.
384+
385+
These APIs make it easier to extend the base TCP server for custom command handling,
386+
background tasks, and temporary connections.

src/command_control_extension_tcp.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import ast
33
import json
4+
import copy
45
import shlex
56
import shutil
67
import traceback
@@ -10,7 +11,7 @@
1011
from datetime import datetime
1112
server_instance=None
1213
client_instance=None
13-
command_counter=0
14+
command_counter={}
1415
command_counter_lock=threading.Lock()
1516
def _setup_command():
1617
print("Setting up server command...")
@@ -57,15 +58,19 @@ def _command_handler(sock, addr, cmd):
5758
clients_num=0
5859
clients_list=[]
5960
commands_list=[]
61+
client_id=0
6062
for pair in command_client_pair:
6163
for msg in pair[0]:
62-
command_msg="/command"+" "+shlex.quote(msg)+" "
64+
command_msg=("/command"+" "+shlex.quote(msg)+" "+
65+
shlex.quote(str(len(pair[0])))+" ")
6366
for client in pair[1]:
64-
temp_msg=command_msg+shlex.quote(str(client))+"\n"
67+
temp_msg=(command_msg+shlex.quote(str(client_id))+
68+
" "+shlex.quote(str(client))+"\n")
6569
client_socket=client_class[client]["socket"]
6670
server_instance.send_message(
6771
client_socket=client_socket, message=temp_msg)
6872
print(f"Sending command to clients: {command_msg}")
73+
client_id+=1
6974
def _command_handler_server_setup(sock, addr, cmd):
7075
global command_counter
7176
print(f"Received command from {addr}: {cmd}")
@@ -78,14 +83,19 @@ def _command_handler_server_setup(sock, addr, cmd):
7883
print("Invalid command format.")
7984
return
8085
command = cmd_parts[1]
81-
client_addr = cmd_parts[2]
82-
with command_counter_lock:
83-
command_counter += 1
84-
cmd_id = command_counter
86+
client_addr = cmd_parts[4]
87+
command_total_num=int(cmd_parts[2])
88+
client_id=cmd_parts[3]
8589
log_dir = os.path.join(os.path.dirname(__file__), 'logs')
8690
if not os.path.exists(log_dir):
8791
os.makedirs(log_dir)
88-
log_filename = f"{cmd_id}.json"
92+
with command_counter_lock:
93+
if str(client_id) not in command_counter:
94+
command_counter[str(client_id)]=1
95+
else:
96+
command_counter[str(client_id)] += 1
97+
cmd_id = copy.copy(command_counter[str(client_id)])
98+
log_filename = "logs{}.json".format("_"+str(client_id))
8999
log_path = os.path.join(log_dir, log_filename)
90100
try:
91101
result = subprocess.run(
@@ -103,6 +113,8 @@ def _command_handler_server_setup(sock, addr, cmd):
103113
log_line = {
104114
"timestamp": datetime.now().isoformat(),
105115
"command": command,
116+
"cmd_id": str(cmd_id),
117+
"client_id": str(client_id),
106118
"client": client_addr,
107119
"from": str(addr),
108120
"output": output,
@@ -123,28 +135,34 @@ def _command_handler_server_setup(sock, addr, cmd):
123135
json.dump(log_data, f, ensure_ascii=False, indent=2)
124136
print(f"Log written to {log_path}")
125137
msg="/file \"{}\"".format(log_path)
126-
client_instance.file_transfer_client_recv_client_start(
127-
message=msg, file_folder_abspath=None)
128-
client_instance.send_message(
129-
client_socket=client_instance.client_socket,
130-
message="/command_done \"{}\"".format(log_filename))
138+
if cmd_id==command_total_num:
139+
command_counter[str(client_id)]=0
140+
client_instance.file_transfer_client_recv_client_start(
141+
message=msg, file_folder_abspath=None)
142+
client_instance.send_message(
143+
client_socket=client_instance.client_socket,
144+
message="/command_done \"{}\" \"{}\"".format(log_filename, log_path))
145+
else:
146+
pass
131147
print("Dealing the command seccessfully!")
132148
def _command_done_dealing_server(sock, addr, cmd):
133-
log_filename=shlex.split(cmd)[1]
149+
cmd_parts = shlex.split(cmd)
150+
log_filename = cmd_parts[1]
151+
log_path = cmd_parts[2]
134152
log_dir=os.path.join(os.path.dirname(__file__), 'logs')
135153
if os.path.exists(log_dir):
136154
pass
137155
else:
138156
os.mkdir(log_dir)
139157
received_log_file=os.path.join(
140-
server_instance.file_transfer_dir, log_filename)
158+
server_instance.file_transfer_dir, log_path)
141159
try:
142160
shutil.move(
143161
received_log_file, os.path.join(log_dir, log_filename))
144162
print("Command Done!")
145163
except:
146164
traceback.print_exc()
147-
print("ErrorWhileMovingTheLogFile: moving log file faled.")
165+
print("ErrorWhileMovingTheLogFile: moving log file failed.")
148166
def client_setup():
149167
global client_instance
150168
client_instance=connect_tcp.TCP_Client_Base(

src/connect_tcp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ def handle_command(self, client_socket, client_address, command): # deal with s
496496
run_in_thread = self._custom_handler_threaded[0].get(
497497
cmd_name, False)
498498
if run_in_thread:
499-
self._custom_executor.submit(
499+
self.submit_task(
500500
self._execute_custom_handler, handler, command,
501501
client_socket, client_address)
502502
return "Command received, processing in background.\n"
@@ -1194,7 +1194,7 @@ def console_input(self): # deal consule input
11941194
run_in_thread = self._custom_handler_threaded[1].get(
11951195
cmd_name, False)
11961196
if run_in_thread:
1197-
self._custom_executor.submit(
1197+
self.submit_task(
11981198
self._execute_custom_handler, handler, deal_cmd)
11991199
pass
12001200
else:

0 commit comments

Comments
 (0)