当前位置 博文首页 > 粽子小哥的博客:C++实现Tcp通信(考虑客户端和服务端断开重连的

    粽子小哥的博客:C++实现Tcp通信(考虑客户端和服务端断开重连的

    作者:[db:作者] 时间:2021-07-26 21:02

    目录

    实现功能:

    服务端实现流程:

    客户端实现流程

    运行结果

    其他问题


    实现功能:

    • 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编译

    ?


    cs