【网络编程开发】17.“自动云同步“项目实践

17."自动云同步"项目实践

文章目录

项目简介

功能需求

  1. 保持云端数据和终端数据的一致
  2. 上传和下载
  3. 实时同步
  4. 定时同步
  5. 手动同步

需求分析

  1. 文件的上传和下载
  2. 文件的大小不确定
  3. 文件的个数不确定
  4. 实时同步需要获取文件事件
  5. 定时同步需要设置定时器

实现步骤

  1. 实现TCP通信
  2. 使用TCP实现文件的上传和下载
  3. 实现整个目录下的文件的同步
  4. 实现项目框架
  5. 完成项目

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
  1. 编译 gcc -o server server.c -Wall
  2. 运行 ./server 0 8080 '0' 代指本地回环地址,通常为127.0.0.1
  3. nc命令模拟客户端 nc 0 8080
  4. 发送数据,验证程序

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;
}
  1. 编译 gcc -o client client.c
  2. 运行服务端 ./sever 0 8080
  3. 运行客户端./client 0 8080
  4. 开始通信,按一下回车发一次数据

函数封装

将上面写的代码封装成函数方便后期阅读,一共四个代码文件: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;
}

编译运行

  1. 服务端编译:gcc -g -Wall server.c tcp.c -o server
  2. 客户端编译:gcc -g -Wall client.c tcp.c -o client
  3. 运行服务端:./server
  4. 运行客户端:./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

编译运行

  1. 将文件 picture.jpg 放在客户端目录:/home/linux/Study/study8/Project/test/
  2. 切换到服务端的目录下:cd /home/linux/Study/study8/Project/v3/
  3. 编译:make
  4. 移动客户端可执行文件:make mv_client
  5. 运行服务端:./server 0 8080
  6. 再开一个终端,切换到客户端目录下:cd /home/linux/Study/study8/Project/test/
  7. 运行客户端:./client 0 8080
  8. 查看服务端目录,文件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,在客户端文件夹

编译运行

  1. 切换到服务端的目录下:cd /home/linux/Study/study8/Project/v4/
  2. 编译:make
  3. 移动客户端可执行文件的客户端目录下:make mv_client
  4. 运行服务端:./server 0 8080
  5. 再开一个终端,切换到客户端目录下:cd /home/linux/Study/study8/Project/test/
  6. 运行客户端:./client 爱的箴言-郑钧.mp3
  7. 查看服务端目录,文件爱的箴言-郑钧.mp3 是否被传输,试听一下

项目基本的功能已经实现,请自己尝试完成项目,完整项目代码看下一章

相关推荐
ladymorgana3 分钟前
【运维笔记】windows 11 中提示:无法成功完成操作,因为文件包含病毒或潜在的垃圾软件。
运维·windows·笔记
Rain_Rong14 分钟前
linux检测硬盘
linux·运维·服务器
过过过呀Glik18 分钟前
在 Ubuntu 上安装 Muduo 网络库的详细指南
linux·c++·ubuntu·boost·muduo
oneouto22 分钟前
selenium学习笔记(一)
笔记·学习·selenium
m0_7482382730 分钟前
WebClient HTTP 请求问题处理模板(泛型响应、忽略 SSL 证书等)
网络协议·http·ssl
真真-真真1 小时前
WebXR
linux·运维·服务器
stm 学习ing1 小时前
HDLBits训练5
c语言·fpga开发·fpga·eda·hdlbits·pld·hdl语言
୧⍢⃝୨ LonelyCoder1 小时前
FreePBX修改IP地址和端口以及添加SSL证书开启HTTPS访问
tcp/ip·https·ssl
轩辰~2 小时前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
燕雀安知鸿鹄之志哉.2 小时前
攻防世界 web ics-06
网络·经验分享·安全·web安全·网络安全