概述
项目功能总述:
该项目使用TCP进行通信,实现文件的上传和下载。云盘的文件同步有手动同步、实时同步、定时同步这三种。本项目主要实现的是手动同步的功能,重点训练在如何使用TCP进行文件传输。
选择TCP的原因:
文件的传输需要可靠,因此选择TCP传输。
项目中需要解决的问题:
- 文件如何上传和下载
- 文件大小不确定如何处理
- 文件个数不确定如何处理
- 如何获取文件事件来实现实时同步
- 如何使用定时器和守护进程来实现定时同步
传输文件时要解决的关键问题:
- 如何处理文件路径,从而使得创建新文件的路径名正确
- 如何处理TCP连包问题,封包拆包的自定义协议如何设定
编写Makefile
makefile编写整体思路:
该makefile的编写参考博文"8.Linux_Makefile" - "8、makefile编写技巧" - "8.3 分文件处理"。博文连接如下:8.Linux_Makefile-优快云博客
文件创建:
创建一个文件夹linux_test,在该文件夹下创建 "src、inc、obj目录"、"makefile文件"、"test.c文件"
在src文件中也创建一个 "makefile文件"
主目录下的makefile代码如下:
SRCDIR = $(shell pwd)/src
INCDIR = $(shell pwd)/inc
OBJDIR = $(shell pwd)/obj
SRC = $(wildcard $(SRCDIR)/*.c)
OBJ = $(patsubst %.c,$(OBJDIR)/%.o,$(notdir $(SRC)))
MAIN_SRCDIR = $(shell pwd)
SERVER_SRC = $(wildcard $(MAIN_SRCDIR)/server.c)
CLIENT_SRC = $(wildcard $(MAIN_SRCDIR)/client.c)
SERVER_OBJ = $(patsubst %.c,$(OBJDIR)/%.o,$(notdir $(SERVER_SRC)))
CLIENT_OBJ = $(patsubst %.c,$(OBJDIR)/%.o,$(notdir $(CLIENT_SRC)))
CC = gcc
CFLAGS = -c -g -Wall -I $(INCDIR)
export OBJ OBJDIR CC CFLAGS
all:debug $(SRCDIR) $(SERVER_OBJ) $(CLIENT_OBJ) server client
debug:
# @echo "OBJDIR = $(OBJDIR)"
# @echo "TEST_OBJ = $(TEST_OBJ)"
#调用其他makefile进行编译、汇编
$(SRCDIR):echo
@make -C $@
echo:
@echo "make start"
#编译、汇编server.c client.c 文件
$(SERVER_OBJ):server.c
@$(CC) $(CFLAGS) $^ -o $@
$(CLIENT_OBJ):client.c
@$(CC) $(CFLAGS) $^ -o $@
#链接全部.o生成可执行文件server client
server:$(OBJ) $(SERVER_OBJ)
@$(CC) $^ -o server
client:$(OBJ) $(CLIENT_OBJ)
@$(CC) $^ -o client
.PHONY:clean
clean:
rm ./obj/*
src目录下的makefile代码如下:
all:debug $(OBJ)
debug:
# @echo "OBJ = $(OBJ)"
# @echo "OBJDIR = $(OBJDIR)"
$(OBJDIR)/%.o:%.c
@$(CC) $(CFLAGS) $^ -o $@
TCP通信功能实现
下面内容是以多进程并发形式编写服务端与客户端的代码,主要目的是实现通信的框架,使得双方可以进行通信。
概述:
网络相关的代码封装在my_net.c、my_net.h中。
基本功能就是实现服务器与客户端可以相互通信。这里先使用单线程的方式进行框架的搭建。
具体实现逻辑见博文"13.1 Linux_网络编程_TCP/UDP" - "TCP" - "并发" - "多进程" 博文连接如下:
基本功能的my_net.h函数总览:
#ifndef __MY_NET_H
#define __MY_NET_H
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#define BACKLOG 5 //最大接入客户端数量
//TCP Server
int Socket_TcpServerInit(int argc,char** argv);
int Socket_TcpServerAccept(int fd);
//TCP Client
int Socket_TcpClientInit(int argc,char** argv);
//通用
ssize_t Socket_Read(int fd, void *buf, size_t count);
ssize_t Socket_Write(int fd, void *buf, size_t count);
//多进程并发
void Set_SIGCHLD(void);
void SIGCHLD_Handler(int sig);
#endif
1、服务端
1、初始化socket服务器
该函数实现了创建IPv4的TCPsocket、地址快速复用、绑定端口号、监听客户端的功能。
/*
* socket_init: 初始化socket服务器
* @param argc: main中的argc
* @param argv: main中的argv
* @ret 创建的socket文件描述符,如果失败会自动退出进程,该值不需要判断有效性
* */
int Socket_TcpServerInit(int argc,char** argv){
int fd;
struct sockaddr_in addr;
//参数有效性判断
if(argc != 3){
printf("param err\n");
printf("%s<ip><port>\n",argv[0]);
exit(-1);
}
printf("server ip = %s\n",argv[1]);
printf("server port = %s\n",argv[2]);
//1.创建socket
if((fd=socket(AF_INET,SOCK_STREAM,0))<0){//IPv4,TCP协议
perror("socket");
exit(-1);
}
//地址快速重用
int flag=1,len=sizeof(int);
if(setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&flag,len) == -1){
perror("setsockopt");
exit(-1);
}
//2.绑定IP、端口号
addr.sin_family = AF_INET; //IPv4
addr.sin_port = htons(atoi(argv[2])); //端口号,要转化为大端子节序
addr.sin_addr.s_addr = inet_addr(argv[1]); //IP地址:0表示在本网络上的本主机,即:自己
if(bind(fd,(struct sockaddr*)&addr,sizeof(struct sockaddr_in)) == -1){
perror("bind");
exit(-1);
}
//3.监听socket
if(listen(fd,BACKLOG) == -1){ //允许最多接入5个客户端
perror("listen");
exit(-1);
}
return fd;
}
2、优化的accept函数
这个函数主要功能还是accept,但是封装了一些错误处理和接入客户端信息打印的操作。
/*
* Socket_TcpServerAccept: 优化的accept函数
* @param fd: 服务器的socket文件描述符
* @ret: 接入的客户端的socket文件描述符,如果失败会自动退出进程,该值不需要判断有效性
* */
int Socket_TcpServerAccept(int fd){
int newfd;
struct sockaddr_in newAddr;
socklen_t newAddrlen;
do{
//printf("Debug:again accept\n");
newfd = accept(fd,(struct sockaddr*)&newAddr,&newAddrlen);
}while(newfd < 0 && errno == EINTR);//如果是信号中断导致的错误,则重新执行accept
if(newfd < 0){
perror("accept");
exit(-1);
}else{
printf("[%s,%d]connect,fd=%d\n",inet_ntoa(newAddr.sin_addr),ntohs(newAddr.sin_port),newfd);
}
return newfd;
}
2、客户端
1、初始化socket客户端
该函数实现了创建IPv4的TCPsocket、设置要连接服务器的信息、连接服务器的操作
/*
* Socket_TcpClientInit: 初始化socket客户端
* @param argc: main中的argc
* @param argv: main中的argv
* @ret 创建的socket文件描述符,如果失败会自动退出进程,该值不需要判断有效性
* */
int Socket_TcpClientInit(int argc,char** argv){
int fd;
struct sockaddr_in addr;
//参数有效性判断
if(argc != 3){
printf("param err\n");
printf("%s<ip><port>\n",argv[0]);
exit(-1);
}
printf("connect server ip = %s\n",argv[1]);
printf("connect server port = %s\n",argv[2]);
//1.创建socket
if((fd=socket(AF_INET,SOCK_STREAM,0))<0){//IPv4,TCP协议
perror("socket");
exit(-1);
}
//2.设置链接服务器的IP、端口号
addr.sin_family = AF_INET; //IPv4
addr.sin_port = htons(atoi(argv[2])); //端口号,要转化为大端子节序
addr.sin_addr.s_addr = inet_addr(argv[1]); //IP地址:0表示在本网络上的本主机,即:自己
//3.链接服务器
if(connect(fd,(struct sockaddr*)&addr,sizeof(struct sockaddr_in)) == -1){
perror("connect");
exit(-1);
}
return fd;
}
3、通用读/写
1、优化的read函数
该函数主要功能还是read,但封装了一些错误处理、读前清空缓冲区、自动回收退出的客户端资源的操作。
/*
* Socket_Read: 优化的read函数
* @param: 参数含义与read一样
* @ret: 返回值含义与read一样,如果失败会自动退出进程,该值不需要判断有效性
* */
ssize_t Socket_Read(int fd, void *buf, size_t count){
ssize_t read_num;
//读取数据
do{
//printf("Debug:again read\n");
//清空缓冲区
memset(buf,0,count);
read_num = read(fd,buf,count);
}while(read_num < 0 && errno == EINTR);//如果是信号中断导致的错误,则重新执行read
if(read_num < 0){
perror("read");
exit(-1);
}else if(read_num == 0){
printf("Debug:read_num = 0,close fd=%d\n",fd);
close(fd);
}
return read_num;
}
2、 优化的write函数
该函数主要功能还是write,但封装了一些错误处理、自动回收退出的客户端资源的操作。
/*
* Socket_W