聊天室

上线

新建控制台程序

image-20240717221332976

新建头文件

image-20240717222316417

Proto.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#pragma once

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

//包类型
enum PackageType
{
PT_ONLINE,
PT_OFFLINNE,
PT_PUBLIC,//群聊,公聊
PT_PRIVATE//私聊
};

#define NAMELEN 64
struct CLientInfo
{
sockaddr_in m_si;
char m_szName[NAMELEN];
};

#define MASLEN 1300
struct CPackage
{
PackageType m_pt;//包的类型
CLientInfo m_ci;//客户端的信息
char m_szMsg[MASLEN];
};

添加类

image-20240717223532082

CSockInit.h

1
2
3
4
5
6
7
8
9
10
11
12
#pragma once

#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

class CSockInit
{
private:
CSockInit();
~CSockInit();
static CSockInit m_si;
};

CSockInit.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include "CSockInit.h"

CSockInit::CSockInit()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD(2, 2);

err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
return;
}

/* Confirm that the WinSock DLL supports 2.2.*/
/* Note that if the DLL supports versions greater */
/* than 2.2 in addition to 2.2, it will still return */
/* 2.2 in wVersion since that is the version we */
/* requested. */

if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
WSACleanup();
return;
}

/* The WinSock DLL is acceptable. Proceed. */
}

CSockInit::~CSockInit()
{
WSACleanup();
}

CSockInit CSockInit::m_si;

新建MFC应用

image-20240717221821200

image-20240718113527951

image-20240718113518112

类向导添加控制变量

image-20240718114117173

同理

image-20240718114417123

image-20240718114428503

image-20240718114437313

Server和Client都要用到Proto.h,所以将Proto.h从项目中排除,重新建一个common文件夹来存放

image-20240718115525234

头文件包含格式变成

1
#include "..\common\Proto.h"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

// ChatClientDlg.h: 头文件
//

#pragma once

#include "..\common\Proto.h"//添加
// CChatClientDlg 对话框
class CChatClientDlg : public CDialogEx
{
// 构造
public:
CChatClientDlg(CWnd* pParent = nullptr); // 标准构造函数

// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_CHATCLIENT_DIALOG };
#endif

protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持


// 实现
protected:
HICON m_hIcon;
SOCKET m_sockClient;//添加
sockaddr_in m_siServer;//添加
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
CString m_strMsg;
CString m_strNick;
CString m_strShowMsg;
CListBox m_lstClients;
afx_msg void OnBnClickedOnline();
afx_msg void OnBnClickedOffline();
afx_msg void OnBnClickedPublic();
afx_msg void OnBnClickedPrivate();
};

不使用预编译头

image-20240718135411891

getsockname

image-20240718140730965

参数:

  • sockfd:套接字描述符
  • localaddr:用来保存地址信息的套接字结构体
  • addrlen:相对于参数2的结构体地址大小指针

返回值:

  • 成功:0
  • 失败:-1

返回sockfd当前所绑定的地址,存入addr所指缓冲区。
addrlen表示addr缓冲区的长度。

返回时,addrlen用于返回实际上的addr的大小。

如果addr缓冲区太小,地址会被截断,这种情况下,addrlen会返回一个比初始提供的更大的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// ChatServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <list>
using namespace std;
#include "..\common\Proto.h"

list<CLientInfo> g_lstCIs;

void OnLine(SOCKET sock, CPackage& pkg);
void OffLine(SOCKET sock, CPackage& pkg);
void OnPublic(SOCKET sock, CPackage& pkg);
void OnPrivate(SOCKET sock, CPackage& pkg);

int main()
{
SOCKET sockServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockServer == INVALID_SOCKET)
{
cout << "socket 失败" << endl;
return 0;
}
//127.0.0.1-回环地址
sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(0x7788);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//0x0100007f;
int nRet = bind(sockServer, (sockaddr*)&si, sizeof(si));
if (nRet == SOCKET_ERROR)
{
cout << "bind 失败" << endl;
return 0;
}

while (true)
{
CPackage pkg;
sockaddr_in siFrom = {};
int nSizeofSi = sizeof(sockaddr_in);
nRet = recvfrom(sockServer, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&siFrom, &nSizeofSi);
if (nRet == 0 || nRet == SOCKET_ERROR)
{
cout << "recvfrom失败" << endl;
}
else
{
switch (pkg.m_pt)
{
case PT_ONLINE:
OnLine(sockServer, pkg);
break;
case PT_OFFLINE:
OffLine(sockServer, pkg);
break;
case PT_PUBLIC:
OnPublic(sockServer, pkg);
break;
case PT_PRIVATE:
OnPrivate(sockServer, pkg);
break;
default:
break;
}
}
}

