网编day2-tcp实现ftp功能

7.2 网络编程_day2
tcp实现ftp功能
模拟FTP核心原理:客户端连接服务器后,向服务器发送一个文件。文件名可以通过参数指定,服务器端接收客户端传来的文件(文件名随意),如果文件不存在自动创建文件,如果文件存在,那么清空文件然后写入。
要求:
基于TCP写出服务器和客户端代码,
在同一路径下,将客户端可执行代码复制到其他的路径下,接下来再不同的路径下运行服务器和客户端。
相当于另外一台电脑在访问服务器。
项目功能介绍:
客户端和服务器链接成功后出现以下提示:四个功能
*list //列出服务器所在目录下的文件名(除目录不显示)
*put filename //上传一个文件
get filename //从服务器所在路径下载文件
quit
//退出(可只退出客户端,服务器等待下一个客户端链接)
在这里插入图片描述

TCP粘包问题:
在本项目中: 如果发送方频率过快, 且发送的数据较小, 则会产生粘包
粘包: TCP协议栈会把多个数据包当做一个数据报发送出去
例如: send() 发送内容为5个字符, “hello”
send()发送的内容为6个字符 “return”
另一端的接收recv() 接受内容为 256个字符
则"hello" 和 “return” 两个数据包, 会被当作"helloreturn"一个数据包发送出去
stat函数:

头文件:  #include <sys/types.h>   #include <sys/stat.h>  #include <unistd.h>
原型:   int stat(const char *filename, struct stat *buf);
        //用的时候定义一个结构体变量将变量地址传给 struct stat * buf
功能: 通过文件名filename获取文件信息,并保存在buf所指的结构体stat中
参数:  pathname:文件名
            buf: 存放文件属性信息(地址) 
返回值:成功0    失败:-1,更新errno

struct stat 结构体的属性如下:
    
struct stat {   
dev_t st_dev; / device 文件的设备编号 
ino_t st_ino; / inode 文件的i-node 
mode_t st_mode; / protection 文件的类型和存取的权限(见下面内容) 
nlink_t st_nlink; / number of hard links 连到该文件的硬连接数目, 刚建立的文件值为1. 
uid_t st_uid; / user ID of owner 文件所有者的用户识别码(用户ID) 
gid_t st_gid; / group ID of owner 文件所有者的组识别码 (组ID)
dev_t st_rdev; / device type 若此文件为装置设备文件, 则为其设备编号 
off_t st_size; / total size, in bytes 文件大小, 以字节计算 

unsigned long st_blksize; 
/ blocksize for filesystem I/O 文件系统的I/O 缓冲区大小. 

unsigned long st_blocks; 
/number of blocks allocated 占用文件区块的个数,每一区块大小为512 个字节. 

time_t st_atime; 
/ time of lastaccess 文件最近一次被存取或被执行的时间 

time_t st_mtime;   与localtime配合转换为月日时间
/ time of last modification 文件最后一次被修改的时间, 一般只有在用

time_t st_ctime; 
/ time of last change i-node 最近一次被更改的时间, 此参数会在文件所有者、组、 权限被更新
}; 

使用: struct stat st;  //获取文件的inode号
         stat(argv[1],&st);      printf(%ld\n”,st.st_ino);

判断文件类型:
st_mode //这些宏需要st_mode参数传入
1. S_IFMT  0170000 文件类型的位遮罩(st_mode先和S_IFMT & 运算,再得到 以下2-11 的内容)
2、S_IFSOCK 0140000 套接字   s 
3、S_IFLNK 0120000 符号连接   l
4、S_IFREG 0100000 普通文件   - if((st.st_mode & S_IFMT) ==S_IFREG)
5、S_IFBLK 0060000 区块设备   b
6、S_IFDIR 0040000 目录      d  
7、S_IFCHR 0020000 字符设备   c
8、S_IFIFO 0010000  管道      p


文件类型:(另一种表示方法)   
其实就是 #define S_ISREG(mt_mode)   mt_mode & S_IFMT == S_IFREG 

21S_ISLNK (st_mode) 是否是一个连接     l      if(S_ISLNK(st_mode)) 
22S_ISREG (st_mode) 是否是一个普通文件  -      if(S_ISREG(st_mode)) 
23S_ISDIR (st_mode) 是否是一个目录     d      if(S_IFDIR(st_mode)) 
24S_ISCHR (st_mode) 是否是一个字符设备  c      if(S_ISCHR(st_mode)) 
25S_ISBLK (st_mode) 是否是一个块设备    b      if(S_ISBLK(st_mode)) 
26S_ISFIFO (st_mode) 是否是个管道文件   p      if(S_ISIFO(st_mode)) 
27S_ISSOCK (st_mode) 是否是个套接字文件 s     if(S_ISSOCK(st_mode))

    
文件权限: 
用户:  
1. S_IRUSR (S_IREAD) 00400 文件所有者(用户)具可读取权限    r
   使用: if(st_mode & S_IRUSR)    
