prosource

C/C++(GCC/G++)을 사용하는 Linux의 소켓 프로그래밍에서 파일 보내기 및 받기

probook 2023. 6. 27. 22:23
반응형

C/C++(GCC/G++)을 사용하는 Linux의 소켓 프로그래밍에서 파일 보내기 및 받기

리눅스에서 실행되는 클라이언트-서버 아키텍처를 소켓과 파일 송수신이 가능한 C/C++ 언어를 사용하여 구현하고 싶습니다.이 일을 쉽게 할 수 있는 도서관이 있습니까?예를 들어주실 분 있나요?

가장 휴대하기 쉬운 솔루션은 파일을 청크로 읽은 다음 소켓에 데이터를 루프 형태로 쓰는 것입니다(이와 마찬가지로 파일을 수신할 때도 마찬가지입니다).버퍼를 해당 버퍼에 할당하고 해당 버퍼에서 소켓으로 할당합니다(사용할 수도 있음).send그리고.recv이는 소켓별 데이터 쓰기 및 읽기 방법입니다.개요는 다음과 같습니다.

while (1) {
    // Read data into buffer.  We may not have enough to fill up buffer, so we
    // store how many bytes were actually read in bytes_read.
    int bytes_read = read(input_file, buffer, sizeof(buffer));
    if (bytes_read == 0) // We're done reading from the file
        break;
    
    if (bytes_read < 0) {
        // handle errors
    }
    
    // You need a loop for the write, because not all of the data may be written
    // in one call; write will return how many bytes were written. p keeps
    // track of where in the buffer we are, while we decrement bytes_read
    // to keep track of how many bytes are left to write.
    void *p = buffer;
    while (bytes_read > 0) {
        int bytes_written = write(output_socket, p, bytes_read);
        if (bytes_written <= 0) {
            // handle errors
        }
        bytes_read -= bytes_written;
        p += bytes_written;
    }
}

다에대설한반읽보오시십어드시를음에 대한 를 반드시 .read그리고.write특히 오류를 처리할 때 주의해야 합니다.일부 오류 코드는 다시 시도해야 한다는 것을 의미합니다. 예를 들어 다음과 같이 반복합니다.continue진술, 반면 다른 것들은 무언가가 깨져서 당신이 멈춰야 한다는 것을 의미합니다.

소켓에 파일을 보내기 위해 원하는 대로 시스템 호출이 있습니다.커널에 한 파일 설명자에서 다른 파일 설명자로 파일을 보내라고 하면 커널이 나머지를 처리할 수 있습니다.소스 파일 설명자가 지원해야 하는 주의 사항이 있습니다.mmap(예: 소켓이 아닌 실제 파일이어야 함) 대상은 소켓이어야 합니다. 따라서 파일을 복사하거나 한 소켓에서 다른 소켓으로 직접 데이터를 보낼 수 없습니다. 즉, 소켓으로 파일을 보내는 용도를 지원하도록 설계되었습니다.그러나 파일을 받는 데는 도움이 되지 않습니다. 이 경우 루프를 직접 수행해야 합니다.나는 왜 거기에 있는지 당신에게 말할 수 없습니다.sendfile한 호지만유사지않음하하가 없습니다.recvfile.

해, 조심해요.sendfileLinux 전용이므로 다른 시스템으로 이동할 수 없습니다.다른 시스템에는 종종 자체 버전이 있습니다.sendfile정확한 인터페이스는 다를 수 있습니다(FreeBSD, Mac OS X, Solaris).

Linux 2.6.17에서는 시스템 호출이 도입되었으며 2.6.23 기준으로 내부적으로 구현에 사용됩니다.splice는 보일반입용도 API보다 더 인 API입니다.sendfile의좋은 위하여을명설에 대한 좋은 요.splice그리고.tee리누스 자신의 설명을 들어보세요.그는 어떻게 사용하는지 지적합니다.splice위의 로 적으로루같습프다니와위를 사용합니다.read그리고.write버퍼가 커널에 있으므로 커널과 사용자 공간 사이에서 데이터를 전송할 필요가 없거나 CPU("제로 복사 I/O")를 통과하지 못할 수도 있습니다.

