Linux에 C로 FTP Server와 Client 작성하기

FTP Server

컴파일 및 실행 방법

1
2
$ gcc -o server server.c
$ ./server 9999

server.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <netinet/in.h>
#include <fcntl.h>

int tcp_listen(int host, int port, int backlog) {
int sd;
struct sockaddr_in servaddr;

sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd == -1) {
perror("socket fail");
exit(1);
}
// servaddr 구조체의 내용 세팅
bzero((char *)&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(host);
servaddr.sin_port = htons(port);
if (bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind fail"); exit(1);
}
// 클라이언트로부터 연결요청을 기다림
listen(sd, backlog);
return sd;
}

int main(int argc, char *argv[])
{
struct sockaddr_in server, client;
struct stat obj;
int sock1, sock2;
char buf[100], command[5], filename[20];
int k, i, size, len, c;
int filehandle;

sock1 = tcp_listen(INADDR_ANY, atoi(argv[1]), 5);

len = sizeof(client);
sock2 = accept(sock1, (struct sockaddr*)&client, &len);
while (1) {
recv(sock2, buf, 100, 0);
sscanf(buf, "%s", command);
if (!strcmp(command, "ls")) {//ls명령어를 입력받았다면
system("ls >temps.txt");
stat("temps.txt", &obj);
size = obj.st_size;
send(sock2, &size, sizeof(int), 0);
filehandle = open("temps.txt", O_RDONLY);
sendfile(sock2, filehandle, NULL, size);
}
else if (!strcmp(command, "get")) {//get명령어를 입력받았다면
sscanf(buf, "%s%s", filename, filename);
stat(filename, &obj);
filehandle = open(filename, O_RDONLY);
size = obj.st_size;
if (filehandle == -1)
size = 0;
send(sock2, &size, sizeof(int), 0);
if (size)
sendfile(sock2, filehandle, NULL, size);

}
else if (!strcmp(command, "put")) {//put명령어를 입력받았다면
int c = 0, len;
char *f;
sscanf(buf + strlen(command), "%s", filename);
recv(sock2, &size, sizeof(int), 0);

while (1) {
filehandle = open(filename, O_CREAT | O_EXCL | O_WRONLY, 0666);
if (filehandle == -1)
sprintf(filename + strlen(filename), "_1");
else break;
}
f = malloc(size);
recv(sock2, f, size, 0);
c = write(filehandle, f, size);
close(filehandle);
send(sock2, &c, sizeof(int), 0);
}
else if (!strcmp(command, "pwd")) {//pwd명령어를 입력받았다면
system("pwd>temp.txt");
i = 0;
FILE*f = fopen("temp.txt", "r");
while (!feof(f)) buf[i++] = fgetc(f);
buf[i - 1] = '\0';
fclose(f);
send(sock2, buf, 100, 0);
}
else if (!strcmp(command, "cd")) {//cd명령어를 입력받았다면
if (chdir(buf + 3) == 0) c = 1;
else c = 0;
send(sock2, &c, sizeof(int), 0);
}
else if (!strcmp(command, "bye") || !strcmp(command, "quit")) {
//종료 명령어를 입력받았다면
printf("FTP server quitting..\n");
i = 1;
send(sock2, &i, sizeof(int), 0);
exit(0);
}
}
return 0;
}

FTP Client

컴파일 및 실행 방법

1
2
$ gcc -o client client.c
$ ./client 127.0.0.1 9999

client.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>

#define MAXLINE 511

int tcp_connect(int af, char *servip, unsigned short port) {
struct sockaddr_in servaddr;
int s;
// 소켓 생성
if ((s = socket(af, SOCK_STREAM, 0)) < 0)
return -1;
// 채팅 서버의 소켓주소 구조체 servaddr 초기화
bzero((char *)&servaddr, sizeof(servaddr));
servaddr.sin_family = af;
inet_pton(AF_INET, servip, &servaddr.sin_addr);
servaddr.sin_port = htons(port);

// 연결요청
if (connect(s, (struct sockaddr *)&servaddr, sizeof(servaddr))
< 0)
return -1;
return s;
}

