Simple use of HP socket in C + +

brief introduction

HP socket is a general high-performance TCP/UDP /HTTP communication framework, including server components, client components and Agent components. It is widely applicable to TCP/UDP /HTTP communication systems in various application scenarios, and provides programming language interfaces such as C/C + +, c#, Delphi, E (easy language), Java and Python.

HP socket is a set of domestic open source communication library, which is implemented in C + + language, provides interfaces of multiple programming languages, and supports Windows and Linux platforms:

HP socket contains more than 30 components, which can be classified according to the communication role (Client/Server), communication protocol (TCP/UDP/HTTP) and receiving model (PUSH/PULL/PACK). Here is a brief introduction:

  • Server component: Based on IOCP/EPOLL communication model, combined with cache pool, private heap and other technologies, it realizes efficient memory management and supports super large-scale and high concurrency communication scenarios.
  • Agent component: in essence, it is a multi client component, which adopts the same technical architecture as the Server component, and can establish and efficiently handle large-scale Socket connections at the same time.
  • Client component: Based on the Event Select/POLL communication model, each component object creates a communication thread and manages a Socket connection, which is suitable for small-scale client scenarios.
  • Thread Pool component: an efficient and easy-to-use Thread Pool component implemented by HP socket. It can be used as an ordinary third-party Thread Pool library.

The TCP component of HP socket supports three receiving models: PUSH, PULL and PACK:

  • PUSH model: when the component receives data, it will trigger the OnReceive(pSender,dwConnID,pData,iLength) event of the listener object to "PUSH" the data to the application. This model is the most free to use.
  • PULL model: when the component receives data, it will trigger the onReceive (psender, dwconnid, itotalength) event of the listener object to tell the application how much data has been received. The application will check the length of the data. If it meets the needs, it will call the * * fetch (dwconnid, pdata, idatalongth) method of the component to
    Data to be "pulled" out.
  • PACK model: PACK model series components are a combination of PUSH and PULL models. The application does not have to deal with subcontracting and data capture. The components ensure that each OnReceive event provides a complete data packet to the application.

Note: the PACK model component will automatically add 4 bytes (32-bit packet header) to each packet sent by the application. The first 10 bits are the packet header identification bits for packet verification, and the last 22 bits are the length bits for recording the length of the packet body.

Mode of use

HP socket supports MBCS and Unicode character sets, and supports 32-bit and 64 bit applications. HP socket can be used through source code, DLL or LIB. HPSocket DLL and HPSocket4C DLL have been provided in the HP socket distribution package.
HP socket provides dll files in various cases without recompiling. dll files are divided into two categories according to programming interface:

  • HPSocket DLL: it is the preferred way to export C + + programming interface and C + + program. When using, you need to add SocketInterface.h (and its dependent file HPTypeDef.h), HPSocket.h and *. lib file corresponding to DLL to the project. When using SSL components, you also need HPSocket-SSL.h file.
  • HPSocket4C DLL: export the C programming interface and provide it to C language or other programming languages. When using, you need to add HPSocket4C.h and the *. lib file corresponding to the DLL to the project. When using SSL components, you also need HPSocket4C-SSL.h file.

Implement simple thread pool

Using the thread pool component of HP socket, you can implement a simple and public thread pool in the program. The thread pool will be used for disconnection and reconnection of TCP communication and sending heartbeat. The main functions of the thread pool component are as follows:

  • Start: start the thread pool. For specific use, please refer to the notes of the source code.
  • Submit: to submit a task, BOOL Submit(fnTaskProc,pvArg,dwMaxWait=INFINITE) is mainly used. Another function overload uses a special data type (encapsulating Socket task parameters and task functions into a data structure) as parameters.
  • Stop: close the thread pool. The parameter dwMaxWait represents the maximum waiting time (milliseconds. Default: INFINITE, waiting all the time).

First implement the CHPThreadPoolListener interface of the thread pool, and then construct the IHPThreadPool smart pointer. The subsequent operations of the thread pool are operated through the smart pointer. The code is as follows:

class CHPThreadPoolListenerImpl : public CHPThreadPoolListener
{
private:
	void LogInfo(string logStr)
	{
		cout <<"ThreadPool " <<logStr << endl;
	}
public:
	virtual void OnStartup(IHPThreadPool* pThreadPool) 
	{
		LogInfo("Thread pool startup");
	}
	virtual void OnShutdown(IHPThreadPool* pThreadPool) 
	{
		LogInfo("Thread pool startup and shutdown");
	}
	virtual void OnWorkerThreadStart(IHPThreadPool* pThreadPool, THR_ID dwThreadID) 
	{				
		LogInfo("[" + to_string(dwThreadID) + "] " + "Worker thread start");
	}
	virtual void OnWorkerThreadEnd(IHPThreadPool* pThreadPool, THR_ID dwThreadID) 
	{
		LogInfo("[" + to_string(dwThreadID) + "] " + "Worker thread exit");
	}
};

CHPThreadPoolListenerImpl ThreadPoolListener;
//Global shared variables are decorated with the extern keyword
extern CHPThreadPoolPtr ThreadPool(&ThreadPoolListener);

Implement TCP client

First, implement a print function to display the information related to the client. The code is as follows:

void PrintInfo(ITcpClient* pSender, CONNID dwConnID)
{
	char buffer[20];	
	TCHAR* ipAddr = buffer;
	int ipLen;
	USHORT port;

	pSender->GetLocalAddress(ipAddr, ipLen, port);	
	cout << string(ipAddr,0,ipLen) << ":" << port << " " << " [" << dwConnID << "] -> ";

	pSender->GetRemoteHost(ipAddr, ipLen, port);	
	cout << string(ipAddr, 0, ipLen) << ":" << port << " ";
}

Implement the CTcpClientListener listening interface. After the client is disconnected, it automatically reconnects and divides the received string with a newline character. The code is as follows:

bool SysExit = false;
void ReConnect(ITcpClient* pSender)
{
	while (pSender->GetState() != SS_STOPPED)
	{
		Sleep(10);
	}
	pSender->Start("127.0.0.1", 60000);
}

class CClientListenerImpl : public CTcpClientListener
{

public:	
	virtual EnHandleResult OnConnect(ITcpClient* pSender, CONNID dwConnID) 
	{ 
		PrintInfo(pSender, dwConnID);
		cout << "Connection succeeded" << endl;
		return HR_OK;		
	}

	string resStr = "";
	string commStr="";
	virtual EnHandleResult OnReceive(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) 
	{
		
		string str((char*)pData,0, iLength);
		resStr.append(str);
		int index;
		while (true)
		{
			index = resStr.find("\r\n");
			if (index == -1)break;

			commStr = resStr.substr(0, index);
			resStr = resStr.substr(index +2, resStr.length() - (index +2));
			if (commStr!="")
			{
				PrintInfo(pSender, dwConnID);
				cout << "Split string received " << commStr << endl;
			}
		}	

		PrintInfo(pSender, dwConnID);
		cout << "Data acceptance " << str << endl;

		return HR_OK;
	}

	virtual EnHandleResult OnClose(ITcpClient* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode)
	{
		resStr = "";

		PrintInfo(pSender, dwConnID);
		cout << "Disconnected,"<< enOperation <<"Error caused by operation, error code " << iErrorCode<< endl;
		if (!SysExit) 
		{
			ThreadPool->Submit((Fn_TaskProc)(&ReConnect), (PVOID)pSender);
		}		
		return HR_OK;
	}	
};

The circular input string is sent to the server, and the code is as follows:

int main()
{
	//Start thread pool
	ThreadPool->Start();

	CClientListenerImpl listener;
	CTcpClientPtr client(&listener);

	if (!client->Start("127.0.0.1", 60000))
	{
		cout << "Connection error:" << client->GetLastError() << "-" << client->GetLastErrorDesc();
	}
	
	string sendMsg;
	while (!SysExit)
	{		
		cin >> sendMsg;
		if (sendMsg == "esc") 
		{
			SysExit = true;
			break;
		}

		if (client->GetState() == SS_STARTED) 
		{
			const BYTE* data = (BYTE*)(sendMsg.c_str());
			if (client->Send(data, sizeof(data)))
			{
				PrintInfo(client, client->GetConnectionID());
				cout << "Sent successfully "<<sendMsg<<endl;
			}
			else
			{
				PrintInfo(client, client->GetConnectionID());
				cout << "Sending failed, error description " << client->GetLastError() << "-" << client->GetLastErrorDesc() << endl;
			}
		}
		else 
		{
			PrintInfo(client, client->GetConnectionID());			
			cout << "Unable to send, current status " <<client->GetState()<< endl;
		}
	}	
	client->Stop();
	//Close thread pool
	ThreadPool->Stop();

	return 0;	
}

