간단하게짠 ftp프로그램인데요 문제가좀있어서요. 좀 도와주세요
글쓴이: taiha / 작성시간: 화, 2003/02/11 - 1:46오전
다른건 다 잘되는데요 get 명령으로 16kB 이상의 데이터를 받아오면
파일이 이상해저버려요..
그리고 실행파일의 경우는 파일 크기가 줄어저 생기는데 실행이 아주잘(ㅡ.ㅡ;)
되거든요..
왜근런지 못잡겠어요.. 소켓프로그램은 잘못해서 ㅜ.ㅜ
Redhat 7.3 에서 프로그램 한거에요
사용법은
get a.c b.c
ls
pwd
이런 식이에요
Makefile
all: gcc ftpcli.c -o ftpcli gcc ftpserv.c -o ftpserv -lcrypt
net.h
#define MYEXIT(str) {perror(str); exit(0);}
#define STR_MAX 512
#define PORT_COMM 56161
#define NUM_CMD 10
#define EOT "^EOT^"
#define LEN_EOT 5
#define OPK "^OPK^" //
#define LEN_OPK 5
#define OPF "^OPF^" //명령 수행에 오류생김
#define LEN_OPF 5
unsigned int size_tran;
/*
//네트웍 바이트오더 고려한 코드
#define MYRECV( fd, buf){ read( fd, &size_tran, sizeof(int) ); size_tran = ntohl(size_tran); read( fd, buf, size_tran ); }
#define MYSEND( fd, buf, size){ size_tran = htonl(size); write( fd, &size_tran, sizeof(int) ); size_tran = ntohl(size_tran); write( fd, buf, size_tran );}
#define SENDSTR( fd, buf ){ size_tran = htonl(strlen(buf)+1); write( fd, &size_tran, sizeof(int) ); size_tran = ntohl(size_tran); write( fd, buf, size_tran );}
*/
//네트웍 바이트오더 고려안한 코드
#define MYRECV( fd, buf){ if( !read( fd, &size_tran, sizeof(int) ) )exit(0); read( fd, buf, size_tran ); }
#define MYSEND( fd, buf, size){ size_tran = size; write( fd, &size_tran, sizeof(int) ); write( fd, buf, size_tran ); }
#define SENDSTR( fd, buf ){ size_tran = strlen(buf)+1; write( fd, &size_tran, sizeof(int) ); write( fd, buf, size_tran ); }
ftpserv.c
#include <pwd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#define _XOPEN_SOURCE
#include <unistd.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "net.h"
void service( int );
void sig_chld(int);
void myls(int);
void mycd(int);
void mypwd( int );
void myrename( int );
void myput( int );
void myget( int );
void myrm( int );
int login( int );
int fd_sock_serv, fd_sock_cli;
int len_addr_serv, len_addr_cli;
int num_arg;
struct sockaddr_in addr_serv, addr_cli, addr_data;
struct passwd *pwent;
char arg[2][STR_MAX];
int main()
{
int error;
// if( fork() != 0 )
// exit(0); //안해도 별지장없을거같네쩝.
chdir( "/" );
fd_sock_serv = socket(AF_INET, SOCK_STREAM, 0);
if(fd_sock_serv == -1)
MYEXIT("socket()")
addr_serv.sin_family = AF_INET;
addr_serv.sin_addr.s_addr = htonl(INADDR_ANY);
addr_serv.sin_port = htons(PORT_COMM);
len_addr_serv = sizeof(addr_serv);
error = bind( fd_sock_serv, (struct sockaddr *)&addr_serv, len_addr_serv);
if(error == -1)
MYEXIT("bind()")
error = listen(fd_sock_serv, 5);
if(error == -1)
MYEXIT("listen()");
for( ; ; )
{
fd_sock_cli = accept( fd_sock_serv, (struct sockaddr *)&addr_cli, &len_addr_cli );
if(fd_sock_cli == -1)
MYEXIT("accept() ")
if( fork() == 0 ) //자식 프로세스인 경우 서비스를 시작
{
service( fd_sock_cli );
exit(0);
}
else
{
signal( SIGCHLD, sig_chld);
close(fd_sock_cli);
}
}
close(fd_sock_serv);
return 0;
}
/************************* sig_child() 함수 ******************************/
void sig_chld(int signo)
{
wait();
signal( SIGCHLD, sig_chld);
}
/************************** login() ***************************************/
int login( int fd_sock_cli )
{
FILE *fd;
int i;
int index=0;
char id[STR_MAX], passwd[STR_MAX];
char tmppw[STR_MAX], tmp[STR_MAX];
char shadow[STR_MAX];
for( i = 0 ; i < 3 ; i++ )
{
MYRECV( fd_sock_cli, id );
MYRECV( fd_sock_cli, passwd );
pwent = getpwnam( id );
if( pwent == NULL )
{
MYSEND( fd_sock_cli, OPF, LEN_OPF );
SENDSTR( fd_sock_cli, "사용자가 존재하지않음" );
continue;
}
if( strlen(pwent->pw_passwd) == 1 ) //shadow 쓰는경우. 아 진짜 힘들다
{
fd = fopen("/etc/shadow", "r");
if( fd == NULL )
MYEXIT(" /etc/shadow 요거 여는데 에러나네ㅡ.ㅡ;;");
while(1)
{
fgets( tmppw, STR_MAX-1, fd );
for( i = 0, index = 0; ; i++, index++)
{
if( tmppw[i] ==':' ) //id부분을 다읽음
{
index = 0;
if( strncmp( tmp, pwent->pw_name, strlen(pwent->pw_name) ) == 0) //id 일치하면
{
for( i++ ; ;i++, index++)
{
if( tmppw[i] == ':' ) //password 다읽음
{
tmp[index] = '\0';
goto getpw;
}
tmp[index] = tmppw[i];
}
}
else
break;
}
tmp[index] = tmppw[i];
}
}
}
getpw:
if( tmp[0] == '$' ) //MD5 인경우
{
if( strcmp( (char *)crypt(passwd, tmp ), tmp) == 0 ) //비번이 일치하면
{
MYSEND( fd_sock_cli, OPK, LEN_OPK );
return 1;
}
else
{
MYSEND( fd_sock_cli, OPF, LEN_OPF );
SENDSTR( fd_sock_cli, "비밀번호틀림. 바부팅이!" );
continue;
}
}
else //DES인 경우
{
char salt[3];
salt[0] = tmp[0];
salt[1] = tmp[1];
salt[2] = '\0';
if( strcmp( (char *)crypt(passwd, salt), tmp) == 0 ) //비번이 일치하면
{
MYSEND( fd_sock_cli, OPK, LEN_OPK );
return 1;
}
else
{
MYSEND( fd_sock_cli, OPF, LEN_OPF );
SENDSTR( fd_sock_cli, "비밀번호틀림. 바부팅이!" );
continue;
}
}
}
return 0;
}
/**************************** service() 함수 *******************************************/
void service( int fd_sock_cli)
{
int cmd;
int i;
for( i = 0; i < 3; i++)
{
if( login( fd_sock_cli) ) //로그인 성공
goto ok;
}
exit(0);
ok:
if( setgid( pwent->pw_gid ) == -1)
exit(0);
if( setuid( pwent->pw_uid ) == -1)
exit(0);
chdir( pwent->pw_dir );
while(1)
{
cmd = -1;
num_arg = -1;
MYRECV( fd_sock_cli, &cmd ); //명령 번호르 읽어옴
MYRECV( fd_sock_cli, &num_arg); //인자의 수를 읽어옴
if( num_arg == 1)
{
MYRECV( fd_sock_cli, arg[0] );
}
else
{
if( num_arg == 2 )
{
MYRECV( fd_sock_cli, arg[0]);
MYRECV( fd_sock_cli, arg[1]);
}
}
switch( cmd )
{
case 0: //ls
myls(fd_sock_cli); break;
case 1: //cd
mycd(fd_sock_cli); break;
case 2: //rename
myrename(fd_sock_cli); break;
case 3: //pwd
mypwd(fd_sock_cli); break;
case 4: //rm
myrm(fd_sock_cli); break;
case 5: //get
myget(fd_sock_cli); break;
case 6: //put
myput(fd_sock_cli); break;
}
for( i =0; i < STR_MAX; i++)
{
arg[0][i] = '\0';
arg[1][i] = '\0';
}
}
}
/**************************** myls() ****************************/
void myls(int fd_sock_cli)
{
DIR *p_dir;
struct dirent *dir_ent;
struct stat stat_buf;
char msg[STR_MAX];
if( (p_dir = opendir(".")) == NULL )
{
MYSEND( fd_sock_cli, OPF, LEN_OPF);
SENDSTR( fd_sock_cli, "can't open directory" );
return;
}
else
MYSEND( fd_sock_cli, OPK, LEN_OPK);
while( dir_ent = readdir(p_dir) )
{
stat(dir_ent->d_name,&stat_buf);
if( S_ISDIR(stat_buf.st_mode) )
{
sprintf( msg,"%s/", dir_ent->d_name, strlen(dir_ent->d_name) );
SENDSTR( fd_sock_cli, msg );
}
else
{
sprintf( msg,"%s", dir_ent->d_name, strlen( dir_ent->d_name));
SENDSTR( fd_sock_cli, msg );
}
}
closedir(p_dir);
MYSEND( fd_sock_cli, EOT, LEN_EOT);
}
/**************************** mycd() ****************************/
void mycd(int fd_sock_cli)
{
if( chdir(arg[0]) == -1 )
{
MYSEND( fd_sock_cli, OPF, LEN_OPF);
SENDSTR(fd_sock_cli, "directory가 없는디 ㅡ.ㅡ?" );
return;
}
else
MYSEND( fd_sock_cli, OPK, LEN_OPK);
MYSEND( fd_sock_cli, EOT, LEN_EOT);
}
/**************************** myget() ****************************/
void myget( int fd_sock_cli)
{
int fd_get;
int readn;
struct stat stats;
mode_t mode;
char buf[STR_MAX];
fd_get = open( arg[0], O_RDONLY);
if( fd_get == -1) //파일이없는경우
{
MYSEND( fd_sock_cli, OPF, LEN_OPF);
SENDSTR( fd_sock_cli, "저쪽 파일여는데 실패해부렀네 @.@" );
return;
}
MYSEND( fd_sock_cli, OPK, LEN_OPK); //파일 열기 성공
fstat( fd_get, &stats);
mode = htonl(stats.st_mode);
MYSEND( fd_sock_cli, &mode, sizeof( mode_t ) ); //파일모드 전송
MYRECV( fd_sock_cli, buf );
if( strncmp( buf, OPF, LEN_OPF) == 0 )
{
close(fd_get);
return;
}
while(readn = read( fd_get , buf, STR_MAX))
{
MYSEND( fd_sock_cli, buf, readn );
}
MYSEND( fd_sock_cli, EOT, LEN_EOT );
close(fd_get);
}
/**************************** myput() ****************************/
void myput( int fd_sock_cli)
{
int fd_put;
mode_t mode;
char buf[STR_MAX];
MYRECV( fd_sock_cli, buf ); //local 파일 열기 성공여부를 받음
if( strncmp( buf, OPF, LEN_OPF ) == 0 )
return;
unlink( arg[1]); //일단 지움
MYRECV( fd_sock_cli, &mode); //파일 모드를 받음
mode = ntohl(mode);
fd_put = open( arg[1], O_WRONLY|O_CREAT|O_TRUNC, mode); //파일을 염.
if( fd_put == -1 )
{
MYSEND( fd_sock_cli, OPF, LEN_OPF);
SENDSTR( fd_sock_cli, "저짝 파일 여는데 실패해부러써야.." );
return;
}
MYSEND( fd_sock_cli, OPK, LEN_OPK);
while(1)
{
MYRECV( fd_sock_cli, buf);
if( strncmp( buf, EOT, LEN_EOT ) == 0 )
{
close( fd_put);
return;
}
write( fd_put, buf, size_tran );
}
}
/**************************** myrename() ****************************/
void myrename(int fd_sock_cli)
{
if( rename( arg[0], arg[1] ) == -1 )
{
MYSEND( fd_sock_cli, OPF, LEN_OPF);
SENDSTR(fd_sock_cli,"파일의 이름을 변경할수 없습니다" );
return;
}
else
{
MYSEND( fd_sock_cli, OPK, LEN_OPK);
SENDSTR(fd_sock_cli,"파일의 이름이 변경 되었습니다." );
}
MYSEND( fd_sock_cli, EOT, LEN_EOT);
}
/**************************** mypwd() ****************************/
void mypwd(int fd_sock_cli)
{
char dir_name[STR_MAX] = "\0", msg[STR_MAX] = "\0";
if( getcwd(dir_name,STR_MAX) == NULL ) //현재 디렉토리명을 얻을수 없으면 에러를 출력
{
MYSEND( fd_sock_cli, OPF, LEN_OPF);
SENDSTR(fd_sock_cli,"디렉토리가 없는가본디...");
return;
}
else
{
MYSEND( fd_sock_cli, OPK, LEN_OPK);
sprintf( msg, "저쪽 directory는 %s.", dir_name, strlen(dir_name) );
SENDSTR(fd_sock_cli, msg );
}
MYSEND( fd_sock_cli, EOT, LEN_EOT );
}
/**************************** myrm() ****************************/
void myrm(int fd_sock_cli)
{
if( unlink( arg[0] ) == -1)
{
MYSEND( fd_sock_cli, OPF, LEN_OPF);
SENDSTR( fd_sock_cli, "파일 제거에 실패하였습니다" );
return;
}
else
{
MYSEND( fd_sock_cli, OPK, LEN_OPK);
SENDSTR( fd_sock_cli, "파일을 제거하였습니다." );
}
MYSEND( fd_sock_cli, EOT, LEN_EOT );
}
ftpcli.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>
#include "net.h"
int token( char a[]);
void mylcd();
void mycmd();
void myget( int );
void myput( int );
int login(void); //반환값은 0,1(실패,성공)
int num_arg;
int fd_sock;
char cmd_list[NUM_CMD][10] = {"ls", "cd", "rename", "pwd", "rm", "get", "put", "lcd", "quit", "!" };
char cmd[3][STR_MAX+1];
struct sockaddr_in addr_serv;
struct hostent *host_ent_serv;
int main( int argc, char *argv[])
{
int i;
int error;
int len_addr_serv;
int cmd_num;
char msg[STR_MAX];
char buf[STR_MAX];
if( argc != 2 )
{
MYEXIT("ftpcli [host] 요거이 표준이제..");
}
fd_sock = socket( AF_INET, SOCK_STREAM, 0 );
if( fd_sock == -1)
MYEXIT("SOCKET() ");
host_ent_serv = gethostbyname( argv[1] );
if(host_ent_serv == NULL )
MYEXIT("gethostbyname() error ");
addr_serv.sin_family = AF_INET;
addr_serv.sin_addr = *( (struct in_addr *)host_ent_serv->h_addr );
addr_serv.sin_port = htons(PORT_COMM);
len_addr_serv = sizeof( addr_serv );
error = connect( fd_sock, (struct sockaddr *)&addr_serv, len_addr_serv );
if( error == -1 )
MYEXIT("connect() ");
printf(" %s 서버에 연결되었습니다 \n" , argv[1]);
for( i = 0; i < 3 ; i++)
{
if( login() )
goto login_ok;
}
printf("접속 실패\n");
exit(0);
login_ok:
while(1)
{
printf("MyFtp>> ");
fgets( buf, STR_MAX-1, stdin);
if( buf[0] == '\n' )
continue;
buf[strlen(buf)-1] = '\0'; //-1을 한 것은 입력에서의 '\n'을 없애기위함
cmd_num = token( buf);
if( cmd_num == -1) //잘못된 명령이면
{
printf( "%s is bad command\n" ,cmd[0]);
continue;
}
MYSEND( fd_sock , &cmd_num, sizeof(int) );
MYSEND( fd_sock , &num_arg, sizeof(int) );
if( num_arg == 1)
{
SENDSTR( fd_sock, cmd[1]);
}
else
{
if( num_arg == 2 )
{
SENDSTR( fd_sock, cmd[1] );
SENDSTR( fd_sock, cmd[2] );
}
}
switch( cmd_num )
{
case 0: //ls
case 1: //cd
case 2: //rename
case 3: //pwd
case 4: //rm
MYRECV( fd_sock, msg );
if( strncmp( msg, OPF, LEN_OPF ) == 0 )
{
MYRECV( fd_sock, msg );
printf("%s\n", msg);
break;
}
while(1)
{
for( i =0; i < STR_MAX; i++)
msg[i] = '\0';
MYRECV( fd_sock, msg );
if( strlen(msg) == LEN_EOT && strncmp( msg, EOT, LEN_EOT ) == 0 )
{
break;
}
printf("%s\n", msg);
}
break;
case 5: //get
myget( fd_sock);
break;
case 6: //put
myput( fd_sock );
break;
case 7:
mylcd();
break;
case 8 : //quit
close(fd_sock);
return 0;
break;
case 9 : // !
mycmd(); break;
}
}
close(fd_sock);
return 0;
}
/************************ login() *************************************/
//반환값은 0(실패), 1(성공)
int login(void)
{
char id[STR_MAX], passwd[STR_MAX];
char buf[STR_MAX];
printf("ID : ");
fgets( id, STR_MAX-1, stdin);
id[strlen(id)-1] = '\0'; //-1을 한 것은 입력에서의 '\n'을 없애기위함
printf("PASSWORD : ");
fgets( passwd, STR_MAX-1, stdin);
passwd[strlen(passwd)-1] = '\0'; //-1을 한 것은 입력에서의 '\n'을 없애기위함
SENDSTR( fd_sock, id );
SENDSTR( fd_sock, passwd );
MYRECV( fd_sock, buf );
if( strncmp( buf, OPF, 5 ) == 0 )
{
MYRECV( fd_sock, buf );
printf("%s\n", buf);
return 0;
}
return 1;
}
/******************************** token() **********************************/
//올바른 명령이면 해당 명령의 번호, 잘못된 명령이면 -1을 반환
int token( char buf[] )
{
int i, index= 0, flag;
for( i =0; i < STR_MAX; i++)
{
cmd[0][i] = '\0';
cmd[1][i] = '\0';
cmd[2][i] = '\0';
}
num_arg = 0;
flag = 0; //공백이면 0, 아니면 1
for( i = 0; buf[i] != '\0'; i++)
{
if( num_arg > 2)
return -1;
if( buf[i] != ' ' && buf[i] != '\0' ) //공백또는 스트링의 끝이 아니면
{
cmd[num_arg][index] = buf[i];
index++;
flag = 1;
continue;
}
if( flag != 0 ) //이전 문자가 공백이 아니면 카운트를 증가
{
flag = 0;
cmd[num_arg][index] = '\0';
num_arg++;
index = 0;
}
}
for( i = 0; i < NUM_CMD; i++) //입력받은 명령어의 번호 return
{
if( strcmp( cmd_list[i], cmd[0] ) == 0 )
return i;
}
return -1;
}
/**************************** mylcd() ****************************/
void mylcd()
{
if( chdir( cmd[1] ) == -1 )
{
printf("디렉토리 변경이 실패 하였습니다\n");
}
}
/**************************** mycmd() ****************************/
void mycmd()
{
system( cmd[1] );
}
/****************************** myget() *************************/
void myget(int fd_sock)
{
mode_t mode;
int fd_get;
char buf_get[STR_MAX];
MYRECV( fd_sock, buf_get ); //받을 파일을 열었는지 확인
if( strncmp( buf_get, OPF, LEN_OPF) == 0 ) //받을 파일 열기 실패
{
MYRECV( fd_sock, buf_get );
printf("%s\n", buf_get);
return;
}
unlink( cmd[2] ); //안묻고걍지움
MYRECV( fd_sock, &mode); //파일 모드를 받음
mode = ntohl(mode);
fd_get = open( cmd[2], O_WRONLY|O_CREAT|O_TRUNC, mode); //파일 염
if( fd_get == -1 )
{
printf("Can't open local file\n");
close(fd_get);
MYSEND(fd_sock, OPF, LEN_OPF);
return;
}
MYSEND(fd_sock, OPK, LEN_OPK);
while(1)
{
MYRECV( fd_sock, buf_get );
if( strncmp( buf_get, EOT, LEN_EOT ) == 0 )
{
close( fd_get);
return;
}
write( fd_get, buf_get, size_tran );
}
close(fd_get);
}
/********************************* myput() **************************/
void myput( int fd_sock )
{
int fd_put;
int readn;
struct stat stats;
mode_t mode;
char buf_put[STR_MAX];
fd_put = open( cmd[1], O_RDONLY);
if( fd_put == -1)
{
MYSEND(fd_sock, OPF, LEN_OPF );
printf("Can't open file\n");
return;
}
MYSEND(fd_sock, OPK, LEN_OPK ); //local 파일 열기 성공
fstat( fd_put, &stats);
mode = htonl(stats.st_mode);
MYSEND( fd_sock, &mode, sizeof( mode_t ) ); //파일모드 전송
MYRECV( fd_sock, buf_put );
if( strncmp( buf_put, OPF, 5) == 0) //서버측 파일열기 실패
{
MYRECV( fd_sock, buf_put );
printf("%s\n", buf_put);
return;
}
while( readn = read( fd_put , buf_put, STR_MAX))
{
MYSEND( fd_sock, buf_put, readn );
}
MYSEND( fd_sock, EOT, LEN_EOT);
close(fd_put);
}
Forums:


송수신 Macro를 너무 신뢰하고 계신듯합니다.
unsigned int size_tran; #define MYRECV( fd, buf){ read( fd, &size_tran, sizeof(int) ); size_tran = ntohl(size_tran); read( fd, buf, size_tran ); } #define MYSEND( fd, buf, size){ size_tran = htonl(size); write( fd, &size_tran, sizeof(int) ); size_tran = ntohl(size_tran); write( fd, buf, size_tran );}두번송신에 두번 수신하는 모습으로 되어있는데, 사실 이렇게 되려면,
처음 송신한 길이부분이 확실히 전송되었는지, 확실히 수신되었는지를
확인시켜줘야합니다.
즉, 두번째에 있는 read,write를 보면,
송신하는 녀석의 return 값이 원하는 만큼 송신되지 않을 수 도 있고,
수신하는 녀석의 return 값이 기대하는 만큼 오지 않을 수 도 있습니다.
두 경우에 대해서 원하는 만큼이 될 때까지 loop을 돌아야하구요...
loop을 돌다가 connection이 끊어지는 것도 처리해줘야하지요....
macro보다는 함수로 만드는 것이 좋겠네요.
제가 자주하는 말인데, TCP/IP에서처럼 연결지향 전송에서는 송신한 수와 수신한 수는 일치하지 않을 수 있고, 송신한 양과 수신한 양이 일치하는 것을 전제로 합니다.
---
http://coolengineer.com
다른부분은 문제가없는데 get 에서만 문제가 생기거든요
전송량을 확인하기위해 매크로뒷부분에
MYRECV {..... printf("%d\n",size_tran}; }
이런식으로해서 전송된 크기를 보면 서버측의 전송크기는 정확한데
클라이언트즉의 전송크기가 16kB 전송 이후로 이상한값이 들어가버립니다.
put 의 경우에는 위의 문제가 없고요. 다른경우도 마찬가지 입니다
님의 말씀대로 해보긴 하겠습니다. 근데 get 에서만 문제가 생기는 이유는 정말 모르겠네요
매크로를 readn, writen으로 바꿔보세요.
그렇게 하시고,
if ( n != readn ( sd, buff, n ))
{
에러처리;
}
이런식으로 사용하세요. 물론 이것도 문제는 있을 수 있지만....
큰 문제는 없을 겁니다.
댓글 달기