EasyTCP 服务器简单自定义协议编程

在服务器客户端通信的时候,并不是像简单的echo服务器那样,还是发送特定的数据包协议。

数据包的协议可以用自定义结构体定义。

 

自定义协议分简单分为消息头, 和内容,消息头具有消息的长度,和类型,使用继承的方法,简单实现

#ifndef _MessageHeader_hpp_
#define _MessageHeader_hpp_

enum CMD
{
	CMD_LOGIN,
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,
	CMD_LOGOUT_RESULT,
	CMD_NEW_USER_JOIN,
	CMD_ERROR
};

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;
		result = 0;
	}
	int result;
	char data[1024];
};

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;
		result = 0;
	}
	int result;
};

struct NewUserJoin : public DataHeader
{
	NewUserJoin()
	{
		dataLength = sizeof(NewUserJoin);
		cmd = CMD_NEW_USER_JOIN;
		scok = 0;
	}
	int scok;
};

#endif // !_MessageHeader_hpp_

服务器使用C++ 面向对象的方式封装,主函数代码只有以下几行,很好理解

#include "EasyTcpServer.hpp"

int main()
{

	EasyTcpServer server;
	server.InitSocket();
	server.Bind(nullptr, 4567);
	server.Listen(5);

	while (server.isRun())
	{
		server.OnRun();
		//printf("空闲时间处理其它业务..\n");
	}
	server.Close();
	printf("已退出。\n");
	getchar();
	return 0;
}

 

EasyTCPServer这个类封装了 服务器的基本设置函数,同时使用了一个STL vector<int> 数组来保存连接的客户端

class EasyTcpServer
{
private:
	SOCKET _sock;
	std::vector<SOCKET> g_clients;
}

这里最重要的逻辑函数是

每次调用select函数之前,都要重新设置fd_set

	bool OnRun()
	{
		if (isRun())
		{
			//伯克利套接字 BSD socket
			fd_set fdRead;//描述符(socket) 集合
			fd_set fdWrite;
			fd_set fdExp;
			//清理集合
			FD_ZERO(&fdRead);
			FD_ZERO(&fdWrite);
			FD_ZERO(&fdExp);
			//将描述符(socket)加入集合
			FD_SET(_sock, &fdRead);
			FD_SET(_sock, &fdWrite);
			FD_SET(_sock, &fdExp);
			SOCKET maxSock = _sock;
			for (int n = (int)g_clients.size() - 1; n >= 0; n--)
			{
				FD_SET(g_clients[n], &fdRead);
				if (maxSock < g_clients[n])
				{
					maxSock = g_clients[n];
				}
			}
			///nfds 是一个整数值 是指fd_set集合中所有描述符(socket)的范围,而不是数量
			///既是所有文件描述符最大值+1 在Windows中这个参数可以写0
			//timeval t = { 1,0 };
			int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, nullptr); //&t
			//printf("select ret=%d count=%d\n", ret, _nCount++);
			if (ret < 0)
			{
				printf("select任务结束。\n");
				Close();
				return false;
			}
			//判断描述符(socket)是否在集合中
			if (FD_ISSET(_sock, &fdRead))
			{
				FD_CLR(_sock, &fdRead);
				Accept();
			}
			for (int n = (int)g_clients.size() - 1; n >= 0; n--)
			{
				if (FD_ISSET(g_clients[n], &fdRead))
				{
					if (-1 == RecvData(g_clients[n]))
					{
						auto iter = g_clients.begin() + n;//std::vector<SOCKET>::iterator
						if (iter != g_clients.end())
						{
							g_clients.erase(iter);
						}
					}
				}
			}
			return true;
		}
		return false;

	}

其中RecvData函数的实现也特别关键

一次读取 1024 * 400 = 400K数据,再拆分处理

//缓冲区
	char szRecv[409600] = {};

	//接收数据 处理粘包 拆分包
	int RecvData(SOCKET _cSock)
	{
		// 5 接收客户端数据
		int nLen = (int)recv(_cSock, szRecv, 409600, 0);
		//printf("nLen=%d\n", nLen);
		if (nLen <= 0)
		{
			printf("客户端<Socket=%d>已退出,任务结束。\n", _cSock);
			return -1;
		}
		LoginResult ret;
		SendData(_cSock, &ret);
		/*
		DataHeader* header = (DataHeader*)szRecv;
		
		recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);

		OnNetMsg(_cSock, header);
		*/
		return 0;
	}

