心跳包和tcp

心跳包

这节用的还是上节课的代码,算是对上一节的补充扩展

keep-alive-保活

在ChatClient中添加类向导

image-20240719140940129

1
2
3
4
5
6
void CChatClientDlg::OnTimer(UINT_PTR nIDEvent)
{
CPackage pkg(PT_HEARTBEAT, m_client);
sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer));
CDialogEx::OnTimer(nIDEvent);
}

添加类

image-20240719144412512

1
2
3
4
5
6
7
8
9
10
11
12
13
//CLock.h
#pragma once
#include<Windows.h>
class CLock
{
public:
CLock();
~CLock();
void Lock();
void Unlock();
private:
CRITICAL_SECTION m_cs;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//CLock.cpp
#include "CLock.h"

CLock::CLock()
{
InitializeCriticalSection(&m_cs);
}

CLock::~CLock()
{
DeleteCriticalSection(&m_cs);
}

void CLock::Lock()
{
EnterCriticalSection(&m_cs);
}

void CLock::Unlock()
{
LeaveCriticalSection(&m_cs);
}

因为修改的地方太多直接放完整代码

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
//Proto.h
#pragma once

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

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

#define NAMELEN 64
struct CLient
{
bool operator==(const CLient& 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];
};

#define MASLEN 1200
struct CPackage
{
CPackage(){}
CPackage(PackageType pt, CLient& client, char* szMsg = NULL):
m_pt(pt),
m_client(client)
{
memset(m_szMsg, 0, sizeof(m_szMsg));
if (szMsg != NULL)
{
strcpy(m_szMsg, szMsg);
}
}
CPackage(PackageType pt, CLient& ci, CLient& ciDst, char* szMsg = NULL) :
m_pt(pt),
m_client(ci),
m_ciDst(ciDst)
{
memset(m_szMsg, 0, sizeof(m_szMsg));
if (szMsg != NULL)
{
strcpy(m_szMsg, szMsg);
}
}
PackageType m_pt;//包的类型
CLient m_client;//客户端的信息
CLient m_ciDst;//客户端的信息
char m_szMsg[MASLEN];
};
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
// ChatServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <list>
#include <vector>
using namespace std;

#include "..\common\Proto.h"
#include "CLock.h"

struct CClientInfo
{
time_t m_nLastTime = 0;
CLient m_client;
};

vector<CClientInfo> g_lstCIs;
CLock g_lckForLstCis;

void OnLine(SOCKET sock, CPackage& pkg);
void OffLine(SOCKET sock, CPackage& pkg);
void OnPublic(SOCKET sock, CPackage& pkg);
void OnPrivate(SOCKET sock, CPackage& pkg);
void OnHeartBeat(SOCKET sock, CPackage& pkg);
DWORD WINAPI CheckHeartBeatThreadFunc(LPVOID lpParam);

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;
}

HANDLE hThread = CreateThread(
NULL,
0,
CheckHeartBeatThreadFunc,
(LPVOID)sockServer,
0,
NULL
);
if (hThread == NULL)
{
cout << "启动心跳线程 失败" << endl;
closesocket(sockServer);
return 0;
}
CloseHandle(hThread);

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;
case PT_HEARTBEAT:
OnHeartBeat(sockServer, pkg);
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_client.m_si.sin_addr),
pkg.m_client.m_si.sin_port,
pkg.m_client.m_szName);

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

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

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

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 OnHeartBeat(SOCKET sock, CPackage& pkg)
{
//更新心跳时间
g_lckForLstCis.Lock();
for (auto itr = g_lstCIs.begin(); itr != g_lstCIs.end(); ++itr)
{
if (itr->m_client == pkg.m_client)
{
//日志
printf("[Log]:%s %d %s heartbeat\r\n",
inet_ntoa(pkg.m_client.m_si.sin_addr),
pkg.m_client.m_si.sin_port,
pkg.m_client.m_szName);
itr->m_nLastTime = time(NULL);
break;
}
}
g_lckForLstCis.Unlock();
}