std::cout << "Hello World!\n";
}

void OnLine(SOCKET sock, CPackage& pkg)
{
//日志
printf("[Log]:%s %d %s online\r\n",
inet_ntoa(pkg.m_ci.m_si.sin_addr),
pkg.m_ci.m_si.sin_port,
pkg.m_ci.m_szName);

//保存登录的客户端的列表
g_lstCIs.push_back(pkg.m_ci);

//通知其他客户端,有新客户端登录
CPackage pkgSend(PT_ONLINE, pkg.m_ci);
for (auto& ci : g_lstCIs)
{
sendto(sock, (char*)&pkgSend, sizeof(pkgSend), 0, (sockaddr*)&ci.m_si, sizeof(ci.m_si));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

// ChatClientDlg.cpp: 实现文件
//

void CChatClientDlg::OnBnClickedOnline()
{
UpdateData(TRUE);
if (m_strNick.IsEmpty())
{
AfxMessageBox("昵称不能为空");
return;
}

m_sockClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (m_sockClient == INVALID_SOCKET)
{
AfxMessageBox("socket 失败");
return;
}

sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = 0;
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//0x0100007f;
int nRet = bind(m_sockClient, (sockaddr*)&si, sizeof(si));

//向服务器发送登录包
int nLen = sizeof(si);
getsockname(m_sockClient, (sockaddr*)&si, &nLen);

CLientInfo ci;
ci.m_si.sin_family = AF_INET;
ci.m_si.sin_port = si.sin_port;
ci.m_si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
strcpy(ci.m_szName, m_strNick.GetBuffer());

CPackage pkg(PT_ONLINE, ci);
sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer));
}

成功上线

image-20240718143023591

1
2
3
4
5
6
7
8

// ChatClientDlg.h: 头文件
// 实现
protected:
HICON m_hIcon;
SOCKET m_sockClient;
sockaddr_in m_siServer;
static UINT WorkThreadFunc(LPVOID lpParam);//添加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// ChatClientDlg.cpp: 实现文件
UINT CChatClientDlg::WorkThreadFunc(LPVOID lpParam)//添加这个函数
{
CChatClientDlg* pDlg = (CChatClientDlg*)lpParam;
while (true)
{
CPackage pkg;
sockaddr_in siFrom = {};
int nSizeofSi = sizeof(sockaddr_in);
int nIdx = recvfrom(pDlg->m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&siFrom, &nSizeofSi);
if (nIdx == 0 || nIdx == SOCKET_ERROR)
{
continue;
}
else
{
switch (pkg.m_pt)
{
case PT_ONLINE:
{
int nIdx = pDlg->m_lstClients.AddString(pkg.m_ci.m_szName);
CLientInfo* pCi = new CLientInfo(pkg.m_ci);
pDlg->m_lstClients.SetItemData(nIdx, (DWORD_PTR)pCi);
break;
}
case PT_OFFLINE:
break;
case PT_PUBLIC:
break;
case PT_PRIVATE:
break;
}
}
}
return 0;
}

void CChatClientDlg::OnBnClickedOnline()
{
UpdateData(TRUE);
if (m_strNick.IsEmpty())
{
AfxMessageBox("昵称不能为空");
return;
}

m_sockClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (m_sockClient == INVALID_SOCKET)
{
AfxMessageBox("socket 失败");
return;
}

sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = 0;
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//0x0100007f;
int nRet = bind(m_sockClient, (sockaddr*)&si, sizeof(si));

//向服务器发送登录包
int nLen = sizeof(si);
getsockname(m_sockClient, (sockaddr*)&si, &nLen);

CLientInfo ci;
ci.m_si.sin_family = AF_INET;
ci.m_si.sin_port = si.sin_port;
ci.m_si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
strcpy(ci.m_szName, m_strNick.GetBuffer());

CPackage pkg(PT_ONLINE, ci);
sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer));

//启动工作线程(接受数据)
AfxBeginThread(WorkThreadFunc, (LPVOID)this);//添加
}

运行后打开3个聊天室,轮流上线3个用户,可以在列表中看到上线的用户

image-20240718152811978

公聊

UpdateData

image-20240718181313649

UpdateData(true);//用于将屏幕上控件中的数据交换到变量中。

UpdateData(false);//用于将数据在屏幕中对应控件中显示出来。

