TCP chat room 03 server-side programming

//Server.cpp

/*-----------------------------------------------------------------
   Chat room sample program using TCP protocol (server side)
-----------------------------------------------------------------*/
#include <Windows.h>
#include  <process.h>
#include "resource.h"

#pragma comment(lib,"Ws2_32.lib")

#include "Msg.h"

extern int iMsgSeq;
extern CRITICAL_SECTION csMsgQueue;
TCHAR szApp[] = TEXT("Tcp chat room server");
TCHAR szSysInfo[] = TEXT("System Message");
TCHAR szLogin[] = TEXT("Enter the chat room!");
TCHAR szLogout[] = TEXT("Log out of the chat room!");

int iThreadCnt = 0;
HWND hWnd = NULL;//Dialog box handle
BOOL bStopFlag = FALSE;//Exit flag


int CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	//invoke	DialogBoxParam,eax,DLG_MAIN,NULL,offset _ProcDlgMain,0
	DialogBoxParamW(hInstance, TEXT("CHATSERVICE"), NULL, DlgProc, 0);
    return 0;
}

//Detect the last activity time of the link
//pBuffer - points to the link detection packet to be sent
//pSession - points to the last session information
//Return value: link up (TRUE); link down (FALSE)
BOOL LinkCheck(SOCKET hSocket, char* lpszBuff, PSSESSION pSession){
	BOOL bRet = FALSE;
	PSMSGPKG pMsg = (PSMSGPKG)lpszBuff;/*The lpszBuff at this time is encapsulated into a link check packet*/
	DWORD dwTime = GetTickCount();
	//Check if you need to detect the link
	if((dwTime - pSession->dwLastTime) < 30*1000)		return TRUE;

	//If there is no data communication within 30 seconds, send a link detection packet
	pSession->dwLastTime = GetTickCount();
	pMsg->stMsgHdr.iCmdType = CMD_LINK_CHECK;
	pMsg->stMsgHdr.iPkgLen = sizeof(SMSGHDR);

	 //Send the packet to detect the link (just send the packet header)
	return (send(hSocket, lpszBuff, pMsg->stMsgHdr.iPkgLen, 0) != SOCKET_ERROR);
}
/ / Loop to take the chat statement in the message queue and send it to the client until all messages are sent
//pBuffer - pointer to the buffer of the message taken from the message queue, which will be sent to the client
//pSession - points to the last session information
//Return value: TRUE - normal
// FALSE - an error occurred
BOOL SendMsgFromQueue(SOCKET hSocket, char* lpszBuff, PSSESSION pSession){
	PSMSGPKG pMsg = (PSMSGPKG)lpszBuff;/*The lpszBuff at this time is encapsulated into a delivery packet*/
	int iMsgId = pSession->iMsgId + 1;//iMsgId is the last message obtained by the session, take its next message
	while(!bStopFlag){
		int iRet = GetMsgFromQueue(iMsgId++, pMsg->stMsg2Client.szSender, pMsg->stMsg2Client.szContent);
		if(iRet == 0)			break;

		pSession->iMsgId = iRet;
		pMsg->stMsg2Client.iContent = (lstrlen(pMsg->stMsg2Client.szContent)+1)*sizeof(TCHAR);
		pMsg->stMsgHdr.iPkgLen = sizeof(SMSGHDR) + OFFSET(SMSG2CLIENT, szContent) + pMsg->stMsg2Client.iContent;
		pMsg->stMsgHdr.iCmdType = CMD_MSG_TO_CLIENT;
		iRet = send(hSocket, (char*)pMsg, pMsg->stMsgHdr.iPkgLen, 0);
		if(iRet == SOCKET_ERROR)				return FALSE;
		pSession->dwLastTime = GetTickCount();//Update the last session time

		 //When multiple people chat, the messages in the queue will increase sharply, in order to prevent the sending speed from being slow
        //The messages in the queue will accumulate more and more, resulting in no chance to exit the loop to receive messages from this SOCKET
        //(that is, the client served by this thread) message, so after each data is sent, go to WaitData through WaitData
        //Look, whether there is data arriving, if so, exit the process of sending a message, and give priority to processing the data to be received
		iRet = WaitSocket(hSocket, 0);
		if(iRet == SOCKET_ERROR) return FALSE;//If the link is broken
		if(iRet > 0) break;//If there is data to be received, exit and process first
	}
	return TRUE;
}


