# Applied IPC with Semaphore

ในตัวอย่างข้างล่างนี้จะเพิ่มเทคนิคของ semaphore เข้าไปในตัวอย่าง [Multi-Remote Commanders ](/computer-operation-systems/linux-os-dev.-engineer/applied-ipc/multi-remote-commanders.md)โดยก่อนที่ thread จะเริ่มประมวลผลคำขอของ client ที่เชื่อมต่อผ่าน socket เข้ามา มันจะพยายามลดค่าของ semaphore โดยใช้ `sem_wait()` หากค่าของ semaphore เป็นศูนย์ (สล็อตทั้งหมดสำหรับการประมวลผลพร้อมกันถูกจองแล้ว)  thread นั้นก็จะถูกบล็อก และรอจนกว่า thread อื่นจะประมวลผลเสร็จสิ้นและเรียก `sem_post()` (ซึ่งจะเพิ่มค่าของ semaphore)

วิธีนี้ semaphore จะจำกัดจำนวน thread ที่ทำงานพร้อมกันบนส่วนของโค้ดที่ใช้ทรัพยากรมาก ทำให้เซิร์ฟเวอร์ไม่ต้องเผชิญกับคำขอมากเกินไปในเวลาเดียวกัน วิธีการนี้เป็นหนึ่งในรูปแบบของการจำกัดอัตรา

ในบริบทของ client-server semaphores โดยทั่วไปจะถูกใช้ในฝั่งเซิร์ฟเวอร์ เนื่องจากเกิดการแข่งขันทรัพยากรเพเพราะมีการเข้าถึงพร้อมกันของ clients หลายตัว ดังนั้นรูปแบบการควบคุมนี้เป็นสิ่งที่พื้นฐานในสถานการณ์ที่คำขอของลูกค้าอาจต้องการทรัพยากรมาก และเซิร์ฟเวอร์มีความเสี่ยงที่จะขาดทรัพยากรหากมีการประมวลผลคำขอมากเกินไปในเวลาเดียวกัน&#x20;

```c
// new_server_with_semaphore.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pthread.h>

#include <semaphore.h>

#define PORT 8080
#define BUFFER_SIZE 1024

#define MAX_CONCURRENT_CLIENTS 3  // Max number of clients to handle concurrently

sem_t sem;

// 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};

    // Wait for the semaphore to become available
    sem_wait(&sem);

    // 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
            
            // After done processing the client's request
            sem_post(&sem); // Signal that this thread is done with the resource

        } 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 8080
    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);

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

    // Display the server's IP and listening port
    char host[256];
    char *IP;
    struct hostent *host_entry;
    gethostname(host, sizeof(host));
    host_entry = gethostbyname(host);
    IP = inet_ntoa(*((struct in_addr*) host_entry->h_addr_list[0]));

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

    // Initialize the semaphore to limit the number of concurrent client processing
    sem_init(&sem, 0, MAX_CONCURRENT_CLIENTS);

    printf("Server is running at IP: %s on Port: %d\n", IP, PORT);

    // 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
        }
    }

    // Destroy semaphore in any cleanup or exit code, if you ever break out of the infinite loop
    sem_destroy(&sem);

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

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

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

```shell-session
$ gcc -o new_server_with_semaphore new_server_with_semaphore.c -lpthread -Wall
$ ./new_server_with_semaphore
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
```

{% endtab %}

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

```shell-session
$ ./client 10.211.55.3 8080                                                                                         ─╯
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-with-semaphore.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.
