C++网络编程学习:缓冲区溢出与粘包分包

  • 使用的语言为C/C++
  • 源码支持的平台为:Windows / Linux

一、关于缓冲区溢出

1.缓冲区溢出的原因

  之前我们所编写的服务端与客户端的数据量都是很小的,且操作也不频繁,需要键入指令发送报文。

  我们可以尝试在之前客户端代码的循环里,不断发送一种数据包,且把数据包的大小加大到1000字节,会发现很快服务端和客户端就会出现问题——要么是数据接收出现问题,要么是服务端或者客户端程序直接卡掉。这里出现问题的原因就是socket内核缓冲区溢出。

  首先,send和recv函数并不是直接通过网卡操作。在使用send函数时,send函数首先把数据写入到发送缓冲区,随后通过网卡发出;在使用recv函数时,网卡首先把接收到的消息写入接收缓冲区,recv函数再从中copy数据。注意,上文中的两个缓冲区是存在于内核中的,并不是程序中自定义的缓冲区。

  我们在之前的源码中,recv的逻辑是先接收包头,随后根据包头接收包体。而当网卡接收数据太多时,我们接收一个包头的时间,网卡可能就新接收了两个完整的数据包,这就导致内核接收缓冲区里的数据量是在不断增加的,最终导致接收缓冲区溢出,造成无法正常发送以及程序阻塞的问题。

  举个例子,缓冲区就像一个浴缸,而我们是一个拿盆子舀水的人。我们之前先接收一个包头就相当于舀出一个包头那么多的水,随后再舀出包体那么多的水。舀了两次仅仅舀出一个报文那么多的水。如果浴缸放水的速度比较大的话,我们很容易就会处理不过来。最终造成浴缸溢出(缓冲区溢出)。

2.缓冲区溢出的处理方法

  接着看上文的例子,我们怎么能阻止浴缸(缓冲区)溢出呢?首先我们不大可能改变浴缸的大小,因为太过麻烦以及治标不治本,只要浴缸放水的时间够长,总会溢出。接着,舀水的速度我们也不好改变,因为一时半会是改不了的。那我们就只能改变舀水的次数和数量了。

  如何改变舀水的数量和次数?我们可以一次舀出足够多的水,随后再从舀出的水中分出想要数量的水,这样浴缸溢出的可能性就大大减少了。

  从代码层面来看上面的思路,只要我们程序内新建一个足够大的缓冲区,一次从内核缓冲区上recv足够的数据,就可以避免内核缓冲区溢出了。

  • 大概思路如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    char _Recv_buf[4096];
    int DataRecv
    {
    //接收客户端发送的数据
    int recv_len = recv(socket, _Recv_buf, 4096, 0);
    if(recv_len <= 0)
    {
    return -1;
    }
    while(_Recv_buf内不为空)
    {
    处理_Recv_buf内的数据
    }
    return 0;
    }
      但是这样会出现新的问题,即粘包与分包问题,请看下文。

二、粘包与分包

1.粘包与分包的原因

  上文中处理缓冲区溢出的思路是没有问题的,但是上文中的源码写法会存在问题。

  我们一次接收那么多数据,其中数据的界限是没有限定的,比如上文中是想要一次接收4096个字节。假如缓冲区内有5个1000字节大小的数据包,我们这次接收4096字节,等于说接收的数据中有4.096个数据包,其中就包含了新的问题。

  首先是粘包问题。即一次接收中含有多个数据包,这就导致数据包界限不清,粘在了一起。像上文中的4.096个包,接收端是不清楚的,接收端只知道有4096字节的数据,但是它不知道一个包是多大。所以我们可以通过包头来获取一个数据包的大小,由此来处理相应大小的数据以解决粘包问题。

  接着是分包问题。即一次接收中含有不完整的包。例如上文中的4096个字节,其中包含了4个完整的包,和一个包的前96个字节。对此,我们只能处理前4个完整的数据包。那么问题来了,对于上文中的缓冲区,由于recv函数每次都会覆盖这个缓冲区,这就导致缓冲区内无法存放未处理的消息。对于这个问题,我们可以新建一个缓冲区,来存放未处理的消息,实现双缓冲,即可处理分包问题。

  • TCP是面向数据流的协议,所以会出现粘包分包问题;UDP是面向消息的协议,每一个数据段都是一条消息,所以不会出现粘包分包问题。