man 2 sendfile클라이언트의 원본 파일과 서버의 대상 파일만 연 다음 sendfile을 호출하면 커널이 데이터를 잘라내고 이동합니다.

한 POSIX 최소능한가 POSIXread+write를 들어보기

용도:

  1. LAN에 두 대의 컴퓨터를 연결합니다.

    예를 들어, 대부분의 경우 두 컴퓨터가 모두 홈 라우터에 연결되어 있으면 작동합니다. 이것이 바로 제가 테스트한 방법입니다.

  2. 서버 컴퓨터에서:

    1. IP로 ifconfig를 들어, 예를 들어, 기호입니다.192.168.0.10

    2. 실행:

      ./server output.tmp 12345
      
  3. 클라이언트 시스템에서 다음을 수행합니다.

    printf 'ab\ncd\n' > input.tmp
    ./client input.tmp 192.168.0.10 12345
    
  4. : 파일: 파일: 파일output.tmp 포함서시생다성니됩이 포함된 서버 됩니다.'ab\ncd\n'!

server.c

/*
Receive a file over a socket.

Saves it to output.tmp by default.

Interface:

    ./executable [<output_file> [<port>]]

Defaults:

- output_file: output.tmp
- port: 12345
*/

#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>

#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>

int main(int argc, char **argv) {
    char *file_path = "output.tmp";
    char buffer[BUFSIZ];
    char protoname[] = "tcp";
    int client_sockfd;
    int enable = 1;
    int filefd;
    int i;
    int server_sockfd;
    socklen_t client_len;
    ssize_t read_return;
    struct protoent *protoent;
    struct sockaddr_in client_address, server_address;
    unsigned short server_port = 12345u;

    if (argc > 1) {
        file_path = argv[1];
        if (argc > 2) {
            server_port = strtol(argv[2], NULL, 10);
        }
    }

    /* Create a socket and listen to it.. */
    protoent = getprotobyname(protoname);
    if (protoent == NULL) {
        perror("getprotobyname");
        exit(EXIT_FAILURE);
    }
    server_sockfd = socket(
        AF_INET,
        SOCK_STREAM,
        protoent->p_proto
    );
    if (server_sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }
    if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
        perror("setsockopt(SO_REUSEADDR) failed");
        exit(EXIT_FAILURE);
    }
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(server_port);
    if (bind(
            server_sockfd,
            (struct sockaddr*)&server_address,
            sizeof(server_address)
        ) == -1
    ) {
        perror("bind");
        exit(EXIT_FAILURE);
    }
    if (listen(server_sockfd, 5) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    fprintf(stderr, "listening on port %d\n", server_port);

    while (1) {
        client_len = sizeof(client_address);
        puts("waiting for client");
        client_sockfd = accept(
            server_sockfd,
            (struct sockaddr*)&client_address,
            &client_len
        );
        filefd = open(file_path,
                O_WRONLY | O_CREAT | O_TRUNC,
                S_IRUSR | S_IWUSR);
        if (filefd == -1) {
            perror("open");
            exit(EXIT_FAILURE);
        }
        do {
            read_return = read(client_sockfd, buffer, BUFSIZ);
            if (read_return == -1) {
                perror("read");
                exit(EXIT_FAILURE);
            }
            if (write(filefd, buffer, read_return) == -1) {
                perror("write");
                exit(EXIT_FAILURE);
            }
        } while (read_return > 0);
        close(filefd);
        close(client_sockfd);
    }
    return EXIT_SUCCESS;
}

client.c

/*
Send a file over a socket.

Interface:

    ./executable [<input_path> [<sever_hostname> [<port>]]]

Defaults:

- input_path: input.tmp
- server_hostname: 127.0.0.1
- port: 12345
*/

#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>

#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>