//Communication service thread, each client logged in connection will generate a thread
unsigned int WINAPI ServiceProc(void* lpParam)
{
	int iRet = -1;//For receiving the return value of the calling function
	SOCKET hSrvSock = (SOCKET) lpParam;
	//szBuff message receiving and sending buffer (can be subdivided into any type of message) let pMsgStruct point to the buffer
	char szBuff[512]; memset(szBuff, 0, sizeof(char)*512); PSMSGPKG pMsg = (PSMSGPKG)szBuff;
	//Save a session area for each client
	SSESSION stSession; memset(&stSession, 0, sizeof(SSESSION)); stSession.iMsgId = iMsgSeq;
	//Add 1 to the number of connected clients and display them
	++iThreadCnt; SetDlgItemInt(hWnd, IDC_COUNT, iThreadCnt, FALSE);
	 /*********************************************************************
     Username and password detection, to simplify the procedure, arbitrary usernames and passwords can now be used
    *********************************************************************/
    //Receive the username and password entered by the user.
    //The client will send a MSGLOGIN packet, the command code is CMD_LOGIN, this is the service
    //The server receives the first data packet from the client. If not, close the connection.
	if(!RecvPkg(hSrvSock, szBuff, sizeof(SMSGHDR) + sizeof(SMSGLOGIN))){
		closesocket(hSrvSock); SetDlgItemInt(hWnd, IDC_COUNT, --iThreadCnt, FALSE);
		return FALSE;
	}
	if(pMsg->stMsgHdr.iCmdType != CMD_LOGIN_REQUEST){//Determine whether it is a login packet
		closesocket(hSrvSock); SetDlgItemInt(hWnd, IDC_COUNT, --iThreadCnt, FALSE);
		return FALSE;
	}
	StringCchCopy(stSession.szUser, lstrlen(pMsg->stMsgLogin.szUser)+1, pMsg->stMsgLogin.szUser);
	//Omit the verification username and password, any username and password can be passed
	pMsg->stMsgResp.iResult = 1;//Here is 1, indicating that the verification is passed
	pMsg->stMsgHdr.iCmdType = CMD_LOGIN_RESPONSE;
	pMsg->stMsgHdr.iPkgLen = sizeof(SMSGHDR) + sizeof(SMSGRESP);
	iRet = send(hSrvSock, szBuff, pMsg->stMsgHdr.iPkgLen, 0);
	if(iRet == SOCKET_ERROR){
		closesocket(hSrvSock); SetDlgItemInt(hWnd, IDC_COUNT, --iThreadCnt, FALSE);
		return FALSE;
	}
	 /*********************************************************************
      Broadcast: xxx entered the chat room
    *********************************************************************/
	StringCchCopy((TCHAR*)szBuff, lstrlen(stSession.szUser)+1, stSession.szUser);
	StringCchCat((TCHAR*)szBuff, (lstrlen((TCHAR*)szBuff)+lstrlen(szLogin)+1), szLogin);
	PutMsgIntoQueue(szSysInfo, (TCHAR*)szBuff);
	stSession.dwLastTime = GetTickCount();
	while(!bStopFlag){//Loop processing messages
		//Send the chat record in the message queue to the client
		memset(szBuff, 0, sizeof(char)*512);
		if(!SendMsgFromQueue(hSrvSock, szBuff, &stSession))		break;
		 //Note that the detection link is placed before the receive, not before the SendMsgQueue, why?
        //Because the detection link is realized by sending packets, and in SendMsgQueue itself
        //Send data packets and return SOCKET_ERROR to indicate that the link is broken. But the received data is different if
        //Before receiving, the network is interrupted abnormally. At this time, the system does not set the status of the socket to be disconnected.
        //Because the other party has not sent data, and is waiting. So calling recv or select at this time will not return
        //SOCKET_ERROR, only detect detection by actively sending data, when no response is received after multiple send
        //The system will set the socket to disconnect, and all subsequent operations will fail.
		pMsg->stMsgHdr.iCmdType = CMD_LINK_CHECK;
		pMsg->stMsgHdr.iPkgLen = sizeof(SMSGHDR);
		if(LinkCheck(hSrvSock, (char*)pMsg, &stSession) == SOCKET_ERROR || bStopFlag)		break;
		iRet = WaitSocket(hSrvSock, 200*1000);//Wait for 200ms
		if(iRet == SOCKET_ERROR) break;//If the connection is interrupted, exit
		if(iRet == 0) continue;//If no data is received, loop

		//Note that the data received here only indicates a complete data packet. It could be a packet of chat sentences, or it could be
        //It is the data packet of the exit command (this example does not implement this, because the link will be disconnected when the client exits, and it will be detected by LinkCheck)
		memset(szBuff, 0, sizeof(char)*512);
		iRet = RecvPkg(hSrvSock, szBuff, sizeof(szBuff));		if(!iRet)		break;
		stSession.dwLastTime = GetTickCount();
		pMsg = (PSMSGPKG)szBuff;
		if(pMsg->stMsgHdr.iCmdType == CMD_MSG_TO_SERVER){
			PutMsgIntoQueue(stSession.szUser, (TCHAR*)(pMsg->stMsg2Server.szContent));
		}
	}
	 /*********************************************************************
    Broadcast: xxx has left the chat room
    *********************************************************************/
	StringCchCopy((TCHAR*)szBuff, lstrlen(stSession.szUser) + 1, stSession.szUser);
	StringCchCat((TCHAR*)szBuff, (lstrlen((TCHAR*)szBuff)+lstrlen(szLogout)+1), szLogout);
	PutMsgIntoQueue(szSysInfo, (TCHAR*)szBuff);
	/*********************************************************************
    close the socket
    *********************************************************************/
	closesocket(hSrvSock); SetDlgItemInt(hWnd, IDC_COUNT, --iThreadCnt, FALSE);
	return TRUE;
}


