从零开始搭建云服务器,利用C/C++与socket在linux环境下完成与服务器的交互

项目的原因,我需要搭建一个云服务器,并且利用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上访问服务器中的内容

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,也将解决方法推荐给大家(我的代码不需要更改)。

持续运行文件

有问题也可也向我发送私信或留言!大家一起进步~

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页