17."自动云同步"项目实践
文章目录
- 17."自动云同步"项目实践
- 项目简介
- 功能需求
- 需求分析
- 实现步骤
- 1.实现TCP通信
- server.c 服务端
- tcp.h
- client.c 客户端
- 函数封装
- tcp.c
- tcp.h
- server.c
- client.c
- 编译运行
- 2.实现文件传输
- sever.c
- client.c
- tcp.c
- tcp.h
- Makeifle
- 编译运行
- 3.实现用文件名传输
- server.c
- client.c
- tcp.c
- tcp.h
- .info
- 文件位置
- 编译运行
项目简介
功能需求
- 保持云端数据和终端数据的一致
- 上传和下载
- 实时同步
- 定时同步
- 手动同步
需求分析
- 文件的上传和下载
- 文件的大小不确定
- 文件的个数不确定
- 实时同步需要获取文件事件
- 定时同步需要设置定时器
实现步骤
- 实现TCP通信
- 使用TCP实现文件的上传和下载
- 实现整个目录下的文件的同步
- 实现项目框架
- 完成项目
1.实现TCP通信
server.c 服务端
#include "tcp.h"int main(int argc, char *argv[])
{int fd, newfd;int ret;char buf[BUFSIZ];Addr_in addr, client_addr;socklen_t addrlen = sizeof(addr);/*检查参数*/if(argc < 3){fprintf(stderr, "%s <addr><port>\n", argv[0]);exit(EXIT_FAILURE);}/*创建套接字*/if( (fd = socket(AF_INET, SOCK_STREAM, 0) ) < 0)ErrExit("socket");/*设置通信结构体*/bzero(&addr, sizeof(addr) );addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );if (inet_aton(argv[1], &addr.sin_addr) == 0) {fprintf(stderr, "Invalid address\n");exit(EXIT_FAILURE);}/*绑定通信结构体*/if( bind(fd, (Addr *)&addr, sizeof(addr) ) )ErrExit("bind");/*监听模式*/if( listen(fd, BACKLOG) )ErrExit("listen");/*接收客户端连接*/do {newfd = accept(fd, (Addr *)&client_addr, &addrlen);}while(newfd < 0 && errno == EINTR); //如果信号导致的错误,继续执行if(newfd < 0)ErrExit("accept");/*接收客户端数据*/while(1){do {ret = recv(newfd, buf, BUFSIZ, 0);}while(ret < 0 && errno == EINTR); //如果信号导致的错误,继续执行if(ret < 0)ErrExit("recv");else if(!ret)break;elseprintf("[%s:%d]buf:%s\n", inet_ntoa(client_addr.sin_addr), //IP地址ntohs(client_addr.sin_port), buf);//端口号,buf}close(newfd);close(fd);return 0;
}
tcp.h
#ifndef _TCP_H_
#define _TCP_H_#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <errno.h>#define BACKLOG 5 //在未完成连接队列中的允许最大连接数#define ErrExit(msg) do { perror(msg); \exit(EXIT_FAILURE); } while(0)typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;#endif
- 编译
gcc -o server server.c -Wall
- 运行
./server 0 8080
‘0’ 代指本地回环地址,通常为127.0.0.1 - nc命令模拟客户端
nc 0 8080
- 发送数据,验证程序
client.c 客户端
#include "tcp.h"int main(int argc, char *argv[])
{int fd;int ret;char buf[BUFSIZ] = {"===test==="};Addr_in addr;/*检查参数*/if(argc < 3){fprintf(stderr, "%s <addr><port>\n", argv[0]);exit(EXIT_FAILURE);}/*创建套接字*/if( (fd = socket(AF_INET, SOCK_STREAM, 0) ) < 0)ErrExit("socket");/*设置通信结构体*/bzero(&addr, sizeof(addr) );addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );if (inet_aton(argv[1], &addr.sin_addr) == 0) {fprintf(stderr, "Invalid address\n");exit(EXIT_FAILURE);}/*发起连接请求*/if( connect(fd, (Addr *)&addr, sizeof(addr) ) )ErrExit("connect");/*发送数据*/while(1){do {ret = send(fd, buf, BUFSIZ, 0);}while(ret < 0 && errno == EINTR); //如果信号导致的错误,继续执行if(ret < 0)ErrExit("recv");else if(!ret)break;printf("send data:%s", buf);fflush(stdout);getchar();}close(fd);return 0;
}
- 编译
gcc -o client client.c
- 运行服务端 ./sever 0 8080
- 运行客户端./client 0 8080
- 开始通信,按一下回车发一次数据
函数封装
将上面写的代码封装成函数方便后期阅读,一共四个代码文件:tcp.c、tcp.h、server.c、client.c
tcp.c
#include "tcp.h"void Argment(int argc, char *argv[]){if(argc < 3){fprintf(stderr, "%s <addr><port>\n", argv[0]);exit(EXIT_FAILURE);}
}int SocketInit(char *argv[], bool server){int fd;Addr_in addr;func_t func = server?bind:connect;/*创建套接字*/if( (fd = socket(AF_INET, SOCK_STREAM, 0) ) < 0)ErrExit("socket");/*设置通信结构体*/bzero(&addr, sizeof(addr) );addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );if (inet_aton(argv[1], &addr.sin_addr) == 0) {fprintf(stderr, "Invalid address\n");exit(EXIT_FAILURE);}/*地址快速重用*/int b_reuse = 1;setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int) );/*发起连接请求或绑定地址*/if( func(fd, (Addr *)&addr, sizeof(addr) ) )ErrExit("connect or bind");if(server){/*监听模式*/if( listen(fd, BACKLOG) )ErrExit("listen");}return fd;
}
tcp.h
#ifndef _TCP_H_
#define _TCP_H_#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdbool.h>#define BACKLOG 5#define ErrExit(msg) do { perror(msg); \exit(EXIT_FAILURE); } while(0)typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;typedef int (* func_t)(int, const Addr *, socklen_t);void Argment(int argc, char *argv[]);
int SocketInit(char *argv[], bool server);
#endif
server.c
#include "tcp.h"int main(int argc, char *argv[])
{int fd, newfd;int ret;char buf[BUFSIZ];Addr_in client_addr;socklen_t addrlen = sizeof(Addr_in);/*检查参数*/Argment(argc, argv);/*创建服务端套接字*/fd = SocketInit(argv, true);/*接收客户端连接*/do {newfd = accept(fd, (Addr *)&client_addr, &addrlen);}while(newfd < 0 && errno == EINTR); //如果信号导致的错误,继续执行if(newfd < 0)ErrExit("accept");/*接收客户端数据*/while(1){do {ret = recv(newfd, buf, BUFSIZ, 0);}while(ret < 0 && errno == EINTR); //如果信号导致的错误,继续执行if(ret < 0)ErrExit("recv");else if(!ret)break;elseprintf("[%s:%d]buf:%s\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buf);}close(newfd);close(fd);return 0;
}
client.c
#include "tcp.h"int main(int argc, char *argv[])
{int fd;int ret;char buf[BUFSIZ] = {"===test==="};/*检查参数*/Argment(argc, argv);fd = SocketInit(argv, false);/*发送数据*/while(1){do {ret = send(fd, buf, BUFSIZ, 0);}while(ret < 0 && errno == EINTR); //如果信号导致的错误,继续执行if(ret < 0)ErrExit("recv");else if(!ret)break;printf("send data:%s", buf);fflush(stdout);getchar();}close(fd);return 0;
}
编译运行
- 服务端编译:
gcc -g -Wall server.c tcp.c -o server
- 客户端编译:
gcc -g -Wall client.c tcp.c -o client
- 运行服务端:
./server
- 运行客户端:
./client
为了方便编译,写一个Makefile文件
Makefile
all:server client
CC=gcc
CFLAGS=-g -Wallserver:tcp.c server.cclient:tcp.c client.cclean:rm server client
- 这样编译时输入
make
命令即可 - 清理生成的可执行文件输入
clean
命令即可
2.实现文件传输
sever.c
添加了接收文件名、创建文件、接收文件
#include "tcp.h"int main(int argc, char *argv[])
{int fd, newfd, file_fd;int ret;char buf[BUFSIZ] = {};Addr_in client_addr;socklen_t addrlen = sizeof(Addr_in);/*检查参数*/Argment(argc, argv);/*创建服务端套接字*/fd = SocketInit(argv, true);/*接收客户端连接*/do {newfd = accept(fd, (Addr *)&client_addr, &addrlen);}while(newfd < 0 && errno == EINTR); //如果信号导致的错误,继续执行if(newfd < 0)ErrExit("accept");/*接收文件名字*/ret = SocketDataHandle(newfd, buf, BUFSIZ, recv);/*创建文件*/if( (file_fd = open(buf, O_WRONLY|O_CREAT, 0660) ) < 0)ErrExit("file_fd");buf[0] = OK;SocketDataHandle(newfd, buf, 1, (DataHand_t)send);/*接收文件*/while(1){ret = SocketDataHandle(newfd, buf, BUFSIZ, recv);if(!ret)break;write(file_fd, buf, ret);}close(file_fd);close(newfd);close(fd);return 0;
}
client.c
添加了发送文件名、发送文件
#include "tcp.h"
#define FILENAME "picture.jpg"//要发送的文件int main(int argc, char *argv[])
{int fd, file_fd;int ret;char buf[BUFSIZ];/*检查参数*/Argment(argc, argv);fd = SocketInit(argv, false);/*打开文件*/if( (file_fd = open(FILENAME, O_RDONLY) ) < 0)ErrExit("open");/*发送文件名字*/SocketDataHandle(fd, FILENAME, strlen(FILENAME), (DataHand_t)send);SocketDataHandle(fd, buf, 1, recv);/*发送文件*/if(buf[0] == OK){while(1){do {ret = read(file_fd, buf, BUFSIZ);}while(ret < 0 && errno == EINTR);if( ret < 0)ErrExit("read");if(!ret)break;ret = SocketDataHandle(fd, buf, ret, (DataHand_t)send);if(!ret)break;}}close(file_fd);close(fd);return 0;
}
tcp.c
添加了发送接收函数SocketDataHandle()
#include "tcp.h"void Argment(int argc, char *argv[]){if(argc < 3){fprintf(stderr, "%s <addr><port>\n", argv[0]);exit(EXIT_FAILURE);}
}int SocketInit(char *argv[], bool server){int fd;Addr_in addr;func_t func = server?bind:connect;/*创建套接字*/if( (fd = socket(AF_INET, SOCK_STREAM, 0) ) < 0)ErrExit("socket");/*设置通信结构体*/bzero(&addr, sizeof(addr) );addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );if (inet_aton(argv[1], &addr.sin_addr) == 0) {fprintf(stderr, "Invalid address\n");exit(EXIT_FAILURE);}/*发起连接请求或绑定地址*/if( func(fd, (Addr *)&addr, sizeof(addr) ) )ErrExit("connect");if(server){/*地址快速重用*/int b_reuse = 1;setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int) );/*监听模式*/if( listen(fd, BACKLOG) )ErrExit("listen");}return fd;
}int SocketDataHandle(int fd, void *buf, size_t len, DataHand_t datahandle){int ret;char *str = datahandle == recv ? "recv" :"send";do {ret = datahandle(fd, buf, len, 0);} while(ret < 0 && errno == EINTR);if(ret < 0)ErrExit(str);return ret;
}
tcp.h
#ifndef _TCP_H_
#define _TCP_H_#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>#define BACKLOG 5#define ErrExit(msg) do { perror(msg); \exit(EXIT_FAILURE); } while(0)typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;typedef int (* func_t)(int, const Addr *, socklen_t);
typedef ssize_t(* DataHand_t)(int, void *, size_t, int);void Argment(int argc, char *argv[]);
int SocketInit(char *argv[], bool server);
int SocketDataHandle(int fd, void *buf, size_t len, DataHand_t datahandle);#include <sys/stat.h>
#include <fcntl.h>#define OK '1'#endif
Makeifle
添加了mv_client
命令,通过终端该文件夹下输入make mv_client
,即可将client
文件移动到/home/linux/Study/study8/Project/test/
目录下
all:server client
CC=gcc
CFLAGS=-g -Wallserver:tcp.c server.cclient:tcp.c client.cmv_client:mv client /home/linux/Study/study8/Project/test/
clean:rm server client
编译运行
- 将文件 picture.jpg 放在客户端目录:/home/linux/Study/study8/Project/test/
- 切换到服务端的目录下:
cd /home/linux/Study/study8/Project/v3/
- 编译:
make
- 移动客户端可执行文件:
make mv_client
- 运行服务端:
./server 0 8080
- 再开一个终端,切换到客户端目录下:
cd /home/linux/Study/study8/Project/test/
- 运行客户端:
./client 0 8080
- 查看服务端目录,文件picture.jpg 是否被传输
3.实现用文件名传输
server.c
#include "tcp.h"int main(int argc, char *argv[])
{int fd, newfd, file_fd;int ret;char buf[BUFSIZ] = {};Addr_in client_addr;socklen_t addrlen = sizeof(Addr_in);/*检查参数*/Argment(argc, argv);/*创建服务端套接字*/fd = SocketInit(argv, true);/*接收客户端连接*/do {newfd = accept(fd, (Addr *)&client_addr, &addrlen);}while(newfd < 0 && errno == EINTR); //如果信号导致的错误,继续执行if(newfd < 0)ErrExit("accept");/*接收文件名字*/ret = SocketDataHandle(newfd, buf, BUFSIZ, recv);/*创建文件*/if( (file_fd = open(buf, O_WRONLY|O_CREAT, 0660) ) < 0)ErrExit("file_fd");buf[0] = OK;SocketDataHandle(newfd, buf, 1, (DataHand_t)send);/*接收文件*/while(1){ret = SocketDataHandle(newfd, buf, BUFSIZ, recv);if(!ret)break;write(file_fd, buf, ret);}close(file_fd);close(newfd);close(fd);return 0;
}
client.c
修改为运行时只读入一个参数‘文件名’
IP地址和端口号改为从.info
文件中读取
#include "tcp.h"#define INFOFILE ".info"int main(int argc, char *argv[])
{int fd, file_fd;int ret;FILE *fp;char buf[BUFSIZ];char *filename = argv[1];if(argc < 2){printf("%s <filename>\n", argv[0]);exit(0);}/*通过配置文件获取IP地址和端口号*/if( (fp = fopen(INFOFILE, "r") ) == NULL)ErrExit("fopen");fgets(buf, 20, fp); //读取第一行buf[strlen(buf)-1] = '\0';argv[1] = buf;fgets(&buf[20], 20, fp);//读取第二行buf[strlen(&buf[20])-1+20] = '\0';argv[2] = &buf[20];fd = SocketInit(argv, false);/*打开文件*/if( (file_fd = open(filename, O_RDONLY) ) < 0)ErrExit("open");/*发送文件名字*/SocketDataHandle(fd, filename, strlen(filename), (DataHand_t)send);SocketDataHandle(fd, buf, 1, recv);/*发送文件*/if(buf[0] == OK){while(1){do {ret = read(file_fd, buf, BUFSIZ);}while(ret < 0 && errno == EINTR);if( ret < 0)ErrExit("read");if(!ret)break;ret = SocketDataHandle(fd, buf, ret, (DataHand_t)send);if(!ret)break;printf("ret = %d\n", ret);}}close(file_fd);close(fd);return 0;
}
tcp.c
#include "tcp.h"void Argment(int argc, char *argv[]){if(argc < 3){fprintf(stderr, "%s <addr><port>\n", argv[0]);exit(EXIT_FAILURE);}
}int SocketInit(char *argv[], bool server){int fd;Addr_in addr;func_t func = server?bind:connect;/*创建套接字*/if( (fd = socket(AF_INET, SOCK_STREAM, 0) ) < 0)ErrExit("socket");/*设置通信结构体*/bzero(&addr, sizeof(addr) );addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );if (inet_aton(argv[1], &addr.sin_addr) == 0) {fprintf(stderr, "Invalid address\n");exit(EXIT_FAILURE);}/*发起连接请求或绑定地址*/if( func(fd, (Addr *)&addr, sizeof(addr) ) )ErrExit("connect");if(server){/*地址快速重用*/int b_reuse = 1;setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int) );/*监听模式*/if( listen(fd, BACKLOG) )ErrExit("listen");}return fd;
}int SocketDataHandle(int fd, void *buf, size_t len, DataHand_t datahandle){int ret;char *str = datahandle == recv ? "recv" :"send";do {ret = datahandle(fd, buf, len, 0);} while(ret < 0 && errno == EINTR);if(ret < 0)ErrExit(str);return ret;
}
tcp.h
#ifndef _TCP_H_
#define _TCP_H_#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>#define BACKLOG 5#define ErrExit(msg) do { perror(msg); \exit(EXIT_FAILURE); } while(0)typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;typedef int (* func_t)(int, const Addr *, socklen_t);
typedef ssize_t(* DataHand_t)(int, void *, size_t, int);void Argment(int argc, char *argv[]);
int SocketInit(char *argv[], bool server);
int SocketDataHandle(int fd, void *buf, size_t len, DataHand_t datahandle);#include <sys/stat.h>
#include <fcntl.h>#define OK '1'#endif
.info
用来储存IP地址和端口号
用ls
命令查看它时,加-a
选项
127.0.0.1
8080
文件位置
server.c,client.c,tcp.c,tcp.h,在服务端文件夹
.info,爱的箴言-郑钧.mp3,在客户端文件夹
编译运行
- 切换到服务端的目录下:
cd /home/linux/Study/study8/Project/v4/
- 编译:
make
- 移动客户端可执行文件的客户端目录下:
make mv_client
- 运行服务端:
./server 0 8080
- 再开一个终端,切换到客户端目录下:
cd /home/linux/Study/study8/Project/test/
- 运行客户端:
./client 爱的箴言-郑钧.mp3
- 查看服务端目录,文件
爱的箴言-郑钧.mp3
是否被传输,试听一下
项目基本的功能已经实现,请自己尝试完成项目,完整项目代码看下一章