zl程序教程

您现在的位置是:首页 >  系统

当前栏目

Windows平台Socket通信实例

2023-09-11 14:22:29 时间

1. 概述

Windows平台下的Socket通信方式主要采用的有TCP(SOCK_STREAM)通信和UDP通信(SOCK_DGRAM)两种。对于第一种通信方式需要建立可靠的连接且要进行校验;另外一种网络传输方式不需要建立可靠的连接,也不进行校验,使用在语音通信和视频通信中。下面就将对Windows平台下创建TCP通信进行流程的说明和相关解释。
Windows上TCP通信的步骤:
文件发送端:
1. 加载和创建套接字,使用到的函数有WSAStartup()(初始化Windows的相关DLL)和socket()(创建一个套接字绑定到一个特定的传输方式如传输的方式、通信的类型)。
2. 向目标端发送连接请求使用的是connect()函数,函数调用成功返回0;
3. 向目标端发送数据send()函数;
4. 关闭套接字,关闭加载的套接字库closesocket()、WSACleanup()。

文件接收端:
1. 加载和创建套接字,使用到的函数有WSAStartup()(初始化Windows的相关DLL)和socket()(创建一个套接字绑定到一个特定的传输方式如传输的方式、通信的类型)。
2. 绑定套接字到一个IP地址和一个端口上,使用bind()函数;
3. 将套接字设置为监听模式等待连接请求,listen();
4. 配置好监听之后,调用accept()函数等待请求,当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字;
5. 用等待连接函数返回的套接字,调用send()和recv()客户端进行通信;
6. 处理完数据之后,就等待另一连接请求;
7. 关闭套接字,关闭加载的套接字库closesocket()、WSACleanup()。

2. 编码

头文件内容:
#pragma once
#include <winsock.h>
#include <string>

#define BufferSize 1024*1024	//发送的缓存的大小
// CTCPFileTrans

class CTCPFileTrans : public CWnd
{
	DECLARE_DYNAMIC(CTCPFileTrans)

public:
	CTCPFileTrans();
	CTCPFileTrans(std::string ip, int port);
	virtual ~CTCPFileTrans();

protected:
	DECLARE_MESSAGE_MAP()

private:
	SOCKADDR_IN addr;

public:
	CProgressCtrl* m_SendProcessBar;	//socket文件发送进度条
	CEdit* m_SendLog;		//socket文件发送打印日志

public:
	static DWORD WINAPI SendThreadProc(LPVOID lpParameter);    //创建线程去执行Socket操作
	static DWORD WINAPI ReceiveThreadProc(LPVOID lpParameter);    //创建线程去执行Socket操作
	CString* file_path;	//需要发送的文件路劲字符串
	int m_SendFileNum;	//需要发送的文件数目
	void SetSendFilePath(CString* file_name, const int file_num);	//发送字符串初始化,里面保存了需要发送的文件的路径

public:
	void ShowSocketMessage(int Error_Code, bool is_show=true);	//根据错误代码,提示相关错误
	bool TestConnection();					//检查设备连接

	//发送数据
public:
	std::string ip_address;		//接收方的IP地址
	unsigned int port_num;		//接收方的端口号
	SOCKET m_socket;			//发送的套接字变量

public:
	bool GetSendSocket();			//初始化SOCKET
	bool Sendfile(CString file_name);	//
	bool Sendfile(CString* file_name, const int file_count);	//

	//接收数据
public:
	bool GetRecSocket(int listen_num = 5);		//初始化监听调套接字
	bool ReceiveFile();			//
	SOCKET m_RecSocket;			//监听套接字
};
源代码文件内容:
// TCPFileTrans.cpp : 实现文件
//

#include "stdafx.h"
#include "File_Trans.h"
#include "TCPFileTrans.h"
#include <winsock.h>
#include <windows.h>


// CTCPFileTrans

IMPLEMENT_DYNAMIC(CTCPFileTrans, CWnd)

CTCPFileTrans::CTCPFileTrans()
{
	this->file_path = nullptr;
}

//
CTCPFileTrans::CTCPFileTrans(std::string ip, int port) : ip_address(ip), port_num(port)
{
	this->file_path = nullptr;
}


CTCPFileTrans::~CTCPFileTrans()
{
	if (file_path)
	{
		delete[] file_path;
		file_path = nullptr;
	}
}


BEGIN_MESSAGE_MAP(CTCPFileTrans, CWnd)
END_MESSAGE_MAP()

