TCP实现 FTP功能、UDP编程

TCP实现 FTP功能

模拟 FTP 核心原理:客户端连接服务器后,向服务器发送一个文件。文件名可以通过参数指定,服务器端接收客户端传来的文件(文件名随意)。如果文件不存在,则自动创建文件;如果文件存在,那么清空文件,然后写入。

功能介绍

所有服务器和客户端代码,基于 TCP 编写。
在同一路径下,客户端可执行代码、服务器端可执行代码 位于不同子路径下,在不同的路径下运行服务器和客户端。模拟另外一台电脑在访问服务器。
客户端和服务器链接成功后出现以下提示:四个功能
*list // 列出服务器所在目录下的文件名(除目录不显示)
*put filename // 上传一个文件
get filename // 从服务器所在路径下载文件
quit
// 退出(可只退出客户端,服务器等待下一客户端连接)

注意 fgets / read 的区别:

read() 在读取数据时会将最后的回车 ‘\n’ 同时读入到 buf[] 中,但是 不会在后面加上字符串结束符 ‘\0’。 (记得手动补入 ‘\n’ )
fgets() 在读取数据时会将最后的回车 ‘\n’ 同时读入到 buf[] 中,并且 会在后面加上字符串结束符 ‘\0’。

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 {   
   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 文件最近一次被存取或被执行的时间, 一般只有在用 mknod, utime, read 
   time_t st_mtime;   // 与 localtime 配合转换为 月、日、时间
   // time of last modification 文件最后一次被修改的时间, 一般只有在用mknod、utime, write 
   time_t st_ctime; 
   // time of last change i-node 最近一次被更改的时间, 此参数会在文件所有者, 组, 权限被更新
}; 


使用: 
   struct stat st;  				
       stat(argv[1], &st);      			// argv[1] 写路径/文件
   	printf(“%ld\n”, st.st_ino);			// 获取文件的 inode 号
       // ……
// 判断文件类型:

// 方法一:
// 以下宏需要 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

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

用户: 
1. S_IRUSR (S_IREAD) 	00400 文件所有者(用户)具可读取权限   r
                                   								if(st_mode & S_IRUSR); 
13、S_IWUSR (S_IWRITE)	00200 文件所有者(用户)具可写入权限   w
                                                                if(st_mode & S_IWUSR); 
14、S_IXUSR (S_IEXEC) 	00100 文件所有者(用户)具可执行权限   x
                                                                if(st_mode & S_IXUSR); 

组: 
15、S_IRGRP 00040 用户组具可读取权限   r  	 	if(st_mode & S_IRGRP);
16、S_IWGRP 00020 用户组具可写入权限   w   	 	if(st_mode & S_IWGRP);  
17、S_IXGRP 00010 用户组具可执行权限   x   	 	if(st_mode & S_IXGRP);

其他用户: 
18、S_IROTH 00004 其他用户具可读取权限    r		if(st_mode & S_IROTH);  
19、S_IWOTH 00002 其他用户具可写入权限    w		if(st_mode & S_IWOTH);  
20、S_IXOTH 00001 其他用户具可执行权限	x		if(st_mode & S_IXOTH);
ser_ftp.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h> 
#include <sys/socket.h> 
#include <netinet/in.h>    
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <fcntl.h>
#include <dirent.h>

void ls_stat(int accfd, char *buf, int size);
void get_file(int accfd, char *buf, int size);
void put_file(int accfd, char *buf, int size);

int main(int argc, char const *argv[])
{
    if (argc != 2){
        printf("Please input %s <port>. \n", argv[0]);
        return -1;
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0){
        perror("Failed to create a socket");
        return -1;
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    saddr.sin_port = htons(atoi(argv[1]));

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

    if (listen(sockfd, 6) < 0){
        perror("Failed to listen");
        return -1;
    }

    struct sockaddr_in caddr;
    socklen_t length = sizeof(caddr);

    while (1){
        int accfd = accept(sockfd, (struct sockaddr *)&caddr, &length);
        if (accfd < 0){
            perror("Failed to accept");
            return -1;
        }
        printf("Client IPv4: %s\tport: %d\n", 
               inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));

        char buf[256] = {};
        while (1){
            int receiver = recv(accfd, buf, sizeof(buf), 0);
            if (receiver < 0){
                perror("Failed to receive (server)");
                return -1;
            } else if (receiver == 0){
                printf("Client exited.\n");
                break;
            } else {
                if (!(strncmp(buf, "list", 4) && strncmp(buf, "List", 4)))
                    ls_stat(accfd, buf, sizeof(buf));
                if (!(strncmp(buf, "put ", 4) && strncmp(buf, "Put ", 4)))
                    get_file(accfd, buf, sizeof(buf));
                if (!(strncmp(buf, "get ", 4) && strncmp(buf, "Get ", 4)))
                    put_file(accfd, buf, sizeof(buf));
                // if (!(strncmp(buf, "quit", 4) && strncmp(buf, "Quit", 4)))
                //     break;
            }
        }
        close(accfd);
    }

    close(sockfd);
    return 0;
}

