소켓을 이용하여 파일 복사하기
글쓴이: ratmhun / 작성시간: 토, 2005/02/05 - 9:03오전
소켓을 이용해서 클라이언트에서 서버로 파일을 전송하는 프로그램을 만들어 봤습니다. 문제는 파일 이름을 어떻게 보낼 것인가 하는 점인데요...
먼저 서버에서 프로그램을 실행할 때에 argv[1]으로 object 파일을 명시해 주고 클라이언트에서 역시 프로그램을 실행할 때에 argv[1]으로 source 파일을 명시해 주었습니다. 이것은 아주 단순한 원리로 손쉽게 구현이 가능했습니다만 제가 원하는 방법은 아니었습니다.
클라이언트에서 파일을 전송할 때에 데이터와 함께 파일 이름까지 보내면 서버에서 지정된 파일 이름으로 파일을 생성하고 복사가 이루어지도록 만들고 싶었습니다.
$ ./server&
$ ./client source object
문제는 클라이언트에서 보내는 파일 이름의 크기입니다. object 파일명 argv[2]를 보낼때 sizeof(argv[2]) 이런 식으로 보내게 되면 서버에서는 파일 이름을 받을 때에 이 크기를 어떻게 처리할 방법이 없지 않겠습니까? 그래서 Magic Number 10으로 파일 이름을 #define해서 쓰고 있습니다.
저는 이 방식이 마음에 들지 않는군요. 좀더 효과적으로 파일의 데이터와 더불어 파일명을 클라이언트에서 서버로 전송할 수 있는 방법은 없는지 궁금합니다.
아래 서버와 클라이언트의 소스코드를 첨부합니다.
server.c
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <fcntl.h> #include <stdio.h> #define PORT 12345 #define PERMS 0666 #define FILENAMELEN 10 int main(void) { void fatal(char *); char buf[BUFSIZ]; char objectname[FILENAMELEN]; int n, s, ns, len; struct sockaddr_in name; int object; if((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) fatal("socket"); memset(&name, 0, sizeof(struct sockaddr_in)); name.sin_family = AF_INET; name.sin_port = htons(PORT); len = sizeof(struct sockaddr_in); n = INADDR_ANY; memcpy(&name.sin_addr, &n, sizeof(long)); if(bind(s, (struct sockaddr *)&name, len) < 0) fatal("bind"); if(listen(s, 5) < 0) fatal("listen"); if((ns = accept(s, (struct sockaddr *)&name, &len)) < 0) fatal("accept"); if(recv(ns, objectname, FILENAMELEN, 0) < 0) fatal("recv objectfile"); if((object = open(objectname, O_WRONLY|O_CREAT|O_TRUNC, PERMS)) < 0) fatal("write open"); while((n = recv(ns, buf, sizeof(buf), 0)) > 0) write(object, buf, n); close(ns); close(s); close(object); return 0; } void fatal(char *error_name) { perror(error_name); exit(1); }
client.c
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <netdb.h> #include <stdio.h> #include <fcntl.h> #define PORT 12345 #define FILENAMELEN 10 int main(int argc, char **argv) { void fatal(char *); int n, s, len; char buf[BUFSIZ]; char hostname[64]; struct hostent *hp; struct sockaddr_in name; int source; if(argc < 2) { puts("Usage: client source object"); exit(1); } if((source = open(argv[1], O_RDONLY)) < 0) fatal("open"); if(gethostname(hostname, sizeof(hostname)) < 0) fatal("gethostname"); if((hp = gethostbyname(hostname)) == NULL) fatal("gethostbyname"); if((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) fatal("socket"); memset(&name, 0, sizeof(struct sockaddr_in)); name.sin_family = AF_INET; name.sin_port = htons(PORT); memcpy(&name.sin_addr, hp->h_addr_list[0], hp->h_length); len = sizeof(struct sockaddr_in); if(connect(s, (struct sockaddr *)&name, len) < 0) fatal("connect"); if(send(s, argv[2], FILENAMELEN, 0) < 0) fatal("send objectfile"); while((n = read(source, buf, sizeof(buf))) > 0) { if(send(s, buf, n, 0) < 0) fatal("send"); } close(s); close(source); return 0; } void fatal(char *error_name) { perror(error_name); exit(1); }
Forums:
우선 화일이름은 어쩔수 없이 MAX size 를 정해야 합니다.만약에
우선 화일이름은 어쩔수 없이 MAX size 를 정해야 합니다.
만약에 들어온 화일이름이 MAX size 보다 크다면 어쩔수없이 truncate 해야겠죠.
아니면, 간단한 프로토콜을 설정하시고, 그 헤더를 달아주는겁니다.
헤더에 패킷의 길이를 제일 앞에 써주는것은 매우 좋은 습관입니다.
헤더에 적힌 길이로 malloc 하실수도 있으실테고요.
그외 필요하다면 헤더에 msg id 같은걸 써주셔도 좋을것 같네요.
근데, 화일이름이 이미 서버에 존재한다면 어떻하시는지요?
그냥 간단히 덮어쓰신다면야, 뭐 문제가 안되지만, 보통을 잘 그러지 않거든요.
그러다 보면 프로토콜이 반드시 필요하실듯 사료됩니다.
삽질의 대마왕...
파일을 전송할 때..
보통의 파일 전송은 위와 같이 전송하는게 대부분입니다.
일반적으로 파일의 길이는 255정도로 헤더에 추가해주고..
또한 전송될 데이터의 길이도 헤더에 추가해서 보내줍니다.
굳이 위와 같은 방법이 맘에 들지 않는다면 제가 아는 다른 방법은
두가지가 있는데요..
하나는 1 바이트씩 받는겁니다..-,.ㅡ;; 그러다가 NULL 을 만나면
파일이름의 끝으로 판단하고 다음부터 데이터를 받으면 되겠지요..
정말 추천하고 싶지 않은 방법입니다..
두번째 방법은 필드에 구분자(Delimeter)를 넣는 겁니다. 구분자는
프로토콜 설계자의 임의대로 정하시면 됩니다. 하지만 주의하셔야 할점은
데이터 자체에도 이 구분자가 나타날 수 있으며 .. 이런것들을 적절히
처리해주셔야 합니다.
데이터에 구분자가 나타나면 그 문자를 적절히 다른 문자로 치환하신다던가
아니면 데이터 필드에 절대 나타날 수 없는 문자를 구분자로 사용하신다던가..
하는 방법으로 말입니다.
하지만 제생각도 윗분의 말씀처럼 헤더에 파일 이름의 길이를 명시해 주시는
것이 가장 편하고 일반적인 방법인것 같습니다.
..
댓글 달기