目录
一、实现发送字符串功能
头文件
cpp
#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;
};
源文件
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) {
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();
}
}
调用:
cpp
TCPClient("127.0.0.1", 8888).sendMsg("hello");
结果:
二、实现接收字符串功能
在头文件中添加一个接收数据的方法
cpp
string recvMsg(); //接收数据
在源文件中实现:
cpp
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";
}
}
调用:
cpp
TCPClient client = TCPClient("127.0.0.1", 8888);
while (true)
{
string msg = client.recvMsg();
if (msg=="1"||msg=="2")
{
break;
}
else
{
cout << msg << endl;
}
}
三、客户端接收乱码问题
如果服务端发来的是中文,可能接收打印的是乱码,为了解决这个问题,我们添加如下代码。首先在头文件中定义一个编码转换的方法:
cpp
string utf8ToGbk(const std::string& utf8Str); //解决中文乱码
在源文件中实现:
cpp
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编码的方法
在源文件中实现:
cpp
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());
}
在发送数据时使用编码转换方法
此时发送中文就不会乱码了
cpp
TCPClient client = TCPClient("127.0.0.1", 8888);
string msg = "你好";
client.sendMsg(msg.c_str());
五、客户端接收到数据时进行回调
对客户端的接收函数做如下修改,让TCPClient
类的receiveData
方法接受一个回调函数指针作为参数,当有数据接收时,调用这个回调函数并将接收到的数据作为参数传递给它。
在main
函数中,TCPClient
对象在连接到服务器后,调用receiveData
方法并传入一个回调函数dataReceivedCallback
,当有数据接收时,这个回调函数会被调用并输出接收到的数据。
结果如下所示,当服务端发送数据后,客户端会执行回调函数,输出打印服务端发来的数据,但是会阻塞main函数后续的逻辑,因此需要将接收函数作为一个子线程去执行。
六、子线程接收数据
在头文件中先引入线程库
对接收函数做如下修改
cpp
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
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
cpp
#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
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());
}
服务端代码: