【C++】创建TCP客户端

news2024/10/23 15:28:36

目录

一、实现发送字符串功能

二、实现接收字符串功能

三、客户端接收乱码问题

四、客户端发送乱码问题

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

六、子线程接收数据

七、发送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.csdn.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());
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2213217.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

快速体验 Spring AI_ 绘图样例_Spring AI alibaba

Spring AI : 一套面向java开发者的大模型统一接口 过去&#xff0c;用Java开发AI应用时面临的主要困境是缺乏统一标准的封装库&#xff0c;导致开发者需针对不同AI服务提供商&#xff08;如OpenAI、阿里云等&#xff09;各自独立对接接口文档&#xff0c;增加了学习成本与迁移…

Labview创建FPGA项目异常解决

安装了FPGA模块后&#xff0c;无法在没有真实FPGA模块时&#xff0c;创建FPGA项目。 此时需要安装多个驱动后可以解决该问题。

【帕鲁杯应急响应WP】

文章目录 一、序言 二、背景信息 三、网络拓扑 四、资产清单 五、应急响应题目 一、序言 也是终于把帕鲁杯应急响应完整做一遍了&#xff0c;主办单位真用心了&#xff0c;这么大环境&#xff0c;总结下来还是很有收获&#xff0c;但是毕竟是做题有时候不知道出题人意图不…

mitmproxy安装以及使用

mitmproxy 是一个强大的中间人代理工具&#xff0c;可以用来拦截、查看、修改HTTP/HTTPS流量。它对于测试应用程序、分析网络请求、安全审计等场景非常有用。以下是 mitmproxy 的安装和基本使用流程。 在 Linux 和 macOS 上安装 确保已安装 Python&#xff1a;mitmproxy 需要 P…

技术干货|企业如何通过创建可持续能源的数字孪生,实现即时洞察

主要看点 行业&#xff1a;能源 挑战&#xff1a;聚变动力装置在设计评估阶段需要复杂的数字仿真模型。能源企业通常希望利用昂贵的设计模型&#xff0c;通过输入从电厂现场实时传感器数据来创建数字孪生模型&#xff0c;从而帮助工程师了解电厂的结构完整性&#xff0c;并进一…

李德仁院士携实验室及大势文旅团队参加“湖北旅游、武当突破”名家谈,分享数智文旅发展新经验

10月12日上午&#xff0c;2024世界武当太极大会在湖北省十堰市武当山盛大开幕。 2023年国家科学技术最高奖获得者、中国科学院、中国工程院院士、武汉大学李德仁教授携测绘遥感信息工程国家重点实验室&#xff08;后简称“实验室”&#xff09;团队以及大势智慧文旅团队&#…

线性层(全连接层)pytorch

**前置知识&#xff1a; 1、线性层和非线性激活函数&#xff1a; 组合使用&#xff1a;通常&#xff0c;线性层后面会接一个非线性激活函数。这样&#xff0c;网络先做一次简单的转换&#xff08;线性&#xff09;&#xff0c;然后用激活函数&#xff08;非线性&#xff09;添…

迈巴赫S480升级原厂魔毯悬挂功能有哪些作用

迈巴赫 S480 升级魔毯空气悬挂系统的功能介绍如下&#xff1a; 1. 平稳驾驶体验&#xff1a; • 路况适应&#xff1a;通过摄像头和雷达扫描车前方路面状况&#xff0c;提前获取路况信息&#xff0c;然后根据这些信息自动调节空气悬挂的软硬程度。无论是在平坦的高速公路&…

最新Spring Boot3框架入门教程,基础知识讲解(参考官方文档),同时基于MybatisPlus+MYSQL搭建后台管理系统基础流程(附源码)

本文所涉及的代码以及相关文件均上传至仓库:GitHub - yang66-hash/XDPropertyManagementSystemDemo: This is a demo template based on SpringBoot3 in the background of property management system. Spring Boot 是由 Pivotal 团队开发的一款开源框架&#xff0c;它可以帮助…

如何防止箱包发霉 工厂箱包防霉方案