// CTCPFileTrans 消息处理程序
//************************************************************************
// 函数名称:    	InitSocket
// 访问权限:    	public 
// 创建日期:		2017/04/10
// 创 建 人:		
// 函数说明:		初始化SOCKET
// 返 回 值:   	bool
//************************************************************************
bool CTCPFileTrans::GetSendSocket()
{
	if (this->ip_address=="")
	{
		MessageBox(_T("SOCKET初始化过程中,输入的IP地址为空,请检查配置"), _T("错误"), MB_OK|MB_ICONERROR);
		return false;
	}
	if (this->port_num<=1024)
	{
		MessageBox(_T("SOCKET初始化过程中,输入的端口号与系统端口号冲突,请检查配置"), _T("错误"), MB_OK | MB_ICONERROR);
		return false;
	}

	WSAData data;
	if (0 != WSAStartup(MAKEWORD(2, 2), &data))						//初始化Windows Socket
	{
		int error_code(WSAGetLastError());	//获取错误代码
		CString error_num=_T("");
		error_num.Format(_T("%d"), error_code);
		MessageBox(_T("windows socket 初始化错误,错误代码:")+error_num, _T("错误"), MB_OK|MB_ICONERROR);
		WSACleanup();
		return false;
	}
	m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	//网络流的TCP传输

	memset(&this->addr, 0, sizeof(SOCKADDR_IN));					//初始化结构体
	this->addr.sin_family = AF_INET;								//通信类型
	this->addr.sin_port = htons(this->port_num);							//设置端口号
	this->addr.sin_addr.S_un.S_addr = inet_addr(this->ip_address.c_str());		//设置ip地址
	if (0 != connect(m_socket, (SOCKADDR*)&this->addr, sizeof(this->addr)))			//连接到客户端
	{
		int error_code(WSAGetLastError());	//获取错误代码
		this->ShowSocketMessage(error_code);
		closesocket(this->m_socket);
		WSACleanup();
		return false;
	}

	return true;
}

//************************************************************************
// 函数名称:    	Sendfile
// 访问权限:    	public 
// 创建日期:		2017/04/10
// 创 建 人:		
// 函数说明:		TCP发送单个文件
// 函数参数: 	std::string file_name	文件的路径
// 返 回 值:   	bool
//************************************************************************
bool CTCPFileTrans::Sendfile(CString file_name)
{
	if (!this->GetSendSocket())
	{
		MessageBox(_T("发送单个文件,获取TCP连接失败!"), _T("错误"), MB_OK|MB_ICONERROR);
		return false;
	}

	HANDLE hFile = CreateFile(file_name, GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_ALWAYS, 0, 0);	//获取文件句柄
	if (INVALID_HANDLE_VALUE == hFile)
	{
		MessageBox(_T("打开需要发送的本地文件失败!"), _T("错误"), MB_OK | MB_ICONERROR);
		return false;
	}	//有效性检查
	LARGE_INTEGER m_SendFileSize;			//文件的大小变量
	GetFileSizeEx(hFile, &m_SendFileSize);	//获取文件的字节大小
	LARGE_INTEGER m_SizeLeft2Send(m_SendFileSize);	//剩余需要发送的字节数

	CString m_FileSize;
	m_FileSize.Format(_T("%d"), m_SendFileSize.QuadPart);	//转换得到的大小

	//设置发送文件日志
	this->m_SendLog->SetWindowTextW(_T(" 发送文件:") + file_name + _T(" 开始..."));

	//发送文件的名字和大小
	CString m_SendStr = file_name + _T("&&&&&&&&") + m_FileSize;
	int send_length(m_SendStr.GetLength());
	std::string send_str = CStringA(m_SendStr);
	send(m_socket, send_str.c_str(), 2*m_SendStr.GetLength(), 0);

	//定义发送缓存
	char* m_SendBuffer = new char[BufferSize];
	memset(m_SendBuffer, 0, sizeof(char)*BufferSize);

	while (m_SizeLeft2Send.QuadPart > 0)
	{
		DWORD m_Bytes2Send(BufferSize);	//每次发送字节的大小
		DWORD m_ByteReaded(0);	//每次读取文件读取到的字节数 

		if (m_SizeLeft2Send.QuadPart <= m_Bytes2Send)
		{
			m_Bytes2Send = m_SizeLeft2Send.QuadPart;
		}	//如果需要传输的文件字节数小于发送的缓存大小,就将发送的缓存设置为文件的大小
		ReadFile(hFile, m_SendBuffer, m_Bytes2Send, &m_ByteReaded, 0);
		m_SizeLeft2Send.QuadPart -= m_ByteReaded;
		//更新发送进度条
		this->m_SendProcessBar->SetPos(int(m_SendFileSize.QuadPart-m_SizeLeft2Send.QuadPart)/m_SendFileSize.QuadPart);

		send(m_socket, m_SendBuffer, m_ByteReaded, 0);
	}

	//设置发送文件日志
	//更新发送进度条
	this->m_SendProcessBar->SetPos(100);
	this->m_SendLog->SetWindowTextW(_T(" 发送文件:") + file_name + _T(" 100%"));

	CloseHandle(hFile);	//关闭文件句柄
	closesocket(this->m_socket);
	WSACleanup();
	delete[] m_SendBuffer;
	m_SendBuffer = nullptr;

	return true;
}

