根据之前的框架添加新的功能

登录

点击相关按钮

// 登录按钮的响应
void CMainDialog::OnBnClickedLogin()
{
// 1. 获取用户输入的数据
UpdateData(TRUE);

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

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

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

发送的结构体

// 1. 登录使用的的结构体
using LOGIN_INFO = _REGISTER_INFO;
using PLOGIN_INFO = _REGISTER_INFO*;

python接收到后处理的方法

# 实例方式:用于处理登录消息
   def on_login(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' AND pswd=MD5('%s');" %(username, password)):
           # 如果用户名和密码匹配,返回结果 => 类型 + 句柄 + 是否成功 + 服务器返回的信息
           client.send(struct.pack("iii40s", Type.LOGIN.value, hwnd, 1, "登录成功".encode("UTF16")))
           # 将登录成功的用户添加到【在线用户】字典中
           print(username, "登录了聊天室")
           self.dict_client[username] = client
       else:
           # 失败的信息
           client.send(struct.pack("iii40s", Type.LOGIN.value, hwnd, 0, "用户名或密码不匹配".encode("UTF16")))

 

客户端接接受到服务端发来的消息做的反应

主窗口响应,转发

// 响应登录
case TYPE::LOGIN:
{
// 对于注册是否成功的消息,wParam 没有用,lParam 指向 RECV_STATE
::SendMessage(RecvInfo->hWnd, UM_RECV_LOGIN, NULL, (LPARAM)& RecvInfo->RecvState);
break;
}

转发到的地方响应

// 是否登录成功
afx_msg LRESULT CMainDialog::OnUmRecvLogin(WPARAM wParam, LPARAM lParam)
{
// 将 lParam 转换成对应的的结构
PRECV_STATE RecvState = (PRECV_STATE)lParam;

// 判断是否登录成功
if (RecvState->IsSuccess)
{
// 弹出用户话框(好友、群组)
CUserDialog* dialog = new CUserDialog;
dialog->Create(IDD_USERDIALOG, this);
// [ 对话框必须是非模态的,否则会导致消息的阻塞 ]
// [ 对话框必须是保存在堆空间中,否则离开作用域会销毁 ]
return dialog->ShowWindow(SW_SHOWNORMAL);
}

// 不成功执行这里
MessageBox(RecvState->MsgInfo);
}

登录之后进到一个新的界面

获取父窗口句柄

//构造函数内设置主窗口
CUserDialog::CUserDialog(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_USERDIALOG, pParent)
, m_AddEdit(_T(""))
{
// 保存父窗口
MainDialog = (CMainDialog*)AfxGetMainWnd();
}

设置标题

初始化树控件

// 初始化对话框(获取用户和群组)
BOOL CUserDialog::OnInitDialog()
{
CDialogEx::OnInitDialog();

// 隐藏父窗口
MainDialog->ShowWindow(SW_HIDE);
SetWindowText(MainDialog->m_UserName + L"主窗口");

// 添加好友分组和群组的节点
m_FrdNode = m_TreeCtrl.InsertItem(L"好友列表");
m_GrpNode = m_TreeCtrl.InsertItem(L"群组列表");

return TRUE;
}

C++客户端添加好友功能

客户端向服务器发送要添加的消息

获取要添加的名字

打包发送给服务器

/ 添加好友按钮的响应
void CUserDialog::OnBnClickedAddfrd()
{
// 1. 获取自己的名字和目标名称
UpdateData(TRUE);
CString MyName = MainDialog->m_UserName;

// 2. 自己不能添加自己为好友
if (MyName == m_AddEdit)
{
MessageBox(L"无法添加自己为好友!");
return;
}

// 3. 组合消息
SEND_INFO SendInfo = { TYPE::ADDFRD, GetSafeHwnd() };
memcpy(SendInfo.AddFrdInfo.User1, MyName.GetBuffer(), MyName.GetLength() * 2);
memcpy(SendInfo.AddFrdInfo.User2, m_AddEdit.GetBuffer(), m_AddEdit.GetLength() * 2);

// 4. 通过主窗口发送消息      
::SendMessage(MainDialog->GetSafeHwnd(), UM_SEND_MSG, (WPARAM)& SendInfo, sizeof(SendInfo));
}

打包前得先弄信息结构体

// 2. 添加好友的消息
typedef struct _ADDFRD_INFO
{
WCHAR User1[32];
WCHAR User2[32];
} ADDFRD_INFO, * PADDFRD_INFO;
服务端接收到消息之后的处理

添加类型

添加方法

定义方法

添加新的表单friend_table

user1

user2

发送回客户端

# 添加好友的消息
   def on_addfrd(self, client, message):
       # 解包获取发送来的数据
       hwnd, user1, user2 = struct.unpack("i64s64s", message[:132])
       # 获取双方的用户名
       user1 = user1.decode("UTF16").strip("\x00")
       user2 = user2.decode("UTF16").strip("\x00")
       # 是否有这样一个用户
       if not self.mysql.select("SELECT * FROM user_table WHERE user='%s';" % user2):
           client.send(struct.pack("iii40s", Type.ADDFRD.value, hwnd, 0, "用户不存在".encode("UTF16")))
       # 是否已经是我的好友
       elif self.mysql.select("SELECT * FROM friend_table WHERE user1='%s' AND user2='%s';" % (user1, user2)):
           client.send(struct.pack("iii40s", Type.ADDFRD.value, hwnd, 0, "不能重复添加好友".encode("UTF16")))
       # 添加成功的情况
       else:
           # 向数据库中添加一组信息 (我,好友)
           self.mysql.exec("INSERT INTO friend_table VALUE('%s', '%s');" % (user1, user2))
           # 在返回的消息处填写添加的好友的名称
           client.send(struct.pack("iii40s", Type.ADDFRD.value, hwnd, 1, user2.encode("UTF16")))

 
客户端接收到服务器的反馈的反应

要用非模态的对话框,否则信息会堵塞

主窗口响应,转发

// 响应加好友
case TYPE::ADDFRD:
{
// 对于注册是否成功的消息,wParam 没有用,lParam 指向 RECV_STATE
::SendMessage(RecvInfo->hWnd, UM_RECV_ADDFRD, NULL, (LPARAM)& RecvInfo->RecvState);
break;
}

转发到的地方

// 查看是否成功
afx_msg LRESULT CUserDialog::OnUmRecvAddfrd(WPARAM wParam, LPARAM lParam)
{
// 将 lParam 转换成对应的的结构
PRECV_STATE RecvState = (PRECV_STATE)lParam;

// 判断是否添加成功
if (RecvState->IsSuccess)
{
// 如果成功执行直接添加到列表
m_TreeCtrl.InsertItem(RecvState->MsgInfo, m_FrdNode);
return 0;
}

// 不成功执行这里
MessageBox(RecvState->MsgInfo);
return 0;
}

更新好友列表

打开好友的时候会自动更新好友列表

初始化的时候发送请求

// 发消息给服务器更新好友列表
SEND_INFO SendInfo = { TYPE::UPDATEFRD, GetSafeHwnd() };
memcpy(SendInfo.UpdateFrdInfo.User, MainDialog->m_UserName.GetBuffer(),
MainDialog->m_UserName.GetLength() * 2);

// 通过主窗口向服务器发送消息
::SendMessage(MainDialog->GetSafeHwnd(), UM_SEND_MSG, (WPARAM)& SendInfo, sizeof(SendInfo));

相关的结构体

// 3. 更新好友列表
typedef struct _UPDATEFRD_INFO
{
WCHAR User[32];
} UPDATEFRD_INFO, * PUPDATEFRD_INFO;

服务器接收到处理之后发回来

# 更新好友的消息
   def on_updatefrd(self, client, message):
       # 解包获取发送来的数据
       hwnd, user = struct.unpack("i64s", message[:68])
       # 获取目标用户名
       user = user.decode("UTF16").strip("\x00")
       # 查询目标用户的所有好友名称
       friends = self.mysql.select("SELECT user2 FROM friend_table WHERE user1='%s';" % user)
       # 通过一个循环,将每进一个用户名发送到客户端
       for name in friends:   # (("1"),("2"),("3"))
           client.send(struct.pack("ii64s", Type.UPDATEFRD.value, hwnd, name[0].encode("UTF16")))
           time.sleep(0.1)

 

客户端接受到消息之后处理

主窗口响应,转发

// 响应更新好友
case TYPE::UPDATEFRD:
{
// 对于注册是否成功的消息,wParam 没有用,lParam 指向 RECV_STATE
::SendMessage(RecvInfo->hWnd, UM_RECV_UPDATEFRD, NULL, (LPARAM)& RecvInfo->RecvState);
break;
}

转发到的地方

// 响应更新好友的消息
afx_msg LRESULT CUserDialog::OnUmRecvUpdatefrd(WPARAM wParam, LPARAM lParam)
{
// 转换成对应的结构体
PUPDATEFRD_INFO UserInfo = (PUPDATEFRD_INFO)lParam;

// 【添加到树控件的好友节点中,收到的消息前两个字节会是 ff fe】
m_TreeCtrl.InsertItem(&UserInfo->User[1], m_FrdNode);

return 0;
}

好友私聊

私聊窗口的界面

客户端的话,双击树控件的好友的时候要能弹出聊天窗口,

所以需要响应树控件按钮

字典#include<map>

using namespace std:

判断是不是好友窗口,群窗口同理

窗口可以双击打开,

// 响应窗口聊天
void CUserDialog::OnDblclkTree1(NMHDR* pNMHDR, LRESULT* pResult)
{
// 1. 获取当前点击的这一个子节点的内容
HTREEITEM CurrentNode = m_TreeCtrl.GetSelectedItem();
CString Name = m_TreeCtrl.GetItemText(CurrentNode);

// 2. 判断节点是不是[好友列表]或者[群组列表]
if (Name.Compare(L"好友列表") && Name.Compare(L"群组列表"))
{
// 3.1 判断窗口是否已经存在
if (MainDialog->WindowsMap.find(Name) != MainDialog->WindowsMap.end())
{
// 存在则显示,确保一个窗口只会被打开一次
MainDialog->WindowsMap[Name]->ShowWindow(SW_SHOWNORMAL);
}
// 3.2 判断是不是好友聊天窗口
else if (m_TreeCtrl.GetParentItem(CurrentNode) == m_FrdNode)
{
// 3.2.1 创建用户聊天窗口
CFrdDialog* dialog = new CFrdDialog(Name);
dialog->Create(IDD_DIALOG2, this);
// 3.2.2 设置用户聊天窗口的窗口名为点击的文字
dialog->SetWindowText(Name);
dialog->ShowWindow(SW_SHOWNORMAL);
// 3.2.3 将新创建的窗口添加到字典中
MainDialog->WindowsMap[Name] = dialog;
}
// 3.3 判断是不是群组聊天窗口
else if (m_TreeCtrl.GetParentItem(CurrentNode) == m_GrpNode)
{
// 3.3.1 创建群组聊天窗口
CGrpDialog* dialog = new CGrpDialog(Name);
dialog->Create(IDD_DIALOG3, this);
// 3.3.2 设置群组聊天窗口的窗口名为点击的文字
dialog->SetWindowText(Name);
dialog->ShowWindow(SW_SHOWNORMAL);
// 3.3.3 将新创建的窗口添加到字典中
MainDialog->WindowsMap[Name] = dialog;
}
}

*pResult = 0;
}

初始化的时候绑定参数(私聊对象跟自己)

CFrdDialog::CFrdDialog(CString TargetName, CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_DIALOG2, pParent)
, m_ShowEdit(_T(""))
{
m_TargetName = TargetName;
MainDialog = (CMainDialog*)AfxGetMainWnd();
}

接下来是发送信息,先能显示在窗口上

往服务器发消息

// 主动的发送一个消息
void CFrdDialog::OnBnClickedSendmsg()
{
// 1. 获取编辑框(聊天记录 + 输入框)的内容
UpdateData(TRUE);

// 2. 将用户的输入拼接到聊天框的结尾
SYSTEMTIME SystemTime = { 0 };
GetSystemTime(&SystemTime);
CString TimeString;
TimeString.Format(L"%d:%d:%d %d:%d:%d", SystemTime.wYear,
SystemTime.wMonth, SystemTime.wDay, SystemTime.wHour,
SystemTime.wMinute, SystemTime.wSecond);
m_ShowEdit += L"你 说(" + TimeString + L"):\r\n   "
+ m_InputEdit + L"\r\n\r\n";

// 3. 将输入的数据进行组合,句柄是无效的
CString MyName = MainDialog->m_UserName;
SEND_INFO SendInfo = { TYPE::FRDMSG, NULL };
memcpy(SendInfo.FrdMsg.From, MyName.GetBuffer(), MyName.GetLength() * 2);
memcpy(SendInfo.FrdMsg.To, m_TargetName.GetBuffer(), m_TargetName.GetLength() * 2);
memcpy(SendInfo.FrdMsg.Msg, m_InputEdit.GetBuffer(), m_InputEdit.GetLength() * 2);

// 4. 通过主窗口发送到服务器
::SendMessage(MainDialog->GetSafeHwnd(), UM_SEND_MSG, (WPARAM)& SendInfo, sizeof(SEND_INFO));

// 5. 将组合的数据显示到输出框
m_InputEdit = "";
UpdateData(FALSE);
}

所需结构体

// 4. 聊天消息
typedef struct _FRDMSG_INFO
{
WCHAR From[32];
WCHAR To[32];
WCHAR Msg[100];
} FRDMSG_INFO, * PFRDMSG_INFO;

服务器处理消息发送回来

# 发送好友的消息
   def on_frdmsg(self, client, message):
       # 解包获取发送来的数据
       hwnd, fromuser, to, msg = struct.unpack("i64s64s200s", message[:332])
       # 获取发送给的用户的名字
       to = to.decode("UTF16").strip("\x00")
       # 查看目标用户是否在线
       if to in self.dict_client:
           # 自己提供消息类型+原有的所有数据
           self.dict_client[to].send(b'\x04\x00\x00\x00' + message)

 

客户端处理消息显示好友发来的信息。

// 响应好友发过来的消息
afx_msg LRESULT CFrdDialog::OnUmRecvFrdmsg(WPARAM wParam, LPARAM lParam)
{
// 0. 转换成对应的结构体
PFRDMSG_INFO Msg = (PFRDMSG_INFO)lParam;

// 1. 获取编辑框(聊天记录)的内容
UpdateData(TRUE);

// 2. 将用户的输入拼接到聊天框的结尾
SYSTEMTIME SystemTime = { 0 };
GetSystemTime(&SystemTime);
CString TimeString;
TimeString.Format(L"%d:%d:%d %d:%d:%d", SystemTime.wYear,
SystemTime.wMonth, SystemTime.wDay, SystemTime.wHour,
SystemTime.wMinute, SystemTime.wSecond);
m_ShowEdit += CString(Msg->From) + L" 说(" + TimeString + L"):\r\n   "
+ Msg->Msg + L"\r\n\r\n";

// 3. 将组合的数据显示到输出框
UpdateData(FALSE);
return 0;
}

私聊发送文件

点击传送文件

//文件传送
void CPrivateChat::OnBnClickedTransferfiles()
{ //设置过滤器
TCHAR szFilter[] = _T("文本文件(*.txt)|*.txt|所有文件(*.*)|*.*||");
// 构造打开文件对话框
CFileDialog fileDlg(TRUE, _T("txt"), NULL, 0, szFilter, this);
CString strFilePath, strText;

// 显示打开文件对话框
if (IDOK == fileDlg.DoModal())
{
CString MyName = MainDialog->m_UserName;
SEND_INFO SendInfo = { TYPE::TRSFERFILE,NULL };
memcpy(SendInfo.TrsferFile.From, MyName.GetBuffer(), MyName.GetLength() * 2);
memcpy(SendInfo.TrsferFile.To, m_TargetName.GetBuffer(), m_TargetName.GetLength() * 2);
strFilePath = fileDlg.GetPathName(); //获取文件路径
CStdioFile file2;
if (!file2.Open(strFilePath, CFile::modeRead)) //打开文件
return;
while (true)//按行循环读取文件内容存入strText
{
if (!file2.ReadString(strText))
{
SetDlgItemText(IDC_EDIT2, strFilePath + " \r\n发送完毕...\r\n");
break;
}
MessageBox(strText);
memcpy(SendInfo.TrsferFile.Msg, strText.GetBuffer(),strText.GetLength() * 2);
::SendMessage(MainDialog->GetSafeHwnd(), UM_SEND_MSG, (WPARAM)&SendInfo, sizeof(SEND_INFO));
} }

​ }

所需结构体

using TRSFERFILE_INFO = FRDMSG_INFO;
using PTRSFERFILE_INFO = PFRDMSG_INFO;

服务器

    def on_trsferfile(self,client,message):
       hwnd, fromuser, to, msg = struct.unpack("i64s64s200s", message[:332])
       fromuser = fromuser.decode("UTF16").strip("\x00")
       to = to.decode("UTF16").strip("\x00")
       msg = msg.decode("UTF16").strip("\x00")
       if to in self.dict_client:
           self.dict_client[to].send(b'\x0b\x00\x00\x00' + message)

客户端

case TYPE::TRSFERFILE:
{
//获取到是谁发来的,判断是否有窗口。
CString Name = RecvInfo->TrsferFileInfo.From;
if (WindowsMap.find(Name) == WindowsMap.end())
{
CPrivateChat* dialog = new CPrivateChat(Name);
dialog->Create(IDD_PRIVATE, this);
dialog->SetWindowTextW(Name);
dialog->ShowWindow(SW_SHOWNORMAL);
WindowsMap[Name] = dialog;
}
::SendMessage(WindowsMap[Name]->GetSafeHwnd(), UM_RECV_TRSFERFILE, NULL, (LPARAM)&RecvInfo->TrsferFileInfo);
break;
}
//接收文件传送
afx_msg LRESULT CPrivateChat::OnUmRecvTrsferfile(WPARAM wParam, LPARAM lParam)
{
PTRSFERFILE_INFO Msg = (PTRSFERFILE_INFO)lParam;
// 1. 创建\打开文件,返回文件句柄,失败返回 -1
HANDLE FileHandle = CreateFile(
L"E:\\2.txt",               // 文件的路径
GENERIC_ALL,                // 表示可以对文件执行任何操作
NULL,                       // 共享方式,我正在操作的时候,允许其他进程执行的操作
NULL,                       // 安全属性
OPEN_ALWAYS,              // 始终创建这个文件
FILE_ATTRIBUTE_NORMAL,      // 普通的文件
NULL);

DWORD RealWrite = 0;
WriteFile(FileHandle,Msg->Msg , 100, &RealWrite, NULL);

MessageBox(L"接受到了 ");
CloseHandle(FileHandle);
return 0;
}

聊天记录

数据库msg_log(fromuser,touser,msg,datetime)

点击按钮

//获取聊天记录
void CPrivateChat::OnBnClickedLogmsg()

{

CString MyName = MainDialog->m_UserName;
SEND_INFO SendInfo = { TYPE::LOGMSG,NULL };
memcpy(SendInfo.LogMsg.User1, MyName.GetBuffer(), MyName.GetLength() * 2);
memcpy(SendInfo.LogMsg.User2, m_TargetName.GetBuffer(), m_TargetName.GetLength() * 2); ::SendMessage(MainDialog->GetSafeHwnd(), UM_SEND_MSG, (WPARAM)&SendInfo, sizeof(SEND_INFO));

}

所需结构体

using LOGMSG_INFO = ADDFRD_INFO;
using PLOGMSG_INFO = PADDFRD_INFO;

服务器

    def on_logmsg(self,client,message):
       hwnd,user1,user2=struct.unpack("i64s64s",message[:132])
       user1 =user1.decode("UTF16").strip("\x00")
       user2 =user2.decode("UTF16").strip("\x00")
       len, result = self.mysql.getresult("SELECT * FROM msg_log WHERE fromuser='%s' AND touser = '%s' "
                           " OR fromuser='%s' AND touser='%s';" % (user1, user2, user2, user1))
       for index in range(len):

           fromuser =result[index][0].encode("UTF16")
           touser =result[index][1].encode("UTF16")
           LogMsg =result[index][2].encode("UTF16")
           times = result[index][3].strftime("%Y-%m-%d %H:%M:%S")

           client.send(struct.pack("ii64s64s64s200s60s",Type.LOGMSG.value,hwnd,user2.encode("UTF16"),fromuser,touser
                                  ,LogMsg,times.encode("UTF16")))
           print(111)
           if(index + 1)% 4==0:
               time.sleep(0.1)

客户端

case TYPE::LOGMSG:
{
//获取到跟谁的聊天记录,判断是否有窗口
CString Name = &RecvInfo->LogMsg.From[1];
int  a = 10;
if (WindowsMap.find(Name) == WindowsMap.end())
{
CPrivateChat* dialog = new CPrivateChat(Name);
dialog->Create(IDD_PRIVATE, this);
dialog->SetWindowTextW(Name);
dialog->ShowWindow(SW_SHOWNORMAL);
WindowsMap[Name] = dialog;
}
::SendMessage(WindowsMap[Name]->GetSafeHwnd(), UM_RECV_LOGMSG, NULL, (LPARAM)&RecvInfo->FrdMsg);
break;
}
//接受获取到的聊天记录
afx_msg LRESULT CPrivateChat::OnUmRecvLogmsg(WPARAM wParam, LPARAM lParam)
{
//解包
PRECV_LOGMSG Msg = (PRECV_LOGMSG)lParam;
CString User1 = Msg->User1;
CString msg = &Msg->Msg[1];
//解密
CString receive("");
for (int i = 0; i < msg.GetLength(); i++)
{
receive += WCHAR(msg[i] ^ 1);
}

//更新窗口信息

if (User1== MainDialog->m_UserName)
{
m_ShowEdit += CString(Msg->User1) + L"说(" + Msg->DateTime + L"):\r\n   "
+ receive + L"\r\n\r\n";
}
else
{
m_ShowEdit += CString(Msg->User2) + L"说(" + Msg->DateTime + L"):\r\n   "
+ receive + L"\r\n\r\n";
} UpdateData(FALSE);
return 0;
}

接受聊天记录所需要结构体

typedef struct _RECV_LOGMSG
{
WCHAR From[32];
WCHAR User1[32];
WCHAR User2[32];
WCHAR Msg[100];
WCHAR DateTime[30]; ​
}RECV_LOGMSG,*PRECV_LOGMSG;

聊天室(C++客户端+Pyhton服务器)2.基本功能添加的更多相关文章

  1. 聊天室(C++客户端+Pyhton服务器)_1.框架搭设

    聊天室 一.客户端发送 用MFC可视化做个客户端登录界面. 先点击注册账号按钮,注册账号的时候就需要连接到服务器, 服务器需要查数据库,并做出相应的回应. 所以开始写C++客户端套接口类用来连接到服务 ...

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

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

  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. Node.js下基于Express + Socket.io 搭建一个基本的在线聊天室

    一.聊天室简单介绍 采用nodeJS设计,基于express框架,使用WebSocket编程之 socket.io机制.聊天室增加了 注册登录模块 ,并将用户个人信息和聊天记录存入数据库. 数据库采用 ...

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

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

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

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

  9. WinForm聊天室

    前几天开始学Socket编程,跟着老师一点一点的做.最后做了一个WinForm版的小聊天室.这个聊天室的客户端和服务端都只是在本机上运行. 这里我首先和大家谈谈我对聊天室的一点理解,聊天室其实是服务端 ...

随机推荐

  1. Spring中的扩展点

    Spring作为一个常用的IOC框架,在设计上预留了很多的扩展点,很多第三方开源框架,包括Spring自身也是基于这些扩展点实现的,这很好的体现了对修改关闭.对扩展开放的原则.总的来说Spring的扩 ...

  2. const 和指针

    c++用了那么久,觉得 const 和指针配合到一起的时候就会有点点分不出来. 如下: const Data* pData; Data const * pData Data * const pData ...

  3. Hello,Cardboard!!-如何开发一个最简单的Cardboard虚拟现实应用(一)

    [原创文章,转载请注明出处,谢谢 !] 温馨提醒,本篇第一节主要介绍cardboard虚拟现实系统的组成,如果只想看如何开发的具体步骤请直接跳到第二节^_^ 前述:恕我啰嗦一下,主要照顾对cardbo ...

  4. DFS系列 POJ(自认为的讲解)

    C - Sum It Up POJ1564 题意: 给你一个N,然后给你一堆数The numbers in each list appear in nonincreasing order, and t ...

  5. python __builtins__ bool类 (6)

    6.'bool',  函数用于将给定参数转换为布尔类型,如果没有参数,返回 False. class bool(int) # 继承于int类型 | bool(x) -> bool # 创建boo ...

  6. 树莓派 zero w 一根线使用

    参考网站:https://sspai.com/post/40086 硬件: 一台mac电脑 一根micro b usb线 一块zero w板子 一张micro sd卡 一.制卡 格式化 烧写镜像文件 ...

  7. 第二篇 .NET高级技术之密闭类和静态类及扩展方法

    1.密闭类是修饰为sealed的类, sealed不能有子类.一般只有系统中的一些基本类声明为sealed.面试题:是否可以编写一个类继承自String类? 答:不能,因为string被声明为了sea ...

  8. Luogu P1262 间谍网络 【强连通分量/缩点】By cellur925

    题目传送门 真是一道好题呀~~~~qwq 知道这题是tarjan,但是想了很久怎么用上强连通分量.因为样例们...它显然并不是一个强联通分量! (被样例迷惑的最好例子) 然后...就没有然后了...感 ...

  9. Oracle之Char VarChar VarChar2

    Oracle之Char VarChar VarChar2 在Oracle数据库中,字符类型有Char.VarChar和VarChar2三种类型,但不大清楚各自区别在哪儿,平时基本上就是用VarChar ...

  10. BFS Codeforces Round #297 (Div. 2) D. Arthur and Walls

    题目传送门 /* 题意:问最少替换'*'为'.',使得'.'连通的都是矩形 BFS:搜索想法很奇妙,先把'.'的入队,然后对于每个'.'八个方向寻找 在2*2的方格里,若只有一个是'*',那么它一定要 ...