void ls_stat(int accfd, char *buf, int size){

    DIR * dir = opendir("./");
    if(dir == NULL){
        perror("Failed to open a directory");
        return;
    }

    struct dirent *reader = NULL;
    struct stat st;
    while((reader = readdir(dir)) != NULL){
    
       stat(reader->d_name, &st);       // reader->d_name 为拿到的文件名
       
       if(S_ISREG(st.st_mode)){         // 判断是否为普通文件
         strcpy(buf, reader->d_name);
         send(accfd, buf, size, 0);
       }
    }

    strcpy(buf, "It's done."); 
    send(accfd, buf, size, 0);

    closedir(dir);  
}

void get_file(int accfd, char *buf, int size){

    int fd = open(buf + 4, O_TRUNC | O_CREAT | O_WRONLY, 0666);
    if(fd < 0){
        perror("Failed to open this file (get_file)");
        return;
    }

    while(1){
        int receiver = recv(accfd, buf, size, 0);

        if(receiver < 0){
            perror("Failed to receive (get_file)");
            return;

        } else if (receiver == 0){
            printf("Client exited. \n");
            break;

        } else {
            if(strncmp(buf, "Upload done.", 12) == 0)
                break;
            write(fd, buf, strlen(buf));
        }
    }   
    close(fd); 
}

void put_file(int accfd, char *buf, int size){

    int fd = open(buf + 4, O_RDONLY);
    if(fd < 0){
        perror("Failed to open this file (put_file");
        return;
    }

    int ret;
    while((ret = read(fd, buf, size-1)) != 0){
        buf[ret] = '\0';
        send(accfd, buf, size, 0);
    }

    strcpy(buf, "Download successfully.");
    send(accfd, buf, size, 0);

    close(fd);
}
cli_ftp.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h> 
#include <sys/socket.h>  
#include <netinet/in.h>    
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

void show();
void list(int sockfd, char *buf, int size);
void up_load(int sockfd, char *buf, int size);
void down_load(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;
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0){
        perror("Failed to create a socket");
        return -1;
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    saddr.sin_port = htons(atoi(argv[2]));

    if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){
        perror("Failed to connect");
        return -1;
    }

    char buf[256] = {};
    while (1){

        show();
        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) && strncmp(buf, "List", 4)))
            list(sockfd, buf, sizeof(buf));
        if (!(strncmp(buf, "put ", 4) && strncmp(buf, "Put ", 4)))
            up_load(sockfd, buf, sizeof(buf));
        if (!(strncmp(buf, "get ", 4) && strncmp(buf, "Get ", 4)))
            down_load(sockfd, buf, sizeof(buf));
        if (!(strncmp(buf, "quit", 4) && strncmp(buf, "Quit", 4))){
            printf("Client exited. \n");
            break;
        }
    }
    
    close(sockfd);
    return 0;
}

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

void list(int sockfd, char *buf, int size){
    printf("----------------------\n");
    while(1){

        // 接收服务器发来的目录文件名
        int receiver = recv(sockfd, buf, size, 0);
        if(receiver < 0){
            perror("Failed to receive (client)");
            return;

        } else {
            // 服务器发送完毕,须告知客户端,以"It's done. ......"为信号
            if(strncmp(buf, "It's done.", 10) == 0)
               break;

            printf("%s\n", buf);
        }
    }
    // putchar(10);
}

void up_load(int sockfd, char *buf, int size){
    // 打开文件, 读
    // 应当跳过 “put <filename>” 中的 "put "
    int fd = open(buf + 4, O_RDONLY);
    if(fd < 0){
        perror("Failed to open this file (upload)");
        return;
    }

    int ret;
    while((ret = read(fd, buf, size-1))!= 0)
    {
        // read不会自动补入'\0',需要手动添加, 否则会与下次读取的内容合并(粘包)
        buf[ret] = '\0';
        send(sockfd, buf, size, 0);
    } 

    // 读取完毕后,不能让服务器一致阻塞recv
    strcpy(buf, "Upload done.");
    send(sockfd, buf, size, 0);
    printf("Upload successfully! \n");
    close(fd);
}