//listen thread
/*The incoming parameter is the address of the socket variable in the main thread*/
unsigned int WINAPI ListenProc(PVOID pSocket){
	TCHAR szErrBind[] = TEXT("Unable to bind to TCP port 1234, please check if other programs are using it!");
	 //create socket
	SOCKET hListenSock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
	*((SOCKET*)(pSocket)) = hListenSock;
	 //bind socket
	SOCKADDR_IN stSa; memset(&stSa, 0, sizeof(SOCKADDR_IN));
	stSa.sin_port = htons(1234); stSa.sin_family = AF_INET; stSa.sin_addr.S_un.S_addr = INADDR_ANY;
	if(bind(hListenSock, (PSOCKADDR)&stSa, sizeof(SOCKADDR_IN))){//Return 0 means no error, it is successful
		MessageBox(hWnd, szErrBind, szApp, MB_OK|MB_ICONSTOP);
		closesocket(hListenSock);
		return FALSE;
	}
	// start listening
	listen(hListenSock, 5);
	while(true){//Wait for connections and create a new service thread for each connection
		SOCKET hServiceSock = accept(hListenSock, NULL, NULL);
		unsigned uThreadId;
		HANDLE hServiceThread = (HANDLE)_beginthreadex(NULL, 0, &ServiceProc, (LPVOID)(hServiceSock), 0, &uThreadId);
		CloseHandle(hServiceThread);
	}
	closesocket(hListenSock);
	return TRUE;
}


int CALLBACK DlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    WSADATA stWSA;
    static SOCKET hListenSocket;
    static HANDLE hListenThread;

    switch (message)
    {
    case WM_INITDIALOG:
        hWnd = hwnd;
        InitializeCriticalSection(&csMsgQueue); //Initialize the critical section object;
        WSAStartup(MAKEWORD(2, 0), &stWSA); //The information of the dynamic library is returned to the WSAdata variable
        
        //create listener thread
		unsigned uThreadId;
		hListenThread = (HANDLE)_beginthreadex(NULL, 0, &ListenProc, (LPVOID)(&hListenSocket), 0, &uThreadId);
        CloseHandle(hListenThread);
        return TRUE;
    case WM_CLOSE:
        closesocket(hListenSocket); //When there is no client connection, the socket is created in the thread and does not exit the thread.
												//So to listen to the socket here, accept will return failure at this time, and the listening thread will exit.
        bStopFlag = TRUE; //Set the exit flag so that the service thread is aborted
        while (iThreadCnt > 0); //Wait for the service thread to close
        WSACleanup();
        DeleteCriticalSection(&csMsgQueue);
        EndDialog(hwnd, 0);
        return TRUE;
    }
    return FALSE;
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324823295&siteId=291194637