聊天室

一.客户端发送

用MFC可视化做个客户端登录界面。

先点击注册账号按钮,注册账号的时候就需要连接到服务器,

服务器需要查数据库,并做出相应的回应。

所以开始写C++客户端套接口类用来连接到服务器。

demosocket.cpp文件

#include "pch.h"
#include <WS2tcpip.h>
#include "DemoSocket.h"


// 构造函数,用于执行初始化和套接字的创建
DemoSocket::DemoSocket()
{
// 0. 使用必须的结构体
WSAData WsaData = { 0 };

// 1. 初始化网络环境并请求指定版本
if (WSAStartup(MAKEWORD(2, 2), &WsaData))
{
MessageBox(NULL, L"网络环境初始化失败!", L"错误", MB_OK | MB_ICONERROR);
ExitProcess(0);
}

// 2. 判断版本信息是否匹配
if (MAKEWORD(2,2) != WsaData.wVersion)
{
MessageBox(NULL, L"套接字版本不匹配!", L"错误", MB_OK | MB_ICONERROR);
ExitProcess(0);
}

// 3. 创建一个套接字
ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ClientSocket == INVALID_SOCKET)
{
MessageBox(NULL, L"创建套接字失败!", L"错误", MB_OK | MB_ICONERROR);
ExitProcess(0);
}
}


// 析构函数,主要用于清理网络环境和释放套接字
DemoSocket::~DemoSocket()
{
// 清理网络环境和释放套接字
closesocket(ClientSocket);
WSACleanup();

// 结束接收线程并关闭内核对象句柄
}


// 用于设置对应的主窗口
void DemoSocket::SetMainWnd(CDialogEx* MainDialog)
{
// 主窗口对象主要用于获取主窗口的句柄,并发送数据
this->MainWnd = MainDialog;
}


