无界面linux文件服务器项目

开发环境:linux环境

项目功能:实现客户端对服务端文件的远程访问,远程传输功能。类似于无界面的FTP服务器。

对于本地客户端功能有:

1)lls显示当前目录有哪些文件

2)lpwd打印当前工作目录

3)lcd  xx 进入xx文件夹

4) quit退出

对于服务端功能有:

1)put xxx上传客户端的xxx文件到服务端

2)get xxx从服务端下载xxx文件到客户端

3)pwd打印服务端的当前工作目录

4)ls显示服务端当前目录有哪些文件

5)cd xx进入xx文件夹

开发步骤:

1.对于服务端会使用socket函数创建套接字,bind函数添加信息(IP地址和端口号),listen函数监听网络连接,accept函数接收客户端的连接。对于客户端会使用socket函数创建套接字,connect()函数连接服务器。

 

函数int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen)中第二个参数对应的结构体如下:

struct sockaddr {
               sa_family_t sa_family;  /*协议族*/
               char        sa_data[14];   /*端口号*/
           }

同等替换:

struct sockaddr_in {
  __kernel_sa_family_t  sin_family;     /* 协议族            */
  __be16                sin_port;       /* 端口号            */
  struct in_addr        sin_addr;       /* IP地址结构体           */

  /* 填充  没有实际意义,只是为跟sockaddr结构在内存中对齐,这样两者才能相互转换 */
  unsigned char         __pad[__SOCK_SIZE__ - sizeof(short int) -
                        sizeof(unsigned short int) - sizeof(struct in_addr)];
};
 

如何找到这个结构体?

1.切换路径至  /usr/include/

2.使用指令   grep "struct sockaddr_in  {"   -nir   查找

3.vi 编辑进入该头文件

字节序转换api

       uint32_t htonl(uint32_t hostlong);//返回网络字节序的值

       uint16_t htons(uint16_t hostshort);//返回网络字节序的值

       uint32_t ntohl(uint32_t netlong);//返回主机字节序的值uint21_t

       uint16_t ntohs(uint16_t netshort);//返回主机字节序的值

h代表host,n代表net,s代表short(两个字节),l代表long(4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。

地址转换API

int inet_aton(const char *cp, struct in_addr *inp);
把字符串形式的“192.168.1.123”转为网络能识别的格式

 char *inet_ntoa(struct in_addr in);
把网络格式的ip地址转为字符串形式

服务端代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>


int main (int argc,char *argv[])
{

     int s_fd;
     int bind_ret;
     int listen_ret;
     int c_fd;
     struct sockaddr_in c_sockaddr;

     /*socket创建套接字*/

     s_fd = socket(AF_INET,SOCK_STREAM,0);

     if(s_fd == -1)
     {
         printf("socket error!\n");
         perror("error is:");
         exit(-1);

     }

     /*bind添加信息:IP地址和端口号*/
     struct sockaddr_in sockaddr;

     sockaddr.sin_family = AF_INET;
     sockaddr.sin_port = htons(atoi(argv[2]));
     inet_aton(argv[1],&sockaddr.sin_addr);


        /*命令行参数判断*/
     if(argc != 3)
     {
         printf("param error!\n");
         exit(-1);
     }



     bind_ret = bind(s_fd,(struct sockaddr *)&sockaddr,sizeof(struct sockaddr_in));

     if(bind_ret == -1)
     {
         printf("bind error\n");
         perror("error is:");
         exit(-1);

     }

     /*listen函数监听网络连接*/

     listen_ret = listen(s_fd,10);/*最多接入10个客户端*/


     /*accept函数接入客户端*/
     int size = sizeof(struct sockaddr_in);

     c_fd = accept(s_fd,(struct sockaddr *)&c_sockaddr,&size);

     if(c_fd == -1)
     {
         printf("accept error\n");
         perror("error is:");
         exit(-1);

     }

     printf("connect successfully!the IP address is %s\n",inet_ntoa(c_sockaddr.sin_addr));


      return 0;
}

客户端代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>


int main (int argc,char** argv)
{
   int c_fd;
   int connect_ret;

   /*命令行参数判断*/

   if(argc != 3)
   {
       printf("param error!\n");
       exit(-1);

   }


   /*socket创建套接字*/
   c_fd = socket(AF_INET,SOCK_STREAM,0);

   if(c_fd == -1)
   {
      printf("socket error!\n");
      exit(-1);
   }

   /*connect连接客户端*/

   struct sockaddr_in sockaddr;
   sockaddr.sin_family = AF_INET;
   sockaddr.sin_port = htons(atoi(argv[2]));
   inet_aton(argv[1],&(sockaddr.sin_addr));


   connect_ret = connect(c_fd,(struct sockaddr*)&sockaddr,sizeof(struct sockaddr_in));

   if( connect_ret == -1)
   {
      printf("connect error!\n");
      exit(-1);
   }

    return 0;

}

运行结果如下:

说明服务端和客户端通过socket套接字连接成功了。

2.当有客户端接入的时候,服务端通过fork()函数创建子进程,让子进程和客户端进行数据的交互。父进程则继续等待下一个客户端的连接。

服务端创建子进程代码如下:

     /*循环等待客户端的接入*/
     while(1)
     {


                     /*accept函数接入客户端,如果没有客户端的可入将会阻塞等待客户端的接入*/
                     int size = sizeof(struct sockaddr_in);

                     c_fd = accept(s_fd,(struct sockaddr *)&c_sockaddr,&size);

                     if(c_fd == -1)
                     {
                             printf("accept error\n");
                             perror("error is:");
                             exit(-1);

                     }

                     /*打印接入客户端的地址*/
                     printf("connect successfully!the IP address is %s\n",inet_ntoa(c_sockaddr.sin_addr));


                     /*创建子进程进行数据的交互*/
                     pid = fork();

                     if(pid == -1)
                     {
                          printf("no child process is created!\n");
                          eixt(-1);

                     }
                     else(pid == 0)/*child process*/
                     {





                     }


     }

3.在进行数据交互的时候,会使用一个结构体作为数据交互的载体,这个结构体里面有两个数组,一个数组用于存放指令,一个数组用于存放数据。这个结构体和一些指令的宏定义会存放在一个配置文件里面。

config.h文件内容如下:

#define LLS    1
#define LPWD   2
#define PWD    3
#define LS     4
#define GET    5
#define QUIT   6
#define PUT    7
#define CD     8
#define LCD    9


struct message
{
    char cmd[128];
    char data[1024];


};

4.客户端使用gets()函数获取用户的输入,构造一个指令处理函数cmd_handler,在这个指令处理函数里面又会构造一个解析指令函数switch_cmd,在这个函数里面会通过字符串比较函数strcmp,查找子串函数strstr对指令进行解析。解析完之后会返回一个指令给指令处理函数cmd_handler。而指令处理函数会根据这个指令,会使用swtichcase走不同的分支进行指令处理。服务端的操作也是类似的。

5.对于不同的指令功能从简到难分步实现的。

1)实现本地的LLS查看客户端本地文件,LPWD查看客户端工作路径。使用system()函数调用系统指令ls,pwd来实现。