//************************************************************************
// 函数名称:    	Sendfile
// 访问权限:    	public 
// 创建日期:		2017/04/10
// 创 建 人:		
// 函数说明:		TCP发送一组文件
// 函数参数: 	std::string * file_name	一组文件路径
// 函数参数: 	const int file_count	文件的数目
// 返 回 值:   	bool
//************************************************************************
bool CTCPFileTrans::Sendfile(CString* file_name, const int file_count)
{
	//if (!this->GetSendSocket())
	//{
	//	MessageBox(_T("发送一组文件,获取TCP连接失败!"), _T("错误"), MB_OK | MB_ICONERROR);
	//	return false;
	//}

	for (int i=0; i<file_count; i++)
	{
		if (!this->Sendfile(file_name[i]))
		{
			MessageBox(_T("在发送文件:") + file_name[i] + _T(" 时发生错误,发送失败!"),
				_T("错误"), MB_ICONERROR|MB_OK);
			return false;
		}
	}

	return true;
}

//************************************************************************
// 函数名称:    	SetSendFilePath
// 访问权限:    	public 
// 创建日期:		2017/04/12
// 创 建 人:		
// 函数说明:		发送字符串初始化,里面保存了需要发送的文件的路径
// 函数参数: 	CString * file_name
// 函数参数: 	const int file_num
// 返 回 值:   	void
//************************************************************************
void CTCPFileTrans::SetSendFilePath(CString* file_name, const int file_num)
{
	this->file_path = new CString[file_num];
	this->m_SendFileNum = file_num;
	for (int i=0; i<file_num; i++)
	{
		this->file_path[i] = file_name[i];
	}
}

//************************************************************************
// 函数名称:    	SendThreadProc
// 访问权限:    	public static 
// 创建日期:		2017/04/12
// 创 建 人:		
// 函数说明:		socket发送文件的线程
// 函数参数: 	LPVOID lpParameter
// 返 回 值:   	DWORD WINAPI
//************************************************************************
DWORD WINAPI CTCPFileTrans::SendThreadProc(LPVOID lpParameter)   //创建线程去执行Socket操作
{
	DWORD temp = 0;
	CTCPFileTrans* file_send = (CTCPFileTrans*)lpParameter;
	if (1 == file_send->m_SendFileNum)
	{
		file_send->Sendfile(file_send->file_path[0]);
	}
	else if (1 <= file_send->m_SendFileNum)
	{
		file_send->Sendfile(file_send->file_path, file_send->m_SendFileNum);
	}

	return temp;
}

//************************************************************************
// 函数名称:    	ReceiveThreadProc
// 访问权限:    	public static 
// 创建日期:		2017/04/12
// 创 建 人:		
// 函数说明:		socket文件接收线程
// 函数参数: 	LPVOID lpParameter
// 返 回 值:   	DWORD WINAPI
//************************************************************************
DWORD WINAPI CTCPFileTrans::ReceiveThreadProc(LPVOID lpParameter)    //创建线程去执行Socket操作
{
	DWORD temp = 0;
	CTCPFileTrans* file_send = (CTCPFileTrans*)lpParameter;
	while (true)
	{
		file_send->ReceiveFile();
	}

	return temp;
}

