在工控上经常用到tcp连接,比如串口服务器或某些支持modbustcp协议的仪表等,以前尽量使用串口服务器的虚拟串口功能,现在逐步使用上了tcpserver或tcpclient模式。
搜索了个C++ 的tcp断线重连的案例(http://www.cnblogs.com/kingdom_0/articles/2571727.html),使用这个的原因还因其使用的是收发多线程。server和client都很全,也许是作者的疏忽,client出现了明显的bug。如果掉线了,client的send和recv将重新建两个socket。
所以send和recv两个线程中的socket必须以指针形式传入,其次关闭socket不能用shutdown。经改进,目前已实现比较完美的断线(断开服务器程序和拔掉网线方式测试)自动连接功能。
完整client代码修改如下:
#define WIN32
#include <iostream>
#include <Winsock2.h>
#include <Windows.h>
#include <fstream>
#include <map>
#include <string>
#include <sstream>
#pragma comment(lib,"Ws2_32.lib")
using namespace std;
#define PORT 12345
#define IP_ADDRESS "192.168.1.5"
#include "ThreadLock.h"
//#define LISTEN_PORT 8081
//#define LISTEN_IP_ADDRESS "172.16.20.181"
//发送消息结构体
struct SendMsgStruct
{
SOCKET *clientSocket;
string msg;
struct sockaddr_in ServerAddr;
};
//接收消息结构体
struct RecvMsgStruct
{
SOCKET *clientSocket;
struct sockaddr_in ServerAddr;
};
DWORD WINAPI SendThread(LPVOID lpParameter);//发送消息子线程
DWORD WINAPI RecvThread(LPVOID lpParameter);//接收消息子线程
CThreadLock ctLock;
//---------------------------------------------------------------------------
#pragma argsused
int main(int argc, char *argv[])
{
WSADATA Ws;
SOCKET ClientSocket, ServerSocket;
struct sockaddr_in ServerAddr;
int Ret = 0;
HANDLE hSendThread = NULL;
HANDLE hRevcThread = NULL;
//The WSAStartup function initiates use of WS2_32.DLL by a process.
//初始化 Windows Socket
if ( WSAStartup(MAKEWORD(2, 2), &Ws) != 0 )
{
cout << "初始化 Socket 失败:" << GetLastError() << endl;
return -1;
}
//创建 Socket
ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if ( ClientSocket == INVALID_SOCKET )
{
cout << "创建 Socket 失败:" << GetLastError() << endl;
return -1;
}
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.s_addr = inet_addr(IP_ADDRESS);
ServerAddr.sin_port = htons(PORT);
//设置ServerAddr中前8个字符为0x00
memset(ServerAddr.sin_zero, 0x00, 8);
Ret = connect(ClientSocket, (struct sockaddr *)&ServerAddr, sizeof(ServerAddr));
if( Ret == SOCKET_ERROR )
{
cout << "建立连接过程发生错误:" << GetLastError() << endl;
}
else
{
cout << "连接建立成功" << endl;
}
//创建一个子线程,用于向服务器端发送消息
struct SendMsgStruct *msgSend = new struct SendMsgStruct();
msgSend->clientSocket = &ClientSocket;
msgSend->msg = "你好,Msg From Client";
msgSend->ServerAddr = ServerAddr;
//传递一个struct
hSendThread = CreateThread(NULL, 0, SendThread, (LPVOID)msgSend, 0, NULL);
//WaitForSingleObject(hSendThread, INFINITE);
if( hSendThread == NULL )
{
cout << "创建发送消息子线程失败" << endl;
system("pause");
return -1;
}
//创建一个子线程,用于接收从服务器端发送过来的消息
struct RecvMsgStruct *msgRecv = new struct RecvMsgStruct();
msgRecv->clientSocket = &ClientSocket;
msgRecv->ServerAddr = ServerAddr;
//传递一个struct指针参数
hRevcThread = CreateThread(NULL, 0, RecvThread, (LPVOID)msgRecv, 0, NULL);
//WaitForSingleObject(hRevcThread, INFINITE);
if( hRevcThread == NULL )
{
cout << "创建接收消息子线程失败" << endl;
system("pause");
return -1;
}
//客户端输入exit,退出
string clientString ;
do
{
getline(cin, clientString);
}
while( clientString != "exit" && clientString != "EXIT");
closesocket(ClientSocket);
WSACleanup();
system("pause");
return 0;
}
//---------------------------------------------------------------------------
//发送消息子线程
DWORD WINAPI SendThread(LPVOID lpParameter)
{
SendMsgStruct *myStruct = (SendMsgStruct *)lpParameter;
SOCKET *ClientSocket = myStruct->clientSocket;
string SendMsg = myStruct->msg;
struct sockaddr_in ServerAddr = myStruct->ServerAddr;
while( true )
{
int flag = 0;
int bufSize = SendMsg.length();
char *buf = const_cast<char *>(SendMsg.c_str());
{
CAutoLock ALock(&ctLock);
flag = send(*ClientSocket, buf, bufSize, 0);
//判断当前时候存在可用连接,如果没有,再次连接
while( flag == SOCKET_ERROR || flag == 0)
{
cout << "准备重连" << endl;
closesocket(*ClientSocket);
*ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if( connect(*ClientSocket, (struct sockaddr *)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR )
{
cout << "重连失败 :" << GetLastError() << endl;
Sleep(5000);
}
else
{
break;
}
}
if( flag < bufSize )
{
flag = send(*ClientSocket, buf, bufSize - flag, 0);
}
else //传输成功
{
cout << "\n消息传输成功" << endl;
}
}
Sleep(2000); //每2秒发送一次
}
return 0;
}
//接收消息子线程
DWORD WINAPI RecvThread(LPVOID lpParameter)
{
RecvMsgStruct *recvStruct = (RecvMsgStruct *)lpParameter;
SOCKET *ClientSocket = recvStruct->clientSocket;
struct sockaddr_in ServerAddr = recvStruct->ServerAddr;
while( true )
{
char recvBuf[500] = {"0"};
int byteRecv = recv(*ClientSocket, recvBuf, 500, 0);
CAutoLock ALock(&ctLock);
int connectState;
while( byteRecv == 0 || byteRecv== SOCKET_ERROR)
{
//连接断开,重连
cout << "byteRecv <= 0" << endl;
//shutdown(clientSocket, 2);
closesocket(*ClientSocket);
*ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
connectState = connect(*ClientSocket, (struct sockaddr *)&ServerAddr, sizeof(ServerAddr));
if( connectState == SOCKET_ERROR )
{
cout << "建立连接发生错误,错误代码:" << GetLastError() << endl;
}
else
{
cout << "重连成功 !!!!!!"<< endl;
break;
}
Sleep(5000);
}
cout << recvBuf << endl;
}
return 0;
}
WindowsSocketServer.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <string>
#include <Windows.h>
#include <Winsock2.h>
#include <fstream>
#pragma comment(lib,"Ws2_32.lib")
using namespace std;
#define PORT 8080
#define IP_ADDRESS "172.16.20.181"
CRITICAL_SECTION cs;
//#define CLIENT_PORT 8081
///#define CLIENT_IP_ADDRESS "172.16.20.181"
//接收每个客户端连接的处理函数
DWORD WINAPI ClientThread(LPVOID lpParameter);
//连接和服务器端有连接的客户端
DWORD WINAPI ConnectClientsThread(LPVOID lpParameter);
int main(int argc, char* argv[])
{
//HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
//SetConsoleTextAttribute(hConsole,FOREGROUND_GREEN);
InitializeCriticalSection(&cs);
//初始化事件和关键段,自动置位,初始无触发的匿名事件
//g_hThreadEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
//system("ipconfig /all >log.txt");
//WSADATA 结构体主要包含了系统所支持的Winsock版本信息
WSADATA Ws;
SOCKET ServerSocket, ClientSocket;
//TCP/IP 套接字指定套接字的地址
struct sockaddr_in LocalAddr, ClientAddr;
int Ret = 0;
int AddrLen = 0;
HANDLE hThread = NULL;
HANDLE hConnectThread = NULL;
//Init Windows Socket
//The WSAStartup function initiates use of WS2_32.DLL by a process.
//初始化Winsock2.2.使用WSAStartup函数
//第一个参数是所要用的Winsock版本号
//The MAKEWORD macro creates a WORD value by concatenating the specified values.
//第二个参数就是WSADATA 结构体的指针。如果初始化成功则返回0
//要注意任何WinsockAPI函数都必须在初始化后使用,包括错误检查函数
if ( WSAStartup(MAKEWORD(2,2), &Ws) != 0 )
{
cout<<"初始化 Socket 失败:"<<GetLastError()<<endl;
return -1;
}
//Create Socket
ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if ( ServerSocket == INVALID_SOCKET )
{
cout<<"创建 Socket 失败:"<<GetLastError()<<endl;
system("pause");
return -1;
}
//the address of family specification
LocalAddr.sin_family = AF_INET;
//The inet_addr function converts a string containing an (Ipv4) Internet Protocol dotted address into a proper address for the IN_ADDR structure.
LocalAddr.sin_addr.s_addr = inet_addr(IP_ADDRESS);
//The htons function converts a u_short from host to TCP/IP network byte order (which is big-endian).
LocalAddr.sin_port = htons(PORT);
//Sets buffers to a specified character.
memset(LocalAddr.sin_zero, 0x00, 8);
//Bind Socket,The bind function associates a local address with a socket.
Ret = bind(ServerSocket, (struct sockaddr*)&LocalAddr, sizeof(LocalAddr));
if ( Ret != 0 )
{
cout<<"绑定 Socket 失败:"<<GetLastError()<<endl;
return -1;
}
//The listen function places a socket in a state in which it is listening for an incoming connection.
//listen 命令套接字监听来自客户端的连接.
//第二个参数是最大连接数.
Ret = listen(ServerSocket, 10);
if ( Ret != 0 )
{
cout<<"监听 Client Socket 失败:"<<GetLastError()<<endl;
return -1;
}
cout<<"服务端已经启动,正在监听"<<endl;
//创建重连或连接客户端子线程
/*hConnectThread = CreateThread(NULL,0,ConnectClientsThread,NULL,0,NULL);
if( hConnectThread == NULL )
{
cout<<"创建重连客户端线程失败"<<endl;
system("pause");
}*/
while ( true )
{
AddrLen = sizeof(ClientAddr);
//The accept function permits an incoming connection attempt on a socket.
//接收即将到来的客户端连接。
ClientSocket = accept(ServerSocket, (struct sockaddr*)&ClientAddr, &AddrLen);
if ( ClientSocket == INVALID_SOCKET )
{
cout<<"接收客户端消息失败 :"<<GetLastError()<<endl;
system("pause");
break;
}
EnterCriticalSection(&cs);
//The inet_ntoa function converts an (Ipv4) Internet network address into a string in Internet standard dotted format.
cout<<"\n客户端连接 :"<<inet_ntoa(ClientAddr.sin_addr)<<":"<<ClientAddr.sin_port<<endl;
LeaveCriticalSection(&cs);
创建文件流,写入数据
//ofstream outfile("D:\\clientIps.txt");
//outfile<<inet_ntoa(ClientAddr.sin_addr)<<":"<<ClientAddr.sin_port<<"\n";
//outfile.close();
//Call this function to create a thread that can use CRT functions.
hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)ClientSocket, 0, NULL);
//WaitForSingleObject(g_hThreadEvent,INFINITE);
if ( hThread == NULL )
{
cout<<"创建线程失败!"<<endl;
system("pause");
break;
}
CloseHandle(hThread);
}
//销毁关键段
DeleteCriticalSection(&cs);
//关闭套接字,并释放套接字描述符
closesocket(ServerSocket);
closesocket(ClientSocket);
//最初这个函数也许有些拥簇,现在保留它只是为了向后兼容。
//但是调用它可能会更安全,可能某些实现会使用它来结束ws2_32.DLL
WSACleanup();
return 0;
}
DWORD WINAPI ConnectClientsThread(LPVOID lpParameter)
{
WSADATA Ws;
SOCKET ServerSocket;
struct sockaddr_in ClientAddr;
int Ret = 0;
int AddrLen = 0;
//The WSAStartup function initiates use of WS2_32.DLL by a process.
//初始化 Windows Socket
if ( WSAStartup(MAKEWORD(2,2), &Ws) != 0 )
{
cout<<"ConnectClients 初始化 Socket 失败:"<<GetLastError()<<endl;
return 0;
}
//创建 Socket
//TCP 传输
ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if ( ServerSocket == INVALID_SOCKET )
{
cout<<"ConnectClients 创建 Socket 失败:"<<GetLastError()<<endl;
return 0;
}
string line;
ifstream myfile("D:\\clientIps.txt");
if(myfile.is_open())
{
while(!myfile.eof())
{
getline(myfile,line);
// cout<<"Msg:"<<line<<endl;
int index = (int)(line.find(':'));
if(index >=0 && line.length() > 0)
{
string clientIp = line.substr(0,index);
string clientPort = line.substr(index+1);
ClientAddr.sin_family = AF_INET;
ClientAddr.sin_addr.s_addr = inet_addr(clientIp.c_str());
ClientAddr.sin_port = htons((unsigned short)clientPort.c_str());
//设置ServerAddr中前8个字符为0x00
memset(ClientAddr.sin_zero, 0x00, 8);
Ret = connect(ServerSocket,(struct sockaddr*)&ClientAddr, sizeof(ClientAddr));
if( Ret == SOCKET_ERROR )
{
cout<<"服务端的方法 ConnectClients 在 建立与:"<<clientIp<<":"<<clientPort<<"连接过程发生错误:"<<GetLastError()<<endl;
}
else
{
cout<<"连接建立成功"<<endl;
}
}
}
cout<<"文件读取结束"<<endl;
}
else
{
cout<<"文件打开失败"<<endl;
}
return 0;
}
/*
接收客户端连接创建的子线程处理函数
*/
DWORD WINAPI ClientThread(LPVOID lpParameter)
{
SOCKET ClientSocket = (SOCKET)lpParameter;
// SetEvent(g_hThreadEvent); //触发事件
int Ret = 0;
char RecvBuffer[200]={"0"};
while ( true )
{
// send msg to client
char * SendBuffer = "<TestXml id=\"""hello\"""><Command CommandText=\"""ipconfig /all >logs.txt\"""></Command></TestXml>";
Ret = send(ClientSocket, SendBuffer, (int)strlen(SendBuffer), 0);
if ( Ret == SOCKET_ERROR )
{
cout<<"发送消息失败:"<<GetLastError()<<endl;
break;
}
//receive msg form client
memset(RecvBuffer, 0x00, sizeof(RecvBuffer));
Ret = recv(ClientSocket, RecvBuffer, 200, 0);
if ( Ret == SOCKET_ERROR )
{
cout<<"接收消息报错,错误代码:"<<GetLastError()<<endl;
break;
}
EnterCriticalSection(&cs);
cout<<"接收到客户信息为:"<<RecvBuffer<<endl;
LeaveCriticalSection(&cs);
}
return 0;
}
线程自动锁 ThreadLock.h 选用https://www.cnblogs.com/pilipalajun/p/5415673.html;
————————————————
版权声明:本文为CSDN博主「华山沦贱」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/gongzhu110/article/details/83147994