2)实现本地的LCD进入客户端某个文件夹,需要构造一个获取文件名函数getDir,在这个函数中会使用字符串切割函数获取文件名.获取文件名之后,使用chdir()函数切换目录。

3)对于输入的指令有误,则打印错误信息。

4)实现LS查看服务端有哪些文件,和PWD查看服务端工作路径。将指令写入socket套接字传到服务端, 服务端解析之后调用popen()函数,将运行结果写入socket套接字传回到客户端。

5)实现put上传文件到服务端,先使用access()函数判断本地是否有该文件,如果有,将文件写入管道。服务端读取管道中的数据,创建该文件,将数据写入该文件。

6)实现get下载服务端的文件,将指令传到服务端,服务端使用access()函数判断是否存在该文件,如果不存在,就将“nonexist”写入管道,如果存在就将文件数据写入管道。   而客户端通过判断是否有“nonexit”来确定是否能下载,如果有“nonexit”字符就提示客户端没有该文件,否则,就将创建该文件,将管道的数据写入该文件。

7)实现CD进入服务端某个文件夹,将指令写入管道,客户端读取指令后调用chdir()函数实现切换文件夹。​​​​​​

8)实现quit指令退出,关闭客户端套接字,服务端退出子进程。

 

服务端代码完整版:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#include "config.h"
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>


/*全局变量结构体用于数据的交互*/
struct message mesg;

/*解析指令函数*/
int switch_cmd(char *cmd)
{
     if(strcmp(cmd,"ls")  == 0)    return LS;
     if(strcmp(cmd,"pwd") == 0)    return PWD;
     if(strstr(cmd,"put") != NULL)    return PUT;
     if(strstr(cmd,"get") != NULL)    return GET;
     if(strstr(cmd,"cd")  != NULL)    return CD;
     if(strcmp(cmd,"quit") == 0)   return QUIT;
}

/*获取文件名函数*/
char *getDir(char *temp)
{
      char *dir;
      dir = strtok(temp," ");
      dir = strtok(NULL," ");

      return dir;
}

