반응형

서버-클라이언트 모델에는 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

반응형