// 用于连接到对应的服务器
void DemoSocket::Connect(LPCSTR Ip, u_short Port)
{
// 1. 使用传入的 Ip 和 Port 填充结构体
sockaddr_in ServerAddr = { AF_INET };
ServerAddr.sin_port = htons(Port);
inet_pton(AF_INET, Ip, &ServerAddr.sin_addr);

// 2. 调用 connect 连接到服务器
if (SOCKET_ERROR == connect(ClientSocket, (sockaddr*)& ServerAddr, sizeof(ServerAddr)))
{
MessageBox(NULL, L"连接服务器失败!", L"错误", MB_OK | MB_ICONERROR);
ExitProcess(0);
}
客户端点击注册的时候具体怎么实现连接到服务器呢。

把登录窗口作为主窗口,主窗口初始化的时候实例化套接口类连接服务器,并把套接字绑定在主窗口上。

在主窗口类构造函数直接初始化连接到服务器

CMainDialog::CMainDialog(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_DIALOG1, pParent)
, m_UserName(_T("xiaoming"))
, m_PassWord(_T("xiaoming"))
{
// 让套接字指向当前主窗口
MySocket.SetMainWnd(this);

// 进行网络连接,传入地址和端口
MySocket.Connect("127.0.0.1", 6666);
}
客户端点击注册按钮后,需要发送信息给服务器,那么套接字类需要添加发送的函数,同时定义发送的信息的结构体长什么样。

信息的结构体

结构体枚举

结构体的联合应用

通过自定义消息发送到服务器

自定义消息宏定义未识别,所以要自己定义

发送函数

发送

//放基础信息  pch.h文件里面
// 拟定的消息类型
enum TYPE {
REGISTER = 0, LOGIN, ADDFRD, UPDATEFRD, FRDMSG,
CRTGRP = 5, ADDGRP, UPDATEGRP, GRPMSG, UPDATEGRPUSER
};

// 0. 注册的结构体
typedef struct _REGISTER_INFO
{
WCHAR UserName[32];
WCHAR PassWord[32];
} REGISTER_INFO, *PREGISTER_INFO;

// 用于保存发送的数据
typedef struct _SEND_INFO
{
// 1. 类型:标识发送的消息是什么
TYPE Type;

// 2. 句柄:表示消息由谁发送
HWND hWnd;

// 3. 联合体: 不同类型对应不同的结构体
union {
REGISTER_INFO RegisterInfo;
LOGIN_INFO LoginInfo;
ADDFRD_INFO AddFrdInfo;
UPDATEFRD_INFO UpdateFrdInfo;
FRDMSG_INFO FrdMsg;
CRTGRP_INFO CrtGrpInfo;
ADDGRP_INFO AddGrpInfo;
UPDATEGRP_INFO UpdateGrpInfo;
GRPMSG_INFO GrpMsg;
UPDATEGRPUSER_INFO UpdateGrpUserInfo;
};
}SEND_INFO, * PSEND_INFO;

//demosocket.cpp文件里的函数
// 用于发送数据给服务器的函数
void DemoSocket::Send(LPVOID Buffer, INT Size)
{
// 发送消息并判断是否成功
if (SOCKET_ERROR == send(ClientSocket, (PCHAR)Buffer, Size, 0))
{
MessageBox(NULL, L"发送消息失败!", L"错误", MB_OK | MB_ICONERROR);
ExitProcess(0);
}
}


//点击注册按钮CMainDlg.cpp文件里面
// 注册按钮的响应
void CMainDialog::OnBnClickedRegister()
{
// 1. 获取用户输入的数据
UpdateData(TRUE);

// 2. 判断输入的数据是否为空
if (m_UserName.IsEmpty() || m_PassWord.IsEmpty())
{
MessageBox(L"请输入用户名和密码");
return;
}

// 3. 填充需要发送的数据内容
SEND_INFO SendInfo = { TYPE::REGISTER, GetSafeHwnd() };
memcpy(SendInfo.RegisterInfo.UserName, m_UserName.GetBuffer(), m_UserName.GetLength() * 2);
memcpy(SendInfo.RegisterInfo.PassWord, m_PassWord.GetBuffer(), m_PassWord.GetLength() * 2);

// 4. 将数据通过自定义消息发送到服务器
SendMessage(UM_SEND_MSG, (WPARAM)& SendInfo, sizeof(SEND_INFO));
}

//自定义消息,打开类向导-选择自定义消息-编辑UM_SEND_MSG
// 任何一个窗口都需要通过这个消息发送数据到服务器
#define UM_SEND_MSG WM_USER + 1
// 发送消息, wParam 是需要发送的数据, lPram 是大小
afx_msg LRESULT CMainDialog::OnUmSendMsg(WPARAM wParam, LPARAM lParam)
{
// 直接的发送消息
MySocket.Send((LPVOID)wParam, lParam);
return 0;
}

二.python服务器

接受到客户端发来的消息处理完再发回给客户端

Mysql

数据库 user_table(user,pswd)

账号密码需要保存在数据库。所以开始写数据库相关的东西。

两个方法,一个有返回值,一个没有。

import pymysql


# 用于执行数据库操作
class Mysql(object):

   # 构造函数,用于连接数据库
   def __init__(self):
       try:
           # 获取连接对象,用于操作连接数据库
           self.connect = pymysql.connect(host="127.0.0.1",
               port=3306, user="root", password="123456",db="chatroom")
           # 获取游标对象
           self.cursor = self.connect.cursor()
       except Exception as info:
           print(info)

   # 执行带返回值的指令
   def select(self, sql):
       try:
           # 执行sql指令
           self.cursor.execute(sql)
           # 获取执行的结果
           return self.cursor.fetchall()
       except Exception as info:
           # 回滚操作
           self.connect.rollback()
           # 输出错误信息
           print(info)


   # 执行不带返回值的指令
   def exec(self, sql):
       try:
           # 执行sql指令
           self.cursor.execute(sql)
           # 提交完成的操作
           self.connect.commit()
       except Exception as info:
           # 回滚操作
           self.connect.rollback()
           # 输出错误信息
           print(info)

首先是账号密码。

客户端连接上服务器,判断账号密码是否正确是在服务器上,所以数据库跟python对接,

# 构造函数,用于初始化网络以及创建套接字
   def __init__(self):
       # 1.创建套接字,是服务器的套接字
       self.server = socket.socket()
       # 2.绑定到指定的地址和端口,与客户端请求的一致
       self.server.bind(("127.0.0.1", 6666))
       # 3.设置当前套接字的监听状态
       self.server.listen(socket.SOMAXCONN)
       # 4. 初始化mysql对象
       self.mysql = mysql.Mysql()
           # 实例方法,用于接收客户端的连接
   def accept(self):
       while True:
           # 1. 直接调用函数接收客户端并保存套接字和地址
           client, address = self.server.accept()
           # 2. 输出显示客户端的连接消息
           print(address, "连接到了服务器")
           # 3. 为每一个客户端创建一个接收线程
           threading.Thread(target=self.recevice, args=(client,)).start()

C++点击登录之后,信息打包发给服务器,服务器要不断接受到信息,并根据不同消息分配不同函数。

 # 实例方法,用于分别接收每一个客户端的消息
   def recevice(self, client):
       while True:
           try:
               # 1. 获取了客户端发送的消息
               message = client.recv(2048)
               # 2. 解包获取其中的类型(返回的是元组)
               type, = struct.unpack("i", message[:4])
               # 3. 根据不同的类型调用不同的函数,第一个参数是self
               DemoChat.dict_func[type](self, client, message[4:])
           except Exception as info:
               # 如果产生了一场,说明客户端已经退出了
               for username, client_socket in self.dict_client.items():
                   # 如果套接字相同,就从字典中移除,说明退出登录
                   if client_socket == client:
                       self.dict_client.pop(username)
                       print(username, "退出了聊天室")
                       #################################
                       print(info)
                       #################################
                       break
               break

   

服务器做判断。根据数据建判断

# 定义一个枚举类型,标注消息类型
class Type(enum.Enum):
   REGISTER = 0
   LOGIN = 1
   ADDFRD = 2
   UPDATEFRD = 3
   FRDMSG = 4
   CRTGRP = 5
   ADDGRP = 6
   UPDATEGRP = 7
   GRPMSG = 8
# 创建一个字典,用于保存类型和与之匹配的处理函数
   dict_func = {
       Type.REGISTER.value: on_register,
       Type.LOGIN.value: on_login,
       Type.ADDFRD.value: on_addfrd,
       Type.UPDATEFRD.value: on_updatefrd,
       Type.FRDMSG.value: on_frdmsg,
       Type.CRTGRP.value: on_crtgrp,
       Type.ADDGRP.value: on_addgrp,
       Type.UPDATEGRP.value: on_updategrp,
       Type.GRPMSG.value: on_grpmsg
  }
分配到了注册的方法

根据数据库检查是否存在账号,不存在就注册

# 实例方式:用于处理注册消息
   def on_register(self, client, message):
       # 解包获取发送来的数据
       hwnd, username, password = struct.unpack("i64s64s", message[:132])
       # 将用户名和密码进行解码
       username = username.decode("UTF16").strip("\x00")
       password = password.decode("UTF16").strip("\x00")
       # 判断用户是否已存在
       if self.mysql.select("SELECT * FROM user_table WHERE user='%s'" % username):
           # 发送的内容 => 类型 + 句柄 + 是否成功 + 服务器返回的信息
           client.send(struct.pack("iii40s", Type.REGISTER.value, hwnd, 0, "用户名已存在".encode("UTF16")))
       else:
           # 如果不存在,向数据库中添加一组数据
           self.mysql.exec("INSERT INTO user_table(user,pswd) VALUE('%s',MD5('%s'))" % (username, password))
           # 将注册结果进行返回
           client.send(struct.pack("iii40s", Type.REGISTER.value, hwnd, 1, "注册成功".encode("UTF16")))

三.客户端接收到服务器发来的消息根据消息处理。

客户端发送消息结构体给服务器后,要循环接收服务器的反馈,所以在客户端要写个线程不断接受来自服务器的信息,
// 3. 创建线程用于接收服务器发过来的消息,线程函数必须是静态或全局的
Thread = CreateThread(NULL, NULL, WorkerThread, this, NULL, NULL);
if (NULL == Thread)
{
MessageBox(NULL, L"接收线程创建失败!", L"错误", MB_OK | MB_ICONERROR);
ExitProcess(0);
}
}
// 线程函数,用于接收消息并将消息发送给主窗口
DWORD WINAPI DemoSocket::WorkerThread(LPVOID lpThreadParameter)
{
// 1. 获取需要用到的所有数据
PCHAR Buffer = new CHAR[sizeof(RECV_INFO)]{ 0 };
DemoSocket* pMySocket = (DemoSocket*)lpThreadParameter;

// 2. 循环的接收服务器发送的数据
while (recv(pMySocket->ClientSocket, Buffer, sizeof(RECV_INFO), 0) > 0)
{
// 3. 将接收到的消息转交给主窗口进行分发
SendMessage(pMySocket->MainWnd->GetSafeHwnd(), UM_RECV_MSG, NULL,(LPARAM)Buffer);
}

// 4. 网络断开连接(自己的网\服务器)
MessageBox(NULL, L"网络连接已断开", L"错误", MB_OK | MB_ICONERROR);
delete[] Buffer; ExitProcess(0);
}

