前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++服务端-事件循环EventLoop基础框架搭建-实现多人聊天

C++服务端-事件循环EventLoop基础框架搭建-实现多人聊天

原创
作者头像
晨星成焰
发布2024-11-01 09:35:13
940
发布2024-11-01 09:35:13
举报
文章被收录于专栏:网络编程

以下思路来源于部分网络库

一个简单的流程图描述
一个简单的流程图描述

核心源码

EventLoop.hpp

以select为核心的基础事件循环句柄实现

代码语言:cpp
复制
#ifndef _EVENTLOOP_HPP_
#define _EVENTLOOP_HPP_
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <windows.h>
#include <Winsock2.h>


#include <functional>
#include <unordered_map>
#include <iostream>

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


namespace X
{
    typedef struct sNetPoint
    {
        SOCKET sock;
        SOCKADDR_IN addr;
    } NetPoint;

    class EventLoop
    {
    public:
        typedef struct sHandle
        {
            SOCKET sock;
            std::function<void()> readEvent;
            std::function<void()> closeEvent;
        } Handle;
    private:
        std::unordered_map<SOCKET, Handle *> _events;
        std::list<Handle *> _removeEvents;
    public:
        EventLoop()
        {

        }

        ~EventLoop()
        {

        }

    public:
        void AddEvent(Handle *handle)
        {
#ifdef _DEBUG
            if (_events.find(handle->sock) != _events.end())
            {
                std::cout << "AddEvent 失败, handle->sock = " << handle->sock << " 没有移除" << std::endl;
                return;
            }
#endif
            _events.insert(std::pair<SOCKET, Handle *>(handle->sock, handle));
//      _events[handle->sock] = handle;
        }

        void RemoveEvent(Handle *handle)
        {
            _removeEvents.push_back(handle);
        }

    public:
        void LoopOnce(int ms = 1)
        {
            fd_set reads;
            // 清空或者初始化reads
            FD_ZERO(&reads);

            // 将所有的socket加入reads
            for (auto iter = _events.begin(); iter != _events.end(); iter++)
            {
                FD_SET(iter->first, &reads);
            }

            int sRet = select(0, &reads, 0, 0, 0);

            // 表示select超时或者出错
            if (sRet <= 0) return;

            for (auto iter = _events.begin(); iter != _events.end(); iter++)
            {
                if (FD_ISSET(iter->first, &reads))
                {
                    iter->second->readEvent();
                }
            }

            for (auto iter = _removeEvents.begin(); iter != _removeEvents.end(); iter++)
            {
                _events.erase((*iter)->sock);
                (*iter)->closeEvent();
            }
            _removeEvents.clear();

        }
    };

}

#endif

TcpAccept.hpp

创建一个 TCP 服务端->处理TCP 连接请求

代码语言:cpp
复制
#ifndef _TCPACCEPT_H_
#define _TCPACCEPT_H_

#include "EventLoop.hpp"

namespace X
{
    class TcpAccept
    {
    private:
        EventLoop::Handle _handle;
        EventLoop *_loop;
        std::function<void(NetPoint *point)> _cb;
    public:
        TcpAccept(EventLoop *loop, std::function<void(NetPoint *point)> cb)
        {
            _handle.readEvent = std::bind(&TcpAccept::OnRead, this);
            _loop = loop;
            _cb = cb;
        }

        ~TcpAccept()
        {
        }

        bool Listen(int port, const char *const ip = "0.0.0.0")
        {

            SOCKET sockServer = socket(AF_INET, SOCK_STREAM, 0);

            if (INVALID_SOCKET == sockServer)
            {
                printf("创建服务端句柄失败\n");
                return false;
            }
            SOCKADDR_IN addr = {};
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port); // 端口号  host to net short

            addr.sin_addr.S_un.S_addr = inet_addr(ip); // IP地址

            int ret = bind(sockServer, (sockaddr *) &addr, sizeof(SOCKADDR_IN));
            if (SOCKET_ERROR == ret)
            {
                printf("绑定端口号失败\n");
                return false;
            }

            ret = listen(sockServer, 5);
            if (SOCKET_ERROR == ret)
            {
                printf("监听端口号失败\n");
                return false;
            }
            std::cout << "IP:" << ip << " Port:" << port << std::endl;
            _handle.sock = sockServer;
            _loop->AddEvent(&_handle);
            return true;

        }