2.粘包与分包的处理方法

2.1客户端升级思路

  首先是新建两个缓冲区,一个用来存放recv到的数据,一个用来存放所有待处理数据。首先第一个缓冲区recv到数据,随后把第一个缓冲区内的数据copy到第二个缓冲区内,即可实现数据的存放。随后处理数据之类的还是先获取包头,随后根据包头处理包体数据。

  • 大致思路如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//接收数据
char 接收缓冲区[4096]
char 消息缓冲区[40960];
int RecvData(SOCKET temp_socket)//处理数据
{
//接收客户端发送的数据
int recv_len = recv(temp_socket, 接收缓冲区, 4096, 0);
if(recv_len <= 0)
{
printf("与服务器断开连接,任务结束\n");
return -1;
}
1.将接收缓冲区的数据拷贝到消息缓冲区
while(2.判断消息缓冲区的数据长度是否大于等于包头长度)
{
3.选出包头数据 //解决粘包问题
if(4.判断消息缓冲区内数据长度是否大于等于报文长度) //解决少包问题
{
5.响应数据
6.将处理过的消息移出消息缓冲区
}
}
return 0;
}

2.2服务端升级思路

  与客户端整头思路相似,但是需要注意,服务端有多个连接,如果多个连接共用一个缓冲区会存在错误,所以每一个客户端连接都需要有自己的缓冲区。对此,我们可以新建一个客户端连接类,来存放每一个客户端的socket以及它的缓冲区。

  • 大致思路如下:
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
class 客户端连接
{
public:
1.获取socket()
2.获取缓冲区()
3.获取缓冲区长度()
4.设置缓冲区长度()
private:
1.socket
2.缓冲区
};

std::vector<客户端连接*> _clients;//储存客户端socket
char 接收缓冲区[4096];

0.此时前面OnRun函数里的判断过程也需要改变
//遍历所有socket 查看是否有待处理事件
for(int n=0; n<_clients.size(); ++n)
{
if(FD_ISSET(_clients[n]->获取socket(),&fdRead))
{
if(-1 == RecvData(_clients[n]))//处理请求 客户端退出的话
{
std::vector<客户端连接*>::iterator iter = _clients.begin()+n;//找到退出客户端的地址
if(iter != _clients.end())//如果是合理值
{
delete _clients[n];
_clients.erase(iter);//移除
}
}
}
}

int RecvData(客户端连接* client)//处理数据
{
//接收客户端发送的数据
int recv_len = recv(client->获取socket(), 接收缓冲区, 4096, 0);
if(recv_len <= 0)
{
printf("与服务器断开连接,任务结束\n");
return -1;
}
1.将接收缓冲区的数据拷贝到传入对象的消息缓冲区 client->获取缓冲区();
while(2.判断消息缓冲区的数据长度是否大于等于包头长度) client->获取缓冲区长度();
{
3.选出包头数据 //解决粘包问题
if(4.判断消息缓冲区内数据长度是否大于等于报文长度) //解决少包问题
{
5.响应数据
6.将处理过的消息移出消息缓冲区 client->设置缓冲区长度();
}
}
return 0;
}

三、升级后的源码及其详细注释

1.客户端源码 TcpClient.hpp

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
#ifndef _TcpClient_hpp_
#define _TcpClient_hpp_

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include<winSock2.h>
#include<windows.h>
#pragma comment(lib,"ws2_32.lib")
#else
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>

