11.1 סוקטים פתרון
פתרון - סוקטים - sockets¶
פתרון 1 - לקוח TCP בסיסי¶
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUFFER_SIZE 1024
int main(int argc, char *argv[])
{
if (argc != 3) {
fprintf(stderr, "usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
const char *ip = argv[1];
int port = atoi(argv[2]);
/* יצירת סוקט */
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
exit(1);
}
/* הגדרת כתובת השרת */
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0) {
fprintf(stderr, "invalid address: %s\n", ip);
close(sockfd);
exit(1);
}
/* התחברות */
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect");
close(sockfd);
exit(1);
}
printf("connected to %s:%d\n", ip, port);
/* לולאה ראשית */
char input[BUFFER_SIZE];
char buffer[BUFFER_SIZE];
while (1) {
printf("> ");
if (fgets(input, sizeof(input), stdin) == NULL)
break;
/* הסרת newline */
input[strcspn(input, "\n")] = '\0';
if (strcmp(input, "quit") == 0)
break;
/* שליחה לשרת */
write(sockfd, input, strlen(input));
/* קבלת תשובה */
ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
if (n <= 0) {
printf("server closed connection\n");
break;
}
buffer[n] = '\0';
printf("server: %s\n", buffer);
}
close(sockfd);
return 0;
}
פתרון 2 - שרת הד משופר¶
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUFFER_SIZE 1024
#define PREFIX "[ECHO] "
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(1);
}
int port = atoi(argv[1]);
/* יצירת סוקט */
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(1);
}
/* SO_REUSEADDR */
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
/* bind */
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(server_fd);
exit(1);
}
/* listen */
if (listen(server_fd, 5) == -1) {
perror("listen");
close(server_fd);
exit(1);
}
printf("echo server listening on port %d...\n", port);
/* לולאה ראשית */
while (1) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd == -1) {
perror("accept");
continue;
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
printf("client connected from %s:%d\n", client_ip, ntohs(client_addr.sin_port));
/* טיפול בלקוח */
char buffer[BUFFER_SIZE];
char response[BUFFER_SIZE + sizeof(PREFIX)];
ssize_t n;
while ((n = read(client_fd, buffer, sizeof(buffer) - 1)) > 0) {
buffer[n] = '\0';
snprintf(response, sizeof(response), "%s%s", PREFIX, buffer);
write(client_fd, response, strlen(response));
}
printf("client %s:%d disconnected\n", client_ip, ntohs(client_addr.sin_port));
close(client_fd);
}
close(server_fd);
return 0;
}
פתרון 3 - שרת זמן - time server¶
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 9999
int main(void)
{
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(1);
}
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(server_fd);
exit(1);
}
if (listen(server_fd, 5) == -1) {
perror("listen");
close(server_fd);
exit(1);
}
printf("time server listening on port %d...\n", PORT);
while (1) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd == -1) {
perror("accept");
continue;
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
printf("serving time to %s:%d\n", client_ip, ntohs(client_addr.sin_port));
/* קבלת הזמן הנוכחי ושליחתו */
time_t now = time(NULL);
char *time_str = ctime(&now);
write(client_fd, time_str, strlen(time_str));
close(client_fd);
}
close(server_fd);
return 0;
}
הפונקציה ctime מחזירה מחרוזת בפורמט כמו "Sun Mar 8 14:30:00 2026\n". היא כבר כוללת newline בסוף. שימו לב ש-ctime מחזירה מצביע לבאפר סטטי - לא צריך (ולא צריך!) לשחרר אותו.
פתרון 4 - בדיקת פורטים - port scanner¶
#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 <errno.h>
int main(int argc, char *argv[])
{
if (argc != 4) {
fprintf(stderr, "usage: %s <ip> <start_port> <end_port>\n", argv[0]);
exit(1);
}
const char *ip = argv[1];
int start_port = atoi(argv[2]);
int end_port = atoi(argv[3]);
if (start_port < 1 || end_port > 65535 || start_port > end_port) {
fprintf(stderr, "invalid port range\n");
exit(1);
}
printf("scanning %s ports %d-%d...\n", ip, start_port, end_port);
int open_count = 0;
for (int port = start_port; port <= end_port; port++) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
exit(1);
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET, ip, &addr.sin_addr);
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
printf("port %d - open\n", port);
open_count++;
}
close(sockfd);
}
printf("\nscan complete. %d open ports found.\n", open_count);
return 0;
}
הסורק הזה פשוט ועובד, אבל הוא איטי - כל connect לפורט סגור מחכה לtimeout (בדרך כלל כמה שניות). בפרק 11.2 נלמד על I/O multiplexing שיאפשר לנו לסרוק הרבה פורטים במקביל.
פתרון 5 - הפניית קלט/פלט לסוקט¶
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#define PORT 4444
int main(void)
{
/* טיפול ב-SIGCHLD כדי לנקות תהליכי ילד */
signal(SIGCHLD, SIG_IGN);
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(1);
}
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(server_fd);
exit(1);
}
if (listen(server_fd, 5) == -1) {
perror("listen");
close(server_fd);
exit(1);
}
printf("shell server listening on port %d...\n", PORT);
while (1) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd == -1) {
perror("accept");
continue;
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
printf("client connected from %s:%d\n", client_ip, ntohs(client_addr.sin_port));
pid_t pid = fork();
if (pid == -1) {
perror("fork");
close(client_fd);
continue;
}
if (pid == 0) {
/* תהליך ילד */
close(server_fd); /* הילד לא צריך את סוקט השרת */
/* הפניית stdin, stdout, stderr לסוקט */
dup2(client_fd, STDIN_FILENO);
dup2(client_fd, STDOUT_FILENO);
dup2(client_fd, STDERR_FILENO);
close(client_fd); /* כבר משוכפל, לא צריך את המקורי */
/* הרצת shell */
execvp("/bin/sh", (char *[]){"sh", NULL});
perror("execvp"); /* מגיעים לכאן רק אם exec נכשל */
exit(1);
}
/* תהליך אב */
printf("spawned shell for client (pid %d)\n", pid);
close(client_fd); /* האב לא צריך את סוקט הלקוח */
}
close(server_fd);
return 0;
}
נקודות חשובות בפתרון:
signal(SIGCHLD, SIG_IGN)- אומר למערכת ההפעלה לנקות תהליכי ילד אוטומטית כשהם מסיימים, כדי למנוע תהליכי zombie. למדנו על זה בפרק 5.4.- הילד סוגר את
server_fdכי הוא לא צריך להאזין לחיבורים חדשים. - האב סוגר את
client_fdכי הילד כבר מטפל בלקוח הזה. dup2משכפלת את ה-fd - אחרי השכפול, גם fd 0, 1, 2 וגםclient_fdמצביעים לאותו סוקט. לכן אפשר לסגור אתclient_fdהמקורי.