# Multi-Remote Commanders

จากโปรแกรม [`server.c`](/computer-operation-systems/linux-os-dev.-engineer/applied-ipc/remote-commander.md) ก่อนหน้านี้ ยังมีข้อจำกัดในการรองรับ client ที่เชื่อมต่อผ่าน socket เข้ามา จึงได้ปรับปรุงโปรแกรม server ให้สามารถรองรับการเชื่อมต่อจากตัว clients ได้หลายตัวพร้อมกันโดยใช้ความสามารถของ `pthread` ดังนี้

```c
// new_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>

#define PORT 8080
#define BUFFER_SIZE 1024

// Struct for arguments to pass to the thread handler
typedef struct {
    int socket;
} client_thread_args;

// Function to execute the command and retrieve the output
void execute_command(char* command, char* result, size_t result_size) {
    // Clear the result buffer
    memset(result, 0, result_size);

    FILE* pipe = popen(command, "r");
    if (!pipe) {
        snprintf(result, result_size, "Error: failed to open pipe.\n");
        return;
    }

    // Read command result into the buffer
    char buffer[128];
    while (!feof(pipe)) {
        if (fgets(buffer, sizeof(buffer), pipe) != NULL) {
            strncat(result, buffer, result_size - strlen(result) - 1);
        }
    }

    pclose(pipe);
}

// Thread function to handle communication with a client
void *client_handler(void *arg) {
    client_thread_args *args = (client_thread_args *)arg;
    int client_socket = args->socket;
    free(arg); // Free memory for arguments

    char buffer[BUFFER_SIZE] = {0};
    char result[BUFFER_SIZE] = {0};

    // Communication loop with the connected client
    while (1) {
        memset(buffer, 0, BUFFER_SIZE); // Clear the buffer
        int read_size = read(client_socket, buffer, BUFFER_SIZE);
        if (read_size > 0) {
            printf("Received command: %s\n", buffer);

            // Special case: close connection if client sends "exit"
            if (strcmp(buffer, "exit") == 0) {
                printf("Closing connection...\n");
                close(client_socket);
                break; // Break out of the loop to end this thread
            }

            // Execute the command received from the client
            execute_command(buffer, result, BUFFER_SIZE);

            // Send back the result
            send(client_socket, result, strlen(result), 0);
            memset(result, 0, BUFFER_SIZE); // Clear the result buffer
        } else {
            printf("Client disconnected or read error...\n");
            close(client_socket);
            break; // Break out of the loop to end this thread
        }
    }

    return NULL; // Thread ends here
}

int main(int argc, char const *argv[]) {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);

    // Creating socket file descriptor
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // Forcefully attaching socket to the PORT
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // Forcefully attaching socket to the PORT
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    printf("Server started... Waiting for connections.\n");

    // Main loop to keep server running and accepting new connections
    while (1) {
        if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
            perror("accept");
            exit(EXIT_FAILURE);
        }

        printf("Connection established...\n");

        // Create a thread for each client
        pthread_t client_thread;
        client_thread_args *args = (client_thread_args *)malloc(sizeof(*args));
        args->socket = new_socket;
        if (pthread_create(&client_thread, NULL, client_handler, args) != 0) {
            perror("Could not create thread");
            free(args); // Don't forget to free memory on failure
            close(new_socket);
        } else {
            pthread_detach(client_thread); // Automatically reclaim thread resources on exit
        }
    }

    return 0; // The server should never reach this part
}
```

ทำการคอมไพล์โปรแกรมทั้งสอง โดยสร้างหน้าต่าง terminal จำนวน 3 หน้าต่าง ดังนี้

{% tabs %}
{% tab title="หน้าต่าง Terminal ที่ 1" %}

```shell-session
$ gcc -o new_server new_server.c -lpthread -Wall
$ ./new_server
Server is running at IP: 10.211.55.3 on Port: 8080
Connection established...
Connection established...
Received command: ls
Result sent back to client. 
Received command: date
Result sent back to client. 
Received command: ifconfig
Result sent back to client. 
Received command: uname -a
Result sent back to client. 
```

{% endtab %}

{% tab title="หน้าต่าง Terminal ที่ 2 - Client #1" %}

```shell-session
$ gcc -Wall -o client client.c -lreadline
$ ./client 10.211.55.3 8080                                                                                         ─╯
shell> ls <------------- พิมพ์คำสั่งเพื่อส่งไปรันที่เครื่องปลายทาง
Results from the remote machine [10.211.55.3]: 
client
client.c
new_server
new_server.c
other.c
server
server.c

shell> ifconfig <------------- พิมพ์คำสั่งเพื่อส่งไปรันที่เครื่องปลายทาง
Results from the remote machine [10.211.55.3]: 
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:88:82:fe:7b  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

enp0s5: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.211.55.3  netmask 255.255.255.0  broadcast 10.211.55.255
        inet6 fe80::c7de:82d6:f3cc:96a0  prefixlen 64  scopeid 0x20<link>
        inet6 fd5c:edc7:dfd5:6910:6ea3:1b97:d393:2793  prefixlen 64  scopeid 0x0<global>
        inet6 fd5c:edc7:dfd5:6910:1956:adea:5a7b:c494  prefixlen 64  scopeid 0x0<global>
        ether 00:1c:42:8a:01:82  txqueuelen 1000  (Ethernet)
        RX packets 1908378  bytes 2334364363 (2.3 GB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 501740  bytes 57225273 (57.2 MB)
        TX errors 0  dropped 0 overruns
```

{% endtab %}

{% tab title="หน้าต่าง Terminal ที่ 3 - Client #2" %}

```shell-session
$ ./client 10.211.55.3 8080                                                                                         ─╯
shell> date  <------------- พิมพ์คำสั่งเพื่อส่งไปรันที่เครื่องปลายทาง
Results from the remote machine [10.211.55.3]: 
อ. 24 ต.ค. 2566 14:09:43 +07

shell> uname -a <------------- พิมพ์คำสั่งเพื่อส่งไปรันที่เครื่องปลายทาง
Results from the remote machine [10.211.55.3]: 
Linux wiroon-ubuntu 6.2.0-32-generic #32~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug 18 12:38:40 UTC 2 aarch64 aarch64 aarch64 GNU/Linux
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.aic-eec.com/computer-operation-systems/linux-os-dev.-engineer/applied-ipc/multi-remote-commanders.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