1
2
3
4
5
6
7
8
9
10
// ChatClientDlg.h: 头文件

// 实现
protected:
HICON m_hIcon;
SOCKET m_sockClient;
sockaddr_in m_siServer;
CLientInfo m_ci;//添加
static UINT WorkThreadFunc(LPVOID lpParam);

1
2
3
4
5
6
7
8
9
10
11
12
//ChatClientDlg.cpp: 实现文件
//向服务器发送登录包
int nLen = sizeof(si);
getsockname(m_sockClient, (sockaddr*)&si, &nLen);

m_ci.m_si.sin_family = AF_INET;//将ci改为m_ci
m_ci.m_si.sin_port = si.sin_port;
m_ci.m_si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
strcpy(m_ci.m_szName, m_strNick.GetBuffer());

CPackage pkg(PT_ONLINE, m_ci);
sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//ChatClientDlg.cpp: 实现文件
UINT CChatClientDlg::WorkThreadFunc(LPVOID lpParam)
{
case PT_PUBLIC:
{
CString strFmt;
strFmt.Format("[群聊]%s 说: \t%s\r\n",pkg.m_ci.m_szName, pkg.m_szMsg);
pDlg->m_strShowMsg += strFmt;
pDlg->GetDlgItem(EDT_SHOWMSG)->SetWindowText(pDlg->m_strShowMsg);
break;
}
}
void CChatClientDlg::OnBnClickedPublic()
{
UpdateData(TRUE);
if (m_strMsg.IsEmpty())
{
AfxMessageBox("消息不能为空");
return;
}

//发送群聊消息
CPackage pkg(PT_PUBLIC, m_ci, m_strMsg.GetBuffer());
sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer));
}
1
2
3
4
5
6
7
8
9
// ChatServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
void OnPublic(SOCKET sock, CPackage& pkg)
{
//将消息发送给所有的客户端
for (auto& ci : g_lstCIs)
{
sendto(sock, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&ci.m_si, sizeof(ci.m_si));
}
}

成功运行后可以实现公聊

image-20240718160711131

私聊

修改Proto.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define MASLEN 1200
struct CPackage
{
CPackage(){}
CPackage(PackageType pt, CLientInfo& ci, char* szMsg = NULL) :
m_pt(pt),
m_ci(ci)
{
memset(m_szMsg, 0, sizeof(m_szMsg));
if (szMsg != NULL)
{
strcpy(m_szMsg, szMsg);
}
}
CPackage(PackageType pt, CLientInfo& ci, CLientInfo& ciDst, char* szMsg = NULL) ://新增构造函数
m_pt(pt),
m_ci(ci),
m_ciDst(ciDst)
{
memset(m_szMsg, 0, sizeof(m_szMsg));
if (szMsg != NULL)
{
strcpy(m_szMsg, szMsg);
}
}
PackageType m_pt;//包的类型
CLientInfo m_ci;//客户端的信息
CLientInfo m_ciDst;//客户端的信息//新增私聊时对面客户段的信息
char m_szMsg[MASLEN];
};
1
2
3
4
5
// ChatServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
void OnPrivate(SOCKET sock, CPackage& pkg)
{
sendto(sock, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&pkg.m_ciDst.m_si, sizeof(pkg.m_ciDst.m_si));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//ChatClientDlg.cpp: 实现文件
UINT CChatClientDlg::WorkThreadFunc(LPVOID lpParam)
{
case PT_PRIVATE:
{
CString strFmt;
strFmt.Format("[私聊]%s 说: \t%s\r\n", pkg.m_ci.m_szName, pkg.m_szMsg);
pDlg->m_strShowMsg += strFmt;
pDlg->GetDlgItem(EDT_SHOWMSG)->SetWindowText(pDlg->m_strShowMsg);
break;
}
}
void CChatClientDlg::OnBnClickedPrivate()
{
UpdateData(TRUE);
if (m_strMsg.IsEmpty())
{
AfxMessageBox("消息不能为空");
return;
}

int nIdx = m_lstClients.GetCurSel();
if (nIdx == -1)
{
AfxMessageBox("未选择私聊对象");
return;
}
CLientInfo* pCi = (CLientInfo*)m_lstClients.GetItemData(nIdx);

//发送私聊信息
CPackage pkg(PT_PRIVATE, m_ci, *pCi, m_strMsg.GetBuffer());
sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer));
}

运行成功后可以实现私聊

image-20240718163649844

下线

