前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于VS2019多线程上传下载器

基于VS2019多线程上传下载器

作者头像
Gabriel
发布2022-11-15 14:14:54
3530
发布2022-11-15 14:14:54
举报
文章被收录于专栏:C/C++C/C++

编译环境:Win10 VS2019

Server端:

server.cpp

代码语言:javascript
复制
// server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。

#include <stdio.h>
#include "stdafx.h"
#include "network.h" //UDP 网络编程

int _tmain(int argc, char* argv[])
{
	int errCode = 0;
	errCode = InitializeSocket(); //初始化套接字库

	if (errCode == -1) //初始化套接字库失败,结束
	{
		return -1;
	}

	int s = BindSocketPort(8090); //绑定到本地地址和8090端口上,成功返回套接字,失败返回-1

	if (s == -1)
	{
		return -1;
	}

	StartServer(s);

	return 0;
}

network.h

代码语言:javascript
复制
#pragma once

#pragma pack(1)

struct FileData
{
	int id; //数据类型标志位(数据或者文件名及大小)
	int index; //索引号
	char fileData[1024]; //对应索引的数据
};

#pragma pack()

//初始化套接字库
int InitializeSocket();

//bind套接字到某个端口之上
int BindSocketPort(short port);

//开始接收和发送数据
void StartServer(int s);

//线程函数
unsigned long RecvProc(void* lpParameter);

network.cpp

代码语言:javascript
复制
#include "network.h"
#include <stdio.h>
#include "targetver.h"
#include <WinSock2.h> 
#pragma comment(lib, "ws2_32.lib") //导入网络库

typedef unsigned long DWORD;

//初始化套接字库
int InitializeSocket()
{
	WORD wVersionRequested;
	WSADATA wsadata;
	int err;

	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsadata);
	if (err != 0)
	{
		printf("WSAStartup failed with error: %d\n", err);
		return -1;
	}

	return 1;
}

//bind套接字到某个端口之上
int BindSocketPort(short port)
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0); //创建一个UDP套接字
	
	sockaddr_in addr; //代表IP地址和端口
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port); //转化字节序
	addr.sin_addr.S_un.S_addr = 0; //愿意在本机的任意IP上接收数据

	//绑定到本机的IP和端口上
	int ret = bind(sockfd, (sockaddr*)&addr, sizeof(sockaddr));

	if (ret == -1)
	{
		printf("bind failed\n");
		return -1;
	}

	return sockfd;
}

//开始接收和发送数据
void StartServer(int s)
{
	sockaddr_in clientaddr;
	int len = sizeof(sockaddr);
	
	char recvBuf[1024] = { 0 };
	recvfrom(s, recvBuf, 1024, 0, (sockaddr*)&clientaddr, &len); //建立与客户端的连接
	printf("client say:%s\n", recvBuf);

	//创建一个线程之后,创建完成后线程函数开始执行,且与主线程同时执行
	CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&RecvProc, (void*)&s, 0, 0);

	while (1)
	{
		char buf[1024] = { 0 };
		gets_s(buf, 1024); //接收用的数据
		sendto(s, buf, strlen(buf), 0, (sockaddr*)&clientaddr, sizeof(sockaddr));
	}
}

//线程函数
unsigned long RecvProc(void *lpParameter)
{
	int sockfd = *(int*)lpParameter; //套接字通过参数传进来
	printf("waiting for receive data...\n");
	while (1)
	{
		char recvBuf[1500] = { 0 };
		int recvLen = recvfrom(sockfd, recvBuf, 1500, 0, 0, 0); //接收数据,阻塞的函数,如果对方不发数据,该函数将一直等待

		//区分消息类型是数据还是文件名及文件大小
		int id = *(int*)recvBuf; //得到id号

		static unsigned char* pRecvFileData; //文件数据指针
		//接收到的数据
		char fileName[256] = { 0 }; //文件名				
		static int fileSize = 0; //文件大小
		static int recvedFileLen = 0; //已经接收到的文件大小

		static FILE* fp = 0;

		switch (id)
		{
			case 1: //消息类型是数据
			{
				//消息到来
				FileData* fd = (FileData*)recvBuf; //消息
				//printf("data: %s %d", recvLen, fd->fileData);
				fd->id;
				fd->fileData;
				memcpy(pRecvFileData + fd->index * 1024, fd->fileData, recvLen - sizeof(int) * 2); //将到来消息上的数据放到指定位置
				//printf("%0x -- %s\n", pRecvFileData, pRecvFileData);
				recvedFileLen += (recvLen - sizeof(int) * 2);
				printf("recvFileLen = %d - %d\n", recvedFileLen, recvLen);
			}
				break;
			case 2: //消息类型为文件名及文件大小
			{
				memcpy(&fileSize, recvBuf + 4, 4); //将收到的数据的前4字节拷贝到filesize得到文件大小
				strcpy_s(fileName, 256, recvBuf + 8); //获得文件名称

				pRecvFileData = new unsigned char[fileSize]; //用来接收数据
				memset(pRecvFileData, 0, fileSize); //清空脏数据

				fopen_s(&fp, fileName, "wb"); //打开文件,只是文件名,没有路径

				printf("fileName: %s, fileSize: %d\n", fileName, fileSize);
			}
				break;
			default:
				break;
		}
		//接收到数据
		//当文件接收完成,关闭文件
		//printf("%d - %d\n", recvedFileLen, fileSize);

		if (recvedFileLen == fileSize && recvedFileLen != 0) //文件接收完毕
		{
			//将所有数据写入文件
			printf("receive file finished: %s\n", pRecvFileData);
			fwrite(pRecvFileData, recvedFileLen, 1, fp);
			fclose(fp);
			recvedFileLen = 0;
		}
	}
	return 0;
}

