基于VS2019多线程上传下载器

2022-11-15 14:14:54 浏览数 (1)

编译环境: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: %dn", 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 failedn");
		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:%sn", 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 -- %sn", pRecvFileData, pRecvFileData);
				recvedFileLen  = (recvLen - sizeof(int) * 2);
				printf("recvFileLen = %d - %dn", 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: %dn", fileName, fileSize);
			}
				break;
			default:
				break;
		}
		//接收到数据
		//当文件接收完成,关闭文件
		//printf("%d - %dn", recvedFileLen, fileSize);

		if (recvedFileLen == fileSize && recvedFileLen != 0) //文件接收完毕
		{
			//将所有数据写入文件
			printf("receive file finished: %sn", 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: %dn", 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: %dn", 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:%sn", 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"

0 人点赞