네트워크/Code snippet
[네트워크] 소켓을 이용한 파일전송
2022. 11. 30. 21:55반응형
서버-클라이언트 모델에는 pthread를 이용한 방식 파일디스크립터(fd)를 이용한 방식이 있다.
Server.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<arpa/inet.h>
#include<dirent.h>
#define MAXLINE 800
#define DEFAULT 1
#define BLUE 2
#define YELLOW 3
#define RED 4
#define BOLD 5
void setColor(int); // 색을 입히기 위한 설정함수
int tcp_listen(int host, int port, int backlog); // TCP 상에서 listen함수
void addClient(int s, struct sockaddr_in *newcliaddr); // client 추가할때 호출되는 함수
void errquit(char *mesg){perror(mesg); exit(1);}
int listen_sock; // listen 요청받은 socket
int client_sock; // 클라이언트 소켓 선언
int max; // 최대 소켓 번호 선언
int main(int argc, char *argv[])
{
char mode[MAXLINE] = {0}; // get or put모드를 입력받기 위한 배열
char file_address[MAXLINE] = {0}; // 절대경로 파일 주소 저장
char dir_address[MAXLINE] = {0}; // 디렉토리 주소 저장
char file_1[MAXLINE] = {0}; // 클라이언트의 put모드에서 쓰이는 파일 내용 저장
char file_2[MAXLINE] = {0}; // 클라이언트의 get모드에서 쓰이는 파일 내용 저장
int length;
int i = 0; // 반복 변수 선언
char file_name[MAXLINE] = {0}; // 파일 목록 이름들을 저장한다.
DIR *dp; // 디렉토리를 보여주기 위한 변수
struct dirent *entry; // 파일 목록을 보여주기 위한 entry 변수 선언
struct stat statbuf; // 상태를 저장하는 변수 선언
struct sockaddr_in cliaddr; // 소켓 구조체 선언
char buf[MAXLINE] = {0}; // buf 설정
int addrlen = sizeof(struct sockaddr_in);
int accp_sock; // accept한 socket
fd_set read_fds; // 읽기위한 파일디스크립터 설정
int fd; // 파일디스크립터
struct sockaddr_in servaddr; // 소켓 구조체 정의
setColor(BLUE);
setColor(BOLD);
printf("*** server open 2013136017 ***\n");
setColor(DEFAULT);
// 5개의 클라이언트를 받도록 설정 후 listen함수 호출
listen_sock = tcp_listen(INADDR_ANY , atoi(argv[1]), 5);
while(1)
{
setColor(DEFAULT);
// FD를 초기 셋팅한다.
FD_ZERO(&read_fds);
FD_SET(listen_sock , &read_fds);
FD_SET(client_sock , &read_fds);
// 최대 소켓번호 증가
max = listen_sock +1;
printf("클라이언트 접속 대기중\n");
// select 방식으로 클라이언트를 감지한다.
if(select(max, &read_fds, NULL,NULL,NULL)<0)
errquit("select fail");
// listen소켓에 fd가 셋팅이 감지되면
if(FD_ISSET(listen_sock , &read_fds))
{
// accept를 호출한다.
accp_sock = accept(listen_sock ,(struct sockaddr *)&cliaddr, &addrlen);
// 그리고 클라이언트를 추가한다.
addClient(accp_sock, &cliaddr);
printf("사용자 추가\n");
}
// 클라이언트가 보낸 메세지를 받는다.
recv(client_sock , buf, MAXLINE , 0);
if(strchr(buf, '0') != NULL) // 0이라는 문자가 포함되어 있으면 get mode 진입
{
// 디렉토리주소, 절대경로파일주소를 초기화
memset(dir_address, 0, MAXLINE);
memset(file_address, 0, MAXLINE);
memset(file_name, 0, MAXLINE);
setColor(BOLD); // BOLD효과를준다.
printf("GET Mode\n");
setColor(BLUE);
// 클라이언트로부터 파일이름을 입력받는다.
recv(client_sock ,dir_address, MAXLINE , 0);
// 클라이언트로부터 받은 디렉터리 주소 출력
setColor(DEFAULT);
printf("클라이언트에서 받은 디렉터리 주소:");
setColor(YELLOW);
printf(" %s\n", dir_address);
setColor(BLUE);
// 클라이언트로부터 받은 경로를 dp에 저장
if((dp = opendir(dir_address)) == NULL)
{
setColor(RED);
fprintf(stderr, "파일경로 찾기 실패\n");
return;
}
// 해당하는 디렉터리 주소에 있는 파일을 모두 출력한다
while((entry = readdir(dp)) != NULL)
{
lstat(entry->d_name, &statbuf);
if(strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0)
{
strcat(file_name, entry->d_name);
strcat(file_name, "\t");
}
}
// 서버에서도 파일 목록출력, 클라이언트에서도 파일 목록 출력하게한다.
setColor(YELLOW); // 색상을 노랑색으로 지정
printf("%s\n", file_name);
// 클라이언트로부터 파일 목록들에 대한 정보를 보낸다.
send(client_sock, file_name, strlen(file_name) , 0);
setColor(DEFAULT);
// 목록을 전송한 뒤
// recv : 클라이언트로부터 파일이름을 받는다.
char select_filename[MAXLINE] = {0};
recv(client_sock ,select_filename, MAXLINE , 0);
// 해당 절대경로의 디렉토리주소+파일이름으로 파일을 연다.
strcpy(file_address, dir_address);
strcat(file_address, select_filename);
fd = open(file_address, O_RDONLY);
// 파일을 연다.
FILE* fp = fopen(file_address, "r");
if(fp == NULL) {
printf("파일이 없습니다!, 소켓을 닫습니다. \n");
close(accp_sock);
continue;
}
if(fd == -1)
printf("file open error\n");
// file_2 배열에 방금 연 파일을 읽어 전송한다.
while((length = read(fd, file_2, MAXLINE)) != 0)
{
write(accp_sock, file_2, length); // 해당 소켓에 대하여 전송
printf("파일 송신 중, 길이 %d\n", length);
}
// 다 끝났다면 송신완료
if((shutdown(accp_sock, SHUT_WR)) != -1)
printf("파일 송신 완료\n");
close(accp_sock);
close(fd);
}
// put mode
else if(strchr(buf, '1') != NULL)
{
setColor(BOLD);
printf("PUT Mode\n");
setColor(DEFAULT);
// 파일 절대경로주소 담는 배열 초기화, 및 파일 내용 담는 배열 초기화
memset(file_address,0,MAXLINE);
memset(file_1,0,MAXLINE);
// 파일 이름을 먼저 가져온다.
recv(client_sock ,file_address, MAXLINE , 0);
if(strlen(file_address) == 0) {
printf("파일이 존재하지 않습니다.\n");
continue;
}
// 파일 디스크립터를 통해 파일을 연다.
//fd = open(file_address, O_WRONLY|O_CREAT|O_TRUNC);
// 파일이 만약 못연다면, 쓰기 오류가 난 것이다.
FILE* fp = fopen(file_address, "w");
if(fp == NULL) {
printf("파일 쓰기 오류\n");
continue;
}
//파일 내용 읽고 쓰기
while((length = recv(accp_sock, file_1, MAXLINE, 0)) > 0)
{
printf(" 파일 수신 중, 길이: %d\n", length);
fputs(file_1,fp);
}
if(length == -1)
errquit("비정상적인 접근입니다.\n");
fclose(fp);
printf(" 파일 수신 완료\n");
}
}
}
// TCP상에서 listen기능을 하는 함수
int tcp_listen(int host, int port, int backlog)
{
int sd;
struct sockaddr_in servaddr;
sd = socket(PF_INET, SOCK_STREAM, 0);
if(sd == -1)
{
perror("socket fail");
exit(1);
}
bzero((char *)&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(host);
servaddr.sin_port = htons(port);
// bind로 설정값을 묶는다.
bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(sd, backlog); // 클라이언트를 listen 함
return sd;
}
// 클라이언트를 추가하기 위한 함수
void addClient(int s, struct sockaddr_in *newcliaddr)
{
char buf[20];
inet_ntop(AF_INET, &newcliaddr->sin_addr,buf,sizeof(buf));
printf("새로운 클라이언트 접속 IP :%s\n", buf);
client_sock = s;
}
void setColor(int color) {
if(color == DEFAULT)
write(1,"\033[0m ", 4); // 디폴트 지정 효과로 출력
else if(color == RED)
write(1,"\033[31m ", 5); // 레드효과를 준다
else if(color == YELLOW)
write(1,"\033[33m ", 5); // 노란효과 준다.
else if(color == BLUE)
write(1,"\033[34m ", 5); //파랑효과 준다.
else if(color == BOLD)
write(1,"\033[1m ", 4); // 볼드 효과를 준다
else
write(1,"\033[0m ", 4); // 디폴트 지정 효과로 출력
}
Client.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<arpa/inet.h>
#include<dirent.h>
#include<netinet/in.h>
// 각 색상지정 값
#define DEFAULT 1
#define BLUE 2
#define YELLOW 3
#define RED 4
#define BOLD 5
#define MAXLINE 800 // 최대 길이
#define NAME 20 // 이름 최대 길이
// 색상지정하기 위한 함수
void setColor(int);
// tcp 연결을 위한 함수
int tcp_connect(int af, char*servip, unsigned short port);
// 에러메세지 출력과 비정상적인 종료류르 하는 함수
void errquit(char *mesg){perror(mesg); exit(1);}
int main(int argc, char *argv[])
{
char *bufmsg; // msg 문자를 저장하는 포인터변수
char bufall[MAXLINE+NAME] = {0}; // 임시 정보 저장배열 선언
int max; // 최대 소켓번호 선언
int s,fd,i,length; // 소켓, 파일디스크립터 선언
int namelen; // 이름길이 선언
fd_set read_fds; // 파일 디스크립터 변수 선언
namelen = strlen(bufall);
bufmsg = bufall+namelen;
setColor(DEFAULT);
printf("모드 선택 (GET: 0, PUT: 1, exit: 2)\n");
//tcp 연결
s = tcp_connect(AF_INET , argv[1], atoi(argv[2]));
max = s +1; // 최대 소켓번호 증가
FD_ZERO(&read_fds); // 읽기 파일디스크립터 0으로 초기화
while(1)
{
char newfile[MAXLINE] = {0}; // put mode 일때 파일내용 저장
char oldfile[MAXLINE] = {0}; // get mode 일때 파일내용 저장
char dir_address[MAXLINE] = {0}; // 디렉토리 파일주소
char file_address[MAXLINE] = {0}; // 절대경로의 실제 파일주소
char file_list[MAXLINE] = {0}; //서버로부터 get 할때 받는 파일목록 정보
char select_file[MAXLINE] = {0}; // 파일명
char cmd[MAXLINE] = "ls -al "; // 커맨드명령어 문자열
FD_SET(0 , &read_fds);
FD_SET(s , &read_fds);
// select를 하여 메세지를 감지한다.
if(select(max, &read_fds, NULL,NULL,NULL)<0)
errquit("select fail");
if(FD_ISSET(0 , &read_fds))
{
// 서버에게 모드에 대한 정보를 보낸다.
if(fgets(bufmsg , MAXLINE, stdin))
{
send(s, bufall, namelen+strlen(bufmsg) , 0);
}
}
// 0이면 get 모드로 진입
if(bufall[0] == '0')
{
// file 정보를 담는 변수들 초기화
memset(file_address, 0, MAXLINE);
memset(file_list, 0, MAXLINE);
// 파일을 제대로 받았는지 안받았는지 체크하는변수
int isRecFile = 0;
setColor(BOLD);
printf("GET 모드\n");
setColor(DEFAULT);
// 파일 목록 송신
printf("다운로드 파일이 있는 경로 입력\n");
setColor(RED);
setColor(BOLD);
scanf("%s", file_address);
setColor(DEFAULT);
send(s, file_address, strlen(file_address) , 0);
// 서버로부터 파일 목록들을 받기위해 0으로 초기화
memset(file_address, 0, MAXLINE);
// 서버로 부터 파일 목록들을 받는다.
recv(s ,file_list, MAXLINE , 0);
// list정보가 없을때 예외처리
if(strlen(file_list) == 0) {
printf("해당 경로에는 파일이 없거나 유효한 경로가 아닙니다.\n");
close(s);
exit(0);
}
setColor(DEFAULT);
printf("파일 목록");
setColor(YELLOW);
printf(" %s\n", file_list); // 파일 목록 출력
setColor(DEFAULT);
printf("파일 선택 : \n");
setColor(RED), setColor(BOLD);
scanf("%s", select_file);
setColor(DEFAULT);
// 선택한 파일 이름 전송
send(s, select_file, strlen(select_file),0);
//파일 포인터 선언
FILE* fp = fopen(select_file, "w");
//파일을 수신하고 다시 쓴다.
while((length = read(s, oldfile, MAXLINE)) != 0) {
printf("파일 수신 중, 길이 %d\n", length );
isRecFile = 1;
fputs(oldfile, fp);
//write(fd, oldfile, length);
}
fclose(fp);
// 1인경우 파일을 제대로 수신했다는 이야기이다.
if(isRecFile == 1) {
printf("파일 수신 완료 \n");
close(fd);
exit(0);
}
else {
printf("해당 파일이 존재하지 않음 \n");
close(fd);
exit(1);
}
}
// 1이면 put 모드로 진입
else if( bufall[0] == '1')
{
setColor(BOLD);
printf("PUT 모드\n");
setColor(DEFAULT);
while(1) {
// 초기 명령어 저장 배열 초기화
memset(cmd, 0, MAXLINE);
strcpy(cmd, "ls -al "); // 명령어 저장
getcwd(dir_address,MAXLINE); // 현재 경로를 얻어온다.
strcat(cmd, dir_address); // 해당 명령어를 해당 디렉토리에 대해 실행
strcat(dir_address,"/");
setColor(YELLOW);
// 널값은 제대로 디렉토리를 찾았다는 걸 의미
// 파일 목록 루력
if(system(cmd) == NULL) {
setColor(DEFAULT);
break;
}
printf("디렉토리를 못찾았습니다. 다시 입력해주세요\n");
}
while(1) {
strcpy(file_address, dir_address);
memset(select_file,0,strlen(select_file));
printf("파일이름 입력 : ");
setColor(RED);
setColor(BOLD);
scanf("%s", select_file); // 파일 이름을 입력받는다.
setColor(DEFAULT);
strcat(file_address, select_file); // 파일의 절대 경로 생성
FILE * fp = fopen(file_address, "r");
// 파일이 존재한다면 그때 send 한다.
if(fp != NULL) {
send(s, select_file, strlen(select_file) , 0);
break;
}
printf("해당하는 파일이 없습니다. 파일이름 다시 입력해주세요!\n");
}
fd = open(file_address , O_RDONLY);
if(fd == -1)
printf("파일 열기 오류\n");
sleep(1);
// 파일 내용 전송
while((length = read(fd, newfile, MAXLINE)) != 0) {
send(s, newfile,length,0);
}
printf("전송 완료\n");
//if((shutdown(s, SHUT_WR)) != -1)
close(fd);
exit(0);
}
// 소켓을 닫고 통신을 종료한다.
else if(bufall[0] == '2') {
printf("통신 종료\n");
close(s);
exit(0);
}
}
}
int tcp_connect(int af, char*servip, unsigned short port)
{
int sd;
struct sockaddr_in servaddr;
sd = socket(af, SOCK_STREAM, 0);
if(sd == -1)
{
perror("socket fail");
exit(1);
}
bzero((char *)&servaddr, sizeof(servaddr));
servaddr.sin_family = af;
inet_pton(AF_INET , servip, &servaddr.sin_addr);
servaddr.sin_port = htons(port);
connect(sd, (struct sockaddr *)&servaddr, sizeof(servaddr));
return sd;
}
void setColor(int color) {
if(color == DEFAULT)
write(1,"\033[0m ", 4); // 디폴트 지정 효과로 출력
else if(color == RED)
write(1,"\033[31m ", 5); // 레드효과를 준다
else if(color == YELLOW)
write(1,"\033[33m ", 5); // 노란효과 준다.
else if(color == BLUE)
write(1,"\033[34m ", 5); //파랑효과 준다.
else if(color == BOLD)
write(1,"\033[1m ", 4); // 볼드 효과를 준다
else
write(1,"\033[0m ", 4); // 디폴트 지정 효과로 출력
}
실행방법
./Client 127.0.0.1 9993
서버아이피와 포트번호를 입력
./Server 9993
반응형