修改Proto.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define NAMELEN 64
struct CLientInfo
{
bool operator==(const CLientInfo& obj)//新增重载函数
{
if (m_si.sin_addr.S_un.S_addr != obj.m_si.sin_addr.S_un.S_addr)
{
return false;
}
if (m_si.sin_port != obj.m_si.sin_port)
{
return false;
}
if (strcmp(m_szName, obj.m_szName) != 0)
{
return false;
}
return true;
}
sockaddr_in m_si;
char m_szName[NAMELEN];
};

修改ChatClientDlg.h

1
2
3
4
5
6
7
8
9
// 实现
protected:
HICON m_hIcon;
SOCKET m_sockClient;
sockaddr_in m_siServer;
CLientInfo m_ci;
BOOL m_bWorking = FALSE;//添加
static UINT WorkThreadFunc(LPVOID lpParam);
void EnableOnOffUI(BOOL bOnLine);//添加

在WorkThreadFunc中修改循环条件

1
2
3
4
5
6
//ChatClientDlg.cpp
UINT CChatClientDlg::WorkThreadFunc(LPVOID lpParam)
{
CChatClientDlg* pDlg = (CChatClientDlg*)lpParam;
while (pDlg->m_bWorking)//修改成以m_bWorking判断循环是否继续
}

在OnBnClickedOnline中补充设定

1
2
3
4
5
6
7
8
//ChatClientDlg.cpp
void CChatClientDlg::OnBnClickedOnline()
{
//启动工作线程(接受数据)
m_bWorking = TRUE;//把m_bWorking设为TRUE
AfxBeginThread(WorkThreadFunc, (LPVOID)this);
EnableOnOffUI(TRUE);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ChatServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
void OffLine(SOCKET sock, CPackage& pkg)
{
//将下线的客户端从链表中删除
for (auto itr=g_lstCIs.begin();itr!=g_lstCIs.end();++itr)
{
if (*itr == pkg.m_ci)
{
g_lstCIs.erase(itr);
break;
}
}

//通知其他客户端,有客户端下线
for (auto& ci : g_lstCIs)
{
sendto(sock, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&ci.m_si, sizeof(ci.m_si));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//ChatClientDlg.cpp: 实现文件
UINT CChatClientDlg::WorkThreadFunc(LPVOID lpParam)
{
case PT_OFFLINE:
{
int nCount = pDlg->m_lstClients.GetCount();
for (size_t i = 0; i < nCount; i++)
{
CLientInfo* pCi = (CLientInfo*)pDlg->m_lstClients.GetItemData(i);
if (*pCi == pkg.m_ci)
{
pDlg->m_lstClients.DeleteString(i);
delete pCi;
break;
}
}
break;
}
}
void CChatClientDlg::OnBnClickedOffline()
{
CPackage pkg(PT_OFFLINE, m_ci);
sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer));
m_bWorking = FALSE;
m_lstClients.ResetContent();
EnableOnOffUI(FALSE);
}

上线时可以点击下线,公聊,私聊

image-20240718175204998

下线后只能点击上线,而且其他聊天室中也显示该用户已下线

image-20240718175259373

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// ChatServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <list>
using namespace std;
#include "..\common\Proto.h"

list<CLientInfo> g_lstCIs;

void OnLine(SOCKET sock, CPackage& pkg);
void OffLine(SOCKET sock, CPackage& pkg);
void OnPublic(SOCKET sock, CPackage& pkg);
void OnPrivate(SOCKET sock, CPackage& pkg);

int main()
{
SOCKET sockServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockServer == INVALID_SOCKET)
{
cout << "socket 失败" << endl;
return 0;
}
//127.0.0.1-回环地址
sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(0x7788);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//0x0100007f;
int nRet = bind(sockServer, (sockaddr*)&si, sizeof(si));
if (nRet == SOCKET_ERROR)
{
cout << "bind 失败" << endl;
return 0;
}

while (true)
{
CPackage pkg;
sockaddr_in siFrom = {};
int nSizeofSi = sizeof(sockaddr_in);
nRet = recvfrom(sockServer, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&siFrom, &nSizeofSi);
if (nRet == 0 || nRet == SOCKET_ERROR)
{
cout << "recvfrom失败" << endl;
}
else
{
switch (pkg.m_pt)
{
case PT_ONLINE:
OnLine(sockServer, pkg);
break;
case PT_OFFLINE:
OffLine(sockServer, pkg);
break;
case PT_PUBLIC:
OnPublic(sockServer, pkg);
break;
case PT_PRIVATE:
OnPrivate(sockServer, pkg);
break;
default:
break;
}
}
}

std::cout << "Hello World!\n";
}

