(C++伺服器學習筆記):報文多次收發轉為一次收發

NGC_2070發表於2020-12-27

報文多次收發轉為一次收發

  • 包頭和資料合在一起,一次傳送一次接收。
  • 資料定義,服務端和客戶端共用
enum CMD             //命令列舉
{
    CMD_LOGIN,
    CMD_LOGIN_RESULT,
    CMD_LOGOUT,
    CMD_LOGOUT_RESULT,
    CMD_ERROR
};

//DataHeader
struct DataHeader      //資料包頭
{
    short dataLength;
    short cmd;
};

//DataPackage
struct Login:public DataHeader          //登入
{
    Login()
    {
        dataLength = sizeof(Login);
        cmd = CMD_LOGIN;
    }
    char UserName[32]{};
    char PassWord[32]{};
};
 
struct LoginResult : public DataHeader     //登入結果
{
    LoginResult()
    {
        dataLength = sizeof(LoginResult);
        cmd = CMD_LOGIN_RESULT;
        lgResult = 0;
    }
    int lgResult;
};

struct LogOut :public DataHeader        //退出登入
{
    LogOut()
    {
        dataLength = sizeof(LogOut);
        cmd = CMD_LOGOUT;
    }
    char UserName[32]{};
};


struct LogOutResult :public DataHeader  //退出結果
{
    LogOutResult()
    {
        dataLength = sizeof(LogOutResult);
        cmd = CMD_LOGOUT_RESULT;
        lgOutResult = 0;
    }
    int lgOutResult;
};

【服務端資料收發處理】

 while (true)
{
    DataHeader dbHeader = {};
    //讀取包頭資料
    int nLen = recv(sockAccpt, (char*)&dbHeader, sizeof(DataHeader), 0);
    if (nLen < 0)
    {
        printf("客戶端已退出,任務結束\n");
        break;
    }
 
    switch (dbHeader.cmd)
    {
    case CMD_LOGIN:
    {
        Login login = {};
        recv(sockAccpt, (char*)&login + sizeof(DataHeader) , sizeof(Login)- sizeof(DataHeader), 0);
        printf("收到命令:CMD_LOGIN ,資料長度: %d, UserName = %s, \
                PassWord = %s \n", login.dataLength, login.UserName, login.PassWord);
        //忽略對使用者密碼進行判斷
        LoginResult lgRet = {};
        send(sockAccpt, (char*)&lgRet, sizeof(LoginResult), 0);
    }
    break;
    case CMD_LOGOUT:
    {
        LogOut logout = {};
        recv(sockAccpt, (char*)&logout + sizeof(DataHeader), sizeof(LogOut) - sizeof(DataHeader), 0);
        printf("收到命令:CMD_LOGOUT ,資料長度: %d, UserName = %s, \
                \n", logout.dataLength, logout.UserName );
        //忽略對使用者密碼進行判斷
        LogOutResult lgOutRet = {};
        send(sockAccpt, (char*)&lgOutRet, sizeof(LogOutResult), 0);
    }
    break;
    default:
        dbHeader.cmd = CMD_ERROR;
        dbHeader.dataLength = 0;
        send(sockAccpt, (char*)&dbHeader, sizeof(DataHeader), 0);
        break;
    }
}

【客戶端資料收發處理】

while (true)
{
	//3.輸入請求命令
	char cmdBuff[128] = {};
	scanf("%s", cmdBuff);

	//4.處理請求
	if (0 == strcmp(cmdBuff, "exit"))
	{
		printf("接收到命令exit,退出 \n");
		break;
	}
	else if (0 == strcmp(cmdBuff, "login"))
	{
		Login login {};
		login.cmd = CMD_LOGIN;
		login.dataLength = sizeof(login);
		strcpy(login.UserName, "喜羊羊");
		strcpy(login.PassWord,"123456");
		//5.向伺服器傳送請求
		send(sockCli, (const char *)&login, sizeof(login), 0);			

		//接收伺服器返回的資料
		LoginResult loginRet = {};
		recv(sockCli, ( char*)&loginRet, sizeof(loginRet), 0);
		printf("LoginResult: %d\n", loginRet.lgResult);
	}
	else if (0 == strcmp(cmdBuff, "logout"))
	{
		LogOut logout{};
		logout.cmd = CMD_LOGOUT;
		logout.dataLength = sizeof(logout);
		strcpy(logout.UserName, "灰太狼");
		//5.向伺服器傳送請求
		send(sockCli, (const char*)&logout, sizeof(logout), 0);

		//接收伺服器返回的資料
		LogOutResult logOutRet = {};
		recv(sockCli, (char*)&logOutRet, sizeof(logOutRet), 0);
		printf("LogOutResult: %d\n", logOutRet.lgOutResult);
	}
	else
	{
		printf("不支援該命令,請從新輸入 \n");
	}
}

原始碼

【服務端原始碼】

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#include <cstdio>

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