//************************************************************************
// 函数名称:    	GetRecSocket
// 访问权限:    	public 
// 创建日期:		2017/04/11
// 创 建 人:		
// 函数说明:		获取监听的Socket
// 返 回 值:   	bool
//************************************************************************
bool CTCPFileTrans::GetRecSocket(int listen_num)
{
	if (this->ip_address == "")
	{
		MessageBox(_T("SOCKET初始化过程中,输入的IP地址为空,请检查配置"), _T("错误"), MB_OK | MB_ICONERROR);
		return false;
	}
	if (this->port_num <= 1024)
	{
		MessageBox(_T("SOCKET初始化过程中,输入的端口号与系统端口号冲突,请检查配置"), _T("错误"), MB_OK | MB_ICONERROR);
		return false;
	}

	WSAData data;
	if (0 != WSAStartup(MAKEWORD(2, 2), &data))						//初始化Windows Socket
	{
		int error_code(WSAGetLastError());	//获取错误代码
		CString error_num = _T("");
		error_num.Format(_T("%d"), error_code);
		MessageBox(_T("windows socket 初始化错误,错误代码:") + error_num, _T("错误"), MB_OK | MB_ICONERROR);
		WSACleanup();
		return false;
	}
	m_RecSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	//网络流的TCP传输

	memset(&this->addr, 0, sizeof(SOCKADDR_IN));					//初始化结构体
	this->addr.sin_family = AF_INET;								//通信类型
	this->addr.sin_port = htons(this->port_num);							//设置端口号
	this->addr.sin_addr.S_un.S_addr = inet_addr(this->ip_address.c_str());		//设置ip地址
	if (0 != bind(m_RecSocket, (SOCKADDR*)&this->addr, sizeof(this->addr)))	//绑定本地端口
	{
		int error_code(WSAGetLastError());	//获取错误代码
		CString error_num = _T("");
		error_num.Format(_T("%d"), error_code);
		MessageBox(_T("windows socket 绑定本地端口错误,错误代码:") + error_num, _T("错误"), MB_OK | MB_ICONERROR);
		WSACleanup();
		return false;
	}

	//配置监听
	if (0 != listen(m_RecSocket, listen_num))
	{
		int error_code(WSAGetLastError());	//获取错误代码
		CString error_num = _T("");
		error_num.Format(_T("%d"), error_code);
		MessageBox(_T("windows socket 绑定本地端口错误,错误代码:") + error_num, _T("错误"), MB_OK | MB_ICONERROR);
		WSACleanup();
		return false;
	}

	return true;
}