void OnLine(SOCKET sock, CPackage& pkg)
{
//日志
printf("[Log]:%s %d %s online\r\n",
inet_ntoa(pkg.m_ci.m_si.sin_addr),
pkg.m_ci.m_si.sin_port,
pkg.m_ci.m_szName);

//将已经上线的客户端发送给新上线的客户端
for (auto& ci : g_lstCIs)
{
CPackage pkgSend(PT_ONLINE, ci);
sendto(sock, (char*)&pkgSend, sizeof(pkgSend), 0, (sockaddr*)&pkg.m_ci.m_si, sizeof(pkg.m_ci.m_si));
}
//保存登录的客户端的列表
g_lstCIs.push_back(pkg.m_ci);

//通知其他客户端,有新客户端登录
CPackage pkgSend(PT_ONLINE, pkg.m_ci);
for (auto& ci : g_lstCIs)
{
sendto(sock, (char*)&pkgSend, sizeof(pkgSend), 0, (sockaddr*)&ci.m_si, sizeof(ci.m_si));
}
}

void OnPublic(SOCKET sock, CPackage& pkg)
{
//将消息发送给所有的客户端
for (auto& ci : g_lstCIs)
{
sendto(sock, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&ci.m_si, sizeof(ci.m_si));
}
}

void OnPrivate(SOCKET sock, CPackage& pkg)
{
sendto(sock, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&pkg.m_ciDst.m_si, sizeof(pkg.m_ciDst.m_si));
}

void OffLine(SOCKET sock, CPackage& pkg)
{
//将下线的客户端从链表中删除
for (auto itr=g_lstCIs.begin();itr!=g_lstCIs.end();++itr)
{
if (*itr == pkg.m_ci)
{
g_lstCIs.erase(itr);
break;
}
}

//通知其他客户端,有客户端下线
for (auto& ci : g_lstCIs)
{
sendto(sock, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&ci.m_si, sizeof(ci.m_si));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335

// ChatClientDlg.cpp: 实现文件
//

#include "pch.h"
#include "framework.h"
#include "ChatClient.h"
#include "ChatClientDlg.h"
#include "afxdialogex.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// 用于应用程序“关于”菜单项的 CAboutDlg 对话框

class CAboutDlg : public CDialogEx
{
public:
CAboutDlg();

// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_ABOUTBOX };
#endif

protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持

// 实现
protected:
DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CChatClientDlg 对话框



CChatClientDlg::CChatClientDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_CHATCLIENT_DIALOG, pParent)
, m_strMsg(_T(""))
, m_strNick(_T(""))
, m_strShowMsg(_T(""))
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CChatClientDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, EDT_MSG, m_strMsg);
DDX_Text(pDX, EDT_NICK, m_strNick);
DDX_Text(pDX, EDT_SHOWMSG, m_strShowMsg);
DDX_Control(pDX, LSTB_CLIENTS, m_lstClients);
}

BEGIN_MESSAGE_MAP(CChatClientDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(BTN_ONLINE, &CChatClientDlg::OnBnClickedOnline)
ON_BN_CLICKED(BTN_OFFLINE, &CChatClientDlg::OnBnClickedOffline)
ON_BN_CLICKED(BTN_PUBLIC, &CChatClientDlg::OnBnClickedPublic)
ON_BN_CLICKED(BTN_PRIVATE, &CChatClientDlg::OnBnClickedPrivate)
END_MESSAGE_MAP()


// CChatClientDlg 消息处理程序

BOOL CChatClientDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();

// 将“关于...”菜单项添加到系统菜单中。

// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != nullptr)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}

// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标

// TODO: 在此添加额外的初始化代码
m_siServer.sin_family = AF_INET;
m_siServer.sin_port = htons(0x7788);
m_siServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}

void CChatClientDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}

// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。

void CChatClientDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文

SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;

// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CChatClientDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}


