Bootstrap

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

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
      • 文件位置
      • 编译运行

项目简介

准备

Linux 操作系统 (ubuntu模拟机)
C语言
文件IO
网络编程(tcp/ip 协议)

功能需求

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

需求分析

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

实现步骤

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

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;
		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

#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 客户端

#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

#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;
		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

#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

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

添加了接收文件名、创建文件、接收文件

#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 -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

#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,在客户端文件夹

编译运行

  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 是否被传输,试听一下

在这里插入图片描述

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

;