C++实现Tcp通信(考虑客户端和服务端断开重连的情况)

目录

实现功能:

服务端实现流程:

客户端实现流程

运行结果

其他问题


实现功能:

  • Tcp客户端
  • Tcp服务端
  • 客户端等待服务端启动
  • 服务端等待客户端启动

服务端实现流程:

  • 链接相关库
	//链接相关库
	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA wsaData;
	if (WSAStartup(sockVersion, &wsaData) != 0)//WSAStartup返回0表示设置初始化成功
	{
		std::cout << "添加相关链接库失败" << std::endl;
		return false;
	}
  • 创建监听socket
/*创建套接字*/
	//AF_INET表示IPv4,SOCK_STREAM数据传输方式,IPPROTO_TCP传输协议;
	listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (listenSocket == INVALID_SOCKET)
	{
		printf("套接字创建失败");
		closesocket(listenSocket);
		return false;
	}
  • bind IP和端口
	//绑定端口
	sockaddr_in addrListen;
	addrListen.sin_family = AF_INET;     //指定IP格式
	addrListen.sin_port = htons(20001);   //绑定端口号
	addrListen.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// INADDR_ANY表示任何IP   
	if (bind(listenSocket, (SOCKADDR*)&addrListen, sizeof(SOCKADDR)) == SOCKET_ERROR) 
	{
		printf("绑定失败");
		closesocket(listenSocket);
		return false;
	}
  • 开始监听
/*开始监听*/
	if (listen(listenSocket, 5) == SOCKET_ERROR)
	{
		printf("监听出错");
		closesocket(listenSocket);
		return false;
	}
  • 开线程,等待客户端socket连接,直到链接为止
  • 连接上后,调用recv,用返回的socket进行阻塞方式数据接收
//开启线程 
_revDataThead = new std::thread(&TcpServerImpl::RevData, this);
//等待链接函数
Bool WaitClientConnect()
{
	/*等待连接,连接后建立一个新的套接字*/
	//对应此时所建立连接的套接字的句柄
	sockaddr_in remoteAddr;   //接收连接到服务器上的地址信息
	int remoteAddrLen = sizeof(remoteAddr);
	/*等待客户端请求,服务器接收请求*/
	revSocket = accept(listenSocket, (SOCKADDR*)&remoteAddr, &remoteAddrLen);  //等待客户端接入,直到有客户端连接上来为止
	if (revSocket == INVALID_SOCKET)
	{
		printf("客户端发出请求,服务器接收请求失败:\n", WSAGetLastError());
		closesocket(revSocket);
		return false;
	}
	else
	{
		printf("客服端与服务器建立连接成功:%s \n", inet_ntoa(remoteAddr.sin_addr));
		return true;
	}
}
//数据接收线程
void TcpServerImpl::RevData()
{
	while (1)//用于阻塞,重新等待链接(客户端断开后重新连接)
	{
		printf("等待连接...\n");
		if (WaitClientConnect())
		{
			char revData[200];
			memset(revData, 0, sizeof(revData));
			while (recv(revSocket, revData, 200, 0) > 0)//用于阻塞接受多个多个客户端,当返回值  <0 or ==0 均需要重新等待客户端连接
			{
				printf("接收到客户端发送的数据: %s\n", revData);
				//收到数据TODO处理
				//------------测试,收到客户端的数据后回复----begin-----
				std::string str = "朕已阅";
				char sendstr[200];
				memset(sendstr, 0, 200);
				memcpy(sendstr, str.c_str(), sizeof(str));
				SendData(sendstr);
				//------------测试-----------------------------end--------
			}
		}
		closesocket(revSocket);
		Sleep(1000);		
	}
}
  • 实现send函数
void TcpServerImpl::SendData(char*sendData)
{

	if (send(revSocket, sendData, strlen(sendData), 0) == SOCKET_ERROR)
	{
		printf("服务端send()出现错误 : %d\n", WSAGetLastError());;
	}
	else
	{
		printf("服务端发送数据:%c 成功!\n", sendData);
	}
}

客户端实现流程

  • 链接相关库
//链接相关库
	WORD sockVerson = MAKEWORD(2, 2);
	WSADATA wsaData;
	if (WSAStartup(sockVerson, &wsaData) != 0)
	{ 
		return false;
	}
  • 开线程,创建客户端socket,循环尝试连接服务器
  • 连接上服务器后,调用recv阻塞进入数据接收阶段
//单开线程保持与服务端的联系,连接成功后等待服务端的消息
	revDataThead = new std::thread(&TcpClientImpl::RevData,this);