void OffLine(SOCKET sock, CPackage& pkg)
{
//日志
printf("[Log]:%s %d %s offline\r\n",
inet_ntoa(pkg.m_client.m_si.sin_addr),
pkg.m_client.m_si.sin_port,
pkg.m_client.m_szName);
//将下线的客户端从链表中删除
g_lckForLstCis.Lock();
for (auto itr=g_lstCIs.begin();itr!=g_lstCIs.end();++itr)
{
if (itr->m_client == pkg.m_client)
{
g_lstCIs.erase(itr);
break;
}
}

//通知其他客户端,有客户端下线
for (auto& ci : g_lstCIs)
{
sendto(sock, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&ci.m_client.m_si, sizeof(ci.m_client.m_si));
}
g_lckForLstCis.Unlock();
}

DWORD WINAPI CheckHeartBeatThreadFunc(LPVOID lpParam)
{
SOCKET sockServer = (SOCKET)lpParam;
const time_t nTmElappse = 5;
while (true)
{
time_t tmCurrent = time(NULL);
g_lckForLstCis.Lock();
for (auto itr = g_lstCIs.begin(); itr != g_lstCIs.end(); ++itr)
{
if (tmCurrent - itr->m_nLastTime >= nTmElappse)
{
//此客户端下线了
CPackage pkg(PT_OFFLINE, itr->m_client);
OffLine(sockServer, pkg);
break;
}
}
g_lckForLstCis.Unlock();
}
return 0;
}
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

// 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;
CLient m_client;
BOOL m_bWorking = FALSE;
static UINT WorkThreadFunc(LPVOID lpParam);
void EnableOnOffUI(BOOL bOnLine);

// 生成的消息映射函数
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();
afx_msg void OnTimer(UINT_PTR nIDEvent);
};
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
336
337
338
339
340
341
342
343
344
345
346
347

// 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)
ON_WM_TIMER()
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_client.m_szName);
CLient* pCi = new CLient(pkg.m_client);
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++)
{
CLient* pCi = (CLient*)pDlg->m_lstClients.GetItemData(i);
if (*pCi == pkg.m_client)
{
pDlg->m_lstClients.DeleteString(i);
delete pCi;
break;
}
}
break;
}
case PT_PUBLIC:
{
CString strFmt;
strFmt.Format("[群聊]%s 说: \t%s\r\n",pkg.m_client.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_client.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_client.m_si.sin_family = AF_INET;
m_client.m_si.sin_port = si.sin_port;
m_client.m_si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
strcpy(m_client.m_szName, m_strNick.GetBuffer());

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

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

SetTimer(1, 1000, NULL);

EnableOnOffUI(TRUE);
}


void CChatClientDlg::OnBnClickedOffline()
{
CPackage pkg(PT_OFFLINE, m_client);
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_client, 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;
}
CLient* pCi = (CLient*)m_lstClients.GetItemData(nIdx);

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


void CChatClientDlg::OnTimer(UINT_PTR nIDEvent)
{
CPackage pkg(PT_HEARTBEAT, m_client);
sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer));
CDialogEx::OnTimer(nIDEvent);
}

运行成功后,用户在线时会发送心跳包表示还该窗口还存活,下线后则停止发送心跳包

image-20240719142050479

tcp

这节的代码是在第一节课的代码上修改得来的,框架与第一节课udp的代码有类似的地方

服务器:

  1. 创建socket
  2. 绑定端
  3. 监听
  4. 等待连接
  5. 收发数据
  6. 关闭socket

客户端:

  1. 创建socket
  2. 连接服务器
  3. 收发数据
  4. 关闭socket

listen

在网络编程中,服务器通常会创建一个套接字(Socket)并将其绑定到一个特定的IP地址和端口上。这个套接字用于监听客户端的连接请求。
当服务器套接字调用 listen() 函数后,它进入监听状态。这意味着服务器已准备好接受客户端的连接请求。
服务器可以监听一个特定的端口,等待客户端尝试建立连接。

