MFC多人在线聊天室

11 篇文章 7 订阅
6 篇文章 1 订阅

我已经在我的资源里上传了这个聊天室的代码了

基于MFC的C++的select模型的TCP聊天室
采用select网络模型,支持多人同时登陆,功能有上线、下线、群聊、私聊
使用CjsonObject进行数据传递,使用了心跳包进行判断下线的情况。

在这里插入图片描述
在这里插入图片描述

服务端并未使用MFC框架,解决方案包含文件如下:
在这里插入图片描述
主函数所在
ChatingServer.cpp

// ChatingServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include"Server.h"
using namespace std;

int main()
{
	Server  tcpServer;
	tcpServer.CreateServer("127.0.0.1", 9527);
	tcpServer.RunServer();

}

创建一个Server类对象,调用其方法开启服务端。

Server类:
Serve.h

#pragma once
#include "common.h"
#include "CJsonObject.hpp"
#include "TcpSocket.h"
#include "CLock.h"
#include <time.h>
#include <string>
#include <list>
#include <utility>
#include <algorithm>
#include<iostream>

using namespace std;

class Server
{
public:
	Server();
	~Server();

	BOOL  CreateServer(const char* szIp, u_short nPort);
	BOOL  RunServer();
	CLock g_lock;
private:
	static DWORD WINAPI HandleClientsThread(LPVOID pParam);

private:
	class ClientInfo
	{
	public:
		ClientInfo(clock_t cc,list<ClientInfo*>*pLstClients): //初始化
			m_clockHeartTime(cc),m_pLstClients(pLstClients)
		{}
		clock_t m_clockHeartTime; //心跳包,上次心跳时间
		list<ClientInfo*>* m_pLstClients;
		CTcpSocket m_tcpsocketClient;
	};

private:
	CTcpSocket m_tcpSocket;
	list<ClientInfo*> m_lstClients;

public:
	bool HandleData(ClientInfo* pCL);

};


用户接口为创建服务的CreateServer和开启服务的RunServer

内部结构中,定义一个ClientInfo结构体存储客户端信息,包括该客户端上次发送心跳包的时间,但没有实例化,待会来看如何实例化。

先看一下m_tcpSocket,它是一个CTcpSocket类的对象,来看一下它的结构:

class CTcpSocket
{
public:
	CTcpSocket();
	~CTcpSocket();

	BOOL CreateSocket();
	BOOL BindListen(char* szIp, u_short nPort);
	BOOL Accept(CTcpSocket* pTcpSocket );
	BOOL Connect(char* szIp, u_short nPort);
	BOOL Recv(char* pBuff, int* pLen);//收数据
	BOOL Send(char* pBuff, int* pLen);//发数据


	BOOL RecvPackage(DATAPACKAGE* pPackage);
	BOOL SendPackage(DATAPACKAGE* pPackage);


	void CloseSocket();

	const sockaddr_in& GetSockaddrIn()const;
	SOCKET m_socket;

private:

	sockaddr_in m_si;
};

看来是将套接字的功能封装在一起了

定义一个用户处理线程HandleClientsThread,用于处理TCP连接请求。注意,MFC中的线程必须被声明成DWORD WINAPI

查看WINAPI的定义,它是这样定义的
#define WINAPI _stdcall
可以发现CALLBACK也是这样定义的
_stdcall规定了编译时的一些选项
WINAPI是一个宏,所代表的符号是__stdcall, 函数名前加上这个符号表示这个函数的调用约定是标准调用约定,windows API函数采用这种调用约定。
具体来说,他们是关于堆栈的一些说明,首先是函数参数压栈顺序,其次是压入堆栈的内容由谁来清除,调用者还是函数自己?
stdcall的调用约定意味着:
1)参数从右向左压入堆栈;
2)函数自身修改堆栈;
3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。

好了,Server的结构我们了解了,看一下调用Server成员函数的情况
CreateServer的实现

BOOL Server::CreateServer(const char* szIp, u_short nPort)
{
	//1.创建tcp客户端
	BOOL bRet = m_tcpSocket.CreateSocket();
	if (!bRet)
	{
		cout << "tcp客户端创建失败" << endl;
		return FALSE;
	}

	//2.绑定端口
	bRet = m_tcpSocket.BindListen((char*)"127.0.0.1", 9527);
	if (!bRet)
	{
		cout << "绑定端口监听失败" << endl;
		return FALSE;
	}

	//3.创建一个线程,用来接收客户端数据
	HANDLE hThread = CreateThread(NULL, 0, HandleClientsThread, (LPVOID)this, 0, NULL);
	CloseHandle(hThread);

	return TRUE;
}

分开来看,其中CreateSocket封装了socket的初始化

BOOL CTcpSocket::CreateSocket()
{
	m_socket = socket(
		AF_INET,
		SOCK_STREAM, //流式 
		IPPROTO_TCP);//tcp协议
	if (m_socket == SOCKET_ERROR)
	{
		return FALSE;
	}
	else
	{
		return TRUE;
	}
}

接着调用BindListen

BOOL CTcpSocket::BindListen(char* szIp, u_short nPort)
{
	// 	2) 绑定端口
	m_si.sin_family = AF_INET;
	m_si.sin_port = htons(nPort);
	m_si.sin_addr.S_un.S_addr = inet_addr(szIp);
	int nRet = bind(m_socket, (sockaddr*)&m_si, sizeof(m_si));
	if (nRet == SOCKET_ERROR)
	{
		return FALSE;
	}

	// 	3) 监听
	nRet = listen(m_socket, SOMAXCONN);
	if (nRet == SOCKET_ERROR)
	{
		return FALSE;
	}

	return TRUE;
}