工厂储存的皮具、箱包保存和防护是一个很重要的问题。尤其是在潮湿多雨的季节&#xff0c;很容易受到霉菌侵袭。那么&#xff0c;如何在不损害品质的前提下&#xff0c;实现其长期保存而不发霉呢?经ihaoer防霉人士介绍箱包发霉处理方法和防霉方法如下&#xff1a; 箱包发霉处理…

JavaScript 字符串魔法:实用技巧——WEB开发系列45

这篇文章&#xff0c;探讨一下 JavaScript 中的字符串操作。字符串是编程中的基本数据类型之一&#xff0c;它们无处不在&#xff0c;几乎在所有的代码中都需要处理。了解字符串的基础知识、常用的内置方法&#xff0c;通过实例来学习高效地操作字符串。 一、什么是字符串&…

史上最好用的html类word编辑器

前言 近期在做一个项目&#xff0c;需要用到大量的word模板生成对应资料还要打印&#xff0c;之前想的是用一款富文本编辑器即可使用&#xff0c;无赖客户比较刁钻要求系统中看到的所有页面尽量和word上效果一样&#xff0c;并且打印出来每一页内容和系统显示的要一致&#xf…

如何根据店铺商品选择合适的礼品代发平台?

随着电商行业的蓬勃发展&#xff0c;礼品代发服务已经成为很多店铺提升客户体验、增加订单量的重要策略。尤其是对于那些想要通过赠送小礼品、促销品或节日礼物来吸引顾客的商家来说&#xff0c;选择一个合适的礼品代发平台至关重要。如何根据店铺的商品特点选择一个最适合自己…

人员跌倒检测系统的设计与实现(Yolov Python38 深度学习)+文档

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

数据结构与算法——Java实现 35.求数据流中的第K大元素

目录 703. 数据流中的第 K 大元素 思路 题目解析&#xff1a; 数据流&#xff1a; 数据流和数组的区别&#xff1a; 代码实现 小顶堆类 测试类 力扣 我想成为一个强大、坦荡又热血的人&#xff0c;我爱霓虹闪烁&#xff0c;也爱高山流水&#xff0c;更爱我自己 —— 24.10.13 7…

视频的编解码格式

文章目录 视频的编解码格式概念术语视频处理流程视频封装格式视频编码格式视频编解码器&#xff0c;视频容器和视频文件格式之间的区别补充视频码率 参考资料 视频的编解码格式 概念术语 两大组织主导视频压缩的组织及其联合(joint)组织 ITU-T(VCEG) ITU-T的中文名称是国际电信…

【wpf】05 几种容器动态创建控件的对比

今天利用一点时间&#xff0c;对wpf中在常用容器中动态创建控件的方法进行了学习和测试&#xff0c;本篇文章用来记录这个过程及一些心得。 1 容器说明 用于对比的wpf常用容器类包括如下七种&#xff1a; StackPanelGridScrollViewerWrapPanelCanvasUniformGridDockPanel 以…

Vue实现动态表单

使用 Vue 实现动态表单 在前端开发中&#xff0c;我们经常遇到根据用户输入动态生成不同表单项的需求。这类动态表单不仅提升了用户体验&#xff0c;还可以让复杂的交互流程变得简洁而高效。本文将详细讲解如何使用 Vue 3 的响应式特性&#xff0c;逐步构建一个递归动态表单。…

服务器虚拟化的好处有哪些

1.什么是服务器虚拟化&#xff1f; 在计算服务中&#xff0c;有7种虚拟化包括硬件虚拟化&#xff08;即服务器虚拟化&#xff09;、软件虚拟化、内存虚拟化、存储虚拟化、数据虚拟化、网络虚拟化和桌面虚拟化。 其中&#xff0c;服务器虚拟化的初衷是让Windows和Linux操作系统…

【环境搭建】更换电脑后的开发环境怎么重建

目录 &#x1f378;前言 &#x1f37b;一、系统配置检查 &#x1f37a;二、开发环境搭建 &#x1f379;三、章末 &#x1f378;前言 小伙伴们大家好&#xff0c;这次文章跟技术没有关联&#xff0c;因为最近刚更换了装备&#xff0c;开发环境啥的残缺不全&#xff0c;也不能…