לדלג לתוכן

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 המקורי.