image-20240719152407136

参数

1
[in] s

标识绑定的未连接的套接字的描述符。

1
[in] backlog

挂起的连接队列的最大长度。 如果设置为 SOMAXCONN,则负责套接字 的基础 服务提供商会将积压工作设置为最大合理值。 如果设置为 SOMAXCONN_HINT (N) (其中 N 为数字) ,则积压工作值为 N,调整为在 200、65535) (范围内。 请注意, SOMAXCONN_HINT 可用于将积压工作设置为比 SOMAXCONN 更大的值。

SOMAXCONN_HINT 仅受 Microsoft TCP/IP 服务提供商支持。 没有用于获取实际积压工作值的标准预配。

返回值

如果未发生错误, 则侦听 返回零。 否则,将返回 值 SOCKET_ERROR ,并且可以通过调用WSAGetLastError来检索特定的错误代码。

accept

accept是一个常用于网络编程的函数,它的作用是使服务器端接受客户端的连接请求。当建立连接后,服务器端就可以和客户端进行收发数据。

image-20240719152511361

语法

1
2
3
4
5
SOCKET WSAAPI accept(
[in] SOCKET s,
[out] sockaddr *addr,
[in, out] int *addrlen
);

参数

1
[in] s

一个描述符,用于标识已使用 听函数置于侦听状态的套接字。 连接实际上是使用 accept 返回的套接字建立的。

1
[out] addr

指向接收连接实体地址的缓冲区的可选指针,该地址称为通信层。 addr 参数的确切格式由创建sockaddr 结构中的套接字时建立的地址系列确定。

1
[in, out] addrlen

指向包含 addr 参数指向的结构长度的整数的可选指针。

返回值

如果未发生错误, 则 accept 将返回 类型为 SOCKET 的值,该值是新套接字的描述符。 此返回值是建立实际连接的套接字的句柄。

否则,将返回 值 INVALID_SOCKET ,并且可以通过调用WSAGetLastError来检索特定的错误代码。

addrlen 引用的整数最初包含 addr 指向的空间量。返回时,它将包含返回的地址的实际长度(以字节为单位)。

recv

recv 函数从连接的套接字或绑定的无连接套接字接收数据。

image-20240719153045165

语法

1
2
3
4
5
6
int recv(
[in] SOCKET s,
[out] char *buf,
[in] int len,
[in] int flags
);

参数

1
[in] s

标识连接的套接字的描述符。

1
[out] buf

指向用于接收传入数据的缓冲区的指针。

1
[in] len

buf 参数指向的缓冲区的长度(以字节为单位)。

1
[in] flags

影响此函数行为的一组标志。 请参阅下面的备注。 有关此参数的可能值的详细信息,请参阅“备注”部分。

返回值

如果未发生错误, recv 将返回收到的字节数, buf 参数指向的缓冲区将包含接收的此数据。 如果连接已正常关闭,则返回值为零。

否则,将返回值 SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

send

send 函数在连接的套接字上发送数据。

不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。

image-20240719153424741

语法

1
2
3
4
5
6
int WSAAPI send(
[in] SOCKET s,
[in] const char *buf,
[in] int len,
[in] int flags
);

参数

1
[in] s

标识已连接套接字的描述符。

1
[in] buf

指向包含要传输的数据的缓冲区的指针。

1
[in] len

buf 参数指向的缓冲区中数据的长度(以字节为单位)。

1
[in] flags

一组指定调用方式的标志。 此参数是使用以下任一值的按位 OR 运算符构造的。

展开表

含义
MSG_DONTROUTE :指定数据不应受到路由的约束。 Windows 套接字服务提供程序可以选择忽略此标志。
MSG_OOB 仅) (流式套接字(例如SOCK_STREAM)发送 OOB 数据。

返回值

如果未发生错误, send 将返回发送的总字节数,该字节数可能小于 在 len 参数中请求发送的字节数。 否则,将返回值 SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

connect

connect 函数与指定的套接字建立连接。

image-20240719154743865

语法

1
2
3
4
5
int WSAAPI connect(
[in] SOCKET s,
[in] const sockaddr *name,
[in] int namelen
);

参数

1
[in] s

标识未连接的套接字的描述符。

1
[in] name

指向应建立连接的sockaddr结构的指针。

1
[in] namelen

name 参数指向的sockaddr 结构的长度(以字节为单位)。

返回值

如果未发生错误, 则连接 返回零。 否则,它将返回SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

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
//Server.cpp
SOCKET sockServer = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP);
//将SOCK_DGRAM改为SOCK_STREAM,将IPPROTO_UDP改为IPPROTO_TCP
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;
}

nRet = listen(sockServer, SOMAXCONN);
if (nRet == SOCKET_ERROR)
{
cout << "listen 失败" << endl;
return 0;
}

sockaddr_in siClient;
int nLen = sizeof(siClient);
SOCKET sock = accept(sockServer, (sockaddr*)&siClient, &nLen);
if (sock == INVALID_SOCKET)
{
cout << "accept 失败" << endl;
}
while (true)
{
char szBuff[MAXBYTE] = {};
nRet = recv(sock, szBuff, sizeof(szBuff), 0);
if (nRet == 0 || nRet == SOCKET_ERROR)
{
cout << "recv 失败" << endl;
}
else
{
printf("from %s %d recv: %s\r\n", inet_ntoa(siClient.sin_addr), ntohs(siClient.sin_port), szBuff);
}

char szSend[] = { "recv ok!" };
nRet = send(sock, szSend, sizeof(szSend), 0);
if (nRet == SOCKET_ERROR)
{
cout << "send失败" << endl;
}
}
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
//Client.cpp	
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//将SOCK_DGRAM改为SOCK_STREAM,将IPPROTO_UDP改为IPPROTO_TCP
if (sockClient == INVALID_SOCKET)
{
cout << "socket 失败" << endl;
return 0;
}
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 = connect(sockClient, (sockaddr*)&si, sizeof(si));
if (nRet == SOCKET_ERROR)
{
cout << "connect 失败" << endl;
return 0;
}
while (true)
{
char szSend[MAXBYTE] = { };
cin >> szSend;
int nRet = send(sockClient, szSend, sizeof(szSend), 0);
if (nRet == SOCKET_ERROR)
{
cout << "sendto 失败" << endl;
}

char szBuff[MAXBYTE];
sockaddr_in siFrom = {};
int nSizeofSi = sizeof(sockaddr_in);
nRet = recv(sockClient, szBuff, sizeof(szBuff), 0);
if (nRet == 0 || nRet == SOCKET_ERROR)
{
cout << "recvfrom失败" << endl;
}
else
{
printf("from %s %d recv: %s \r\n", inet_ntoa(siFrom.sin_addr), ntohs(siFrom.sin_port), szBuff);
}
}

成功运行后从客户端向服务器发消息可以被接收到,同时服务器向客户端回复也可以被接受到

image-20240719160722157

getpeername

image-20240719162638987

参数

1
[in] s

标识连接的套接字的描述符。

1
[out] name

接收对等方地址的 SOCKADDR 结构。

1
[in, out] namelen

指向 name 参数的大小(以字节为单位)的指针。

返回值

如果未发生错误, getpeername 将返回零。 否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 检索特定的错误代码。

使用场景
1、tcp服务端通过accept返回的套接字,通过 getpeername 可以获取到客户端的IP地址、端口;
2、其他需要获取对端IP地址或端口的情况。

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
// Server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
using namespace std;
#define WIN32_LEAN_AND_MEAN//仅保留windows.h中的必要的头文件
//如果不加这一行则要把Winsock2.h放到windows.h前面,否则会因重定义而报错
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib,"Ws2_32.lib")
DWORD WINAPI WorkThreadFunc(LPVOID lpParam);
int main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}

if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}

SOCKET sockServer = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP);
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;
}

nRet = listen(sockServer, 1);
if (nRet == SOCKET_ERROR)
{
cout << "listen 失败" << endl;
return 0;
}

while (true)
{
sockaddr_in siClient;
int nLen = sizeof(siClient);
SOCKET sock = accept(sockServer, (sockaddr*)&siClient, &nLen);
if (sock == INVALID_SOCKET)
{
cout << "accept 失败" << endl;
}

HANDLE hThread = CreateThread(
NULL,
0,
WorkThreadFunc,
(LPVOID)sock,
0,
NULL
);
CloseHandle(hThread);
}



cout << "Hello World!\n";
return 0;
}

DWORD WINAPI WorkThreadFunc(LPVOID lpParam)
{
SOCKET sock = (SOCKET)lpParam;
while (true)
{
char szBuff[MAXBYTE] = {};
int nRet = recv(sock, szBuff, sizeof(szBuff), 0);
if (nRet == 0 || nRet == SOCKET_ERROR)
{
cout << "recv 失败" << endl;
}
else
{
sockaddr_in siClient;
int nLen = sizeof(siClient);
getpeername(sock, (sockaddr*)&siClient, &nLen);
printf("from %s %d recv: %s\r\n", inet_ntoa(siClient.sin_addr), ntohs(siClient.sin_port), szBuff);
}

char szSend[] = { "recv ok!" };
nRet = send(sock, szSend, sizeof(szSend), 0);
if (nRet == SOCKET_ERROR)
{
cout << "send失败" << endl;
}
}
return 0;
}

成功运行后服务器可以接受来自不同客户端的信息

image-20240719163352690

image-20240719163444677

粘包

TCP协议是⾯向流(字节流)的协议,UDP是⾯向消息(报文)的协议。UDP段都是⼀条消息,应⽤程序必须以消息为单位提取数据,不能⼀次提取任意字节的数据

UDP具有保护消息边界,在每个UDP包中就有了消息头(消息来源地址,端⼝等信息),这样对于接收端来说就容易进⾏区分处理了。传输协议把数据当作⼀条独⽴的消息在⽹上传输,接收端只能接收独⽴的消息。接收端⼀次只能接收发送端发出的⼀个数据包,如果⼀次接受数据的⼤⼩⼩于发送端⼀次发送的数据⼤⼩,就会丢失⼀部分数据,即使丢失,接受端也不会分两次去接收。

udp-数据报

tcp-数据流-粘包

粘包:多个数据包被连续存储于连续的缓存中,在对数据包进行读取时由于无法确定发生方的发送边界,而采用某一估测值大小来进行数据读出,若双方的size不一致时就会使指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

udp

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
// Server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
using namespace std;
#define WIN32_LEAN_AND_MEAN//仅保留windows.h中的必要的头文件
//如果不加这一行则要把Winsock2.h放到windows.h前面,否则会因重定义而报错
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib,"Ws2_32.lib")

int main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}

if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}

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;
}

char szBuff[8];
sockaddr_in siFrom = {};
int nSizeofSi = sizeof(sockaddr_in);
nRet = recvfrom(sockServer, szBuff, sizeof(szBuff), 0, (sockaddr*)&siFrom, &nSizeofSi);
nRet = recvfrom(sockServer, szBuff, sizeof(szBuff), 0, (sockaddr*)&siFrom, &nSizeofSi);
cout << "Hello World!\n";
return 0;
}
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
// Client.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
using namespace std;
#define WIN32_LEAN_AND_MEAN//仅保留windows.h中的必要的头文件
//如果不加这一行则要把Winsock2.h放到windows.h前面,否则会因重定义而报错
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib,"Ws2_32.lib")

int main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}

if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}

SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockClient == INVALID_SOCKET)
{
cout << "socket 失败" << endl;
return 0;
}
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;

