c - 如何在 C 中将客户端程序重定向到新的任意端口

标签 c

我正在尝试用C语言实现一个客户端-服务器程序。我希望客户端在特定端口5555上连接到服务器。服务器接受连接后,我希望服务器将任意端口发送回客户端。为了将客户端重定向到客户端可以发送信息的端口。我的问题是客户端收到新端口但无法通过该端口连接到服务器

客户端源代码如下(graham.c):

#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#define PORT 5555 
#define MAX_USERNAME_SIZE 16
#define MAX_FILENAME_SIZE 255
#define BUFFSIZE 1024

char *extract_filename_from_path(char *pathname);
char *extract_filename_from_path(char *pathname)
{
    char *last = strrchr(pathname, '/');
    return (last != NULL) ? last + 1 : pathname;
}


int main(int argc, char *argv[])
{
    int sockfd1, sockfd2, numbytes;
    struct sockaddr_in their_addr;
    struct hostent *he;
    FILE *photo;
    char buff[BUFFSIZE];
    short status = 1;

    // verifier le nombre d'arguments
    if (argc != 7)
    {
        fprintf(stderr, "Compléter sa commande de la façon suivante:\n"
                "graham ip_serveur nom_utilisateur_dans_le_pool"
                   " /chemin/vers/la/photo année mois jour");
        return EXIT_FAILURE;
    }

    if ((he = gethostbyname(argv[1])) == NULL)
    {
        perror("Client: gethostbyname");
        return EXIT_FAILURE;
    }

    // initialiser le socket 1
    if ((sockfd1 = socket(PF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("Client: socket");
        return EXIT_FAILURE;
    }
    their_addr.sin_family = AF_INET;
    their_addr.sin_port = htons(PORT);
    their_addr.sin_addr = *((struct in_addr*)he->h_addr);
    memset(&(their_addr.sin_zero), '\0', 8);

    if (connect(sockfd1, (struct sockaddr *)&their_addr, 
                sizeof(struct sockaddr)) == -1)
    {
        perror("Client: connect 1");
        return EXIT_FAILURE;
    }
    // get port
    short int port;
    if (recv(sockfd1, &port, sizeof(short int), 0) == -1)
    {
        perror("Client: recv port");
        return EXIT_FAILURE;
    }   
    printf("recieved port: %d\n", ntohs(port));
    close(sockfd1);
    // changer de port
    // initialiser le socket 2
    if ((sockfd2 = socket(PF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("Client: socket");
        return EXIT_FAILURE;
    }
    their_addr.sin_family = AF_INET;
    their_addr.sin_port = port; 
    their_addr.sin_addr = *((struct in_addr*)he->h_addr);
    memset(&(their_addr.sin_zero), '\0', 8);

    if (connect(sockfd2, (struct sockaddr *)&their_addr, 
                sizeof(struct sockaddr)) == -1)
    {
        perror("Client: connect 2");
        return EXIT_FAILURE;
    }

    // 2) envoyer le nom d'utilisateur
    if (send(sockfd2, argv[2], MAX_USERNAME_SIZE, 0) == -1)
    {
        perror("Client: send username");
        return EXIT_FAILURE;
    }
    // 3) envoyer la date de prise
    if (send(sockfd2, argv[4], 5, 0) == -1)
    {
        perror("Client: send year");
        return EXIT_FAILURE;
    }
    if (send(sockfd2, argv[5], 3, 0) == -1)
    {
        perror("Client: send month");
        return EXIT_FAILURE;
    }
    if (send(sockfd2, argv[6], 3, 0) == -1)
    {
        perror("Client: send day");
        return EXIT_FAILURE;
    }
    // 4) envoyer le nom de l'image
    if (send(sockfd2, extract_filename_from_path(argv[3]), MAX_FILENAME_SIZE, 0) == -1)
    {
        perror("Client: send filename");
        return EXIT_SUCCESS;
    }
    // 5) envoyer l'image au client
    if ((photo = fopen(argv[3], "r")) == NULL)
    {
        perror("Client: fopen");
        return EXIT_FAILURE;
    }
    do
    {
        if ((numbytes = write(sockfd2, buff, BUFFSIZE)) > 0)
        {
            for (int i = 0; i < numbytes; i++)
            {
                buff[i] = getc(photo);
            }
        }
        else
        {
            break;
        }
    }while(1);
    fclose(photo);
    // 6) recevoir et traiter le code de retour
    if ((numbytes = recv(sockfd2, &status, sizeof(short int), 0)) == -1)
    {
        perror("Client: recv return code");
        return EXIT_FAILURE;
    }
    if ((status = ntohs(status)))
    {
        fprintf(stderr, "Echec de la transaction");
    }
    close(sockfd2);
    return EXIT_SUCCESS;
}

服务器源代码是(spbx.c):

#include "spbx.h"

int my_read(FILE *target, const int source_fd)
{
    char buff[BUFFSIZE];
    memset(buff, '\0', BUFFSIZE);
    int numbytes;
    int status = 0;
    if ((numbytes = read(source_fd, buff, BUFFSIZE)) > 0)
    {
        for (int i = 0; i < numbytes; i++)
        {
            putc(buff[i], target);
        }
    }
    else
    {
        status = -1;    
    }
    return status;
}
int my_mkdir(const char *path, mode_t mode)
{
    struct stat s;
    int status = 0;
    if (stat(path, &s) == -1)
    {
        if (mkdir(path, mode) == -1 && errno != EEXIST)
        {
            status = -1;
        }
    }
    else
    {
        errno = ENOTDIR;
        status = -1;
    }
    return status;
}
int init_socket(int *sockfd, const short int port)
{
    struct sockaddr_in my_addr;
    int yes = 1;
    if ((*sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("Serveur: socket");
        return -1;
    }

    if (setsockopt(*sockfd, SOL_SOCKET,
                SO_REUSEADDR, &yes, sizeof(int)) == -1)
    {
        perror("Serveur: setsockopt");
        return -1;
    }
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(port);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    memset(&(my_addr.sin_zero), '\0', 8);
    if (bind(*sockfd, (struct sockaddr *)&my_addr,
                sizeof(struct sockaddr)) == -1)
    {
        perror("Serveur: bind");
        return -1;
    }

    if (listen(*sockfd, BACKLOG) == -1)
    {
        perror("Serveur: listen");
        return -1;
    }
    return 0;
}
int recv_date(int new_fd, char **year, char **month, char **day)
{
    int numbytes;
    if ((numbytes = recv(new_fd, year, 5, 0)) == -1)
    {
        perror("Serveur: recv year");
        return -1;
    }
    if ((numbytes = recv(new_fd, month, 3, 0)) == -1)
    {
        perror("Serveur: recv month");
        return -1;
    }
    if ((numbytes = recv(new_fd, day, 3, 0)) == -1)
    {
        perror("Serveur: recv day");
        return -1;
    }
    return 0;
}

int main(int argc, char *argv[])
{
    // ecouter sur sockfd et etablir la nouvelle
    // connexion sur new_fd
    int sockfd1, sockfd2, new_fd, numbytes, photo_tmp_fd;
    struct sockaddr_in their_addr;
    unsigned int sin_size;
    char username[MAX_USERNAME_SIZE];
    char filename[MAX_FILENAME_SIZE];
    char path_tmp[MAX_FILEPATH_SIZE];
    char path[MAX_FILEPATH_SIZE];
    memset(path, '\0', MAX_FILEPATH_SIZE);
    char year[5], month[3], day[3];
    FILE *photo_tmp;
    FILE *photo;
    short int status = 1;
    fd_set readfds;
    int max;

    // verifier le nombre d'arguments
    if (argc != 2)
    {
        fprintf(stderr, "Donner le chemin vers le pool 2\n");
        return EXIT_FAILURE;
    }

    // initialiser les socket

    if(init_socket(&sockfd1, MY_PORT) == -1) return EXIT_FAILURE;
    if(init_socket(&sockfd2, 0) == -1) return EXIT_FAILURE;
    struct sockaddr_in my_addr;

    if (getsockname(sockfd2, (struct sockaddr*)&my_addr, &sin_size) == -1)
    {
        perror("Serveur: getsockname");
        return EXIT_FAILURE;
    }

    sin_size = sizeof(struct sockaddr_in);

    while (1)
    {
        //initialisation du fd_set
        FD_ZERO(&readfds);
        FD_SET(sockfd1, &readfds);
        FD_SET(sockfd2, &readfds);
        max = (sockfd1 < sockfd2)? sockfd2 : sockfd1;
        select(max + 1, &readfds, NULL, NULL, NULL);

        if (FD_ISSET(sockfd1, &readfds))
        {
            // on envoie le port
            if ((new_fd = accept(sockfd1,
                    (struct sockaddr *)&their_addr,
                    &sin_size)) == -1)
            {
                perror("Serveur: accept 1");
                return EXIT_FAILURE;
            }
            printf("Serveur: connexion reçue du client %s\n",
                    inet_ntoa(their_addr.sin_addr));
            if (fork() == 0)
            {
                close(sockfd1);
                if (send(new_fd, &my_addr.sin_port, sizeof(short int), 0) == -1)
                {
                    perror("Serveur: send port");
                    return EXIT_FAILURE;
                }
                return EXIT_SUCCESS;
            }
            close(new_fd);
        }
        else if (FD_ISSET(sockfd2, &readfds))
        {
            if ((new_fd = accept(sockfd2,
                (struct sockaddr *)&their_addr, &sin_size)) == -1)
            {
                perror("Serveur: accept 2");
                return EXIT_FAILURE;
            }

            printf("Serveur: connexion reçue du client %s redirigé vers le port %d\n",
                inet_ntoa(their_addr.sin_addr), ntohs(my_addr.sin_port));

            if (fork() == 0)
            {
                close(sockfd1);
                close(sockfd2);

                // 2) recevoir l'utilisateur du pool sous forme
                // de chaîne de caractères
                if ((numbytes = recv(new_fd, &username, MAX_USERNAME_SIZE, 0)) == -1)
                {
                    perror("Serveur: recv username");
                    return EXIT_FAILURE;
                }
                // 3) récevoir la date de prise de vue
                if(recv_date(new_fd, (char **) &year, (char **)&month, (char **)  &day) == -1) 
                    return EXIT_FAILURE;

                // 4) recevoir le nom du fichier
                if ((numbytes = recv(new_fd, &filename, MAX_FILENAME_SIZE, 0)) == -1)
                {
                    perror("Serveur: recv filename");
                    return EXIT_FAILURE;
                }
                // 5) recevoir l'image téléversée par le client
                sprintf(path_tmp, "/tmp/%s", filename);
                if ((photo_tmp = fopen(path_tmp, "w")) == NULL)
                {
                    perror("Serveur: fopen");
                    return EXIT_FAILURE;
                }
                do
                {
                    if (my_read(photo_tmp, sockfd2) == -1) break;
                }while(1);
                fclose(photo_tmp);

                // 6) creer l'arborescence

                sprintf(path, "%s/%s", argv[1], username);
                my_mkdir(path, MODE);

                sprintf(path, "%s/%s/%s", argv[1], username, year);
                my_mkdir(path, MODE);

                sprintf(path, "%s/%s/%s/%s", argv[1], username, year, month);
                my_mkdir(path, MODE);

                sprintf(path, "%s/%s/%s/%s/%s", argv[1], username, year, month, day);
                my_mkdir(path, MODE);

                sprintf(path, "%s/%s/%s/%s/%s/%s", argv[1], username, year, month, day, filename);


                // 7) Enregistrement de la photographie
                if ((photo = fopen(path, "w")) == NULL 
                        || (photo_tmp_fd = open(path_tmp, O_WRONLY)) == -1)
                {
                    perror("Serveur: fopen");
                    return EXIT_FAILURE;
                }
                do
                {
                    if (my_read(photo, photo_tmp_fd) == -1) break;
                }while(1);
                close(photo_tmp_fd);
                fclose(photo);

                // 8) enoyer le code 0 en cas de success
                status = htons(0);
                if (send(new_fd, &status, sizeof(short int), 0) == -1)
                {
                    perror("Serveur: send code");
                    return EXIT_FAILURE;
                }   
                return EXIT_SUCCESS;

            }
            close(new_fd);//9) deconnexion
        }
        else
        {
            fprintf(stderr, "erreur de select\n");
        }
    }
    return EXIT_SUCCESS;
}

当我运行服务器并尝试使用客户端程序连接它两次时,得到以下结果:

服务器端:

Serveur: connexion reçue du client 127.0.0.1
Serveur: connexion reçue du client 127.0.0.1

客户端:

recieved port: 4645
Client: connect 2: Connection refused
recieved port: 4645
Client: connect 2: Connection refused

我唯一注意到的是,对于服务器的同一次执行和客户端的多次执行,端口保持不变。但我不知道我做错了什么。提前致谢。

最佳答案

我应该在客户端连接后创建新的套接字,如所提到的 @克里斯特纳。所以我移动了以下几行:

if(init_socket(&sockfd2, 0) == -1) return EXIT_FAILURE;

if (getsockname(sockfd2, (struct sockaddr*)&my_addr, &sin_size) == -1)
{
    perror("Serveur: getsockname");
    return EXIT_FAILURE;
}

在服务器程序中的第一个 accept() 之后和 fork() 之前。

关于c - 如何在 C 中将客户端程序重定向到新的任意端口,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53868780/

相关文章:

html - 如何在没有客户端脚本的情况下让 Web 客户端处理表单以将页面 ID 信息发送到服务器

c - 是否可以用 tee 捕获标准输入?

c++ - 初始化 Vanilla C 结构

c - strcpy() 创建错误

c - 与 4x4 网格比较时如何设置阈值

objective-c - C printf 格式说明符 %_fi

c - 使用 gcc 和 glib 的奇怪行为?

c - 这个奇怪的函数将字符串转换为二进制吗?

c++ - 在 CMAKE 中添加 -fPIC 编译器选项的惯用方式是什么?

c++ - 流式文件增量编码/解码