#define SOCKET int
#define INVALID_SOCKET (SOCKET)(~0)
#define SOCKET_ERROR (-1)
#endif
//枚举类型记录命令
enum cmd
{
CMD_LOGIN,//登录
CMD_LOGINRESULT,//登录结果
CMD_LOGOUT,//登出
CMD_LOGOUTRESULT,//登出结果
CMD_NEW_USER_JOIN,//新用户登入
CMD_ERROR//错误
};
//定义数据包头
struct DataHeader
{
short cmd;//命令
short date_length;//数据的长短
};
//包1 登录 传输账号与密码
struct Login : public DataHeader
{
Login()//初始化包头
{
this->cmd = CMD_LOGIN;
this->date_length = sizeof(Login);
}
char UserName[32];//用户名
char PassWord[32];//密码
char data[932];
};
//包2 登录结果 传输结果
struct LoginResult : public DataHeader
{
LoginResult()//初始化包头
{
this->cmd = CMD_LOGINRESULT;
this->date_length = sizeof(LoginResult);
}
int Result;
char Data[992];//无意义数据
};
//包3 登出 传输用户名
struct Logout : public DataHeader
{
Logout()//初始化包头
{
this->cmd = CMD_LOGOUT;
this->date_length = sizeof(Logout);
}
char UserName[32];//用户名
char data[964];
};
//包4 登出结果 传输结果
struct LogoutResult : public DataHeader
{
LogoutResult()//初始化包头
{
this->cmd = CMD_LOGOUTRESULT;
this->date_length = sizeof(LogoutResult);
}
int Result;
char data[992];
};
//包5 新用户登入 传输通告
struct NewUserJoin : public DataHeader
{
NewUserJoin()//初始化包头
{
this->cmd = CMD_NEW_USER_JOIN;
this->date_length = sizeof(NewUserJoin);
}
char UserName[32];//用户名
};

#include<bits/stdc++.h>
#define RECV_BUFFER_SIZE 4096

