c语言实现tcp 服务器设计_FTP服务器的实现(C语言)

我们在之前的文章中,曾经对FTP文件传输协议做过详细的介绍。本章,我们对如何用C语言实现FTP服务器做一个简单的介绍。

06ea188444fe0beccf55715179f41d40.png

概述

FTP文件传输协议,是因特网上使用得最广泛的文件传输协议。FTP提供交互式的访问,允许客户指明文件的格式与类型,并允许文件具有存储权限。FTP屏蔽了不同操作系统之前的细节,因此适合在异构网络中任意计算机之间传送文件。

FTP的基本工作原理

FTP使用C/S方式,一个FTP服务器可以为多个客户进程提供服务,FTP服务器进程由两大部分组成:一个主进程,负责接收新的请求;另外有若干个从属进程,负责处理单个请求。

主进程的工作步骤如下:

  1. 打开端口号(一般为21),使客户端能通过此端口号访问;
  2. 等待客户端发出连接请求;
  3. 启动从属进程来处理客户进程发来的请求。从属进程对客户进程的请求处理完后即终止,从属进程在运行期间可能会根据需要另外创建其他一些进程。
  4. 回到等待状态,继续等待其他客户进程发来的连接请求。主进程和从属进程是并发进行的。

在进行文件传输时,FTP的客户和服务器之间要建立两个并行的TCP连接:“控制连接”和“数据连接”。控制连接在整个会话期间一直保持打开,FTP客户所发出的传送请求,通过控制连接发送给服务器端的控制进程,但是控制连接并不会用于传输数据。实际传输文件的是“数据连接”。服务器端的控制进程在接收到FTP客户发送来的文件传输请求后,就会创建“数据传送进程”“数据连接”,用来连接客户端和服务器端的数据传送进程。由于FTP使用了一个分离的控制连接,因此FTP的控制信息是带外控制的。

当客户进程向服务器进程发出建立连接请求时,通过服务器端口号21请求连接,同时会告诉服务器进程自己用于建立数据传送连接的另一个端口号。服务器一般使用端口号20同客户进程建立数据连接,由于FTP使用两个不同的端口号,所以数据连接和控制连接不会发生混乱。

82d06df09f2d04f3d0be70d705251038.png

综上所述,我们可以画出基本的算法流程图

70ac5267f83294f0bbdcdbb48ee74ab8.png

代码实现:

  • 首先是基本的定义
/* Commands enumeration */typedef enum cmdlist {   ABOR, CWD, DELE, LIST, MDTM, MKD, NLST, PASS, PASV,  PORT, PWD, QUIT, RETR, RMD, RNFR, RNTO, SITE, SIZE,  STOR, TYPE, USER, NOOP} cmdlist;/* String mappings for cmdlist */static const char *cmdlist_str[] = {  "ABOR", "CWD", "DELE", "LIST", "MDTM", "MKD", "NLST", "PASS", "PASV",  "PORT", "PWD", "QUIT", "RETR", "RMD", "RNFR", "RNTO", "SITE", "SIZE",  "STOR", "TYPE", "USER", "NOOP" };
控制端口的定义
/* define FTP control port */#define CONTROLPORT 21
主函数
/**  * Sets up server and handles incoming connections * @param port Server port */int main(){    int sock = create_socket(CONTROLPORT );    struct sockaddr_in client_address;    int len = sizeof(client_address);    int connection, pid, bytes_read;        while(1){        connection = accept(sock, (struct sockaddr*) &client_address,&len);        char buffer[BSIZE];        Command *cmd = malloc(sizeof(Command));        State *state = malloc(sizeof(State));        pid = fork();            memset(buffer,0,BSIZE);        if(pid<0){            fprintf(stderr, "Cannot create child process.");            exit(EXIT_FAILURE);        }        if(pid==0){            close(sock);            char welcome[BSIZE] = "220 ";            if(strlen(welcome_message)BSIZE)){                  /* TODO: output this to log */                  buffer[BSIZE-1] = '0';                  printf("User %s sent command: %s",(state->username==0)?"unknown":state->username,buffer);                  parse_command(buffer,cmd);                  state->connection = connection;                            /* Ignore non-ascii char. Ignores telnet command */                  if(buffer[0]<=127 || buffer[0]>=0){                    response(cmd,state);                  }              memset(buffer,0,BSIZE);              memset(cmd,0,sizeof(cmd));            } else{                /* Read error */                perror("server:read");                }         }         printf("Client disconnected.");         exit(0);    }else{      printf("closing... :(");      close(connection);    }  }}
  • 功能的实现