可以看出,是绑定端口并监听的封装。
最后建立处理线程,用来接收客户端数据。

接下来调用RunServer,接受来自客户端的数据

BOOL Server::RunServer()
{
	//接受来自客户端的数据
	while (TRUE)
	{
		ClientInfo* pCI = new ClientInfo(clock(), &m_lstClients);
		BOOL bRet = m_tcpSocket.Accept(&pCI->m_tcpsocketClient);
		if (!bRet)
		{
			break;
		}
		printf("IP:%s port:%d 连接到服务器. \r\n",
			inet_ntoa(pCI->m_tcpsocketClient.GetSockaddrIn().sin_addr),
			ntohs(pCI->m_tcpsocketClient.GetSockaddrIn().sin_port));

		m_lstClients.push_back(pCI);

	}
	return 0;
}

其中的Accept是对accept的封装,用于接受连接

BOOL CTcpSocket::Accept(CTcpSocket* pTcpSocket)
{
	// 	4) 接受连接
	sockaddr_in siClient;
	int nSize = sizeof(siClient);
	SOCKET sockClient = accept(m_socket, (sockaddr*)&siClient, &nSize);
	if (sockClient == SOCKET_ERROR)
	{
		return FALSE;
	}

	pTcpSocket->m_socket = sockClient;
	pTcpSocket->m_si = siClient;
	return TRUE;
}

回到RunServer中继续分析:

ClientInfo* pCI = new ClientInfo(clock(), &m_lstClients);

他会调用ClientInfo的构造函数:

	class ClientInfo
	{
	public:
		ClientInfo(clock_t cc,list<ClientInfo*>*pLstClients):  //初始化
			m_clockHeartTime(cc),m_pLstClients(pLstClients)
		{}
		clock_t m_clockHeartTime; //心跳包,上次心跳时间
		list<ClientInfo*>* m_pLstClients;
		CTcpSocket m_tcpsocketClient;
	};

&m_lstClients是传引用,即之前在main中创建Server类对象tcpServer时实例化的那个,他是一个双向链表,既然是传引用,容易看出,所有的客户端都共享同一个双向链表。

一旦接受连接,存储客户端信息,由于传递的是引用,pTcpSocket亦即RunServer中的pCI,并在RunServer中判断接受成功后,将客户端信息打印到控制台上。

printf("IP:%s port:%d 连接到服务器. \r\n",
			inet_ntoa(pCI->m_tcpsocketClient.GetSockaddrIn().sin_addr),
			ntohs(pCI->m_tcpsocketClient.GetSockaddrIn().sin_port));

若没有连接,则会跳出本次循环,继续调用Accept查询链接。

		if (!bRet)
		{
			break;
		}

连接成功后,还会向m_lstClients这个list中添加客户端信息

m_lstClients.push_back(pCI);

另一边HandleClientsThread这个线程不断从m_lstClients这个list中遍历存储连接信息,调用select来选择处理哪个连接,再调用HandleData()处理数据,包括判断数据类型,群发私聊等。

自己画的思维导图,有点乱。。。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 7
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
本科生产实习做的 客户端实现的详细清单: 1、进入程序的时候光标默认在昵称栏中,回车的默认是“发送”按钮,“断开”按钮默认为不可用 2、用户必须先连接上服务器才能使用聊天室,否则点击“发送”按钮将提示“你还没有登录”,服务器的IP可以进行选择或者输入,默认为本机环回 3、用户不能使用已经使用的昵称重复登录,昵称不能为空,但可以又空格 4、登录成功后“连接”按钮,昵称框,服务器框 都变为不可用,“断开”可用,消息框将显示“xxx刚刚进入了聊天室”,并在用户列表显示当前已登录的用户列表。断开后变化还原 5、说话对象默认为“所有人”,对所有人不能使用“悄悄话”功能 6、双击用户列表的某一行,或者直接在“对象”下拉菜单中选择说话的对象 7、“悄悄话”只有自己和对象可以看见,否则所有人都能看到 8、每一则消息的最大长度为468个字节,约234个汉字(含符号) 9、“清屏”按钮可以清空消息显示框的内容,并将“对象”菜单还原到“所有人” 10、消息显示框滚动条可以自动保持在显示内容的最底端,用户可以任意对消息框中的信息进行拖选和复制 11、所有聊天记录将自动保存在程序目录下的“昵称.txt”文件中 12、“断开”按钮可以不用退出程序而退出聊天事,用户选择“断开”或者关闭程序时,其他用户将提示“xxx刚刚离开了聊天室” 13、服务器踢出某用户,或者被关闭时将提示“被管理员踢出聊天室(或服务器关闭)” 14、服务器显示时能够识别说话对象是否为“你” 15、不能对自己说话 16、不能发空信息 17、每次发送信息后以及通过双击用户列表选中某个用户时,光标将默认处于“消息”框中以方便发送 18、当对某个人说话,而这个人退出时,提示“对象错误” 服务器端实现的详细清单(暂时只支持32个用户) 1、自动启动服务 2、用户登录后显示用户列表 3、可以选中某个用户将其踢出聊天室 4、对需要显示的信息添加时间后缀 5、对用户登录请求进行响应,不允许同名用户重复登录 6、对用户聊天数据进行转发 7、当用户列表发生 变化时,广播用户列表报文(目前的办法只实现了小于32个用户的情况)

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值