int main(int argc, char *argv[])
{
struct sockaddr_in server;
struct stat obj;
int sock;
char bufmsg[MAXLINE];
char buf[100], command[5], filename[MAXLINE], *f;
char temp[20];
int k, size, status;
int filehandle;

if (argc != 3) {
printf("사용법 : %s server_ip port\n", argv[0]);
exit(1);
}

sock = tcp_connect(AF_INET, argv[1], atoi(argv[2]));
if (sock == -1) {
printf("tcp_connect fail");
exit(1);
}

while (1) {
printf("\033[1;33m명령어 : get, put, pwd, ls, cd, quit\n");
printf("\033[1;32mclient> ");
fgets(bufmsg, MAXLINE, stdin); //명령어 입력
fprintf(stderr, "\033[97m"); //글자색을 흰색으로 변경
if (!strcmp(bufmsg, "get\n")) {//get명령어를 입력받았다면
printf("다운로드할 파일 : ");
scanf("%s", filename); //파일 이름 입력
fgets(temp, MAXLINE, stdin); //버퍼에 남은 엔터 제거
strcpy(buf, "get ");
strcat(buf, filename);
send(sock, buf, 100, 0); //명령어 전송
recv(sock, &size, sizeof(int), 0);
if (!size) {//파일이 없다면
printf("파일이 없습니다\n");
continue;
}
f = malloc(size);
recv(sock, f, size, 0);
while (1) {
filehandle = open(filename, O_CREAT | O_EXCL | O_WRONLY, 0666);
if (filehandle == -1) //같은 이름이 있다면 이름 끝에 _1 추가
sprintf(filename + strlen(filename), "_1");
else break;
}
write(filehandle, f, size, 0);
close(filehandle);
printf("다운로드 완료\n");//전송이 잘 되었다면
}
else if (!strcmp(bufmsg, "put\n")) {//put명령어를 입력받았다면
printf("업로드할 파일 : ");
scanf("%s", filename); //파일 이름 입력
fgets(temp, MAXLINE, stdin); //버퍼에 남은 엔터 제거
filehandle = open(filename, O_RDONLY);
if (filehandle == -1) {//파일이 없다면
printf("파일이 없습니다.\n");
continue;
}
strcpy(buf, "put ");
strcat(buf, filename);
send(sock, buf, 100, 0);
stat(filename, &obj);
size = obj.st_size;
send(sock, &size, sizeof(int), 0);//명령어 전송
sendfile(sock, filehandle, NULL, size);//파일 전송
recv(sock, &status, sizeof(int), 0);
if (status)//업로드가 잘 되었다면
printf("업로드 완료\n");
else
printf("업로드 실패\n");
}
else if (!strcmp(bufmsg, "pwd\n")) {//pwd명령어를 입력받았다면
strcpy(buf, "pwd");
send(sock, buf, 100, 0);
recv(sock, buf, 100, 0);
printf("--The path of the Remote Directory--\n%s", buf);
}
else if (!strcmp(bufmsg, "ls\n")) {//ls명령어를 입력받았다면
strcpy(buf, "ls");
send(sock, buf, 100, 0);
recv(sock, &size, sizeof(int), 0);
f = malloc(size);
recv(sock, f, size, 0);
filehandle = creat("temp.txt", O_WRONLY);
write(filehandle, f, size, 0);
close(filehandle);
printf("--The Remote Directory List--\n");
system("cat temp.txt"); //현재 디렉토리의 파일 출력
}
else if (!strcmp(bufmsg, "cd\n")) {//cd명령어를 입력받았다면
strcpy(buf, "cd ");
printf("이동할 경로 이름 : ");
scanf("%s", buf + 3); //위치 입력
fgets(temp, MAXLINE, stdin); //버퍼에 남은 엔터 제거
send(sock, buf, 100, 0); //명령어 전송
recv(sock, &status, sizeof(int), 0);
if (status)
printf("경로 변경 완료\n");
else
printf("경로 변경 실패\n");
}
else if (!strcmp(bufmsg, "quit\n")) {//quit명령어를 입력받았다면
strcpy(buf, "quit");
send(sock, buf, 100, 0);
recv(sock, &status, 100, 0);
if (status) {
printf("서버를 닫는 중..\n");
exit(0);
}
printf("서버 닫기 실패\n");
}
}
}

