【C++】创建TCP客户端

时间:2024-10-15 14:51:50

目录

一、实现发送字符串功能

二、实现接收字符串功能

三、客户端接收乱码问题

四、客户端发送乱码问题

五、客户端接收到数据时进行回调

六、子线程接收数据

七、发送Json格式数据

源码


一、实现发送字符串功能

头文件

#pragma once
#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <string.h>
using namespace std;

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

class TCPClient {
public:
	TCPClient(string ip, int port);
	int createSocket();  //创建套接字
	void setServerAddress();  //设置服务器地址信息
	int connectServer();  //连接到服务器
	void sendMsg(const char* sendbuf);  //发送数据

	int iResult;
	SOCKET clientSocket;
	sockaddr_in serverAddress;
	string IP;
	int Port;
};

源文件

# include "TCPClientTest.h"

TCPClient::TCPClient(string ip,int port):IP(ip),Port(port)
{
	WSADATA wsaData;
	this->iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (iResult != 0) {
		std::cerr << "WSAStartup failed: " << iResult << std::endl;
	}
	else
	{
		this->createSocket();
	}
}

int TCPClient::createSocket()	// 创建套接字
{
	this->clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (clientSocket == INVALID_SOCKET) {
		std::cerr << "Error at socket(): " << WSAGetLastError() << std::endl;
		WSACleanup();
		return 1;
	}
	else
	{
		this->setServerAddress();
	}
}

void TCPClient::setServerAddress()  // 设置服务器地址信息
{
	this->serverAddress;
	serverAddress.sin_family = AF_INET;  //IPv4
	serverAddress.sin_port = htons(Port); // 服务器端口
	inet_pton(AF_INET, IP.c_str(), &serverAddress.sin_addr); // 服务器 IP 地址
	this->connectServer();
}

int TCPClient::connectServer()  // 连接到服务器
{
	this->iResult = connect(clientSocket, reinterpret_cast<sockaddr*>(&serverAddress), sizeof(serverAddress));
	if (this->iResult == SOCKET_ERROR) {
		std::cerr << "connect failed: " << WSAGetLastError() << std::endl;
		closesocket(clientSocket);
		WSACleanup();
		return 1;
	}
	else
	{
		std::cout << "Connected to server." << std::endl;
	}
}

void TCPClient::sendMsg(const char* sendbuf)  // 发送数据
{
	this->iResult = send(clientSocket, sendbuf, strlen(sendbuf), 0);
	if (this->iResult == SOCKET_ERROR) {
		std::cerr << "send failed: " << WSAGetLastError() << std::endl;
		closesocket(clientSocket);
		WSACleanup();
	}
}

调用:

TCPClient("127.0.0.1", 8888).sendMsg("hello");

结果:

二、实现接收字符串功能

在头文件中添加一个接收数据的方法

string recvMsg();  //接收数据

在源文件中实现:

string TCPClient::recvMsg()  //接收数据
{
	char recvbuf[1024];
	this->iResult = recv(clientSocket, recvbuf, sizeof(recvbuf), 0);
	if (this->iResult > 0) {
		return string(recvbuf, iResult);
	}
	else if (this->iResult == 0) {
		std::cout << "Connection closed" << std::endl;
		return "1";
	}
	else {
		std::cerr << "recv failed: " << WSAGetLastError() << std::endl;
		return "2";
	}
}

调用:

TCPClient client = TCPClient("127.0.0.1", 8888);
	while (true)
	{
		string msg = client.recvMsg();
		if (msg=="1"||msg=="2")
		{
			break;
		}
		else
		{
			cout << msg << endl;
		}
	}

三、客户端接收乱码问题

如果服务端发来的是中文,可能接收打印的是乱码,为了解决这个问题,我们添加如下代码。首先在头文件中定义一个编码转换的方法:

string utf8ToGbk(const std::string& utf8Str);  //解决中文乱码

在源文件中实现:

string TCPClient::utf8ToGbk(const string& utf8Str) {
	int len = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, nullptr, 0);
	wchar_t* wstr = new wchar_t[len];
	MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, wstr, len);

	len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
	char* gbkStr = new char[len];
	WideCharToMultiByte(CP_ACP, 0, wstr, -1, gbkStr, len, nullptr, nullptr);

	string result(gbkStr);
	delete[] wstr;
	delete[] gbkStr;
	return result;
}

