仿QQ聊天-C++服务器

仿QQ聊天-C++服务器,第1张

仿QQ聊天-C++服务器

结构图:

使用到的技术:

  • socket编程
  • select轮循
  • TCP协议
  • 消息的分包
TCP四层协议 应用层为用户的应用提供网络服务传输层定义传输的协议,ip,端口号网络层为不同地理位置的网络主机提供连接和路径选择数据链路层让格式化数据以帧为单位传输,差错校验,物理寻址
  1. 什么是socket:socket是对底层网络通信的一层抽象,让程序员可以像文件那也 *** 作网络上发送和接收的数据
  2. 通信地址:
    1. ip:是网络层用来路由和通信的标识符
    2. 端口:传输层管理
    3. 协议:ipv4 ipv6
  3. socket类型:
    1. SOCK_STREAM 流式协议,面向连接的稳定通信,底层式TCP
    2. SOCK_DGRAM 报文式协议,面向无连接底层是UDP协议,需要上层协议保证可靠性
    3. SOCK_RAW 更加灵活的数据控制,可以指定IP头部

术语表:

名称含义socket创建一个通信管道bind把一个地址三元组绑定到socket上,ip,端口,协议listen监听,准备接收某个socket的数据accept等待连接到达connect主动建立连接send发送数据receive接收数据close关闭连接

函数的声明如下:

int select(int nfds,  fd_set* readset,  fd_set* writeset,  fe_set* exceptset,  struct timeval* timeout);

参数:

nfds 需要检查的文件描述字个数
readset 用来检查可读性的一组文件描述字。
writeset 用来检查可写性的一组文件描述字。
exceptset 用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)
timeout 超时,填NULL为阻塞,填0为非阻塞,其他为一段超时时间

select轮循的原理:
select循环遍历它所监测的fd_set内的所有文件描述符所对应的驱动程序的poll函数。
fd_set结构体:
FD_SET(int fd, fd_set *fdset); //将fd加入set集合
FD_CLR(int fd, fd_set *fdset); //将fd从set集合中清除
FD_ISSET(int fd, fd_set *fdset); //检测fd是否在set集合中,不在则返回0
FD_ZERO(fd_set *fdset); //将set清零使集合中不含任何fd
然后是我写的服务器的内核:
sever.h

#include 
#include 

namespace XS
{
	class Client;
	class Sever
	{
	private:
		SOCKET _seversocket;
		std::map _clients;
	public:
		bool Listen(const char* Ip, int Port);
		void Update();
		void Close();
	private:
		void DoAccept();
	public:
		virtual	void onAccept(Client* client) = 0;
		virtual void onNetMsg(Client* client) = 0;
		virtual void Disconncet(Client* client) = 0;

	};
}

sever.cpp

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include "Sever.h"
#include 
#include "Client.h"
#include 
namespace XS
{
	bool Sever::Listen(const char* Ip, int Port)
	{
		//创建一个socket
		//SOCK_STREAM 面向连接的流式协议  TCP
		//AF_INET  IPV4
		SOCKET seversocket = socket(AF_INET, SOCK_STREAM, 0);
		if (seversocket == INVALID_SOCKET)
		{
			printf("创建套接字失败 %dn", GetLastError());
			return false;
		}
		//设置socket端口、协议和ip
		SOCKADDR_IN severAddr;
		severAddr.sin_family = AF_INET;	//协议族类型 ipv4
		severAddr.sin_port = htons(Port);
		severAddr.sin_addr.S_un.S_addr = inet_addr(Ip);
		if (SOCKET_ERROR == bind(seversocket, (SOCKADDR*)&severAddr, sizeof(SOCKADDR_IN)))
		{
			printf("bind Error %dn", GetLastError());
			return false;
		}
		printf("Bind OK IP:[%s] Port:[%d]n", Ip, Port);
		if (SOCKET_ERROR == listen(seversocket, 5))
		{
			printf("Listen Error %dn", GetLastError());
			return false;
		}
		_seversocket = seversocket;
		return true;
	}


	void Sever::Update()
	{
		FD_SET fdReads; //新建集合
		FD_ZERO(&fdReads); //将集合清零
		FD_SET(_seversocket, &fdReads); //将_seversocket加入集合
		
		//将所有客户端的socket加入集合
		auto begin = _clients.begin();
		auto end = _clients.end();

		for (; begin != end; ++begin)
		{
			FD_SET(begin->first, &fdReads);  
		}

		//select轮循
		int nRent = select(0, &fdReads, nullptr, nullptr, 0);
		if (nRent <= 0)
		{
			return;
		}
		  //检测_seversocket是否在集合中
		if (FD_ISSET(_seversocket, &fdReads))
		{
			DoAccept();
		}
		 
		//监听
		begin = _clients.begin();
		end = _clients.end();

		std::vector::iterator> _close;
		for (; begin != end; ++begin)
		{
			if (FD_ISSET(begin->first, &fdReads))  //检测begin->first是否在集合中
			{
				Client* client = begin->second;
				if (!client->DoRecv())
				{
					_close.push_back(begin);
				}

			}
		}

		//关闭
		for (int i = _close.size() - 1; i >= 0; i--)
		{
			_close[i]->second->DoClose();
			delete _close[i]->second;
			_clients.erase(_close[i]);
		}

	}

	void Sever::DoAccept()
	{
		SOCKET clientsocket;
		SOCKADDR_IN clientAddr;
		int clientAddrLen = sizeof(SOCKADDR_IN);
		clientsocket = accept(_seversocket, (SOCKADDR*)&clientAddr, &clientAddrLen);
		if (clientsocket == INVALID_SOCKET)
		{
			printf("Accept Error %dn", GetLastError());
			return;
		}
		printf("accept OK SOCKET:[%d] Ip:[%s] Port:[%d]n",
			clientsocket, inet_ntoa(clientAddr.sin_addr),
			ntohs(clientAddr.sin_port));
		//创建客户端指针
		Client* pclient = new Client(clientsocket,
			inet_ntoa(clientAddr.sin_addr),
			ntohs(clientAddr.sin_port), this);
		//把套接字和客户端指针存入客户端的容器中
		_clients.insert(std::pair(clientsocket, pclient));
		OnAccept(pclient);
	}
}

其他的内容放入到我的资源中,需要源码的请自提!
下面献上运行结果:
用户注册:

用户登录:

其他的就不演示了,这里说一下房间的设计思路!
房间最好是设置一个房间管理器,有房间管理器管理房间和用户,用户下线是修改用户的状态,实际还是存在于用户容器中的,再次上线也只需要修改状态不用再做删除和增加的 *** 作。也就是逻辑删除

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5433007.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-11
下一篇 2022-12-11

发表评论

登录后才能评论

评论列表(0条)

保存