13S_IWUSR (S_IWRITE)00200 文件所有者(用户)具可写入权限   w
   使用: if(st_mode & S_IWUSR)      
14S_IXUSR (S_IEXEC) 00100 文件所有者(用户)具可执行权限   x
        if(st_mode & S_IXUSR): 
15、S_IRGRP 00040 用户组具可读取权限  if(st_mode & S_IRGRP)  r
16、S_IWGRP 00020 用户组具可写入权限 if(st_mode & S_IWGRP)   w
17、S_IXGRP 00010 用户组具可执行权限 if(st_mode & S_IXGRP)   x

其他用户: 
18、S_IROTH 00004 其他用户具可读取权限 if(st_mode & S_IROTH)  r
19、S_IWOTH 00002 其他用户具可写入权限 if(st_mode & S_IWOTH)  w
20、S_IXOTH 00001 其他用户具可执行权限上述的文件类型在 POSIX 中定义了检查这些类型的宏定义 
                                      if(st_mode & S_IXOTH)   x

./server:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

void get_server(int acceptfd,char *buf,int size);
void put_server(int acceptfd,char *buf,int size);
void list_server(int acceptfd);

int main(int argc, char const *argv[])
{
    //1.创建套接字 - 返回建立连接的文件描述符
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {
        perror("socket is err:");
        return -1;
    }
    printf("sockfd: %d\n",sockfd);

    //2. 绑定套接字  -  填充结构体
    struct sockaddr_in saddr,caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1])); //填充端口
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");//填充ip
    
    socklen_t len = sizeof(caddr);

    if(bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)) < 0)
    {
       perror("bind is err:");
       return -1;
    }

    //3.监听 - 将主动套接字转换为被动套接字
    if(listen(sockfd,5) < 0)
    {
       perror("listen is err:");
       return -1;
    }
    //循环服务器, 当客户端退出, 服务器等待下一个客户端的链接
    while(1)
    {
        //4.阻塞等待客户端的连接, 返回一个建立通信的文件描述符
        int acceptfd = accept(sockfd,(struct sockaddr *)&caddr,&len);
        if(acceptfd < 0)
        {
        perror("accept is err:");
        return -1;
        }
        printf("acceptfd is %d\n",acceptfd);
        printf("client ip: %s   port: %d\n",inet_ntoa(caddr.sin_addr),\
                                            ntohs(caddr.sin_port));
       char buf[256];
       while(1)
       {
           int recvbyte = recv(acceptfd,buf,sizeof(buf),0); //put 123.c
           if(recvbyte < 0)
           {
               perror("recv is err:");
               return -1;
           }
           else if(recvbyte == 0)
           {
               printf("client is exit\n");
               close(acceptfd);
               break;
           }
           else
           {
               if(strncmp(buf,"quit",4) == 0)
                   break;
                   
               if(strncmp(buf,"list",4) == 0)
               {
                   //目录IO获取当前目录下的文件名,然后发给客户端
                   list_server(acceptfd);
               }
               else if(strncmp(buf,"put ",4) == 0)
               {
                   //接受客户端上传的文件, 然后写在新建的文件内
                   put_server(acceptfd,buf,sizeof(buf));
               }
               else if(strncmp(buf,"get ",4) == 0)
               {
                   //发送指定的文件给客户端
                   get_server(acceptfd,buf,sizeof(buf));
               }
           }
       }
 }
     //关闭文件描述符
    close(sockfd);
    return 0;
}

void get_server(int acceptfd,char *buf,int size)
{
    //1.打开文件
    int fd = open(buf+4,O_RDONLY);
    if(fd < 0)
    {
       perror("open is err:");
       return;
    }
    int ret;
    //2.循环读取文件内容并发送给客户端
    while((ret = read(fd,buf,size-1)) != 0)
    {
        buf[ret] = '\0';
       send(acceptfd,buf,size,0);
    }
    strcpy(buf,"get ok");
    send(acceptfd,buf,size,0);
}