class TcpClient
{
public:
//构造
TcpClient()
{
_sock = INVALID_SOCKET;
//缓冲区相关
_Recv_buf = new char[RECV_BUFFER_SIZE];
_Msg_buf = new char[RECV_BUFFER_SIZE*10];
_Len_buf = 0;
}
//析构
virtual ~TcpClient()
{
delete[] _Recv_buf;
delete[] _Msg_buf;
//关闭socket
CloseSocket();
}

//初始化socket 返回1为正常
int InitSocket()
{
#ifdef _WIN32
//启动windows socket 2,x环境
WORD ver = MAKEWORD(2,2);
WSADATA dat;
if(0 != WSAStartup(ver,&dat))
{
return -1;//-1为环境错误
}
#endif
//创建socket
if(INVALID_SOCKET != _sock)
{
printf("<Socket=%d>关闭连接\n",_sock);
CloseSocket();//如果之前有连接 就关闭连接
}
_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(INVALID_SOCKET == _sock)
{

return 0;//0为socket创建错误
}
return 1;
}

//连接服务器 返回1为成功
int Connect(const char *ip,unsigned short port)
{
//如果为无效套接字 则初始化
if(INVALID_SOCKET == _sock)
{
InitSocket();
}
//连接服务器
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;//IPV4
_sin.sin_port = htons(port);//端口号
#ifdef _WIN32
_sin.sin_addr.S_un.S_addr = inet_addr(ip);//IP
#else
_sin.sin_addr.s_addr = inet_addr(ip);//IP
#endif
if(SOCKET_ERROR == connect(_sock,(sockaddr*)&_sin,sizeof(sockaddr_in)))
{
return 0;//连接失败
}
else
{
return 1;//连接成功
}
}

//关闭socket
void CloseSocket()
{
if(INVALID_SOCKET != _sock)
{
#ifdef _WIN32
//关闭socket
closesocket(_sock);
//清除windows socket 环境
WSACleanup();
#else
//关闭socket/LINUX
close(_sock);
#endif
_sock = INVALID_SOCKET;
}
}

//查询是否有待处理消息
bool OnRun()
{
if(IsRun())//如果有连接则监听事件
{
fd_set _fdRead;//建立集合
FD_ZERO(&_fdRead);//清空集合
FD_SET(_sock,&_fdRead);//放入集合
timeval _t = {1,0};//select最大响应时间
//新建seclect
int _ret = select(_sock+1,&_fdRead,NULL,NULL,&_t);
if(_ret<0)
{
printf("seclect任务结束\n");
return false;
}
if(FD_ISSET(_sock,&_fdRead))//获取是否有可读socket
{
FD_CLR(_sock,&_fdRead);//清理计数器
if(-1 == RecvData(_sock))
{
CloseSocket();
return false;
}
}
return true;
}
return false;
}

//判断是否工作中
bool IsRun()
{
return _sock != INVALID_SOCKET;
}

//发送数据
int SendData(DataHeader *_head)
{
if(IsRun() && _head)
{
send(_sock,(const char*)_head,_head->date_length,0);
return 1;
}
return 0;
}

//接收数据
int RecvData(SOCKET temp_socket)//处理数据
{
//接收客户端发送的数据
int recv_len = recv(temp_socket, _Recv_buf, RECV_BUFFER_SIZE, 0);
if(recv_len <= 0)
{
printf("与服务器断开连接,任务结束\n");
return -1;
}
//将接收缓冲区的数据拷贝到消息缓冲区
memcpy(_Msg_buf+_Len_buf, _Recv_buf, recv_len);
//消息缓冲区的数据末尾后移
_Len_buf += recv_len;
//判断消息缓冲区的数据长度是否大于等于包头长度
while(_Len_buf >= sizeof(DataHeader))//处理粘包问题
{
//选出包头数据
DataHeader* header = (DataHeader*)_Msg_buf;
//判断消息缓冲区内数据长度是否大于等于报文长度 避免少包问题
if(_Len_buf >= header->date_length)
{
//计算出消息缓冲区内剩余未处理数据的长度
int size = _Len_buf - header->date_length;
//响应数据
NetMsg(header);
//将消息缓冲区剩余未处理的数据前移
memcpy(_Msg_buf, _Msg_buf + header->date_length, size);
//消息缓冲区的数据末尾前移
_Len_buf = size;
}
else
{
//消息缓冲区数据不足
break;
}
}

return 0;
}

//响应数据
virtual void NetMsg(DataHeader *_head)
{
printf("接收到包头,命令:%d,数据长度:%d\n",_head->cmd,_head->date_length);
switch(_head->cmd)
{
case CMD_LOGINRESULT://登录结果 接收登录包体
{
LoginResult *_result = (LoginResult*)_head;
printf("登录结果:%d\n",_result->Result);
}
break;
case CMD_LOGOUTRESULT://登出结果 接收登出包体
{
LogoutResult *_result = (LogoutResult*)_head;
printf("登录结果:%d\n",_result->Result);
}
break;
case CMD_NEW_USER_JOIN://新用户登录通知
{
NewUserJoin *_result = (NewUserJoin*)_head;
printf("用户:%s已登录\n",_result->UserName);
}
break;
case CMD_ERROR://错误
{
printf("错误数据\n");
getchar();
}
break;
default:
{
printf("未知数据\n");
getchar();
}
break;
}
}

private:
SOCKET _sock;
//缓冲区相关
char *_Recv_buf;//接收缓冲区
char *_Msg_buf;//消息缓冲区
int _Len_buf;//缓冲区数据尾部变量
};

#endif

2.服务端源码 TcpServer.hpp

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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
#ifndef _TcpServer_hpp_
#define _TcpServer_hpp_

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include<winSock2.h>
#include<windows.h>
#pragma comment(lib,"ws2_32.lib")//链接此动态链接库 windows特有
#else
#include<arpa/inet.h>//selcet
#include<unistd.h>//uni std
#include<string.h>

#define SOCKET int
#define INVALID_SOCKET (SOCKET)(~0)
#define SOCKET_ERROR (-1)
#endif
//枚举类型记录命令
enum cmd
{
CMD_LOGIN,//登录
CMD_LOGINRESULT,//登录结果
CMD_LOGOUT,//登出
CMD_LOGOUTRESULT,//登出结果
CMD_NEW_USER_JOIN,//新用户登入
CMD_ERROR//错误
};
//定义数据包头
struct DataHeader
{
short cmd;//命令
short date_length;//数据的长短
};
//包1 登录 传输账号与密码
struct Login : public DataHeader
{
Login()//初始化包头
{
this->cmd = CMD_LOGIN;
this->date_length = sizeof(Login);
}
char UserName[32];//用户名
char PassWord[32];//密码
char data[932];
};
//包2 登录结果 传输结果
struct LoginResult : public DataHeader
{
LoginResult()//初始化包头
{
this->cmd = CMD_LOGINRESULT;
this->date_length = sizeof(LoginResult);
}
int Result;
char Data[992];//无意义数据
};
//包3 登出 传输用户名
struct Logout : public DataHeader
{
Logout()//初始化包头
{
this->cmd = CMD_LOGOUT;
this->date_length = sizeof(Logout);
}
char UserName[32];//用户名
char data[964];
};
//包4 登出结果 传输结果
struct LogoutResult : public DataHeader
{
LogoutResult()//初始化包头
{
this->cmd = CMD_LOGOUTRESULT;
this->date_length = sizeof(LogoutResult);
}
int Result;
char data[992];
};
//包5 新用户登入 传输通告
struct NewUserJoin : public DataHeader
{
NewUserJoin()//初始化包头
{
this->cmd = CMD_NEW_USER_JOIN;
this->date_length = sizeof(NewUserJoin);
}
char UserName[32];//用户名
};

#include<bits/stdc++.h>
#define RECV_BUFFER_SIZE 4096

class ClientSocket
{
public:
//构造
ClientSocket(SOCKET sockfd = INVALID_SOCKET)
{
_sockfd = sockfd;
//缓冲区相关
_Msg_buf = new char[RECV_BUFFER_SIZE*10];
_Len_buf = 0;
}
//析构
virtual ~ClientSocket()
{
delete[] _Msg_buf;
}

//获取socket
SOCKET GetSockfd()
{
return _sockfd;
}

//获取缓冲区
char* MsgBuf()
{
return _Msg_buf;
}

//获取缓冲区尾部变量
int GetLen()
{
return _Len_buf;
}

//设置缓冲区尾巴变量
void SetLen(int len)
{
_Len_buf = len;
}

private:
SOCKET _sockfd;
//缓冲区相关
char *_Msg_buf;//消息缓冲区
int _Len_buf;//缓冲区数据尾部变量
};

