项目的原因,我需要搭建一个云服务器,并且利用C/C++上传多个文件至云服务器,网上资料已经非常之多了,站在前辈们的肩膀上确实是事半功倍,我在完成过程中记录了一些很好的blog 和 我自己改进后的程序供大家参考。
首先,为了能够在你的服务器上运行web项目和访问服务器中的文件,我们需要搭建jdk 和 Apache-tomcat 这两个软件。最好是能找到比较适合的版本,最新版遇到问题后缺乏相应的解决案例。我选用的是apache-tomcat-8.5.61 和 jdk-8u171-linux-x64
https://www.cnblogs.com/Bindivas/p/14015843.html
然后,我们需要在tomcat上访问服务器中的内容
能够访问文件以后,终于进入正题了!也就是环境交互,由于项目要求,可能会有多次上传的响应,所以这里我写了一个死循环,服务器端会一直等待我上传文件,不会停止,而client端则是有需要再发送指令上传。
server.cpp:
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#define SERVER_PORT 6666
#define BUFFER_SIZE 512
#define SERVER_PATH "/usr/java/tomcat/apache-tomcat-8.5.61/webapps/res/server_text/"
//该目录即为上传后的文件需要保存到的路径
struct sockaddr_in clientAddr;
int addr_len, serverSocket;
int init_Server(int port) {
int serverSocket;
struct sockaddr_in server_addr;
//socket初始化
if((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket failed");
return -1;
}
//服务端地址类型,地址及端口初始化
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(serverSocket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("connect");
return -1;
}
//设置服务器上的socket为监听状态
if(listen(serverSocket, 10) < 0) {
perror("listen failed");
return -1;
}
return serverSocket;
}
int get_File_Num(char* pathname) {
struct dirent* ptr;
DIR* path = NULL;
int cnt = 0;
path = opendir(pathname);
while((ptr=readdir(path)) != NULL) {
if(strcmp(ptr->d_name,".")==0||strcmp(ptr->d_name,"..")==0)
continue;
if(ptr->d_type==DT_REG)
cnt++;
}
return cnt;
}
void receive_File(int client) {
char recv_buf[BUFFER_SIZE] = {0};
char filename[200] = {0}, pathname[200] = {0};
int cnt,n;
int totalBlock, lenBlock;
recv(client, recv_buf, sizeof(recv_buf), 0);//先接收客户端要传多少个文件
int filenum = atoi(recv_buf);
printf("客户端要发送%d个文件\n",filenum);
for(int i = 1; i <= filenum; i++) {//遍历指定目录下文件的个数,以确定新接收文件的名字
cnt = get_File_Num((char*)SERVER_PATH);//得到服务器目录下的文件个数,方便编号命名
FILE* fp;
memset(filename, 0, sizeof(filename));
sprintf(filename, "%d", cnt+1);
strcpy(pathname, SERVER_PATH);
strcat(pathname,filename);
printf("%s\n",pathname);
fp = fopen(pathname, "wb");
if(fp != NULL)
printf("成功创建文件%s\n",pathname);
recv(client, recv_buf, sizeof(recv_buf), 0);
totalBlock = atoi(recv_buf);
printf("第%d个文件有%d个块\n",i,totalBlock);
for(int j = 1; j <= totalBlock; j++) {
char temp[100] = {0};
recv(client, temp, sizeof(temp), 0);
lenBlock = atoi(temp);
printf("第%d个块长度为%d\n",j,lenBlock);
if((n = recv(client, recv_buf, lenBlock, 0)) > 0)
printf("..........当前块接收%d字符..........\n",n);
else
printf("当前块接收失败!\n");
fwrite(recv_buf, 1, lenBlock, fp);
}
fclose(fp);
printf("文件保存完毕\n");
}
close(client);
}
void work()
{
//等待用户连接
int client;
while(1)
{
client = accept(serverSocket, (struct sockaddr*)&clientAddr, (socklen_t*)&addr_len);
if(client < 0) {
printf("服务端accept失败,程序退出。\n");
return ;
}
else
break;
}
printf("客户已连接:\n");
printf("IP is %s\n", inet_ntoa(clientAddr.sin_addr));//inet_ntoa ip地址转换函数,将网络字节序IP转换为点分十进制IP
printf("Port is %d\n", htons(clientAddr.sin_port));
printf("waiting message...\n");
//将客户端发送过来的消息写入文件
receive_File(client);
return ;
}
int main() {
addr_len = sizeof(clientAddr);
serverSocket = init_Server(SERVER_PORT);
if(serverSocket < 0) {
printf("服务器初始化失败!\n");
return 0;
}
printf("服务器初始化成功,监听端口:%d\n", SERVER_PORT);
while(1)
{
work();
sleep(3);
}
close(serverSocket);
return 0;
}
client.cpp
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define SERVER_PORT 6666
#define BUFFER_SIZE 512
#define IP_ADDR "127.0.0.1"//该目录为服务器的公网ip
#define CLIENT_PATH "/home/zlgmcu/use_socket_tran/client_text/"
//该目录为本地上传目录
char send_buf[BUFFER_SIZE];
int connect_to_Server(char* serverIP, int port) {
struct sockaddr_in serverAddr;
int clientSocket;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
serverAddr.sin_addr.s_addr = inet_addr(serverIP);
if((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("sock生成出错!\n");
return -1;
}
if(connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
printf("connect失败!\n");
return -1;
}
return clientSocket;
}
int get_File_Num(char* pathname) {
struct dirent* ptr;
DIR* path = NULL;
int cnt = 0;
path = opendir(pathname);
while((ptr=readdir(path)) != NULL) {
if(strcmp(ptr->d_name,".")==0||strcmp(ptr->d_name,"..")==0)
continue;
if(ptr->d_type==DT_REG)
cnt++;
}
return cnt;
}
void pack_send_File(int clientSocket, FILE* fp) {
memset(send_buf, 0 , BUFFER_SIZE);
fseek(fp, 0, 2);//将指针放到文件末尾
int len_file = ftell(fp); //统计文本的数据长度
rewind(fp); //让指针指向文件开始位置
int totalBlock = len_file % BUFFER_SIZE == 0 ? len_file / BUFFER_SIZE : ((len_file / BUFFER_SIZE) + 1); //除最后一快的总块数
sprintf(send_buf, "%d", totalBlock);
send(clientSocket,(char*)send_buf,sizeof(send_buf),0);
for(int i = 1; i <= totalBlock; i++) {
char temp[100] = {0};
int len_block = fread(send_buf, 1, BUFFER_SIZE, fp);
send_buf[len_block]='\0';
sprintf(temp, "%d", len_block);
send(clientSocket,(char*)temp,sizeof(temp),0);
usleep(50000);
send(clientSocket,send_buf,len_block,0);
}
fclose(fp);
return ;
}
void submit_Files(int clientSocket) {//提交多个文件,想法是提交某个文件夹下的所有文件
FILE* fp;
char filename[100] = {0}, pre_filename[100] = {0};
struct dirent* ptr;
DIR* path = NULL;
memset(send_buf, 0 , BUFFER_SIZE);
path = opendir((char*)CLIENT_PATH);
while((ptr=readdir(path)) != NULL) {
if(strcmp(ptr->d_name,".") == 0 || strcmp(ptr->d_name,"..") == 0 )
continue;
if(ptr->d_type==DT_REG) {
//printf("%s\n",ptr->d_name);
strcpy(pre_filename,CLIENT_PATH);
strcat(pre_filename,ptr->d_name);
fp = fopen(pre_filename, "rb");
pack_send_File(clientSocket, fp);
printf("文件%s上传成功!\n",ptr->d_name);
}
}
return ;
}
void sig_up(int sig)
{
int clientSocket;
switch (sig)
{
case 37:
memset(send_buf, 0 , BUFFER_SIZE);
if((clientSocket = connect_to_Server((char*)IP_ADDR,SERVER_PORT)) == -1)
{
printf("connect failed!\n");
return ;
}
printf("connect completed!...\n");
int filenum = get_File_Num((char*)CLIENT_PATH);
sprintf(send_buf, "%d\n", filenum);
send(clientSocket, send_buf, sizeof(send_buf), 0);
submit_Files(clientSocket);
sleep(3);
printf("当前已休眠3s,可进行下一步操作!\n");
break;
}
return ;
}
int main() {
struct sigaction act;
act.sa_handler = sig_up;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NODEFER;
sigaction(37, &act, NULL);
while (1)
usleep(100000);
return 0;
}
需要注意的的是:由于项目需求,在client.cpp中,我采用了发送信号的方式进行运行,如果不需要这个功能的话可以将其修改删除。
第二点就是每次用socket发送完数据包后,要等待一段时间(我测试过10ms和100ms,区别非常大,10ms基本上数据包全部丢失,而100ms则能够将数据包完全的保存下来,保证文件的完整性不被丢失。
第三点就是用socket发送数据包时,BUFFER_SIZE(每个包的大小)不能定太大也不能定太小,如果到4096,数据包也是很难保存接收,但是传输速度快,而1024就能够很好兼顾接受速度和稳定度,但是测试了几十次之后发现1024也会有丢包的现象,因此我还是选择了更为稳妥的512,在目前测试的近100次中,未出现过一次文件有损失的现象,只是传输速度就会慢一些,但是更加稳妥。
效果演示:
首先需要在服务器端运行server文件,监听成功之后再运行client 文件
能够看到的是,上传非常成功。
然后就会面临一个更深层次的问题了:怎么样才能够让服务器一直运行server文件呢?
这里我用的是screen,也将解决方法推荐给大家(我的代码不需要更改)。
有问题也可也向我发送私信或留言!大家一起进步~