IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 仿QQ聊天-C++服务器 -> 正文阅读

[网络协议]仿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 <WinSock2.h>
#include <map>

namespace XS
{
	class Client;
	class Sever
	{
	private:
		SOCKET _seversocket;
		std::map<SOCKET, Client*> _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 <iostream>
#include "Client.h"
#include <vector>
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("创建套接字失败 %d\n", 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 %d\n", GetLastError());
			return false;
		}
		printf("Bind OK IP:[%s] Port:[%d]\n", Ip, Port);
		if (SOCKET_ERROR == listen(seversocket, 5))
		{
			printf("Listen Error %d\n", 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<std::map<SOCKET, Client*>::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 %d\n", 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<SOCKET, Client*>(clientsocket, pclient));
		OnAccept(pclient);
	}
}

其他的内容放入到我的资源中,需要源码的请自提!
下面献上运行结果:
用户注册:
在这里插入图片描述
用户登录:
在这里插入图片描述
其他的就不演示了,这里说一下房间的设计思路!
房间最好是设置一个房间管理器,有房间管理器管理房间和用户,用户下线是修改用户的状态,实际还是存在于用户容器中的,再次上线也只需要修改状态不用再做删除和增加的操作。也就是逻辑删除

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-11-11 13:04:03  更:2021-11-11 13:06:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 6:23:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码