int main(int argc, char **argv) {
    char protoname[] = "tcp";
    struct protoent *protoent;
    char *file_path = "input.tmp";
    char *server_hostname = "127.0.0.1";
    char *server_reply = NULL;
    char *user_input = NULL;
    char buffer[BUFSIZ];
    in_addr_t in_addr;
    in_addr_t server_addr;
    int filefd;
    int sockfd;
    ssize_t i;
    ssize_t read_return;
    struct hostent *hostent;
    struct sockaddr_in sockaddr_in;
    unsigned short server_port = 12345;

    if (argc > 1) {
        file_path = argv[1];
        if (argc > 2) {
            server_hostname = argv[2];
            if (argc > 3) {
                server_port = strtol(argv[3], NULL, 10);
            }
        }
    }

    filefd = open(file_path, O_RDONLY);
    if (filefd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    /* Get socket. */
    protoent = getprotobyname(protoname);
    if (protoent == NULL) {
        perror("getprotobyname");
        exit(EXIT_FAILURE);
    }
    sockfd = socket(AF_INET, SOCK_STREAM, protoent->p_proto);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }
    /* Prepare sockaddr_in. */
    hostent = gethostbyname(server_hostname);
    if (hostent == NULL) {
        fprintf(stderr, "error: gethostbyname(\"%s\")\n", server_hostname);
        exit(EXIT_FAILURE);
    }
    in_addr = inet_addr(inet_ntoa(*(struct in_addr*)*(hostent->h_addr_list)));
    if (in_addr == (in_addr_t)-1) {
        fprintf(stderr, "error: inet_addr(\"%s\")\n", *(hostent->h_addr_list));
        exit(EXIT_FAILURE);
    }
    sockaddr_in.sin_addr.s_addr = in_addr;
    sockaddr_in.sin_family = AF_INET;
    sockaddr_in.sin_port = htons(server_port);
    /* Do the actual connection. */
    if (connect(sockfd, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in)) == -1) {
        perror("connect");
        return EXIT_FAILURE;
    }

    while (1) {
        read_return = read(filefd, buffer, BUFSIZ);
        if (read_return == 0)
            break;
        if (read_return == -1) {
            perror("read");
            exit(EXIT_FAILURE);
        }
        /* TODO use write loop: https://stackoverflow.com/questions/24259640/writing-a-full-buffer-using-write-system-call */
        if (write(sockfd, buffer, read_return) == -1) {
            perror("write");
            exit(EXIT_FAILURE);
        }
    }
    free(user_input);
    free(server_reply);
    close(filefd);
    exit(EXIT_SUCCESS);
}

GitHub 업스트림.

추가 의견

개선 가능한 사항:

  • 현의재output.tmp전송이 완료될 때마다 덮어씁니다.

    이것은 파일 이름을 전달하여 여러 파일을 업로드할 수 있는 간단한 프로토콜을 만들 것을 요청합니다. 예를 들어, 파일 이름은 첫 번째 줄 문자까지, 최대 파일 이름은 256자까지, 나머지는 소켓 폐쇄가 내용일 때까지입니다.물론 경로 횡단 취약성을 방지하려면 위생 상태가 필요합니다.

    또는 파일 이름을 찾기 위해 파일을 해시하고 원본 경로에서 디스크의 해시(데이터베이스에 있는)로 맵을 유지하는 서버를 만들 수 있습니다.

  • 한 번에 하나의 클라이언트만 연결할 수 있습니다.

    연결이 오래 지속되는 느린 클라이언트가 있는 경우 특히 위험합니다. 느린 연결로 인해 모든 사용자가 중단됩니다.

    하는 한 은 각각의 입니다.accept즉시 다시 듣기를 시작하고 파일에 대해 파일 잠금 동기화를 사용합니다.

  • 시간 초과를 추가하고 시간이 너무 오래 걸리는 경우 클라이언트를 닫습니다.그렇지 않으면 DoS를 수행하는 것이 쉬울 것입니다.

    poll또는select다음과 같은 옵션이 있습니다.읽기 함수 호출에서 시간 초과를 구현하는 방법은 무엇입니까?

HTTP 순단 HTTPwget구현은 다음에 나와 있습니다: libcurl 없이 C에서 HTTP get request를 만드는 방법은 무엇입니까?

Ubuntu 15.10에서 테스트되었습니다.

이 파일은 당신에게 도움이 될 것입니다.sendfile예: http://tldp.org/LDP/LGNET/91/misc/tranter/server.c.txt

언급URL : https://stackoverflow.com/questions/2014033/send-and-receive-a-file-in-socket-programming-in-linux-with-c-c-gcc-g

반응형