Implement TCP server

First, implement a print function, which is basically the same as that of the client, except for obtaining the local IP. The code is as follows:

void PrintInfo(ITcpServer* pSender, CONNID dwConnID)
{
	char buffer[20];
	TCHAR* ipAddr = buffer;
	int ipLen;
	USHORT port;

	pSender->GetListenAddress(ipAddr, ipLen, port);
	cout << string(ipAddr, 0, ipLen) << ":" << port << " " << "<-  [" << dwConnID << "] ";

	pSender->GetRemoteAddress(dwConnID, ipAddr, ipLen, port);
	cout << string(ipAddr, 0, ipLen) << ":" << port << " ";
}

To demonstrate the binding of client and application data, define a user data type and create a queue. The code is as follows:

class UserData 
{
public:
	UserData(string name="") 
	{
		Name = name;
	}
	string Name;
};
queue<UserData*> qName;  //Create queue object 

Implement the CTcpServerListener listening interface. After receiving the string, add the user name and send it back. The code is as follows:

class CTcpServerListenerImpl : public CTcpServerListener
{
public:	
	virtual EnHandleResult OnAccept(ITcpServer* pSender, CONNID dwConnID, UINT_PTR soClient)
	{ 

		pSender->SetConnectionExtra(dwConnID,qName.front());
		qName.pop();
		PrintInfo(pSender, dwConnID);
		cout << "Connection succeeded" << endl;
		return HR_OK;
	}
	virtual EnHandleResult OnReceive(ITcpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength)
	{ 
		string str((char*)pData, 0, iLength);
		PrintInfo(pSender, dwConnID);
		cout << "Data acceptance " << str<<endl;

		PVOID pInfo = nullptr;		
		pSender->GetConnectionExtra(dwConnID, &pInfo);
		str = "reply-" + ((UserData*)pInfo)->Name + str;

		const BYTE* data = (BYTE*)(str.c_str());		
		pSender->Send(dwConnID, data,str.size());
		return HR_OK;
	}	
	virtual EnHandleResult OnClose(ITcpServer* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode)
	{
		PVOID pInfo = nullptr;
		pSender->GetConnectionExtra(dwConnID, &pInfo);
		qName.push((UserData*)pInfo);
		PrintInfo(pSender, dwConnID);
		cout << "Disconnect"<< endl;

		pSender->SetConnectionExtra(dwConnID, NULL);		
		return HR_OK;
	}
};

Send the cyclic input string to the client and automatically reply to the message sent by the client. The code is as follows:

bool SysExit = false;
int main()
{	
	UserData user1("NO1-User");
	UserData user2("NO2-User");
	UserData user3("NO3-User");
	UserData user4("NO4-User");

	qName.push(&user1);
	qName.push(&user2);
	qName.push(&user3);
	qName.push(&user4);

	CTcpServerListenerImpl listener;
	CTcpServerPtr server(&listener);
		
	if (!server->Start("127.0.0.1", 60000))
	{
		cout << "Startup error:" << server->GetLastError() << "-" << server->GetLastErrorDesc();
	}
	
	string sendMsg;
	while (!SysExit)
	{
		cin >> sendMsg;
		if (sendMsg == "esc")
		{
			SysExit = true;
			break;
		}

		//If the array length is less than the current number of connections, the acquisition fails
		DWORD count= 1000;			
		CONNID pIDs[1000]; 
		ZeroMemory(pIDs, 1000);;

		if (server->GetAllConnectionIDs(pIDs, count)&& count >0)
		{
			for (size_t i = 0; i < count; i++)
			{
				const BYTE* data = (BYTE*)(sendMsg.c_str());
				if (server->Send(*(pIDs+i),data, sendMsg.size()))
				{
					PrintInfo(server, pIDs[i]);
					cout << "Sent successfully " << sendMsg << endl;
				}
				else
				{
					PrintInfo(server, pIDs[i]);
					cout << "Sending failed, error description " << server->GetLastError() << "-" << server->GetLastErrorDesc() << endl;
				}
			}		
		}
		else
		{			
			cout << "Unable to send, current number of connections " << count << endl;
		}
	}
	server->Stop();
}

