본문 바로가기

임베디드 리눅스

네트워크 프로그래밍-소켓 통신 서버 구현 4

728x90
반응형

 

안녕하세요. WH입니다.

기본적으로 서버에 연결하면

연결되었음을 확인하도록 보내는 서버를 구현해 보았는데요,

이번에는 파일을 전송하는 서버를 구현해보겠습니다.

시작할까요?

 

해당 내용을 알기 위해서 필요한 사전 지식에 대해 먼저 정리합니다.

반응형

 

바이너리 파일

한 단어로 정의하자면 실행 파일입니다. 그러나 좀 더 들어가서 보자면 바이너리 파일사용자 또는 프로그램이 사용하던 정보나 숫자 값을 특별한 가공 없이 그대로 파일에 저장한 파일을 의미합니다. 이것은 파일을 전송할 때 큰 장점을 가집니다. 텍스트 파일을 예로 들어보겠습니다.

 

리눅스에서 txt 파일 예시

Hello world!\n

This is a text file\n

 

윈도우에서 txt 파일 예시

Hello world! \r\n

This is a text file \r\n

 

똑같이 두 문장을 가지고 있는 파일인데, 이 파일 뒤에는 \n or \r 과 같은 이상한 문자들이 붙습니다. 이 파일을 문자 그대로 보내려면 이상한 문자들 때문에 문제가 발생합니다. 뭐 컴파일러가 아라서 해줄꺼다.. 그거 너무 무책임한 소리 아닌가요. 컴파일러도 우리가 만드는 건데요. 여튼 저런 사소한 문제가 때로는 파일을 박살내기도 하죠. 결국 우리가 원하는건 뭐죠? 컴퓨터가 파일에서 Hello world! This is a text file 이라는 데이터를 읽어 우리에게 원하는 형태로 보여주길 원하는 거죠? 그래서 그냥 컴퓨터가 사용하는 정보 형태인 바이너리 파일 형태를 보내주면 저런 골머리를 썩지 않고 파일을 전송할 수 있습니다.

 

서론이 길었나요? 그치만 꼭 필요한 지식인걸 어떻게 합니까 설명해 드리고 가야죠. 여튼 이제 코드를 보여드리도록 하겠습니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFSIZE 256

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    char buf[BUFSIZE];
    
    struct sockaddr_in clnt_addr;
    struct sockaddr_in serv_addr;
    socklen_t clnt_addr_size;
    
    FILE* file;
    size_t fsize;
    int nsize = 0;
    
    if(argc !=2){
    	printf("Usage : %s <port>\n", argv[0]);
        }
    
    serv_sock = socket(AF_INET, SOCK_STREAM,0);
    if( serv_sock==-1 )
    	error_handling("socket() error.\n");
        
    bzero((char*)&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));
    
    if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
    	error_handling("bind() error.\n");
        
    if(listen(serv_sock, 5) <0)
    	error_handling("listen() error.\n");
    
    clnt_addr_size = sizeof(clnt_addr);
    clnt_sock= accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    if( clnt_sock < 0)
    	error_handling("accept() error.\n");
        
    file = fopen("img.jpg","rb");// 바이너리 읽기 전용모드로 파일 오픈
    
    fseek(file, 0, SEEK_END);// 파일 지시자 맨 뒤로 이동
    fsize = ftell(file); // 파일크기 계산
    fseek(file, 0, SEEK_SET);// 파일 지시자 맨 앞으로 이동
    
    
    while(nsize != fsize){
    	int fpsize = fread(buf, 1, BUFSIZE, file);
        nsize += fpsize;
        send(clnt_sock, buf, fpsize, 0);
        }
    fclose(file);
    close(clnt_sock);
    close(serv_sock);
    
    return 0;
}
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

와 기네요.. ㅎㅎ 전에 코드랑 다를게 있냐? 글쎄요 조금 다를수도 있고 그럴거에요. 안보고 짜가지고, 다만 이번코드는 에러가 없이 돌아갈겁니다. 전에 코드는 사실대로 고백하자면... 컴파일해보지 않았는데 이번 코드는 컴파일 해보고 확인작업 끝내고 올린 코드거든요. 여튼 시작합니다.

 