    private:
        void OnRead()
        {
            std::cout << "OnRead" << std::endl;
            // 当 accept 可读时表示 有新的连接

            SOCKADDR_IN clientAddr = {};
            int nAddrLen = sizeof(SOCKADDR_IN);
            SOCKET sockClient = accept(_handle.sock, (sockaddr *) &clientAddr, &nAddrLen);

            if (INVALID_SOCKET == sockClient)
            {
                printf("接收客户端连接失败\n");
//            return -1;
            }
            std::cout << "client IP:" << inet_ntoa(clientAddr.sin_addr) << " Port:" << ntohs(clientAddr.sin_port)
                      << std::endl;
            NetPoint point;
            point.sock = sockClient;
            memcpy(&point.addr, &clientAddr, sizeof(clientAddr));
//            closesocket(sockClient);
            _cb(&point);
        }
    };

}

#endif

TcpSocket.hpp

处理TCP链接的数据收发,提供基本的连接管理和数据处理框架

代码语言:cpp
复制
#ifndef _TCPSOCKET_HPP
#define _TCPSOCKET_HPP

#include "EventLoop.hpp"

namespace X
{

    class TcpSocket
    {
    private:
        EventLoop *_loop;
        NetPoint _point;
        EventLoop::Handle _handle;

        char *_recvBuf;
        int _recvMaxSize;
        int _recvCurrSize;
        bool _bClose;
    public:
        TcpSocket() : _point()
        {
            _bClose = false;
            _loop = nullptr;
            _handle.sock = INVALID_SOCKET;
            _handle.readEvent = std::bind(&TcpSocket::OnRead, this);
            _handle.closeEvent = std::bind(&TcpSocket::OnClose, this);
            _recvMaxSize = 4096;
            _recvBuf = new char[_recvMaxSize];
            _recvCurrSize = 0;
        }

        virtual ~TcpSocket()
        {
            delete[] _recvBuf;
        }
        EventLoop::Handle GetHandle()
        {
            return _handle;
        }

    public:
        void OnAccept(EventLoop *loop, NetPoint *netPoint)
        {
            _loop = loop;
            _point = *netPoint;
            _handle.sock = netPoint->sock;
            _loop->AddEvent(&_handle);
        }

        void Close()
        {
            if (_bClose)
                return;
            _bClose = true;
            _loop->RemoveEvent(&_handle);
        }

    private:
        void OnRead()
        {
            int nLen = recv(_point.sock, _recvBuf + _recvCurrSize, _recvMaxSize - _recvCurrSize, 0);
            if (nLen <= 0)
            {
                Close();
                return;
            }
            _recvCurrSize += nLen;

            while (_recvCurrSize > 0)
            {
                int nRet = OnNetMsg(_recvBuf, _recvCurrSize);
                if (nRet <= 0)
                    break;
                _recvCurrSize -= nRet;
                if (_recvCurrSize > 0)
                    memcpy(_recvBuf, _recvBuf + nRet, _recvCurrSize);
            }
        }

        void OnClose()
        {
            closesocket(_point.sock);

            OnDisconnect();

        }

    public:
        virtual int OnNetMsg(const char *const buf, int len) = 0;
        virtual void OnDisconnect() = 0;
    };

}

#endif

main.cpp

TcpSocket的具体实现

一个简单的聊天服务端,只实现了登陆和群聊

通过#号分割信息

代码语言:cpp
复制
#include <iostream>
#include "EventLoop.hpp"
#include "TcpAccept.hpp"
#include "TcpSocket.hpp"
#include <map>

namespace
{
	std::map<int, class TcpSocket*> g_clients;

	class TcpSocket : public X::TcpSocket
	{
	private:
		bool _bLogin = false;
		int _userId = 0;
		std::string _userName; //客户端的用户名
	public:
		TcpSocket() : X::TcpSocket()
		{
			static int userId = 0;
			_userId = ++userId;
			g_clients[_userId] = this;
		}

		virtual ~TcpSocket()
		{
		}