Note: when obtaining connections, the length of the pointer array must be greater than the current number of connections, otherwise it will fail.

Implement Http client

HP socket's Http client has two types: synchronous and asynchronous. The synchronous client does not need to bind a listener. Here, the synchronous client is used for demonstration.

Sync Client: the synchronization HTTP client components (CHttpSyncClient and CHttpsSyncClient) handle all events internally. Therefore, they do not need to bind listeners (the listener parameter of the construction method is passed in null); If a listener is bound, you can track the communication process of the component.

Test client can use Real time weather interface The above test example, the current test example is:

http://api.k780.com/?app=weather.today&weaId=1&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json

Start the test directly, and the code is as follows:

int main()
{
    CHttpSyncClientPtr SyncClient;
    THeader type;
    type.name = "Content-Type";
    type.value = "text/html;charset=UTF-8";
    
    if (SyncClient->OpenUrl("GET", "http://api.k780.com/?app=weather.today&weaId=1&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json",&type))
    {
        LPCBYTE pData = nullptr;
        int iLength = 0;
        SyncClient->GetResponseBody(&pData, &iLength);        
        string body((char*)pData, iLength);
        //The returned is in Chinese, and the encoding format needs to be converted
        cout << body << endl;        
        cout << endl;
        cout << StringToUtf(body) << endl;
        cout << endl;
        cout << UtfToString(StringToUtf(body)) << endl;
    }
    else
    {
        cout << "Open failed:"<<SyncClient->GetLastError()<<"-"<< SyncClient->GetLastErrorDesc()<<endl;
    }   
}

The StringToUtf and UtfToString functions above are reproduced to C + + Chinese garbled code problem , this function realizes the conversion of UTF-8 and ANSI coding formats. The code is as follows:

string UtfToString(string strValue)
{
    int nwLen = ::MultiByteToWideChar(CP_ACP, 0, strValue.c_str(), -1, NULL, 0);
    wchar_t* pwBuf = new wchar_t[nwLen + 1];//Add end '\ 0'
    ZeroMemory(pwBuf, nwLen * 2 + 2);
    ::MultiByteToWideChar(CP_ACP, 0, strValue.c_str(), strValue.length(), pwBuf, nwLen);
    int nLen = ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, -1, NULL, NULL, NULL, NULL);
    char* pBuf = new char[nLen + 1];
    ZeroMemory(pBuf, nLen + 1);
    ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);
    std::string retStr(pBuf);
    delete[]pwBuf;
    delete[]pBuf;
    pwBuf = NULL;
    pBuf = NULL;
    return retStr;
}

string StringToUtf(string strValue)
{
    int nwLen = MultiByteToWideChar(CP_UTF8, 0, strValue.c_str(), -1, NULL, 0);
    wchar_t* pwBuf = new wchar_t[nwLen + 1];//Add end '\ 0'
    memset(pwBuf, 0, nwLen * 2 + 2);
    MultiByteToWideChar(CP_UTF8, 0, strValue.c_str(), strValue.length(), pwBuf, nwLen);
    int nLen = WideCharToMultiByte(CP_ACP, 0, pwBuf, -1, NULL, NULL, NULL, NULL);
    char* pBuf = new char[nLen + 1];
    memset(pBuf, 0, nLen + 1);
    WideCharToMultiByte(CP_ACP, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);
    std::string retStr = pBuf;
    delete[]pBuf;
    delete[]pwBuf;
    return retStr;
}

Note: the function implementation should be placed before the main function.

enclosure

Tags: C++

Posted on Thu, 11 Nov 2021 13:16:27 -0500 by PyraX