# Multi-Remote Commanders

จากโปรแกรม [`server.c`](https://docs.aic-eec.com/computer-operation-systems/linux-os-dev.-engineer/applied-ipc/remote-commander) ก่อนหน้านี้ ยังมีข้อจำกัดในการรองรับ 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 %}