위의 내용은 앞의 내용과 같습니다. 앞의 내용이 기억나질 않는다면, 아래 링크를 참조해 주세요.

2022.02.03 - [임베디드 리눅스] - 네트워크 프로그래밍 - 소켓 통신 서버 구현 2

 

네트워크 프로그래밍 - 소켓 통신 서버 구현 2

안녕하세요. WH입니다. 이번 글에서 정리할 내용은 소켓 프로그래밍 기본 코드 구현 및 시스템 콜 정리입니다. 물론 파일을 전송하는 것까지는 아니고, 서버와 연결됨을 확인하는 정도 선까지만

developer-wh.tistory.com

2022.02.04 - [임베디드 리눅스] - 네트워크 프로그래밍-소켓 통신 서버 구현 3

 

네트워크 프로그래밍-소켓 통신 서버 구현 3

안녕하세요. 개발자 WH입니다. 시간이 정말 눈 깜박하면 하루가 지나갑니다. 시간은 지나가고 물가는 오르는 데, 뭐만 안오르네요 ㅎㅎ 여튼 이번 글도 이어서 시작하겠습니다. 오늘은 저번 글에

developer-wh.tistory.com

 

해당 파일은 img.jpg라는 파일을 바이너리 모드로 여는 파일입니다. 다만 주의할 점은 실행파일과 해당 이름의 이미지 파일은 같은 위치에 있어야합니다. 물론 절대 경로를 설정해서 할 수도 있지만, 학습용 코드라서 짜질 않았거든요..

 

그 다음으로는 파일의 크기를 측정하는 코드입니다. 파일을 한번에 보낼 수 없어서, 나눠서 보내야 하는데, 그러려면 크기를 알아야 합니다. 파일의 크기를 측정한다음, 버퍼의 크기에 맞게 잘라서 앞에서 부터 넣어줄 거랍니다. 주의 사항은 파일 지시자의 이동을 통한 측정 후 파일 지시자의 원상 복귀입니다. 파일 지시자를 옮겨주지 않으면, 버퍼에 복사시 파일 끝에서 시작하게 됩니다.

 

그 다음 반복문에 대한 내용인데요. while () 먼저 보겠습니다. ()안의 변수 nsize는 버퍼 크기 만큼 늘어나는 변수이고, fsize는 파일의 총 크기 입니다. 즉 반복문은 nsize가 계속 커져서 fszie와 같아질 때 까지 반복됩니다.

 

그다음 int fpsize = fread( buf, 1, BUFSIZE, file); 입니다.

fread() 는 file 속 데이터를 buf에 넣어주는데요. 블락 사이즈 1 크기의 데이터를 BUFSZIE 개수만큼 넣어주게 됩니다. 이때 파일 지시자가 이동하게 됩니다. 또한 fread()의 반환값은 데이터를 읽어온 개수를 뜻하는데요. 즉 fpsize는 데이터를 읽어온 개수가 저장되고 buf에는 읽어온 데이터가 저장됩니다.

 

nsize += fpsize; 는 파일 사이즈를 계속 더해주어 최종 fsize와 같도록 하기 위함입니다. 처음부터 끝가지 데이터를 다 읽으면 nsize와 fsize가 같아지겠죠?

 

마지막으로 send(clnt_sock, buf, fpsize, 0); 인데요. clnt_sock 파일 디스크립터를 통해 크기 fpsize만큼을 buf 주소를 가지고 buf에 접근해 그 내용을 보내겠다는 의미입니다.

send는 다음과 같이 정의됩니다. 위의 파란 글씨를 다시 한번 읽어보시길 권합니다.

마지막에 flag라는 부분을 정리하면서 마무리하겠습니다.

 

flag

  - 전송할 데이터 또는 읽는 방법에 대한 옵션으로 bit or 연산으로 설정이 가능합니다.

 

이 글은 파일전송을 위한 서버 구현에 관한 기본적인 글이었습니다.

이상 WH였습니다. 읽어주셔서 감사합니다.

어떤 내용을 다루면 좋을까요?

옵션을 추가해서 사용할 수 있을 정도의 서버를 만들어볼까요?

그것은 차차 다루도록 하겠습니다. 오늘 하루도 즐거운 하루되세요.

728x90
반응형