/*指令处理函数*/
void cmd_handler(int fd,char *cmd)
{
      printf("%s\n",cmd);/*打印指令方便调试*/

      int ret;
      int dirFd;
      FILE *pipe;
      char *temp = (char *)malloc(strlen(cmd));
      strcpy(temp,cmd);

      /*获取文件名*/
      char *dir = getDir(temp);

      ret =switch_cmd(cmd);

      switch(ret)
      {
              case PWD:
              case LS:
                      pipe = popen(cmd,"r");
                      fread(mesg.data,sizeof(mesg.data),1,pipe);
                      pclose(pipe);/*关闭管道*/

                      write(fd,&mesg,sizeof(struct message));

                      break;
              case PUT:
                      dirFd = open(dir,O_RDWR|O_TRUNC|O_CREAT,0777);
                      /*写入大小应该为实际的大小使用strlen而不是sizeof*/
                      write(dirFd,&mesg.data,strlen(mesg.data));
                      close(dirFd);
                      break;
              case GET:
                      /*判断客户端是否客户端所需要下载的文件*/
                      if(access(dir,F_OK) == -1)
                      {
                           memset(mesg.cmd,'\0',sizeof(mesg.cmd));
                           strcpy(mesg.cmd,"nonexist");

                           write(fd,&mesg,sizeof(struct message));

                      }
                      else
                      {
                           memset(mesg.data,'\0',sizeof(mesg.data));
                           dirFd = open(dir,O_RDWR);

                           read(dirFd,&mesg.data,sizeof(mesg.data));
                           close(dirFd);


                           write(fd,&mesg,sizeof(struct message));
                      }


                      break;
              case CD:
                          if(chdir(dir) == -1)
                          {
                             printf("we failed to change directory!\n");
                             perror("chdir error:");
                          }
                      break;
              case QUIT:
                          printf("the client out!\n");
                          exit(-1);/*退出子进程*/
                      break;

      }


}



int main (int argc,char *argv[])
{

     int s_fd;
     int bind_ret;
     int listen_ret;
     int c_fd;
     struct sockaddr_in c_sockaddr;
     int pid;
     int n_read;

     /*socket创建套接字*/

     s_fd = socket(AF_INET,SOCK_STREAM,0);

     if(s_fd == -1)
     {
         printf("socket error!\n");
         perror("error is:");
         exit(-1);

     }

     /*bind添加信息:IP地址和端口号*/
     struct sockaddr_in sockaddr;

     sockaddr.sin_family = AF_INET;
     sockaddr.sin_port = htons(atoi(argv[2]));
     inet_aton(argv[1],&sockaddr.sin_addr);


        /*命令行参数判断*/
     if(argc != 3)
     {
         printf("param error!\n");
         exit(-1);
     }



     bind_ret = bind(s_fd,(struct sockaddr *)&sockaddr,sizeof(struct sockaddr_in));

     if(bind_ret == -1)
     {
         printf("bind error\n");
         perror("error is:");
         exit(-1);

     }

     /*listen函数监听网络连接*/

     listen_ret = listen(s_fd,10);/*最多接入10个客户端*/

    /*循环等待客户端的接入*/
     while(1)
     {


                     /*accept函数接入客户端,如果没有客户端的可入将会阻塞等待客户端的接入*/
                     int size = sizeof(struct sockaddr_in);

                     c_fd = accept(s_fd,(struct sockaddr *)&c_sockaddr,&size);

                     if(c_fd == -1)
                     {
                             printf("accept error\n");
                             perror("error is:");
                             exit(-1);

                     }

                     /*打印接入客户端的地址*/
                     printf("connect successfully!the IP address is %s\n",inet_ntoa(c_sockaddr.sin_addr));


                     /*创建子进程进行数据的交互*/
                     pid = fork();

                     if(pid == -1)
                     {
                          printf("no child process is created!\n");
                          exit(-1);

                     }
                     else if (pid == 0)
                     {
                           while(1)
                           {
                                  memset(mesg.cmd,'\0',sizeof(mesg.cmd));
                                  memset(mesg.data,'\0',sizeof(mesg.data));

                                  n_read = read(c_fd,&mesg,sizeof(struct message));//读取管道中的数据

                                  if(n_read == 0)
                                  {
                                        printf("we failed to recevie message form client!\n");
                                        exit(-1);/*退出子进程*/
                                  }

                                    /*指令处理函数*/
                                    cmd_handler(c_fd,mesg.cmd);

                            }




                     }




     }


      return 0;


}

客户端的代码完整版:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "config.h"
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

/*全局变量结构体用于数据的交互*/
struct message mesg;