服务器回送请求

//响应网络消息
	virtual void OnNetMsg(SOCKET _cSock, DataHeader* header)
	{
		switch (header->cmd)
		{
		case CMD_LOGIN:
		{
			
			Login* login = (Login*)header;
			printf("收到客户端<Socket=%d>请求:CMD_LOGIN,数据长度:%d,userName=%s PassWord=%s\n", _cSock, login->dataLength, login->userName, login->PassWord);
			//忽略判断用户密码是否正确的过程
			LoginResult ret;
			SendData(_cSock, &ret);
		}
		break;
		case CMD_LOGOUT:
		{
			Logout* logout = (Logout*)header;
			printf("收到客户端<Socket=%d>请求:CMD_LOGOUT,数据长度:%d,userName=%s \n", _cSock, logout->dataLength, logout->userName);
			//忽略判断用户密码是否正确的过程
			LogoutResult ret;
			SendData(_cSock, &ret);
		}
		break;
		default:
		{
			DataHeader header = { 0,CMD_ERROR };
			send(_cSock, (char*)&header, sizeof(header), 0);
		}
		break;
		}
	}

客户端的实现比较简单, 开一线程用来接受命令,退出

#include "EasyTcpClient.hpp"
#include<thread>

void cmdThread(EasyTcpClient* client)
{
	while (true)
	{
		char cmdBuf[256] = {};
		scanf("%s", cmdBuf);
		if (0 == strcmp(cmdBuf, "exit"))
		{
			client->Close();
			printf("退出cmdThread线程\n");
			break;
		}
		else if (0 == strcmp(cmdBuf, "login"))
		{
			Login login;
			strcpy(login.userName, "lyd");
			strcpy(login.PassWord, "lydmm");
			client->SendData(&login);

		}
		else if (0 == strcmp(cmdBuf, "logout"))
		{
			Logout logout;
			strcpy(logout.userName, "lyd");
			client->SendData(&logout);
		}
		else {
			printf("不支持的命令。\n");
		}
	}
}

int main()
{
	EasyTcpClient client1;
	client1.Connect("127.0.0.1", 4567);

	//启动UI线程
	std::thread t1(cmdThread, &client1);
	t1.detach();

	Login login;
	strcpy(login.userName, "lyd");
	strcpy(login.PassWord, "lydmm");
	while (client1.isRun())
	{
		client1.OnRun();
		client1.SendData(&login);
		//printf("空闲时间处理其它业务..\n");
		//Sleep(1000);
	}
	client1.Close();

	printf("已退出。\n");
	getchar();
	return 0;
}

客户端也是用select接受数据

	//处理网络消息
	int _nCount = 0;
	bool OnRun()
	{
		if (isRun())
		{
			fd_set fdReads;
			FD_ZERO(&fdReads);
			FD_SET(_sock, &fdReads);
			timeval t = { 0,0 };
			int ret = select(_sock + 1, &fdReads, 0, 0, &t); 
			//printf("select ret=%d count=%d\n", ret, _nCount++);
			if (ret < 0)
			{
				printf("<socket=%d>select任务结束1\n", _sock);
				Close();
				return false;
			}
			if (FD_ISSET(_sock, &fdReads))
			{
				FD_CLR(_sock, &fdReads);

				if (-1 == RecvData(_sock))
				{
					printf("<socket=%d>select任务结束2\n", _sock);
					Close();
					return false;
				}
			}
			return true;
		}
		return false;
	}

