项目_Linux_网络编程_私人云盘
概述
项目功能总述:
该项目使用TCP进行通信,实现文件的上传和下载。云盘的文件同步有手动同步、实时同步、定时同步这三种。本项目主要实现的是手动同步的功能,重点训练在如何使用TCP进行文件传输。
选择TCP的原因:
文件的传输需要可靠,因此选择TCP传输。
项目中需要解决的问题:
- 文件如何上传和下载
- 文件大小不确定如何处理
- 文件个数不确定如何处理
- 如何获取文件事件来实现实时同步
- 如何使用定时器和守护进程来实现定时同步
传输文件时要解决的关键问题:
- 如何处理文件路径,从而使得创建新文件的路径名正确
- 如何处理TCP连包问题,封包拆包的自定义协议如何设定
编写Makefile
makefile编写整体思路:
该makefile的编写参考博文"8.Linux_Makefile" - "8、makefile编写技巧" - "8.3 分文件处理"。博文连接如下:8.Linux_Makefile-CSDN博客
文件创建:
创建一个文件夹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" - "并发" - "多进程" 博文连接如下:
13.1 Linux_网络编程_TCP/UDP-CSDN博客
基本功能的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.创建socketif((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; //IPv4addr.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.监听socketif(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);//如果是信号中断导致的错误,则重新执行acceptif(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.创建socketif((fd=socket(AF_INET,SOCK_STREAM,0))<0){//IPv4,TCP协议perror("socket");exit(-1);}//2.设置链接服务器的IP、端口号addr.sin_family = AF_INET; //IPv4addr.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);//如果是信号中断导致的错误,则重新执行readif(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_Write: 优化的write函数* @param: 参数含义与write一样* @ret: 返回值含义与write一样,如果失败会自动退出进程,该值不需要判断有效性* */
ssize_t So