Client端:

client.cpp

代码语言:javascript
复制
// client.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "stdafx.h"
#include "network.h"

int _tmain(int argc, _TCHAR *argv[])
{
	InitializeSocket();
	StartTalking("127.0.0.1", 8090);

	return 0;
}

network.h

代码语言:javascript
复制
//头文件存放函数声明,类的声明

//防止头文件被重复包含
#pragma once

//初始化套接字库
int InitializeSocket();

//创建套接字,开始发送及接收数据
int StartTalking(char* ip, short port);

//由文件路径得到文件名
char* getFileName(char* filePath);

//获取文件长度
int GetFileLength(char* filename);

//线程函数
unsigned long RecvProc(void* lpParameter);

network.cpp

代码语言:javascript
复制
#include "network.h"
#include <stdio.h>
#include "targetver.h"
#include <WinSock2.h> 
#pragma comment(lib, "ws2_32.lib") //导入网络库

//初始化套接字库
int InitializeSocket()
{
	WORD wVersionRequested;
	WSADATA wsadata;
	int err;

	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsadata);
	if (err != 0)
	{
		printf("WSAStartup failed with error: %d\n", err);
		return 1;
	}

	return 0;
}

struct FileData
{
	int id; //数据标志位(数据或者文件名及大小)
	int index; //索引号
	char fileData[1024]; //对应索引的数据
};

//创建套接字
int StartTalking(char *ip, short port)
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0); //创建一个UDP套接字

	sockaddr_in addr; //代表IP地址和端口
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port); //转化字节序

	addr.sin_addr.S_un.S_addr = inet_addr(ip); //可以在本机的任意IP上接收数据

	if (sockfd != -1)
	{
		sendto(sockfd, "say hello", strlen("say hello"), 0, (sockaddr*)&addr, sizeof(addr)); //和服务器建立连接
	}
	//创建一个线程,创建完之后线程函数开始执行,且与主线程同时执行
	CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&RecvProc, (void*)&sockfd, 0, 0);

	//发送数据
	while (1)
	{
		char buf[1024] = { 0 };
		printf("please input filename to send:");
		gets_s(buf, 1024); //获得用户的输入,发送到对方,buf放的不是文件(路径)

		//文件大小获取
		int filesize = GetFileLength(buf); //获取文件大小,放到filesize中
		
		char sendBuf[1024] = { 0 }; //用于发送到服务器缓冲区
		int fileSizeID = 2; //该消息的标志

		char* fileName = getFileName(buf); //获得文件名

		memcpy(sendBuf, &fileSizeID, 4);
		memcpy(sendBuf + 4, &filesize, 4); //内存拷贝,将文件大小拷贝到缓冲区的前4字节
		memcpy(sendBuf + 8, fileName, strlen(fileName)); //拷贝文件名称到内存缓冲区

		//将文件大小及名称发送到服务器
		sendto(sockfd, sendBuf, sizeof(int) * 2 + strlen(fileName), 0, (sockaddr*)&addr, sizeof(addr));

		printf("filename is sended,press any key to continue.");
		getchar();

		//获取文件数据
		FILE* fp = 0;
		fopen_s(&fp, buf, "rb"); //以读取形式打开文件(r读文本文件,rb可读二进制文件)
		
		//循环发送数据到服务器,直到文件末尾
		int index = 0;
		while(!feof(fp)) //如果没有到文件的结尾,一直循环
		{  
			FileData fd;
			memset(&fd, 0, sizeof(fd)); //清空变量fd(缓冲区清空)
			fd.id = 1; //数据类型的标志
			fd.index = index++;

			int readSize = fread_s(fd.fileData, 1024, 1, 1024, fp); //从文件中读取1024字节的数据,放入fileData中
			printf("reading file: %d\n", readSize);

			//发送文件中的数据
			sendto(sockfd, (char *)&fd, readSize + sizeof(int) * 2, 0, (sockaddr*)&addr, sizeof(addr)); //将fileData发送到服务器
			Sleep(10); //休眠1ms
		}
	}

	return sockfd;
}

//由文件路径得到文件名
char* getFileName(char* filePath)
{
	int len = strlen(filePath);
	for (int i = len; i >= 0; i--)
	{
		if (filePath[i] == '/' || filePath[i] == '\\')
		{
			return &filePath[i + 1];
		}
	}
	return 0;
}

//获取文件大小,文件必须存在
int GetFileLength(char* fileName)
{
	FILE *fp;
	fopen_s(&fp, fileName, "r");
	if (fp == 0)
	{
		return -1;
	}
	fseek(fp, 0, SEEK_END);
	int size = ftell(fp);

	return size;
}

//线程函数
unsigned long RecvProc(void* lpParameter)
{
	int sock = *(int*)lpParameter;
	printf("waiting to receive data...\n");
	while (1)
	{
		char recvBuf[1024] = { 0 };
		int ret = recvfrom(sock, recvBuf, 1024, 0, 0, 0);
		printf("server say:%s\n", recvBuf);
	}

	return 0;
}

共有文件

targetver.h

代码语言:javascript
复制
#pragma once

#include <SDKDDKVer.h> //将定义可用的最高版本的Windows平台

stdafx.h

代码语言:javascript
复制
#pragma once

#include "targetver.h"
#include <stdio.h>
#include <tchar.h>

stdafx.cpp

代码语言:javascript
复制
#pragma once

#include "stdafx.h"
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Server端:
  • Client端:
  • 共有文件
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档