enum CMD             //命令列舉
{
    CMD_LOGIN,
    CMD_LOGIN_RESULT,
    CMD_LOGOUT,
    CMD_LOGOUT_RESULT,
    CMD_ERROR
};

//DataHeader
struct DataHeader      //資料包頭
{
    short dataLength;
    short cmd;
};

//DataPackage
struct Login:public DataHeader          //登入
{
    Login()
    {
        dataLength = sizeof(Login);
        cmd = CMD_LOGIN;
    }
    char UserName[32]{};
    char PassWord[32]{};
};
 
struct LoginResult : public DataHeader     //登入結果
{
    LoginResult()
    {
        dataLength = sizeof(LoginResult);
        cmd = CMD_LOGIN_RESULT;
        lgResult = 0;
    }
    int lgResult;
};

struct LogOut :public DataHeader        //退出登入
{
    LogOut()
    {
        dataLength = sizeof(LogOut);
        cmd = CMD_LOGOUT;
    }
    char UserName[32]{};
};


struct LogOutResult :public DataHeader  //退出結果
{
    LogOutResult()
    {
        dataLength = sizeof(LogOutResult);
        cmd = CMD_LOGOUT_RESULT;
        lgOutResult = 0;
    }
    int lgOutResult;
};


int main()
{
    WORD ver = MAKEWORD(2, 2);
    WSAData dat;
    WSAStartup(ver, &dat);

    //1.建立socket套接字
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    //2,bind 繫結用於接收客戶端連線的埠
    sockaddr_in sinAddr = {};
    sinAddr.sin_family = AF_INET;
    sinAddr.sin_port = htons(5678); //host to net unsigned short
    sinAddr.sin_addr.S_un.S_addr = INADDR_ANY;  //inet_addr("127.0.0.1")
    if (SOCKET_ERROR == bind(sock, (sockaddr*)&sinAddr, sizeof(sockaddr_in)))
    {
        printf("Bind Error\n");
    }
    else
    {
        printf("Bind Success\n");
    }

    //3. listen 監聽網路埠
    if (SOCKET_ERROR == listen(sock, 5))
    {
        printf("Listen Error\n");
    }
    else
    {
        printf("Listen Success\n");
    }

    //4.accept 接收客戶端連線
    sockaddr_in clientAddr = {};
    int clAddrLen = sizeof(sockaddr_in);

    SOCKET sockAccpt = INVALID_SOCKET;
    sockAccpt = accept(sock, (sockaddr*)&clientAddr, &clAddrLen);

    if (INVALID_SOCKET == sockAccpt)
    {
        printf("Accept Error\n");
    }
    else
    {
        printf("Accept Success\n");
    }
    printf("新客戶端加入:Socket = %d,IP = %s \n", (int)sockAccpt, inet_ntoa(clientAddr.sin_addr));


    while (true)
    {
        DataHeader dbHeader = {};
        //讀取包頭資料
        int nLen = recv(sockAccpt, (char*)&dbHeader, sizeof(DataHeader), 0);
        if (nLen < 0)
        {
            printf("客戶端已退出,任務結束\n");
            break;
        }
     
        switch (dbHeader.cmd)
        {
        case CMD_LOGIN:
        {
            Login login = {};
            recv(sockAccpt, (char*)&login + sizeof(DataHeader) , sizeof(Login)- sizeof(DataHeader), 0);
            printf("收到命令:CMD_LOGIN ,資料長度: %d, UserName = %s, \
                    PassWord = %s \n", login.dataLength, login.UserName, login.PassWord);
            //忽略對使用者密碼進行判斷
            LoginResult lgRet = {};
            send(sockAccpt, (char*)&lgRet, sizeof(LoginResult), 0);
        }
        break;
        case CMD_LOGOUT:
        {
            LogOut logout = {};
            recv(sockAccpt, (char*)&logout + sizeof(DataHeader), sizeof(LogOut) - sizeof(DataHeader), 0);
            printf("收到命令:CMD_LOGOUT ,資料長度: %d, UserName = %s, \
                    \n", logout.dataLength, logout.UserName );
            //忽略對使用者密碼進行判斷
            LogOutResult lgOutRet = {};
            send(sockAccpt, (char*)&lgOutRet, sizeof(LogOutResult), 0);
        }
        break;
        default:
            dbHeader.cmd = CMD_ERROR;
            dbHeader.dataLength = 0;
            send(sockAccpt, (char*)&dbHeader, sizeof(DataHeader), 0);
            break;
        }
    }
        //closesocket 關閉套接字
        closesocket(sock);
        closesocket(sockAccpt);

        WSACleanup();

        printf("結束任務\n");
        getchar();
        return 0;
 }

【客戶端原始碼】

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define  _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#include <cstdio>
#pragma comment(lib,"ws2_32.lib")

enum CMD             //命令列舉
{
	CMD_LOGIN,
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,
	CMD_LOGOUT_RESULT,
	CMD_ERROR
};

//DataHeader
struct DataHeader      //資料包頭
{
	short dataLength;
	short cmd;
};

