17."自动云同步"项目实践
文章目录
项目简介
功能需求
- 保持云端数据和终端数据的一致
- 上传和下载
- 实时同步
- 定时同步
- 手动同步
需求分析
- 文件的上传和下载
- 文件的大小不确定
- 文件的个数不确定
- 实时同步需要获取文件事件
- 定时同步需要设置定时器
实现步骤
- 实现TCP通信
- 使用TCP实现文件的上传和下载
- 实现整个目录下的文件的同步
- 实现项目框架
- 完成项目
1.实现TCP通信
server.c 服务端
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;
else
printf("[%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
c
#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 客户端
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
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
c
#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
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;
else
printf("[%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
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
makefile
all:server client
CC=gcc
CFLAGS=-g -Wall
server:tcp.c server.c
client:tcp.c client.c
clean:
rm server client
- 这样编译时输入
make
命令即可 - 清理生成的可执行文件输入
clean
命令即可
2.实现文件传输
sever.c
添加了接收文件名、创建文件、接收文件
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
添加了发送文件名、发送文件
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()
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
c
#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/
目录下
makefile
all:server client
CC=gcc
CFLAGS=-g -Wall
server:tcp.c server.c
client:tcp.c client.c
mv_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
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
文件中读取
c
#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
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
c
#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
选项
txt
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
是否被传输,试听一下
项目基本的功能已经实现,请自己尝试完成项目,完整项目代码看下一章