在接收数据时调用该方法

运行结果如下,可以看到客户端可以正常接收打印中文信息。

四、客户端发送乱码问题

在头文件中定义一个转utf8编码的方法 

在源文件中实现:

string TCPClient::localToUtf8(const string& localStr)
{
	int len = MultiByteToWideChar(CP_ACP, 0, localStr.c_str(), -1, nullptr, 0);
	vector<wchar_t> wstr(len);
	MultiByteToWideChar(CP_ACP, 0, localStr.c_str(), -1, wstr.data(), len);

	len = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), -1, nullptr, 0, nullptr, nullptr);
	vector<char> utf8Str(len);
	WideCharToMultiByte(CP_UTF8, 0, wstr.data(), -1, utf8Str.data(), len, nullptr, nullptr);

	return string(utf8Str.data());
}

在发送数据时使用编码转换方法

此时发送中文就不会乱码了

TCPClient client = TCPClient("127.0.0.1", 8888);
string msg = "你好";
client.sendMsg(msg.c_str());

五、客户端接收到数据时进行回调

对客户端的接收函数做如下修改,让TCPClient类的receiveData方法接受一个回调函数指针作为参数,当有数据接收时,调用这个回调函数并将接收到的数据作为参数传递给它。 

main函数中,TCPClient对象在连接到服务器后,调用receiveData方法并传入一个回调函数dataReceivedCallback,当有数据接收时,这个回调函数会被调用并输出接收到的数据。

结果如下所示,当服务端发送数据后,客户端会执行回调函数,输出打印服务端发来的数据,但是会阻塞main函数后续的逻辑,因此需要将接收函数作为一个子线程去执行。

六、子线程接收数据

在头文件中先引入线程库

对接收函数做如下修改

void TCPClient::recvMsg(void(*callback)(const std::string&))  //接收数据
{
	thread thread_recvMsg([this, callback]() {
		char recvbuf[1024];
		while (true)
		{
			this->iResult = recv(clientSocket, recvbuf, sizeof(recvbuf), 0);
			if (this->iResult > 0) {
				string recvData = utf8ToGbk(string(recvbuf, iResult));
				string receivedData(recvData);
				callback(receivedData);
			}
			else if (this->iResult == 0) {
				cout << "Connection closed" << endl;
				break;
			}
			else {
				cerr << "recv failed: " << WSAGetLastError() << endl;
				break;
			}
		}
	});
	thread_recvMsg.detach();
}

在main函数中调用:

 

结果如下,可以看到接收函数作为子线程去执行,就不会阻塞主线程。

七、发送Json格式数据