void down_load(int sockfd, char *buf, int size){

    // buf 中应去除 "get "
    int fd = open(buf + 4, O_TRUNC | O_CREAT | O_WRONLY, 0666);
    if(fd < 0){
        perror("Failed to open this file (download)");
        return;
    }

    while(1){
        if((recv(sockfd, buf, size, 0)) < 0){
            perror("Failed to receive (download)");
            return;
        }

       // 如果服务器发送来 发送完毕的指令, 则客户端应该停止 recv 
        if(strncmp(buf, "Download successfully.", 22) == 0)
            break;
        
        write(fd, buf, strlen(buf));
    }  
    printf("Download successfully! \n");
    
    close(fd);
}
实现效果

在这里插入图片描述

UDP 编程

流程图

在这里插入图片描述

实现步骤

服务器端:

1)创建数据报套接字(socket(AF_INET, SOCK_DGRAM, 0));
2)绑定网络信息(bind());
3)接收信息(recvfrom()); 必须先接收信息,才能获取到客户端的 IP 和端口号!!
// 4)发送信息(sendto());
5)关闭套接字(close())。

客户端:

1)创建数据报套接字(socket());
2)指定服务器的网络信息;
3)发送信息(sendto()); 必须先发送信息!!
// 4)接收信息(recvfrom());
5)关闭套接字(close())。

函数

recvfrom 接收
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
					struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:
	sockfd:	套接字描述符
	buf:		接收缓存区的首地址
	len:		接收缓存区的大小
	flags:0  	接收数据 并 阻塞
        	MSG_DONTWAIT: 设置非阻塞
	src_addr: 	发送端的网络信息结构体的指针(对方的 caddr)
	addrlen:	发送端的网络信息结构体的大小的指针(对方的 caddr)
返回值:
	成功接收的字节个数(若接收到的数据为0: 返回0)
	失败:-1
sendto 发送
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                  const struct sockaddr *dest_addr, socklen_t addrlen);

功能:发送数据
参数:
	sockfd:	套接字描述符
	buf:		发送缓存区的首地址
	len:		发送缓存区的大小
	flags:0  	发送消息并阻塞
	src_addr:	接收端的网络信息结构体的指针
	addrlen:	接收端的网络信息结构体的大小
返回值: 
	成功发送的字节个数
	失败:-1

udp_ser.c

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

int main(int argc, char const *argv[])
{
    if (argc != 2){
        printf("Please input %s <port>. \n", argv[0]);
        return -1;
    }

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0){
        perror("Failed to create a socket");
        return -1;
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = INADDR_ANY;

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

    char buf[256] = {};
    struct sockaddr_in caddr;
    socklen_t length = sizeof(caddr);
    while (1){
        int receiver = recvfrom(sockfd, buf, sizeof(buf), 0, 
                                (struct sockaddr *)&caddr, &length);
        if (receiver < 0){
            perror("Failed to receive");
            return -1;
        } else {
            printf("Client IPv4: %s\t\tport: %d\n%s\n", 
                   inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port), buf);
        }
    }
    close(sockfd);
    return 0;
}

udp_cli.c

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

int main(int argc, char const *argv[])
{
    if (argc != 3){
        printf("Please input %s <ip> <port>. \n", argv[0]);
        return -1;
    }

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0){
        perror("Failed to create a socket");
        return -1;
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    saddr.sin_port = htons(atoi(argv[2]));
    
    char buf[256] = {};
    while (1){
        fgets(buf, sizeof(buf), stdin);
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '\0';

        sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&saddr, sizeof(saddr));
    }
    
    close(sockfd);
    return 0;
}

实现效果

在这里插入图片描述

注意:

1、对于 TCP ,先运行服务器,客户端才能运行。
2、对于 UDP,服务器和客户端运行顺序没有先后(因为无连接),但客户端应先发送消息给服务器。
3、一个服务器可以同时连接多个客户端(可以在服务器代码里面打印 IP 和 端口号)。
4、对于 UDP,当客户端使用 send 时,需要提前 connect(这个 connect 不是代表连接的作用,而是指定客户端即将要给谁发送数据)。这样就不需要使用 sendto,用 send 就可以。
5、对于 TCP ,也可以使用 recvfrom 和 sendto,将后面的两个参数都写为 NULL 即可。
点击跳转至 TCP 编程

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值