//客户端发来文件, 服务器来接受
void put_server(int acceptfd,char *buf,int size)
{
   //1.创建一个文件用来保存客户端发送的文件内容
   int fd = open(buf+4,O_CREAT|O_TRUNC|O_WRONLY,0666); 
   if(fd < 0)
   {
       perror("open is err:");
       return;
   }
   while(1)
   {
      if(recv(acceptfd,buf,size,0) < 0)
      {
          perror("recv is err:");
          return;
      } 
      if(strncmp(buf,"put ok",6) == 0)
          break;

      write(fd,buf,strlen(buf));
   }
}


//读取服务器当前路径下的文件名并发送
void list_server(int acceptfd)
{
    char buf[256];
    //1.打开当前目录文件
    DIR * dir = opendir("./");
    if(dir == NULL)
    {
        perror("opendir is err:");
        return;
    }
    //2.循环读取当前目录下的文件名
    struct dirent *dp = NULL;
    struct stat st;
    while((dp = readdir(dir))!= NULL)
    {
       //stat获取文件类型
       stat(dp->d_name,&st);
       //判断当前读取的文件名是否是普通文件
      if(S_ISREG(st.st_mode))
      {  //将文件名拷贝到buf内
          strcpy(buf,dp->d_name);
          send(acceptfd,buf,sizeof(buf),0);
      }
    }

    strcpy(buf,"list ok");
    send(acceptfd,buf,sizeof(buf),0);

}

./client:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void show(void);
void list_client(int sockfd);
void put_client(int sockfd, char *buf, int size);
void get_client(int sockfd, char *buf, int size);
int main(int argc, char const *argv[])
{
    if (argc != 3)
    {
        printf("please input %s <ip> <port>\n", argv[0]);
        return -1;
    }
    //1.创建套接子
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err.");
        return -1;
    }

    //填充结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);

    if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("connect err.");
        return -1;
    }
    //收发消息
    char buf[128];
    while (1)
    {
        //1.请求窗口
        show();
        //2.获取请求
        fgets(buf, sizeof(buf), stdin);
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '\0';
        send(sockfd, buf, sizeof(buf), 0);
        if (strncmp(buf, "list", 4) == 0)
        { //循环接收服务器发送过来的普通文件名输出到终端
            list_client(sockfd);
        }
        else if (strncmp(buf, "put ", 4) == 0)
        { //上传文件(本地:打开文件读内容发送给服务器)
            put_client(sockfd,buf,sizeof(buf));
        }
        else if (strncmp(buf, "get ", 4) == 0)
        { //下载服务器路径下的文件
            //(本地:打开新建文件接收服务器发送过来的内容写到文件)
            get_client(sockfd,buf,sizeof(buf));
        }
        else if (strncmp(buf, "quit", 4) == 0)
        {
            break;
        }
    }
    close(sockfd);
    return 0;
}

//1.list:循环接收服务器发送过来的普通文件名输出到终端
void list_client(int sockfd)
{
    char buf[128];
    while (1)
    {
        if (recv(sockfd, buf, sizeof(buf), 0) < 0)
        {
            perror("list recv err.");
            return;
        }
        if (strncmp(buf, "list ok.", 8) == 0)
        {
            break;
        }
        printf("%s\n", buf);
    }
}

//2.put 上传文件(本地:打开文件读内容发送给服务器)
void put_client(int sockfd, char *buf, int size) //buf->put xxx.c
{
    //1>打开文件 读
    int fd = open(buf + 4, O_RDONLY);
    if (fd < 0)
    {
        perror("open file err.");
        return;
    }
    int ret; //实际读到的个数
    //先不写size-1,先写size, 不补入\0, 运行会发现粘包,把putok也写入文件里了
    //再解释粘包, 使用size-1, 手动补入\0,目的是为了另一端的write可以写入实际字节数strlen
    while ((ret = read(fd, buf, size - 1)) != 0)
    {
        buf[ret] = '\0';
        send(sockfd, buf, size, 0);
    }
    strcpy(buf, "put ok.");
    send(sockfd, buf, size, 0);
}

//3.get
void get_client(int sockfd, char *buf, int size)
{
    int fd = open(buf + 4, O_TRUNC | O_CREAT | O_WRONLY, 0666);
    if (fd < 0)
    {
        perror("open err.");
        return;
    }
    while (1)
    {
        if (recv(sockfd, buf, size, 0) < 0)
        {
            perror("recv err.");
            return;
        }
        if (strncmp(buf, "get ok.", 7) == 0)
            break;
        //可以先把strlen(buf)写为size, 后期发现粘包问题,再修改
        write(fd, buf, strlen(buf));
    }
}


void show(void)
{
    printf("************list***************\n");
    printf("************put filename*******\n");
    printf("************get filename*******\n");
    printf("************quit***************\n");
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值