//************************************************************************
// 函数名称:    	ReceiveFile
// 访问权限:    	public 
// 创建日期:		2017/04/11
// 创 建 人:		
// 函数说明:		接收文件
// 返 回 值:   	bool
//************************************************************************
bool CTCPFileTrans::ReceiveFile()
{
	int len(sizeof(this->addr));
	SOCKET m_SocketAccept = accept(m_RecSocket, (SOCKADDR*)&this->addr, &len);	//接入套接字
	if (INVALID_SOCKET == m_SocketAccept)	//获取
	{
		int error_code(WSAGetLastError());	//获取错误代码
		CString error_num = _T("");
		MessageBox(_T("windows socket 接入套接字获取错误,错误代码:") + error_num, _T("错误"), MB_OK | MB_ICONERROR);
		WSACleanup();
		return false;
	}

	int receive_size(-1);
	char* m_ReveiveBuffer = new char[BufferSize];
	memset(m_ReveiveBuffer, 0, sizeof(char)*BufferSize);
	receive_size = recv(m_SocketAccept, m_ReveiveBuffer, BufferSize, 0);

	LARGE_INTEGER m_FileSize;	//文件大小
	HANDLE hFile;	//文件句柄
	std::string file_name;
	if (receive_size > 0)
	{
		std::string file_str = m_ReveiveBuffer;
		file_name = file_str.substr(0, file_str.find_first_of("&&&&&&&&"));
		file_name = file_name.substr(file_name.find_last_of("\\") + 1, file_name.length());
		std::string file_size = file_str.substr(file_str.find_first_of("&&&&&&&&") + 8, file_str.length());
		m_FileSize.QuadPart = std::atoi(file_size.c_str());
	}
	else{ return false; }

	hFile = CreateFile(CA2T(file_name.c_str()), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (INVALID_HANDLE_VALUE == hFile)
	{
		MessageBox(_T("创建下载文件:") + (CString)CA2T(file_name.c_str()) + _T("文件失败"), _T("错误"), MB_OK | MB_ICONERROR);
		return false;
	}

	while (m_FileSize.QuadPart > 0)
	{
		DWORD m_WirtedByte;
		receive_size = ::recv(m_SocketAccept, m_ReveiveBuffer, BufferSize, 0);
		::WriteFile(hFile, m_ReveiveBuffer, receive_size, &m_WirtedByte, NULL);
		m_FileSize.QuadPart -= m_WirtedByte;
	}

	CloseHandle(hFile);
}

//************************************************************************
// 函数名称:    	TestConnection
// 访问权限:    	public 
// 创建日期:		2017/04/11
// 创 建 人:		
// 函数说明:		检查网络连接
// 返 回 值:   	bool
//************************************************************************
bool CTCPFileTrans::TestConnection()
{
	if (this->ip_address == "")
	{
		MessageBox(_T("SOCKET初始化过程中,输入的IP地址为空,请检查配置"), _T("错误"), MB_OK | MB_ICONERROR);
		return false;
	}
	if (this->port_num <= 1024)
	{
		MessageBox(_T("SOCKET初始化过程中,输入的端口号与系统端口号冲突,请检查配置"), _T("错误"), MB_OK | MB_ICONERROR);
		return false;
	}

	WSAData data;
	if (0 != WSAStartup(MAKEWORD(2, 2), &data))						//初始化Windows Socket
	{
		int error_code(WSAGetLastError());	//获取错误代码
		CString error_num = _T("");
		error_num.Format(_T("%d"), error_code);
		MessageBox(_T("windows socket 初始化错误,错误代码:") + error_num, _T("错误"), MB_OK | MB_ICONERROR);
		WSACleanup();
		return false;
	}
	SOCKET m_socket_temp = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	//网络流的TCP传输

	memset(&this->addr, 0, sizeof(SOCKADDR_IN));					//初始化结构体
	this->addr.sin_family = AF_INET;								//通信类型
	this->addr.sin_port = htons(this->port_num);							//设置端口号
	this->addr.sin_addr.S_un.S_addr = inet_addr(this->ip_address.c_str());		//设置ip地址
	if (0 != connect(m_socket_temp, (SOCKADDR*)&this->addr, sizeof(this->addr)))			//连接到客户端
	{
		int error_code(WSAGetLastError());	//获取错误代码
		this->ShowSocketMessage(error_code);
		closesocket(m_socket_temp);
		WSACleanup();
		return false;
	}

	closesocket(m_socket_temp);
	WSACleanup();
	return true;
}

//************************************************************************
// 函数名称:    	ShowSocketMessage
// 访问权限:    	public 
// 创建日期:		2017/04/11
// 创 建 人:		
// 函数说明:		根据Socket返回回来的错误代码,弹出响应的消息提示框
// 函数参数: 	int Error_Code
// 返 回 值:   	void
//************************************************************************
void CTCPFileTrans::ShowSocketMessage(int Error_Code, bool is_show)
{
	if (!is_show)
	{
		return;
	}	//不显示错误代码提示

	int code(Error_Code);
	CString error_num = _T("");
	error_num.Format(_T("%d"), code);
	switch (code)
	{
	case WSAEALREADY:
		MessageBox(_T("非阻塞的连接请求正在特定的socket中处理,错误代码:") + error_num, _T("错误"), MB_OK | MB_ICONERROR);
		break;
	case WSAEADDRNOTAVAIL:
		MessageBox(_T("远程的连接地址不可用,错误代码:") + error_num, _T("错误"), MB_OK | MB_ICONERROR);
		break;
	case WSAEAFNOSUPPORT:
		MessageBox(_T("指定的传输类型在本socket中不受支持,错误代码:") + error_num, _T("错误"), MB_OK | MB_ICONERROR);
		break;
	case WSAECONNREFUSED:
		MessageBox(_T("socket连接请求被阻止,检查客户端是否开机启动,错误代码:") + error_num, _T("错误"), MB_OK | MB_ICONERROR);
		break;
	case WSAEFAULT:
		MessageBox(_T("当前本地地址配置对于当前传输协议不正确,错误代码:") + error_num, _T("错误"), MB_OK | MB_ICONERROR);
		break;
	case WSAEISCONN:
		MessageBox(_T("当前socket已经连接了,错误代码:") + error_num, _T("错误"), MB_OK | MB_ICONERROR);
		break;
	case WSAETIMEDOUT:
		MessageBox(_T("连接次超过了规定次数,错误代码:") + error_num, _T("错误"), MB_OK | MB_ICONERROR);
		break;
	default:
		break;
	}
}