//DataPackage
struct Login :public DataHeader          //登入
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char UserName[32]{};
	char PassWord[32]{};
};

struct LoginResult : public DataHeader     //登入結果
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		lgResult = 0;
	}
	int lgResult;
};

struct LogOut :public DataHeader        //退出登入
{
	LogOut()
	{
		dataLength = sizeof(LogOut);
		cmd = CMD_LOGOUT;
	}
	char UserName[32]{};
};


struct LogOutResult :public DataHeader  //退出結果
{
	LogOutResult()
	{
		dataLength = sizeof(LogOutResult);
		cmd = CMD_LOGOUT_RESULT;
		lgOutResult = 0;
	}
	int lgOutResult;
};


int main()
{
	WORD ver = MAKEWORD(2, 2);
	WSAData dat;
	WSAStartup(ver, &dat);

	//1.建立一個socket
	SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockCli)
	{
		printf("Socket Error\n");
	}
	else
	{
		printf("Socket Success\n");
	}

	//2. connect連線伺服器
	sockaddr_in servAddr = {};
	servAddr.sin_family = AF_INET;
	servAddr.sin_port = htons(5678);
	servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	int ret = connect(sockCli, (sockaddr*)&servAddr, sizeof(sockaddr_in));

	if (SOCKET_ERROR == ret)
	{
		printf("Connect Error\n");
	}
	else
	{
		printf("Connect Success\n");
	}

	while (true)
	{
		//3.輸入請求命令
		char cmdBuff[128] = {};
		scanf("%s", cmdBuff);

		//4.處理請求
		if (0 == strcmp(cmdBuff, "exit"))
		{
			printf("接收到命令exit,退出 \n");
			break;
		}
		else if (0 == strcmp(cmdBuff, "login"))
		{
			Login login {};
			login.cmd = CMD_LOGIN;
			login.dataLength = sizeof(login);
			strcpy(login.UserName, "喜羊羊");
			strcpy(login.PassWord,"123456");
			//5.向伺服器傳送請求
			send(sockCli, (const char *)&login, sizeof(login), 0);			

			//接收伺服器返回的資料
			LoginResult loginRet = {};
			recv(sockCli, ( char*)&loginRet, sizeof(loginRet), 0);
			printf("LoginResult: %d\n", loginRet.lgResult);
		}
		else if (0 == strcmp(cmdBuff, "logout"))
		{
			LogOut logout{};
			logout.cmd = CMD_LOGOUT;
			logout.dataLength = sizeof(logout);
			strcpy(logout.UserName, "灰太狼");
			//5.向伺服器傳送請求
			send(sockCli, (const char*)&logout, sizeof(logout), 0);

			//接收伺服器返回的資料
			LogOutResult logOutRet = {};
			recv(sockCli, (char*)&logOutRet, sizeof(logOutRet), 0);
			printf("LogOutResult: %d\n", logOutRet.lgOutResult);
		}
		else
		{
			printf("不支援該命令,請從新輸入 \n");
		}
	}

	//7.關閉套接字 closesocket
	closesocket(sockCli);

	WSACleanup();
	printf("結束任務\n");
	getchar();
	return 0;
}

【執行結果】

服務端資料收發處理優化

while (true)
{
    //緩衝區
    char szRecv[1024] = {};

    //讀取包頭資料
    int nLen = recv(sockAccpt, (char*)&szRecv, sizeof(DataHeader), 0);
    DataHeader* dbHeader = (DataHeader*)szRecv;
    if (nLen < 0)
    {
        printf("客戶端已退出,任務結束\n");
        break;
    }
    //if(nLen >= sizeof(DataHeader))
    switch (dbHeader->cmd)
    {
    case CMD_LOGIN:
    {
        recv(sockAccpt, szRecv + sizeof(DataHeader) , dbHeader->dataLength- sizeof(DataHeader), 0);
        Login* login = (Login*)szRecv;
        printf("收到命令:CMD_LOGIN ,資料長度: %d, UserName = %s, \
                PassWord = %s \n", login->dataLength, login->UserName, login->PassWord);
        //忽略對使用者密碼進行判斷
        LoginResult lgRet = {};
        send(sockAccpt, (char*)&lgRet, sizeof(LoginResult), 0);
    }
    break;
    case CMD_LOGOUT:
    {
        recv(sockAccpt, szRecv + sizeof(DataHeader), dbHeader->dataLength - sizeof(DataHeader), 0);
        LogOut* logout = (LogOut*)szRecv;
        printf("收到命令:CMD_LOGOUT ,資料長度: %d, UserName = %s, \
                \n", logout->dataLength, logout->UserName );
        //忽略對使用者密碼進行判斷
        LogOutResult lgOutRet = {};
        send(sockAccpt, (char*)&lgOutRet, sizeof(LogOutResult), 0);
    }
    break;
    default:
        DataHeader  HeaderError = { 0, CMD_ERROR };
        send(sockAccpt, (char*)&HeaderError, sizeof(HeaderError), 0);
        break;
    }
}
  • 測試

相關文章