(개인 프로젝트) 파일 전송 서버와 클라이언트 제작

소개

  • 인원 : 1인
  • 담당 : 프로그램 구현 전체
  • 개발 환경 : Ubuntu 16.04 LTS, VMware Workstation 14 Player
  • 문제
    • 파일 전송 서버는 서버에서 실행중이며 클라이언트가 전달해주는 내용을 파일로 저장하고 보내주는 역할을 한다.
    • 파일 전송 클라이언트는 서버에 전송할 파일을 선택하여 전송하고, 서버에서 선택한 파일을 전송받아 저장하는 기능을 수행한다. (get : 다운로드 명령, put : 업로드 명령)
    • 클라이언트는 접속한 디렉토리에 대해서 파일을 업로드, 다운로드 하도록 함.
    • 서버에 전송한 파일을 저장하는 디렉토리는 서버 프로세스가 실행되는 곳으로 함.
    • 다운로드할 수 있는 파일은 전체 디렉토리를 대상으로 가능함.
      (퍼미션을 고려하여 다운로드 가능한 파일)
    • text 파일, binary 파일 모두 전송이 가능하도록 함.
  • 소스코드 : Linux에 C로 FTP Server와 Client 작성하기
  • 사용법 : Linux에 C로 FTP Server와 Client 작성하기

내용

컴퓨터의 수가 부족하여 부득이하게 로컬에서 서버와 클라이언트를 함께 실행하였습니다.


이 중에 위에 있는 터미널은 서버, 아래에 있는 터미널은 클라이언트 입니다.
클라이언트는 6가지의 명령을 사용할 수 있습니다.

  • get : 서버에서 파일 다운로드
  • put : 서버로 파일 업로드
  • pwd : 현재 디렉토리 위치
  • ls : 현재 디렉토리의 파일 목록
  • cd : 디렉토리 이동
  • quit : 접속 해제

어려웠던 점

평소에는 터미널에서 고작 문자나 주고받는 소켓프로그래밍을 하였지만, 이번에 파일을 통째로 전송해야해서 처음에 어떻게 구현을 해야할지 막막했습니다. 그래서 다른 사람의 소스코드를 참고해가면서, 파일 사이즈를 먼저 전송하고 그 이후에 파일을 전송하는 방식으로 문제를 해결했습니다.

배운 점

TCP 부분을 응용해서 구현해야하는 프로젝트였습니다. 처음에는 이 문제를 어떻게 해결해야 할지 고민이 많았지만 하나씩 차근차근 해결해나가다 보니 프로그램이 완성되어 있는 것을 볼 수 있었습니다.
기본적인 기능 구현 이외에 사용자 편의성을 높이기 위해서 더욱 직관적인 사용법을 도입하였고, 색을 통한 구분으로 가독성이 뛰어나도록 더욱 깔끔한 출력을 하기 위해서 많은 시간을 투자하였습니다. 이를 통해 사용자는 보다 편리한 사용이 가능하고, 손쉽게 파일을 주고받을 수 있습니다.
이렇게 사용자를 배려한 프로그램을 구현하다보니 평소에 습득하지 못했던 지식을 더욱 알아갈 수 있는 계기가 되었고, 실력향상에 많은 도움이 되었던 것 같습니다.