这里接受消息包括了粘包的处理

	//处理网络消息
	int _nCount = 0;
	bool OnRun()
	{
		if (isRun())
		{
			fd_set fdReads;
			FD_ZERO(&fdReads);
			FD_SET(_sock, &fdReads);
			timeval t = { 0,0 };
			int ret = select(_sock + 1, &fdReads, 0, 0, &t); 
			//printf("select ret=%d count=%d\n", ret, _nCount++);
			if (ret < 0)
			{
				printf("<socket=%d>select任务结束1\n", _sock);
				Close();
				return false;
			}
			if (FD_ISSET(_sock, &fdReads))
			{
				FD_CLR(_sock, &fdReads);

				if (-1 == RecvData(_sock))
				{
					printf("<socket=%d>select任务结束2\n", _sock);
					Close();
					return false;
				}
			}
			return true;
		}
		return false;
	}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 易语言中的JScript可以使用EasyX支持库。EasyX是易语言的一款图形界面开发库,它提供了丰富的绘图函数和控制台窗口功能,可用于开发各种图形界面应用程序。 EasyX支持库中包含了许多绘图函数,如画线、画矩形、画圆等,开发者可以使用这些函数来绘制各种图形元素。同时,EasyX还提供了控制台窗口相关的函数,如获取键盘输入、清除屏幕、设置光标位置等,方便开发者进行控制台程序的开发。 除了绘图函数和控制台窗口函数,EasyX还提供了一些其他功能,如声音播放、图片处理、鼠标输入等。这些功能可以帮助开发者实现更加丰富和复杂的应用程序。 EasyX支持库的使用相对简单,易语言开发者可以通过引入EasyX.h头文件来使用其中的函数。在使用EasyX函数之前,还需要初始化图形界面和控制台窗口,然后就可以调用相应的函数来实现所需的功能。 总之,EasyX是易语言中用于支持JScript的一款开发库,提供了丰富的绘图函数和控制台窗口功能,可以帮助开发者实现各种图形界面应用程序。 ### 回答2: 易语言jscript可以使用一些支持库来扩展其功能和提高开发效率。以下是一些常用的支持库: 1. 界面支持库:提供了创建窗口、按钮、文本框等常见用户界面元素的功能,如易框架(eui)和ET跨平台GUI库(ETGui)等。 2. 文件操作支持库:用于进行文件读写和管理,常用的有易通用文件操作库(Yi-File)、易文件访问库(Yfrm)、方舟文件操作扩展库(FZFileEx)等。 3. 数据库支持库:用于与数据库进行交互,如易Mysql操作支持库(EasyMysql)和易数据库访问库(EasyDb)等,可以方便地进行数据库的连接、查询和操作。 4. 网络通讯支持库:用于进行网络通讯,如易UDP支持库(EasyUDP)和易TCP通信支持库(EasyTcp)等,可以实现网络编程相关功能。 5. 图像处理支持库:用于进行图像的处理和操作,比如易高级图形支持库(EGraphics)和易图片处理支持库(EasyImage)等,可以进行图像的加载、保存、编辑等操作。 6. 字符串处理支持库:用于进行字符串的处理和操作,如易字符串操作库(YString)和易正则表达式支持库(YRegExp)等,可以方便地进行字符串的查找、替换、截取等操作。 这些支持库可以通过官方网站、开发者论坛等渠道获取和学习,使用它们可以提高易语言jscript的开发效率和功能扩展能力。 ### 回答3: 易语言JScript可以使用EasyX库,这是一个专门为易语言开发者设计的图形界面库。EasyX库不仅提供了丰富的绘图函数,方便开发者绘制各种图形和动画效果,还支持各种输入输出操作,包括键盘输入、鼠标输入、文件读写等。另外,EasyX库还有自己的文本编辑器,可以直接在其中编写代码,并且可以进行调试和运行,方便开发者进行开发和调试。 除了EasyX库,易语言JScript还可以使用其他一些第三方支持库,比如WinApi库和Borland库。WinApi库提供了一系列用于Windows系统编程的函数接口,方便开发者直接调用系统API进行系统级编程。Borland库是易语言中常用的库之一,提供了大量的函数和类,方便开发者进行字符串处理、文件操作、网络通信等常用功能的开发。 总的来说,易语言JScript使用EasyX库是比较常见的选择,因为EasyX库提供了强大的图形界面和输入输出功能,可以满足大部分的开发需求。但如果有特殊需求,开发者还可以选择其他第三方支持库进行开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值