		//  分割字符串函数
		//  INPUIT: const std::string& s 待分割字符串, char delimiter 分割符号
		//  RETURN: std::vector<std::string>  存储分割的字符串的数组
		std::vector<std::string> splitString(std::string_view s, char delimiter)
		{
			std::vector<std::string> result;
			std::string path;
			for (int i = 0; i < s.size(); i++)
			{
				if (s[i] != delimiter)
				{
					path.push_back(s[i]);
				}
				else if (!path.empty()) // 确保path非空时才push_back
				{
					result.push_back(path);
					path.clear();
				}
			}
			if (!path.empty()) // 处理字符串以分隔符结尾的情况
			{
				result.push_back(path);
			}
			return result;
		}

		void SendUserList()
		{
			std::cout << "发送用户列表" << std::endl;
			std::string UserList = "UserList";
			for (auto iter = g_clients.begin(); iter != g_clients.end(); iter++)
			{
				UserList.append("|");
				UserList.append(iter->second->_userName);
			}
			SendMsg(_userId, UserList.c_str());
		}

		//  对特定客户端发送信息函数
		void SendMsg(int uid, const char* msg)
		{
			int msglen = strlen(msg);
			if (g_clients.find(uid) != g_clients.end())
			{
				send(g_clients[uid]->GetHandle().sock, msg, msglen, 0);
			}
		}

		//广播给除了特定客户端以外的端点函数
		void BoardCastMsg(int uid, const char* msg)
		{
			int msglen = strlen(msg);
			for (auto iter = g_clients.begin(); iter != g_clients.end(); iter++)
			{
				if (iter->first != uid)
				{
					send(iter->second->GetHandle().sock, msg, msglen, 0);
				}
			}
		}

		int OnNetMsg(const char* buf, int len) override
		{
			//            std::cout << std::string(buf, len) << std::endl;
			int index = -1;
			for (int i = 0; i < len; ++i)
			{
				//再接受到#号前会一直将消息存储起来
				if (buf[i] == '#')
				{
					index = i;
					break;
				}
			}
			if (index == -1)return 0;

			//拿到缓冲区的消息
			std::string comm(buf, index + 1);

			std::cout << comm << std::endl;
			//清除#号
			comm.pop_back();
			std::vector<std::string> cmdStr = splitString(comm, '|');

			if (cmdStr[0] == "Login")
			{
				// 验证重复登录
				if (this->_userName == cmdStr[1] || cmdStr.size() < 2)
				{
					std::string loginFailedMsg = "Error|LoginFailed";
					SendMsg(_userId, loginFailedMsg.c_str());
					return index + 1;
				}
				_userName = cmdStr[1];
				std::string UserLoginOK = "LoginOK";
				SendMsg(_userId, UserLoginOK.c_str());
				std::cout << "用户[" << _userName << "]登录" << std::endl;
			}
			else if (cmdStr[0] == "Group")
			{
				if (cmdStr.size() < 2)
				{
					std::string chatFailedMsg = "Error|ChatFailed";
					SendMsg(_userId, chatFailedMsg.c_str());
					return index + 1;
				}
				std::cout << "群发:" << cmdStr[1] << std::endl;
				std::string chatMsg = "Group|" + _userName + ":" + cmdStr[1];
				// 发送消息
				BoardCastMsg(-1, chatMsg.data());
			}
			else if (cmdStr[0] == "其他命令")
			{
				//其它命令相关
			}
			//Login|UserName|PassWord#
			//Group|chatMessage#

			return index + 1;
		}

		void OnDisconnect() override
		{
			g_clients.erase(_userId);
			std::cout << "客户端关闭" << std::endl;
			delete this;
		}

	public:
		bool IsLogin() const
		{
			return _bLogin;
		}
	};


}

int main()
{

	// 0. 初始化网络环境
	WSADATA wsaData = {};
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	X::EventLoop loop;

	X::TcpAccept accept(&loop, [&loop](X::NetPoint* point)
		{
			TcpSocket* socket = new TcpSocket();
			socket->OnAccept(&loop, point);

		});

	accept.Listen(9870);

	while (true)
	{
		loop.LoopOnce();
	}

	return 0;
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 核心源码
    • EventLoop.hpp
      • TcpAccept.hpp
        • TcpSocket.hpp
          • main.cpp
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档