bool TcpClientImpl::CreateSocketAndConnect()
{
	//建立客户端socket
	clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (clientSocket == INVALID_SOCKET)
	{
		printf("套接字创建失败: %d  \n", WSAGetLastError());
		closesocket(clientSocket);
		clientSocket = NULL;
		return false;
	}
	//定义要连接的服务器地址
	sockaddr_in addrConServer;
	addrConServer.sin_family = AF_INET;
	addrConServer.sin_port = htons(20001);
	addrConServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (connect(clientSocket, (SOCKADDR*)&addrConServer, sizeof(addrConServer)) == SOCKET_ERROR)
	{
		printf("客户端建立连接失败!\n");
		closesocket(clientSocket);
		clientSocket = NULL;
		return false;
	}
	else
	{
		printf("客户端建立连接成功,准备发送数据!\n");
		return true;
	}
}
void TcpClientImpl::RevData()
{
	while (1)//尝试重新创建与连接(当服务端断开再启动后,期间需要保持尝试连接)
	{
		if (CreateSocketAndConnect())
		{
			char revSerData[200];
			memset(revSerData, 0, sizeof(revSerData));
			while (recv(clientSocket, revSerData, sizeof(revSerData), 0) > 0)//阻塞进入数据接收阶段,<0或==0情况均需要重新连接
			{
				printf("服务器发送的数据: %s\n", revSerData);
				//接收到服务端数据,TODO处理
			}		
		}
		else
		{
			Sleep(1000);
			continue;		
		}
		closesocket(clientSocket);
		clientSocket = NULL;
		Sleep(1000);
	}
}
  • 实现send函数
bool TcpClientImpl::SendData(char* buffer)
{
	//发送数据
	int sendRes = send(clientSocket, buffer, (int)strlen(buffer), 0);
	if (sendRes == SOCKET_ERROR)
	{
		printf("客户端send()出现错误 : %d\n", WSAGetLastError());
		return false;
	}
	else
		printf("客户端发送数据:%c 成功!\n", buffer);

}

运行结果

其他问题

后面遇到涉及在线程中怎么将数据发出到其他线程或者线程以外的对象处理的问题,想了几种方案:

  1. 如果用到数据的线程是等待数据接收后同步的,可以定义全局变量赋值就行了,只是要加锁
  2. 当需要触发其他线程处理的时候,可以用信号的形式触发,实际中用过QT和boost信号实现。
  3. 线程以外对象,处理函数写在对象中,开接受线程是,将对象传入,等待接收到数据后,直接调用对象处理数据。

注:本文采用VS2015编译

 


  • 2
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
可以使用libmodbus库实现C++中的Modbus TCP客户端服务端读写。以下是一个简单的示例代码,其中包含了客户端服务端实现: ```cpp #include <modbus/modbus.h> // 服务端 void server() { modbus_t* ctx = modbus_new_tcp("127.0.0.1", 502); // 创建TCP连接 modbus_set_slave(ctx, 1); // 设置从机地址 modbus_mapping_t* mapping = modbus_mapping_new(10, 10, 10, 10); // 创建映射表 while (true) { uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH]; int rc = modbus_receive(ctx, query); // 接收客户端请求 if (rc > 0) { uint16_t addr, nb; modbus_get_header(ctx, query, &addr, MODBUS_FC_READ_HOLDING_REGISTERS, &nb); // 获取请求信息 if (nb > MODBUS_MAX_READ_REGISTERS) { nb = MODBUS_MAX_READ_REGISTERS; // 防止读取寄存器数量超过最大值 } modbus_reply(ctx, query, rc, mapping->tab_registers + addr, nb); // 响应请求 } else if (rc == -1) { break; // 连接断开 } } modbus_mapping_free(mapping); // 释放映射表内存 modbus_close(ctx); // 关闭连接 modbus_free(ctx); // 释放资源 } // 客户端 void client() { modbus_t* ctx = modbus_new_tcp("127.0.0.1", 502); // 创建TCP连接 modbus_connect(ctx); // 连接到远程设备 uint16_t read_data[10]; uint16_t write_data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; modbus_write_registers(ctx, 0, 10, write_data); // 写入寄存器 modbus_read_registers(ctx, 0, 10, read_data); // 读取寄存器 modbus_close(ctx); // 关闭连接 modbus_free(ctx); // 释放资源 } int main() { // 启动服务端 std::thread server_thread(server); server_thread.detach(); // 启动客户端 client(); return 0; } ``` 需要注意的是,服务端需要在单独的线程中运行,否则会阻塞主线程。同时,服务端也需要创建一个映射表来存储寄存器的值,客户端需要指定从机地址才能与服务端通信
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值