/** * Handle USER command * @param cmd Command with args * @param state Current client connection state */void ftp_user(Command *cmd, State *state){  const int total_usernames = sizeof(usernames)/sizeof(char *);  if(lookup(cmd->arg,usernames,total_usernames)>=0){    state->username = malloc(32);    memset(state->username,0,32);    strcpy(state->username,cmd->arg);    state->username_ok = 1;    state->message = "331 User name okay, need password";  }else{    state->message = "530 Invalid username";  }  write_state(state);}/** PASS command */void ftp_pass(Command *cmd, State *state){  if(state->username_ok==1){    state->logged_in = 1;    state->message = "230 Login successful";  }else{    state->message = "500 Invalid username or password";  }  write_state(state);}/** PASV command */void ftp_pasv(Command *cmd, State *state){  if(state->logged_in){    int ip[4];    char buff[255];    char *response = "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)";    Port *port = malloc(sizeof(Port));    gen_port(port);    getip(state->connection,ip);    /* Close previous passive socket? */    close(state->sock_pasv);    /* Start listening here, but don't accept the connection */    state->sock_pasv = create_socket((256*port->p1)+port->p2);    printf("port: %d",256*port->p1+port->p2);    sprintf(buff,response,ip[0],ip[1],ip[2],ip[3],port->p1,port->p2);    state->message = buff;    state->mode = SERVER;    puts(state->message);  }else{    state->message = "530 Please login with USER and PASS.";    printf("%s",state->message);  }  write_state(state);}/** LIST command */void ftp_list(Command *cmd, State *state){  if(state->logged_in==1){    struct dirent *entry;    struct stat statbuf;    struct tm *time;    char timebuff[80], current_dir[BSIZE];    int connection;    time_t rawtime;    /* TODO: dynamic buffering maybe? */    char cwd[BSIZE], cwd_orig[BSIZE];    memset(cwd,0,BSIZE);    memset(cwd_orig,0,BSIZE);        /* Later we want to go to the original path */    getcwd(cwd_orig,BSIZE);        /* Just chdir to specified path */    if(strlen(cmd->arg)>0&&cmd->arg[0]!='-'){      chdir(cmd->arg);    }        getcwd(cwd,BSIZE);    DIR *dp = opendir(cwd);    if(!dp){      state->message = "550 Failed to open directory.";    }else{      if(state->mode == SERVER){        connection = accept_connection(state->sock_pasv);        state->message = "150 Here comes the directory listing.";        puts(state->message);        while(entry=readdir(dp)){          if(stat(entry->d_name,&statbuf)==-1){            fprintf(stderr, "FTP: Error reading file stats...");          }else{            char *perms = malloc(9);            memset(perms,0,9);            /* Convert time_t to tm struct */            rawtime = statbuf.st_mtime;            time = localtime(&rawtime);            strftime(timebuff,80,"%b %d %H:%M",time);            str_perm((statbuf.st_mode & ALLPERMS), perms);            dprintf(connection,                "%c%s %5d %4d %4d %8d %s %s",                 (entry->d_type==DT_DIR)?'d':'-',                perms,statbuf.st_nlink,                statbuf.st_uid,                 statbuf.st_gid,                statbuf.st_size,                timebuff,                entry->d_name);          }        }        write_state(state);        state->message = "226 Directory send OK.";        state->mode = NORMAL;        close(connection);        close(state->sock_pasv);      }else if(state->mode == CLIENT){        state->message = "502 Command not implemented.";      }else{        state->message = "425 Use PASV or PORT first.";      }    }    closedir(dp);    chdir(cwd_orig);  }else{    state->message = "530 Please login with USER and PASS.";  }  state->mode = NORMAL;  write_state(state);}/** QUIT command */void ftp_quit(State *state){  state->message = "221 Goodbye, friend. I never thought I'd die like this.";  write_state(state);  close(state->connection);  exit(0);}/** PWD command */void ftp_pwd(Command *cmd, State *state){  if(state->logged_in){    char cwd[BSIZE];    char result[BSIZE];    memset(result, 0, BSIZE);    if(getcwd(cwd,BSIZE)!=NULL){      strcat(result,"257 "");      strcat(result,cwd);      strcat(result,""");      state->message = result;    }else{      state->message = "550 Failed to get pwd.";    }    write_state(state);  }}/** CWD command */void ftp_cwd(Command *cmd, State *state){  if(state->logged_in){    if(chdir(cmd->arg)==0){      state->message = "250 Directory successfully changed.";    }else{      state->message = "550 Failed to change directory.";    }  }else{    state->message = "500 Login with USER and PASS.";  }  write_state(state);}/**  * MKD command  * TODO: full path directory creation */void ftp_mkd(Command *cmd, State *state){  if(state->logged_in){    char cwd[BSIZE];    char res[BSIZE];    memset(cwd,0,BSIZE);    memset(res,0,BSIZE);    getcwd(cwd,BSIZE);    /* TODO: check if directory already exists with chdir? */    /* Absolute path */    if(cmd->arg[0]=='/'){      if(mkdir(cmd->arg,S_IRWXU)==0){        strcat(res,"257 "");        strcat(res,cmd->arg);        strcat(res,"" new directory created.");        state->message = res;      }else{        state->message = "550 Failed to create directory. Check path or permissions.";      }    }    /* Relative path */    else{      if(mkdir(cmd->arg,S_IRWXU)==0){        sprintf(res,"257 "%s/%s" new directory created.",cwd,cmd->arg);        state->message = res;      }else{        state->message = "550 Failed to create directory.";      }    }  }else{    state->message = "500 Good news, everyone! There's a report on TV with some very bad news!";  }  write_state(state);}/** RETR command */void ftp_retr(Command *cmd, State *state){  if(fork()==0){    int connection;    int fd;    struct stat stat_buf;    off_t offset = 0;    int sent_total = 0;    if(state->logged_in){      /* Passive mode */      if(state->mode == SERVER){        if(access(cmd->arg,R_OK)==0 && (fd = open(cmd->arg,O_RDONLY))){          fstat(fd,&stat_buf);                    state->message = "150 Opening BINARY mode data connection.";                    write_state(state);                    connection = accept_connection(state->sock_pasv);          close(state->sock_pasv);          if(sent_total = sendfile(connection, fd, &offset, stat_buf.st_size)){                        if(sent_total != stat_buf.st_size){              perror("ftp_retr:sendfile");              exit(EXIT_SUCCESS);            }            state->message = "226 File send OK.";          }else{            state->message = "550 Failed to read file.";          }        }else{          state->message = "550 Failed to get file";        }      }else{        state->message = "550 Please use PASV instead of PORT.";      }    }else{      state->message = "530 Please login with USER and PASS.";    }    close(fd);    close(connection);    write_state(state);    exit(EXIT_SUCCESS);  }  state->mode = NORMAL;  close(state->sock_pasv);}/** Handle STOR command. TODO: check permissions. */void ftp_stor(Command *cmd, State *state){  if(fork()==0){    int connection, fd;    off_t offset = 0;    int pipefd[2];    int res = 1;    const int buff_size = 8192;    FILE *fp = fopen(cmd->arg,"w");    if(fp==NULL){      /* TODO: write status message here! */      perror("ftp_stor:fopen");    }else if(state->logged_in){      if(!(state->mode==SERVER)){        state->message = "550 Please use PASV instead of PORT.";      }      /* Passive mode */      else{        fd = fileno(fp);        connection = accept_connection(state->sock_pasv);        close(state->sock_pasv);        if(pipe(pipefd)==-1)perror("ftp_stor: pipe");        state->message = "125 Data connection already open; transfer starting.";        write_state(state);        /* Using splice function for file receiving.         * The splice() system call first appeared in Linux 2.6.17.         */        while ((res = splice(connection, 0, pipefd[1], NULL, buff_size, SPLICE_F_MORE | SPLICE_F_MOVE))>0){          splice(pipefd[0], NULL, fd, 0, buff_size, SPLICE_F_MORE | SPLICE_F_MOVE);        }        /* TODO: signal with ABOR command to exit */        /* Internal error */        if(res==-1){          perror("ftp_stor: splice");          exit(EXIT_SUCCESS);        }else{          state->message = "226 File send OK.";        }        close(connection);        close(fd);      }    }else{      state->message = "530 Please login with USER and PASS.";    }    close(connection);    write_state(state);    exit(EXIT_SUCCESS);  }  state->mode = NORMAL;  close(state->sock_pasv);}/** ABOR command */void ftp_abor(State *state){  if(state->logged_in){    state->message = "226 Closing data connection.";    state->message = "225 Data connection open; no transfer in progress.";  }else{    state->message = "530 Please login with USER and PASS.";  }  write_state(state);}/**  * Handle TYPE command. * BINARY only at the moment. */void ftp_type(Command *cmd,State *state){  if(state->logged_in){    if(cmd->arg[0]=='I'){      state->message = "200 Switching to Binary mode.";    }else if(cmd->arg[0]=='A'){      /* Type A must be always accepted according to RFC */      state->message = "200 Switching to ASCII mode.";    }else{      state->message = "504 Command not implemented for that parameter.";    }  }else{    state->message = "530 Please login with USER and PASS.";  }  write_state(state);}/** Handle DELE command */void ftp_dele(Command *cmd,State *state){  if(state->logged_in){    if(unlink(cmd->arg)==-1){      state->message = "550 File unavailable.";    }else{      state->message = "250 Requested file action okay, completed.";    }  }else{    state->message = "530 Please login with USER and PASS.";  }  write_state(state);}/** Handle RMD */void ftp_rmd(Command *cmd, State *state){  if(!state->logged_in){    state->message = "530 Please login first.";  }else{    if(rmdir(cmd->arg)==0){      state->message = "250 Requested file action okay, completed.";    }else{      state->message = "550 Cannot delete directory.";    }  }  write_state(state);}/** Handle SIZE (RFC 3659) */void ftp_size(Command *cmd, State *state){  if(state->logged_in){    struct stat statbuf;    char filesize[128];    memset(filesize,0,128);    /* Success */    if(stat(cmd->arg,&statbuf)==0){      sprintf(filesize, "213 %d", statbuf.st_size);      state->message = filesize;    }else{      state->message = "550 Could not get file size.";    }  }else{    state->message = "530 Please login with USER and PASS.";  }  write_state(state);}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值