线程接受到的信息后,转发给主窗口响应UM_RECV_MSG消息

所以要自定义消息,打开类向导-选择自定义消息-编辑UM_RECV_MSG,

主窗口把注册的消息转发给了对应的消息,这里是UM_RECV_REGISTER

// 套接字使用这个消息将接收到的内容发送给主窗口
#define UM_RECV_MSG WM_USER + 2
// 接收到服务器发送的消息并进行处理(关键函数)
afx_msg LRESULT CMainDialog::OnUmRecvMsg(WPARAM wParam, LPARAM lParam)
{
// 1. 将 lParam 转换成对应的结构体
PRECV_INFO RecvInfo = (PRECV_INFO)lParam;

// 2. 根据不同的类型,转发消息给不同的窗口
switch (RecvInfo->Type)
{
// 响应注册
case TYPE::REGISTER:
{
// 对于注册是否成功的消息,wParam 没有用,lParam 指向 RECV_STATE
::SendMessage(RecvInfo->hWnd, UM_RECV_REGISTER, NULL, (LPARAM)&RecvInfo->RecvState);
break;
}

return 0;
}

通过自定义消息响应接受信息

自定义消息宏定义未识别,所以要自己定义

// 是否注册成功
#define UM_RECV_REGISTER WM_USER + 3
// 是否注册成功
afx_msg LRESULT CMainDialog::OnUmRecvRegister(WPARAM wParam, LPARAM lParam)
{
// 将 lParam 转换成对应的的结构
PRECV_STATE RecvState = (PRECV_STATE)lParam;

// 输出服务器返回的消息
MessageBox(RecvState->MsgInfo);

return 0;
}

接受信息的结构体

// 返回服务器处理的结果
typedef struct _RECV_STATE
{
BOOL IsSuccess; // 是否成功
WCHAR MsgInfo[20]; // 如果失败了,原因是什么
}RECV_STATE, *PRECV_STATE;


// 用于保存接收的数据
typedef struct _RECV_INFO
{
// 1. 类型:标识接收的消息是什么
TYPE Type;

// 2. 句柄:表示消息由谁发送
HWND hWnd;

// 3. 联合体: 不同类型对应不同的结构体
union {
RECV_STATE RecvState;
UPDATEFRD_INFO UpdateFrdInfo;
FRDMSG_INFO FrdMsg;
UPDATEGRP_INFO UpdateGrpInfo;
};
} RECV_INFO, * PRECV_INFO;

聊天室(C++客户端+Pyhton服务器)框架搭设完毕。

聊天室(C++客户端+Pyhton服务器)_1.框架搭设的更多相关文章

  1. 利用html 5 websocket做个山寨版web聊天室(手写C#服务器)

    在之前的博客中提到过看到html5 的websocket后很感兴趣,终于可以摆脱长轮询(websocket之前的实现方式可以看看Developer Works上的一篇文章,有简单提到,同时也说了web ...

  2. 聊天室(C++客户端+Pyhton服务器)2.基本功能添加

    根据之前的框架添加新的功能 登录 点击相关按钮 // 登录按钮的响应void CMainDialog::OnBnClickedLogin(){ // 1. 获取用户输入的数据 UpdateData(T ...

  3. 聊天室(C++客户端+Pyhton服务器)3.群功能添加

    创建群 数据库 group_table(user, name) grpuser_table(grpname,user) 按下添加群按钮 // 创建群组void CUserDialog::OnBnCli ...

  4. Qt实现网络聊天室(客户端,服务端)

    1. 效果演示 客户端 服务器 连接成功之后 2. 预备知识 如果不知道网络编程的可以去看我的上一篇文章C++网络编程 在Qt中,实现网络编程的方式比用C++或C实现要方便简单许多,因为Qt已经替我们 ...

  5. Netty学习笔记(六) 简单的聊天室功能之WebSocket客户端开发实例

    在之前的Netty相关学习笔记中,学习了如何去实现聊天室的服务段,这里我们来实现聊天室的客户端,聊天室的客户端使用的是Html5和WebSocket实现,下面我们继续学习. 创建客户端 接着第五个笔记 ...

  6. JavaSE项目之聊天室

    引子: 当前,互联网 体系结构的参考模型主要有两种,一种是OSI参考模型,另一种是TCP/IP参考模型. 一.OSI参考模型,即开放式通信系统互联参考模型(OSI/RM,Open Systems In ...

  7. JavaSE项目之聊天室swing版

    引子: 当前,互联网 体系结构的参考模型主要有两种,一种是OSI参考模型,另一种是TCP/IP参考模型. 一.OSI参考模型,即开放式通信系统互联参考模型(OSI/RM,Open Systems In ...

  8. C 基于UDP实现一个简易的聊天室

    引言 本文是围绕Linux udp api 构建一个简易的多人聊天室.重点看思路,帮助我们加深 对udp开发中一些api了解.相对而言udp socket开发相比tcp socket开发注意的细节要少 ...

  9. Android之聊天室设计与开发

    我们要设计和实现一个有聊天室功能的APP,需要服务器不断读取来自客户端的信息,并即时地将信息发送给每个连接到本服务器上的客户端,每个客户端可以向服务器发送消息,并不断地接收来自服务器的消息,并将消息显 ...

随机推荐

  1. codevs3285转圈游戏

    传送门 3285 转圈游戏 2013年NOIP全国联赛提高组  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 钻石 Diamond 题解       题目描述 Descript ...

  2. 【转】Chrome headless 模式

    原文地址: http://www.cnblogs.com/fnng/p/7797839.html 我们在通过Selenium运行自动化测试时,必须要启动浏览器,浏览器的启动与关闭必然会影响执行效率,而 ...

  3. Button Style

    Button Style BS_3STATE 与复选框一样本样式按钮可被单击变暗.变暗状态通常用于指示本样式的按键正处于禁用状态. BS_AUTO3STATE 与三状态的复选框一样当用户选中它本按钮样 ...

  4. k8s-helm-二十四

    一.介绍 Helm是Kubernetes的一个包管理工具,用来简化Kubernetes应用的部署和管理.可以把Helm比作CentOS的yum工具. yum不光要解决包之间的依赖关系,还要提供具体的程 ...

  5. 004--linux命令tar 软硬链接

    一.tar命令介绍: -c:创建一个新的tar文件 -t:列出tar文件中目录的内容 -x:从tar文件中抽取文件 -f:指定归档文件或磁带(也可能是软盘)设备(一般都要选) -v:显示所打包的文件的 ...

  6. ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 16. 角色管理

    注入UserManager和RoleManager 建立View页面.这段视频中没有录. RoleManager的服务没有注册 注册的地方进行修改 再次运行就可以了 这个ViewModel实际上只需要 ...

  7. linux C之access函数(转载)

    转自:http://blog.sina.com.cn/s/blog_6a1837e90100uh5d.html access():判断是否具有存取文件的权限 相关函数    stat,open,chm ...

  8. Swift4 检验变量

    创建: 2018/05/03  判断类 public func isKind(of aClass: Swift.AnyClass) -> Bool  是否是aClass或其子类的实例  publ ...

  9. python __builtins__ help类 (32)

    32.'help', 接收对象作为参数,更详细地返回该对象的所有属性和方法 class _Helper(builtins.object) | Define the builtin 'help'. | ...

  10. bzoj 3513: [MUTC2013]idiots【生成函数+FFT】

    想了好长时间最后发现真是石乐志 第一反应就是两边之和大于第三边,但是这个东西必须要满足三次-- 任意的两边之和合通过生成函数套路+FFT求出来(记得去掉重复选取的),然后这任意两边之和大于任意第三边可 ...