class TcpServer
{
public:
//构造
TcpServer()
{
_sock = INVALID_SOCKET;
//缓冲区相关
_Recv_buf = new char[RECV_BUFFER_SIZE];
}

//析构
virtual ~TcpServer()
{
delete[] _Recv_buf;
//关闭socket
CloseSocket();
}

//初始化socket 返回1为正常
int InitSocket()
{
#ifdef _WIN32
//启动windows socket 2,x环境
WORD ver = MAKEWORD(2,2);
WSADATA dat;
if(0 != WSAStartup(ver,&dat))
{
return -1;//-1为环境错误
}
#endif
//创建socket
if(INVALID_SOCKET != _sock)
{
printf("<Socket=%d>关闭连接\n",_sock);
CloseSocket();//如果之前有连接 就关闭连接
}
_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(INVALID_SOCKET == _sock)
{
return 0;//0为socket创建错误
}
return 1;
}

//绑定IP/端口
int Bind(const char* ip,unsigned short port)
{
//如果为无效套接字 则初始化
if(INVALID_SOCKET == _sock)
{
InitSocket();
}
//绑定网络端口和IP地址
sockaddr_in _myaddr = {};
_myaddr.sin_family = AF_INET;//IPV4
_myaddr.sin_port = htons(port);//端口
#ifdef _WIN32
if(ip)//ip为空则监听所有网卡
{
_myaddr.sin_addr.S_un.S_addr = inet_addr(ip);//IP
}
else
{
_myaddr.sin_addr.S_un.S_addr = INADDR_ANY;//IP
}
#else
if(ip)//ip为空则监听所有网卡
{
_myaddr.sin_addr.s_addr = inet_addr(ip);//IP
}
else
{
_myaddr.sin_addr.s_addr = INADDR_ANY;//IP
}
#endif
if(SOCKET_ERROR == bind(_sock,(sockaddr*)&_myaddr,sizeof(sockaddr_in)))//socket (强制转换)sockaddr结构体 结构体大小
{
printf("绑定失败\n");
return 0;
}
else
{
printf("绑定成功\n绑定端口为%d\n",port);
return 1;
}
}

//监听端口
int Listen(int n)
{
//如果为无效套接字 则提示
if(INVALID_SOCKET == _sock)
{
printf("请先初始化套接字并绑定IP端口\n");
return 0;
}
//监听网络端口
if(SOCKET_ERROR == listen(_sock,n))//最大连接队列
{
printf("监听失败\n");
return 0;
}
else
{
printf("监听成功\n");
return 1;
}
}

//接受连接
int Accept()
{
//等待接收客户端连接
sockaddr_in clientAddr = {};//新建sockadd结构体接收客户端数据
int addr_len = sizeof(sockaddr_in);//获取sockadd结构体长度
SOCKET temp_socket = INVALID_SOCKET;//声明客户端套接字
#ifdef _WIN32
temp_socket = accept(_sock,(sockaddr*)&clientAddr,&addr_len);//自身套接字 客户端结构体 结构体大小
#else
temp_socket = accept(_sock,(sockaddr*)&clientAddr,(socklen_t*)&addr_len);//自身套接字 客户端结构体 结构体大小
#endif
if(INVALID_SOCKET == temp_socket)//接收失败
{
printf("<Socket=%d>错误,接受到无效客户端SOCKET\n",temp_socket);
return 0;
}
else
{
printf("新客户端加入\nIP地址为:%s \n", inet_ntoa(clientAddr.sin_addr));
//群发所有客户端 通知新用户登录
NewUserJoin *user_join = new NewUserJoin();
strcpy(user_join->UserName,inet_ntoa(clientAddr.sin_addr));
SendDataToAll(user_join);
//将新的客户端加入动态数组
_clients.push_back(new ClientSocket(temp_socket));
return 1;
}
}

//关闭socket
void CloseSocket()
{
if(INVALID_SOCKET != _sock)
{
#ifdef _WIN32
//关闭客户端socket
for(int n=0; n<_clients.size(); ++n)
{
closesocket(_clients[n]->GetSockfd());
delete _clients[n];
}
//关闭socket
closesocket(_sock);
//清除windows socket 环境
WSACleanup();
#else
//关闭客户端socket
for(int n=0; n<_clients.size(); ++n)
{
close(_clients[n]->GetSockfd());
delete _clients[n];
}
//关闭socket/LINUX
close(_sock);
#endif
_sock = INVALID_SOCKET;
_clients.clear();
}
}

//查询是否有待处理消息
bool OnRun()
{
if(IsRun())
{
fd_set fdRead;//建立集合
fd_set fdWrite;
fd_set fdExcept;
FD_ZERO(&fdRead);//清空集合
FD_ZERO(&fdWrite);
FD_ZERO(&fdExcept);
FD_SET(_sock,&fdRead);//放入集合
FD_SET(_sock,&fdWrite);
FD_SET(_sock,&fdExcept);
timeval s_t = {2,0};//select最大响应时间
SOCKET maxSock = _sock;//最大socket
//把连接的客户端 放入read集合
for(int n=_clients.size()-1; n>=0; --n)
{
FD_SET(_clients[n]->GetSockfd(),&fdRead);
if(maxSock < _clients[n]->GetSockfd())
{
maxSock = _clients[n]->GetSockfd();
}
}
//select函数筛选select
int ret = select(maxSock+1,&fdRead,&fdWrite,&fdExcept,&s_t);
if(ret<0)
{
printf("select任务结束\n");
CloseSocket();
return false;
}
if(FD_ISSET(_sock,&fdRead))//获取是否有新socket连接
{
FD_CLR(_sock,&fdRead);//清理
Accept();//连接
}
//遍历所有socket 查看是否有待处理事件
for(int n=0; n<_clients.size(); ++n)
{
if(FD_ISSET(_clients[n]->GetSockfd(),&fdRead))
{
if(-1 == RecvData(_clients[n]))//处理请求 客户端退出的话
{
std::vector<ClientSocket*>::iterator iter = _clients.begin()+n;//找到退出客户端的地址
if(iter != _clients.end())//如果是合理值
{
delete _clients[n];
_clients.erase(iter);//移除
}
}
}
}
//printf("空闲时间处理其他业务\n");
return true;
}
return false;
}

//判断是否工作中
bool IsRun()
{
return _sock != INVALID_SOCKET;
}

//发送数据
int SendData(DataHeader *head,SOCKET temp_socket)
{
if(IsRun() && head)
{
send(temp_socket,(const char*)head,head->date_length,0);
return 1;
}
return 0;
}

//向所有人发送数据
void SendDataToAll(DataHeader *head)
{
for(int n=0;n<_clients.size();++n)
{
SendData(head, _clients[n]->GetSockfd());
}
}

//接收数据
int RecvData(ClientSocket *t_client)//处理数据
{
//接收客户端发送的数据
int buf_len = recv(t_client->GetSockfd(), _Recv_buf, RECV_BUFFER_SIZE, 0);
if(buf_len<=0)
{
printf("客户端已退出\n");
return -1;
}

//将接收缓冲区的数据拷贝到消息缓冲区
memcpy(t_client->MsgBuf() + t_client->GetLen(), _Recv_buf, buf_len);
//消息缓冲区的数据末尾后移
t_client->SetLen(t_client->GetLen() + buf_len);
//判断消息缓冲区的数据长度是否大于等于包头长度
while(t_client->GetLen() >= sizeof(DataHeader))//处理粘包问题
{
//选出包头数据
DataHeader* header = (DataHeader*)t_client->MsgBuf();
//判断消息缓冲区内数据长度是否大于等于报文长度 避免少包问题
if(t_client->GetLen() >= header->date_length)
{
//计算出消息缓冲区内剩余未处理数据的长度
int size = t_client->GetLen() - header->date_length;
//响应数据
NetMsg(header,t_client->GetSockfd());
//将消息缓冲区剩余未处理的数据前移
memcpy(t_client->MsgBuf(), t_client->MsgBuf() + header->date_length, size);
//消息缓冲区的数据末尾前移
t_client->SetLen(size);
}
else
{
//消息缓冲区数据不足
break;
}
}

return 0;
}

//响应数据
void NetMsg(DataHeader *head,SOCKET temp_socket)
{
printf("接收到包头,命令:%d,数据长度:%d\n",head->cmd,head->date_length);
switch(head->cmd)
{
case CMD_LOGIN://登录 接收登录包体
{
Login *login = (Login*)head;
/*
进行判断操作
*/
//printf("%s已登录\n密码:%s\n",login->UserName,login->PassWord);
LoginResult *result = new LoginResult;
result->Result = 1;
SendData(result,temp_socket);
}
break;
case CMD_LOGOUT://登出 接收登出包体
{
Logout *logout = (Logout*)head;
/*
进行判断操作
*/
//printf("%s已登出\n",logout->UserName);
LogoutResult *result = new LogoutResult();
result->Result = 1;
SendData(result,temp_socket);
}
break;
default://错误
{
head->cmd = CMD_ERROR;
head->date_length = 0;
SendData(head,temp_socket);
}
break;
}
}

private:
SOCKET _sock;
std::vector<ClientSocket*> _clients;//储存客户端
//缓冲区相关
char *_Recv_buf;//接收缓冲区
};

#endif