socket编程——VS2019遇到"const char *" 类型的实参与 "LPCTSTR" 类型的形参不兼容之解决方法

socket编程

  • 用C++实现客户端和服务端传输文件
    • 相关知识点
      • 套接字简介
      • Client/Server模式
      • 流式套接口的工作流程
      • 客户/服务器模式需用到的接口
    • 添加ws2_32.lib库
    • 客户端/服务端源码
      • Windows里面的两种字符编码方式
      • 我的解决办法
    • 传输文件

用C++实现客户端和服务端传输文件

相关知识点

套接字简介

socket编程——VS2019遇到
socket编程——VS2019遇到
对于套接字的简单理解
可认为一个IP与一端口(port)联合在一起形成一个套接字,它是网络上的一个传输接口。在网络的另外一端可有一个对应的套接字与通信。

Client/Server模式

TCP/IP网络应用中,最常用的通信模式是客户/服务器模式( Client/Server model),即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务.
socket编程——VS2019遇到
客户端与服务器的连接方式主要有两种:
1.流式套接口(TCP)
流式套接口是可靠的双向通讯的数据流。传送的包会按发送时的顺序到达int s=socket(PF_ INET, SOCK_ STREAM,IPPROTO _
TCP)
2.数据报套接口(UDP)
使用这种方式,传送的包不一定会按发送时的顺序到达int s=socket(PF INET, SOCK DGRAM, IPPROTO UDP

流式套接口的工作流程

socket编程——VS2019遇到
从上面所描述过程可知:
1.客户与服务器进程的作用是非对称的,它们各自完成的功能不同,因此编码也不同。
2.服务进程一般是先于客户请求而启动的,启动后即在相应的 Socket监听来自客户端的请求。只要系统运行,该服务进程一直存在,直到正常或强迫终止( daemon)。

客户/服务器模式需用到的接口

服务器方面初始时需要执行的操作:

int socket() //建立一个 Socket
int bind()  //与某个端口绑定
int listen()   //开始监听端口
int accept()   //等待/接受客户端的连接请求

客户端需要执行的操作:

int socket()   //建立一个 Socket
int connect()  //连接到服务器

添加ws2_32.lib库

环境:Windows10
工具:visual studio 2019
包含头文件:#include
附加:(显示引用dll)#pragma comment(lib,“ws2_32.lib”)
过程:[右键]项目->属性->链接器->输入->附加依赖项->编辑->添加ws2_32.lib,取消勾选"从父级或项目默认设置继承"

客户端/服务端源码

服务端cpp文件

/*
	Socket服务器端代码
	服务器监听端口8888
	建立连接后,接收Client传输的文件路径
	若文件路径存在,则发送该文件给Client,发送完毕则关闭连接。
*/

#include 
#include 
#include 
#include    
#include    
#include 

#pragma comment(lib,"ws2_32.lib")

#define BUFFER_SIZE 2048
#define FILE_NAME_MAX_SIZE 512 

int main(int argc, char* argv[])
{
	/*
	   初始化WSA,使得程序可以调用windows socket
	*/
	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA wsaData;
	if (WSAStartup(sockVersion, &wsaData) != 0)
	{
		return 0;
	}

	/*
		创建监听用套接字,server_socket
		类型是TCP
		并检测是否创建成功
	*/
	SOCKET server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (server_socket == INVALID_SOCKET) {
		//如果创建的socket无效,则结束程序
		perror("socket error !\n");
		return 0;
	}

	/*
	   创建地址,server_addr,并设置端口和IP
	*/
	sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	//端口号 8888
	server_addr.sin_port = htons(8888);
	//INADDR_ANY表示本机任意IP地址
	server_addr.sin_addr.S_un.S_addr = INADDR_ANY;


	//将socket与地址server_addr绑定
	if (bind(server_socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
	{
		perror("bind error !\n");
		return 0;
	}

	//server_socket开始监听
	if (listen(server_socket, 20) == SOCKET_ERROR)
	{
		perror("listen error !\n");
		return 0;
	}
	
	while (1)
	{
		printf("等待连接...\n");

		// 定义客户端的socket和socket地址结构
		SOCKET client_socket;
		sockaddr_in client_addr;
		int client_addr_length = sizeof(client_addr);

		// 接受连接请求,返回一个新的socket(描述符),这个新socket用于同连接的客户端通信 
		// accept函数会把连接到的客户端信息写到client_addr中 
		client_socket = accept(server_socket, (SOCKADDR *)&client_addr, &client_addr_length);
		if (client_socket == INVALID_SOCKET)
		{
			perror("Socket连接建立失败:\n");
			continue;
		}
		
		char IP_BUFFER[256];
		memset(IP_BUFFER, 0, 256);
		InetNtop(AF_INET, &client_addr.sin_addr, IP_BUFFER,256);
		printf("Socket连接建立,客户端IP为:%s,端口为:%d\n", IP_BUFFER, ntohs(client_addr.sin_port));


		//接收客户端请求的的文件路径
		// recv函数接收数据到缓冲区buffer中 
		char buffer[BUFFER_SIZE];
		memset(buffer,0, BUFFER_SIZE);
		if (recv(client_socket, buffer, BUFFER_SIZE, 0) < 0)
		{
			perror("接收文件名失败:\n");
			break;
		}

		// 然后从buffer(缓冲区)拷贝到file_name中 
		char file_name[FILE_NAME_MAX_SIZE + 1];
		memset(file_name, 0,FILE_NAME_MAX_SIZE + 1);
		strncpy_s(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE ? FILE_NAME_MAX_SIZE : strlen(buffer));
		

		// 打开文件并读取文件数据 
		FILE *fp;
		errno_t F_ERR= fopen_s(&fp,file_name, "rb");
		if (F_ERR != 0)
		{
			printf("文件打开失败:%s\n", file_name);
		}
		else
		{
			printf("开始传输文件:%s\n", file_name);
			memset(buffer,0, BUFFER_SIZE);
			int length = 0;
			// 每读取一段数据,便将其发送给客户端,循环直到文件读完为止 
			while ((length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0)
			{
				if (send(client_socket, buffer, length, 0) < 0)
				{
					printf("文件发送失败:%s/n", file_name);
					break;
				}
				memset(buffer,0, BUFFER_SIZE);
			}

			// 关闭文件 
			fclose(fp);
			printf("文件传输完成:%s!\n", file_name);
		}
		// 关闭与客户端的连接 
		closesocket(client_socket);
	}


	// 关闭监听用的socket 
	closesocket(server_socket);
	WSACleanup();
	return 0;

}

客户端cpp

/*
	Socket客户端代码
	服务器127.0.0.1通信,端口8888
	建立连接后,发送给服务器,需要传输的文件路径
	若文件路径存在,接收服务器发送的文件流,发送完毕则关闭连接。
*/

#include 
#include 
#include 
#include   
#include   
#include 

#pragma comment(lib,"ws2_32.lib")

#define BUFFER_SIZE 2048
#define FILE_NAME_MAX_SIZE 512 

int main(int argc, char* argv[])
{
	/*
	初始化WSA,使得程序可以调用windows socket
	*/
	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA wsaData;
	if (WSAStartup(sockVersion, &wsaData) != 0)
	{
		return 0;
	}

	/*
	创建监听用套接字,server_socket
	并检测是否创建成功
	*/
	SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); ;
	if (client_socket == INVALID_SOCKET) {
		//如果创建的socket无效,则结束程序
		perror("socket error !");
		return 0;
	}

	/*
	创建地址结构,server_addr,并设置端口和IP
	*/
	sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	//要连接的服务器端口号 8888
	server_addr.sin_port = htons(8888);
	//指定服务器的地址127.0.0.1
	InetPton(AF_INET, "127.0.0.1",&server_addr.sin_addr.s_addr);
	
	//与地址server_addr建立连接
	if (connect(client_socket, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)))
	{
		perror("connect error !\n");
		return 0;
	}

	char REMOTE_file_name[FILE_NAME_MAX_SIZE + 1];
	memset(REMOTE_file_name, 0, FILE_NAME_MAX_SIZE + 1);
	printf("请输入要获取的服务器文件路径:\n");
	scanf_s("%s", REMOTE_file_name, FILE_NAME_MAX_SIZE);

	char LOCAL_file_name[FILE_NAME_MAX_SIZE + 1];
	memset(LOCAL_file_name, 0, FILE_NAME_MAX_SIZE + 1);
	printf("请输入保存文件的本地路径:\n");
	scanf_s("%s", LOCAL_file_name, FILE_NAME_MAX_SIZE);

	char buffer[BUFFER_SIZE];
	memset(buffer,0 , BUFFER_SIZE);
	strncpy_s(buffer, REMOTE_file_name, strlen(REMOTE_file_name)>BUFFER_SIZE ? BUFFER_SIZE : strlen(REMOTE_file_name));

	// 向服务器发送buffer中的数据 
	if (send(client_socket, buffer, BUFFER_SIZE, 0) < 0)
	{
		perror("发送文件名失败:");
		exit(1);
	}

	// 打开文件,准备写入 
	FILE *fp;
	errno_t F_ERR = fopen_s(&fp, LOCAL_file_name, "wb");
	if (F_ERR != 0)
	{
		printf("文件打开失败:%s\n", LOCAL_file_name);
		exit(1);
	}

	// 从服务器接收数据到buffer中 
	// 每接收一段数据,便将其写入文件中,循环直到文件接收完并写完为止 
	memset(buffer,0, BUFFER_SIZE);
	int length = 0;
	while ((length = recv(client_socket, buffer, BUFFER_SIZE, 0)) > 0)
	{
		if (fwrite(buffer, sizeof(char), length, fp) < length)
		{
			printf("文件写入失败:%s\n", LOCAL_file_name);
			break;
		}
		memset(buffer, 0, BUFFER_SIZE);
	}

	printf("\n成功从服务器接收文件\n存入本地目录:%s\n", REMOTE_file_name, LOCAL_file_name);


	// 接收成功后,关闭文件,关闭socket、WSA 
	fclose(fp);
	closesocket(client_socket);
	WSACleanup();
	system("pause");
	return 0;

}

源码援引自袁华老师MOOC计算机网络中的github
VS2019显示报错:socket编程——VS2019遇到
这属于Windows编程过程中遇到字符串转换的问题

Windows里面的两种字符编码方式

  有ANSI编码和UNICODE编码之分。
  1.ANSI字符集,它们正式的名称应该是多字节字符系统(Multi-Byte Chactacter System,即MBCS)。ANSI(使用"")中的字符采用8bit.对于字符来说ANSI以单字节存放英文字符,以双字节存放中文等字符.8bit的ANSI编码只能表示256种字符,表示26个英文字母是绰绰有余的,但是表示汉字,韩国语等有着成千上万个字符的非西方字符肯定就不够了,正是如此才引入了UNICODE标准。
  2.Unicode码也是一种国际标准编码,采用二个字节编码,与ANSI码不兼容,且Unicode,英文和中文的字符都以双字节存放

因为Windows支持两种字符串,这样对应的就有了两套字符串处理函数,比如:strlen和wcslen,分别用于处理两种字符串.
ANSI:即char,可用字符串处理函数:strcat( ),strcpy( ), strlen( )等 以str打头的函数。 char :单字节变量类型,最多表示256个字符.
UNICODE:即wchar_t 可用字符串处理函数:wcscat(),wcscpy(),wcslen()等 以wcs打头的函数。 宽字节变量类型(即:unsigned short类型),用于表示Unicode

我的解决办法

改char_t为wchar_t
socket编程——VS2019遇到
socket编程——VS2019遇到
报错解决后,我们开始尝试传输文件

传输文件

先运行服务端
socket编程——VS2019遇到
再运行客户端
socket编程——VS2019遇到
socket编程——VS2019遇到
在客户端输入需要传输的文件路径
记得文件的名字要带上格式
输入接受文件的路径
socket编程——VS2019遇到
服务端显示传输成功
socket编程——VS2019遇到

你可能感兴趣的:(计算机网络学习心得)