char szSend0[4] = { '\x11','\x22','\x33', '\x44' };
int nRet = sendto(sockClient, szSend0, sizeof(szSend0), 0, (sockaddr*)&si, sizeof(si));
if (nRet == SOCKET_ERROR)
{
cout << "sendto 失败" << endl;
}

char szSend[4] = { '\x55','\x66','\x77', '\x88' };
nRet = sendto(sockClient, szSend, sizeof(szSend), 0, (sockaddr*)&si, sizeof(si));
if (nRet == SOCKET_ERROR)
{
cout << "sendto 失败" << endl;
}

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

下断点后运行,第一次接收到了客户端传来的4个字节

image-20240719181232122

第二次在同一个地址接收到了客户端传来的剩下的4个字节

image-20240719181411446

tcp

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
// Server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
using namespace std;
#define WIN32_LEAN_AND_MEAN//仅保留windows.h中的必要的头文件
//如果不加这一行则要把Winsock2.h放到windows.h前面,否则会因重定义而报错
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib,"Ws2_32.lib")
DWORD WINAPI WorkThreadFunc(LPVOID lpParam);
int main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}

if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}

SOCKET sockServer = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP);
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;
}

nRet = listen(sockServer, 1);
if (nRet == SOCKET_ERROR)
{
cout << "listen 失败" << endl;
return 0;
}

sockaddr_in siClient;
int nLen = sizeof(siClient);
SOCKET sock = accept(sockServer, (sockaddr*)&siClient, &nLen);
if (sock == INVALID_SOCKET)
{
cout << "accept 失败" << endl;
}

char szBuff[8] = {};
nRet = recv(sock, szBuff, sizeof(szBuff), 0);

cout << "Hello World!\n";
return 0;
}
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
// Client.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
using namespace std;
#define WIN32_LEAN_AND_MEAN//仅保留windows.h中的必要的头文件
//如果不加这一行则要把Winsock2.h放到windows.h前面,否则会因重定义而报错
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib,"Ws2_32.lib")

int main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}

if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}

SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockClient == INVALID_SOCKET)
{
cout << "socket 失败" << endl;
return 0;
}
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 = connect(sockClient, (sockaddr*)&si, sizeof(si));
if (nRet == SOCKET_ERROR)
{
cout << "connect 失败" << endl;
return 0;
}
char szSend0[4] = { 1,2,3,4 };
nRet = send(sockClient, szSend0, sizeof(szSend0), 0);
char szSend[4] = { 5,6,7,8 };
nRet = send(sockClient, szSend, sizeof(szSend), 0);
cout << "Hello World!\n";
}

下断点后运行,可以一次性收到全部的8个字节

image-20240719182940286

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Server.cpp
sockaddr_in siClient;
int nLen = sizeof(siClient);
SOCKET sock = accept(sockServer, (sockaddr*)&siClient, &nLen);
if (sock == INVALID_SOCKET)
{
cout << "accept 失败" << endl;
}

while (true)
{
int nSize = 0;
nRet = recv(sock, (char*)&nSize, sizeof(nSize), 0);
char* szBuff = new char[nSize];
nRet = recv(sock, szBuff, nSize, 0);
}

cout << "Hello World!\n";
return 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Client.cpp
if (nRet == SOCKET_ERROR)
{
cout << "connect 失败" << endl;
return 0;
}
int nSize = 0x1000;
char* szSend = new char[nSize];
memset(szSend, 0x66, nSize);
nRet = send(sockClient, (char*)&nSize, sizeof(nSize), 0);
nRet = send(sockClient, szSend, (nSize), 0);

nSize = 100;
szSend = new char[nSize];
memset(szSend, 0x55, nSize);
nRet = send(sockClient, (char*)&nSize, sizeof(nSize), 0);
nRet = send(sockClient, szSend, (nSize), 0);

cout << "Hello World!\n";

第一次接受到来自客户端发送的信息

image-20240719185841953

image-20240719185859229

第二次接受到来自客户端发送的信息

image-20240719190012146

image-20240719190046318