将封装Json所用到文件拷贝到项目下(资源地址:https://download.****.net/download/ChaoChao66666/89886662

在main函数中使用json库发送json字符串到TCP服务端:

结果:

 

源码

main.cpp 

#include <iostream>
#include "swap.h"
#include <vector>
#include <list>
#include <deque>
#include <algorithm>
#include <map>
#include "InfraredAttenuation.h"
#include "TCPClientTest.h"
#include "Json/json.h"
using namespace std;
using namespace Json;

void dataReceivedCallback(const string& data) {
	cout << "Received data: " << data << endl;
}

int main() {
	TCPClient client = TCPClient("127.0.0.1", 8888);
	
	client.recvMsg(dataReceivedCallback);

	//主线程可以继续执行其它任务
	while (true) {
		Value root;
		root["msg"] = "你好";

		FastWriter fw;
		client.sendMsg(fw.write(root).c_str());  //fw.write()可以将json对象转为string类型
		std::this_thread::sleep_for(std::chrono::seconds(3));
	}
}

TCPClientTest.h 

#pragma once
#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <string.h>
#include <vector>
#include <thread>
using namespace std;

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

class TCPClient {
public:
	TCPClient(string ip, int port);
	int createSocket();  //创建套接字
	void setServerAddress();  //设置服务器地址信息
	int connectServer();  //连接到服务器
	void sendMsg(const char* sendbuf);  //发送数据
	void recvMsg(void(*callback)(const std::string&));  //接收数据
	string utf8ToGbk(const std::string& utf8Str);  //解决接收中文乱码
	string localToUtf8(const string& localStr);  //解决发送中文乱码

	int iResult;
	SOCKET clientSocket;
	sockaddr_in serverAddress;
	string IP;
	int Port;
};

 TCPClientTest.cpp

# include "TCPClientTest.h"

TCPClient::TCPClient(string ip,int port):IP(ip),Port(port)
{
	WSADATA wsaData;
	this->iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (iResult != 0) {
		cerr << "WSAStartup failed: " << iResult << endl;
	}
	else
	{
		this->createSocket();
	}
}

int TCPClient::createSocket()	// 创建套接字
{
	this->clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (clientSocket == INVALID_SOCKET) {
		cerr << "Error at socket(): " << WSAGetLastError() << endl;
		WSACleanup();
		return 1;
	}
	else
	{
		this->setServerAddress();
	}
}

void TCPClient::setServerAddress()  // 设置服务器地址信息
{
	this->serverAddress;
	serverAddress.sin_family = AF_INET;  //IPv4
	serverAddress.sin_port = htons(Port); // 服务器端口
	inet_pton(AF_INET, IP.c_str(), &serverAddress.sin_addr); // 服务器 IP 地址
	this->connectServer();
}

int TCPClient::connectServer()  // 连接到服务器
{
	this->iResult = connect(clientSocket, reinterpret_cast<sockaddr*>(&serverAddress), sizeof(serverAddress));
	if (this->iResult == SOCKET_ERROR) {
		std::cerr << "connect failed: " << WSAGetLastError() << std::endl;
		closesocket(clientSocket);
		WSACleanup();
		return 1;
	}
	else
	{
		cout << "Connected to server." << endl;
	}
}

void TCPClient::sendMsg(const char* sendbuf)  // 发送数据
{
	string sendbuf_utf8 = this->localToUtf8(sendbuf);
	this->iResult = send(clientSocket, sendbuf_utf8.c_str(), strlen(sendbuf_utf8.c_str()), 0);
	if (this->iResult == SOCKET_ERROR) {
		cerr << "send failed: " << WSAGetLastError() << endl;
		closesocket(clientSocket);
		WSACleanup();
	}
}

void TCPClient::recvMsg(void(*callback)(const std::string&))  //接收数据
{
	thread thread_recvMsg([this, callback]() {
		char recvbuf[1024];
		while (true)
		{
			this->iResult = recv(clientSocket, recvbuf, sizeof(recvbuf), 0);
			if (this->iResult > 0) {
				string recvData = utf8ToGbk(string(recvbuf, iResult));
				string receivedData(recvData);
				callback(receivedData);
			}
			else if (this->iResult == 0) {
				cout << "Connection closed" << endl;
				break;
			}
			else {
				cerr << "recv failed: " << WSAGetLastError() << endl;
				break;
			}
		}
	});
	thread_recvMsg.detach();
}

string TCPClient::utf8ToGbk(const string& utf8Str) {
	int len = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, nullptr, 0);
	wchar_t* wstr = new wchar_t[len];
	MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, wstr, len);

	len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
	char* gbkStr = new char[len];
	WideCharToMultiByte(CP_ACP, 0, wstr, -1, gbkStr, len, nullptr, nullptr);

	string result(gbkStr);
	delete[] wstr;
	delete[] gbkStr;
	return result;
}

string TCPClient::localToUtf8(const string& localStr)
{
	int len = MultiByteToWideChar(CP_ACP, 0, localStr.c_str(), -1, nullptr, 0);
	vector<wchar_t> wstr(len);
	MultiByteToWideChar(CP_ACP, 0, localStr.c_str(), -1, wstr.data(), len);

	len = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), -1, nullptr, 0, nullptr, nullptr);
	vector<char> utf8Str(len);
	WideCharToMultiByte(CP_UTF8, 0, wstr.data(), -1, utf8Str.data(), len, nullptr, nullptr);

	return string(utf8Str.data());
}