/*指令解析函数*/
int switch_cmd(char *cmd)
{
        if( strcmp(cmd,"lls")   == 0)                                                            return LLS;
        if( strcmp(cmd,"lpwd")  == 0)                                                            return LPWD;
        if( strstr(cmd,"lcd") != NULL && strcmp(cmd,"lcd") != 0)                                 return LCD;
        if( strcmp(cmd,"ls")    == 0)                                                            return LS;
        if( strcmp(cmd,"pwd")   == 0)                                                            return PWD;
        if(strstr(cmd,"put")  != NULL && strcmp(cmd,"put") != 0)                                 return PUT;
        if(strstr(cmd,"get")  != NULL && strcmp(cmd,"get") != 0)                                 return GET;
        if(strstr(cmd,"cd")   != NULL && strcmp(cmd,"cd")  != 0 && strstr(cmd,"lcd")  == NULL)   return CD;
        if(strcmp(cmd,"quit") == 0 )                                                             return QUIT;

        return -1;
}

/*获取文件名函数*/
char *getDir(char *temp)
{
      char *dir;
      dir = strtok(temp," ");
      dir = strtok(NULL," ");

      return dir;
}


/*指令处理函数*/
void cmd_handler(int fd,char *cmd)
{
    int ret;
    int desFd;
    char *temp = (char *)malloc(sizeof(strlen(cmd)));
    ret = switch_cmd(cmd);
    strcpy(temp,cmd);/*temp用于字符串切割*/

    /*获取文件名*/
    char *dir = getDir(temp);

    switch(ret){

         case PWD:
         case LS:
                    write(fd,&mesg,sizeof(struct message));

                    memset(mesg.cmd,'\0',sizeof(mesg.cmd));
                    memset(mesg.data,'\0',sizeof(mesg.data));

                    read(fd,&mesg,sizeof(struct message));

                    printf("----------------------------\n");
                    printf("%s",mesg.data);
                    printf("----------------------------\n");

                    break;
         case PUT:

                    /*判断本地是否有该文件*/
                    if(access(dir,F_OK) == -1)
                    {
                         printf("%s doesnt exist!\n",dir);
                    }
                    else
                    {
                        desFd = open(dir,O_RDWR);
                        read(desFd,&mesg.data,sizeof(mesg.data));
                        close(desFd);


                        write(fd,&mesg,sizeof(struct message));

                    }
                    break;
         case GET:
                    write(fd,&mesg,sizeof(struct message));

                    memset(mesg.cmd,'\0',sizeof(mesg.cmd));
                    memset(mesg.data,'\0',sizeof(mesg.data));

                    read(fd,&mesg,sizeof(struct message));


                    if(strstr(mesg.cmd,"nonexist") != NULL)
                    {
                            printf("%s doesnt exist!\n",dir);
                    }
                    else
                    {
                            desFd = open(dir,O_RDWR|O_TRUNC|O_CREAT,0777);
                            /*写入的大小需要使用strlen写入实际大小*/
                            write(desFd,&mesg.data,strlen(mesg.data));
                            close(desFd);

                    }
                    break;
         case CD:
                    write(fd,&mesg,sizeof(struct message));


                    break;

         case LLS:
                    printf("----------------------------\n");
                    system("ls");
                    printf("----------------------------\n");
                     break;
         case LPWD:
                    printf("----------------------------\n");
                    system("pwd");
                    printf("----------------------------\n");
                    break;
         case LCD:
                    chdir(dir);
                    break;
         case QUIT:
                    write(fd,&mesg,sizeof(struct message));
                    close(fd);/*关闭套接字*/
                    exit(-1);/*退出进程*/

                    break;
         case -1:
                    printf("command not found!\n");
                    break;

                }

}

int main (int argc,char** argv)
{
        int c_fd;
        int connect_ret;

        /*命令行参数判断*/

        if(argc != 3)
        {
                printf("param error!\n");
                exit(-1);

        }


        /*socket创建套接字*/
        c_fd = socket(AF_INET,SOCK_STREAM,0);

        if(c_fd == -1)
        {
                printf("socket error!\n");
                exit(-1);
        }

        /*connect连接客户端*/

        struct sockaddr_in sockaddr;
        sockaddr.sin_family = AF_INET;
        sockaddr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1],&(sockaddr.sin_addr));


        connect_ret = connect(c_fd,(struct sockaddr*)&sockaddr,sizeof(struct sockaddr_in));

        if( connect_ret == -1)
        {
                printf("we failed to connect the server!\n");
                exit(-1);
        }



        while(1)
        {
             memset(mesg.cmd,'\0',sizeof(mesg.cmd));
             memset(mesg.data,'\0',sizeof(mesg.cmd));


              printf("input cmd:\n");
              /*获取用户指令*/
              gets(mesg.cmd);

              /*指令处理函数*/
              cmd_handler(c_fd,mesg.cmd);

        }




    return 0;

}

问题总结:

1.上传的文件或者下载的文件可能回会出现乱码,大概率是因为写入文件的大小有误。sizeof和strlen使用混淆导致。

2.每次使用完之后都需要使用memset清空数组,不然会导致上一轮指令产生的数据残留下来。

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值