Applied IPC with Semaphore
ในตัวอย่างข้างล่างนี้จะเพิ่มเทคนิคของ semaphore เข้าไปในตัวอย่าง Multi-Remote Commanders โดยก่อนที่ thread จะเริ่มประมวลผลคำขอของ client ที่เชื่อมต่อผ่าน socket เข้ามา มันจะพยายามลดค่าของ semaphore โดยใช้ sem_wait()
หากค่าของ semaphore เป็นศูนย์ (สล็อตทั้งหมดสำหรับการประมวลผลพร้อมกันถูกจองแล้ว) thread นั้นก็จะถูกบล็อก และรอจนกว่า thread อื่นจะประมวลผลเสร็จสิ้นและเรียก sem_post()
(ซึ่งจะเพิ่มค่าของ semaphore)
วิธีนี้ semaphore จะจำกัดจำนวน thread ที่ทำงานพร้อมกันบนส่วนของโค้ดที่ใช้ทรัพยากรมาก ทำให้เซิร์ฟเวอร์ไม่ต้องเผชิญกับคำขอมากเกินไปในเวลาเดียวกัน วิธีการนี้เป็นหนึ่งในรูปแบบของการจำกัดอัตรา
ในบริบทของ client-server semaphores โดยทั่วไปจะถูกใช้ในฝั่งเซิร์ฟเวอร์ เนื่องจากเกิดการแข่งขันทรัพยากรเพเพราะมีการเข้าถึงพร้อมกันของ clients หลายตัว ดังนั้นรูปแบบการควบคุมนี้เป็นสิ่งที่พื้นฐานในสถานการณ์ที่คำขอของลูกค้าอาจต้องการทรัพยากรมาก และเซิร์ฟเวอร์มีความเสี่ยงที่จะขาดทรัพยากรหากมีการประมวลผลคำขอมากเกินไปในเวลาเดียวกัน
// 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 หน้าต่าง ดังนี้
$ 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.
$ 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
$ ./client 10.211.55.3 8080 ─╯
shell> date <------------- พิมพ์คำสั่งเพื่อส่งไปรันที่เครื่องปลายทาง
Results from the remote machine [10.211.55.3]:
อ. 24 ต.ค. 2566 14:09:43 +07
$ ./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
Last updated