UINT CChatClientDlg::WorkThreadFunc(LPVOID lpParam)
{
CChatClientDlg* pDlg = (CChatClientDlg*)lpParam;
while (pDlg->m_bWorking)
{
CPackage pkg;
sockaddr_in siFrom = {};
int nSizeofSi = sizeof(sockaddr_in);
int nIdx = recvfrom(pDlg->m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&siFrom, &nSizeofSi);
if (nIdx == 0 || nIdx == SOCKET_ERROR)
{
continue;
}
else
{
switch (pkg.m_pt)
{
case PT_ONLINE:
{
int nIdx = pDlg->m_lstClients.AddString(pkg.m_ci.m_szName);
CLientInfo* pCi = new CLientInfo(pkg.m_ci);
pDlg->m_lstClients.SetItemData(nIdx, (DWORD_PTR)pCi);
break;
}
case PT_OFFLINE:
{
int nCount = pDlg->m_lstClients.GetCount();
for (size_t i = 0; i < nCount; i++)
{
CLientInfo* pCi = (CLientInfo*)pDlg->m_lstClients.GetItemData(i);
if (*pCi == pkg.m_ci)
{
pDlg->m_lstClients.DeleteString(i);
delete pCi;
break;
}
}
break;
}
case PT_PUBLIC:
{
CString strFmt;
strFmt.Format("[群聊]%s 说: \t%s\r\n",pkg.m_ci.m_szName, pkg.m_szMsg);
pDlg->m_strShowMsg += strFmt;
pDlg->GetDlgItem(EDT_SHOWMSG)->SetWindowText(pDlg->m_strShowMsg);
break;
}
case PT_PRIVATE:
{
CString strFmt;
strFmt.Format("[私聊]%s 说: \t%s\r\n", pkg.m_ci.m_szName, pkg.m_szMsg);
pDlg->m_strShowMsg += strFmt;
pDlg->GetDlgItem(EDT_SHOWMSG)->SetWindowText(pDlg->m_strShowMsg);
break;
}
}
}
}
return 0;
}

void CChatClientDlg::EnableOnOffUI(BOOL bOnline)
{
if (bOnline)
{
GetDlgItem(BTN_ONLINE)->EnableWindow(FALSE);
GetDlgItem(BTN_OFFLINE)->EnableWindow(TRUE);
GetDlgItem(BTN_PUBLIC)->EnableWindow(TRUE);
GetDlgItem(BTN_PRIVATE)->EnableWindow(TRUE);
}
else
{
GetDlgItem(BTN_ONLINE)->EnableWindow(TRUE);
GetDlgItem(BTN_OFFLINE)->EnableWindow(FALSE);
GetDlgItem(BTN_PUBLIC)->EnableWindow(FALSE);
GetDlgItem(BTN_PRIVATE)->EnableWindow(FALSE);
}
}

void CChatClientDlg::OnBnClickedOnline()
{
UpdateData(TRUE);
if (m_strNick.IsEmpty())
{
AfxMessageBox("昵称不能为空");
return;
}

m_sockClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (m_sockClient == INVALID_SOCKET)
{
AfxMessageBox("socket 失败");
return;
}

sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = 0;
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//0x0100007f;
int nRet = bind(m_sockClient, (sockaddr*)&si, sizeof(si));

//向服务器发送登录包
int nLen = sizeof(si);
getsockname(m_sockClient, (sockaddr*)&si, &nLen);

m_ci.m_si.sin_family = AF_INET;
m_ci.m_si.sin_port = si.sin_port;
m_ci.m_si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
strcpy(m_ci.m_szName, m_strNick.GetBuffer());

CPackage pkg(PT_ONLINE, m_ci);
sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer));

//启动工作线程(接受数据)
m_bWorking = TRUE;
AfxBeginThread(WorkThreadFunc, (LPVOID)this);
EnableOnOffUI(TRUE);
}


void CChatClientDlg::OnBnClickedOffline()
{
CPackage pkg(PT_OFFLINE, m_ci);
sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer));
m_bWorking = FALSE;
m_lstClients.ResetContent();
EnableOnOffUI(FALSE);
}


void CChatClientDlg::OnBnClickedPublic()
{
UpdateData(TRUE);
if (m_strMsg.IsEmpty())
{
AfxMessageBox("消息不能为空");
return;
}

//发送群聊消息
CPackage pkg(PT_PUBLIC, m_ci, m_strMsg.GetBuffer());
sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer));
}


void CChatClientDlg::OnBnClickedPrivate()
{
UpdateData(TRUE);
if (m_strMsg.IsEmpty())
{
AfxMessageBox("消息不能为空");
return;
}

int nIdx = m_lstClients.GetCurSel();
if (nIdx == -1)
{
AfxMessageBox("未选择私聊对象");
return;
}
CLientInfo* pCi = (CLientInfo*)m_lstClients.GetItemData(nIdx);

//发送私聊信息
CPackage pkg(PT_PRIVATE, m_ci, *pCi, m_strMsg.GetBuffer());
sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer));
}