网络编程之 socket 心跳

回顾

上一例子已经了解了 TCP 客户端与服务端相互通信;但是在通信过程中,怎么知道对方已经掉线了呢?这就需要心跳机制

心跳,就是每每隔一定时间间隔,给对方发送个消息,让对方知道自己还活着,以确保连接的有效性。心跳的发送方可以是服务端,也可以是客户端;不过比较起来,前者开销就比较大。

本例子由客户端给服务器发送心跳包,基本流程思路:

  1. 如果服务端接收到的是心跳包,将该客户端对应的心跳检查计数器 count 清零;
  2. 在心跳检查线程中,每隔一定时间(假定:10秒)遍历所有已连接的客户端的心跳检查计数器 count
    • count 小于 一定的检查次数(假定:6次),将 count 计数器加 1;
    • count 大于等于设定的检查次数(假定:6次),说明已经(超过 1分钟 )未收到该用户心跳包,则判定该用户已经掉线;
  3. 客户端则建立心跳发送线程,定时(假定:10秒)给服务器发送心跳包。

相关函数

fd_set

struct fd_set 可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄。Unix下任何设备、管道、FIFO等都是文件形式,所以 socket 就是一个文件,socket句柄就是一个文件描述符。fd_set 相关操作如下:

  1. FD_ZERO:清空集合。
  2. FD_SET:将一个给定的文件描述符加入集合之中。
  3. FD_CLR:将一个给定的文件描述符从集合中删除。
  4. FD_ISSET:检查集合中指定的文件描述符是否可以读写。

timeval

用来代表时间值,有两个成员。

  1. tv_sec:秒数。
  2. tv_usec:毫秒数。

select

  1. 原型:

    1
    int select(int maxfdp, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
  2. 参数:

    • maxfdp:是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1
    • readfds:是指向 fd_set 结构的指针,这个集合中应该包括文件描述符,要监视这些文件描述符的读变化的;如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
    • writefds:是指向 fd_set 结构的指针,这个集合中应该包括文件描述符,要监视这些文件描述符的写变化的;如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
    • errorfds:同上面两个参数的意图,用来监视文件错误异常。
    • timeout:select 的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
  3. 返回值:返回状态发生变化的描述符总数。
    • 负值:select错误。
    • 正值:某些文件可读写或出错。
    • 0:等待超时,没有可读写或错误的文件。

例子

Server

server.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
#ifndef TCPServer_hpp
#define TCPServer_hpp
#include <iostream> // cout
#include <sys/socket.h> // socket
#include <netinet/in.h> // sockaddr_in
#include <arpa/inet.h> // inet_addr
#include <unistd.h> // close
#include <map> // map
#define BUFF_SIZE 4096 // 缓存大小
#define HB_INTERVAL 3 // 心跳时间间隔(单位:秒)
#define CHECK_HB_COUNT 5 // 无法检测到心跳最大次数
/** 返回值 枚举 */
typedef enum __retValue {
Ret_error = -2, // 出错
Ret_failed = -1, // 失败
Ret_success = 0, // 成功
}RetValue;
/** 消息类型 枚举 */
typedef enum __msgType {
Msg_heart = 0, // 心跳包
Msg_other = 1, // 其他数据
}MsgType;
/** 消息头 结构体 */
typedef struct __msgHead {
MsgType msgType; // 消息类型
unsigned int msgLen; // 消息长度
char data[0]; // 消息实体
}MsgHead;
void* CheckHeartBeat(void* arg); // 心跳检测
class TCPServer
{
private:
struct sockaddr_in serverAddr; // 服务端地址
socklen_t serAddrLen; // 地址长度
int serverSockfd; // 服务端 socket
struct timeval timeout; // 超时时间
int maxFd; // 最大 sockfd
fd_set masterSet; // 所有 sockfd 集合,包括服务端 sockfd 和所有已连接 sockfd
fd_set workingSet; // 工作集合
std::map<int, std::pair<std::string, int>> sockfdMap; // 记录连接的客户端 : <sockfd, <ip, checkCount>>
/**
接受客户端连接
@return 参见‘RetValue’
*/
RetValue AcceptConn();
/**
接收客户端消息
@return 参见‘RetValue’
*/
RetValue RecvMsg();
/**
心跳检测
@param arg 线程参数(TCPServer实例指针)
*/
friend void* CheckHeartBeat(void* arg);
public:
TCPServer(std::string ipStr, unsigned int port);
~TCPServer();
/**
绑定 服务端 地址
@return 参见‘RetValue’
*/
RetValue BindAddr();
/**
监听端口连接请求
@param queueNum 最大等待处理连接队列数
@return 参见‘RetValue’
*/
RetValue Listen(unsigned int queueNum);
/**
运行服务端
@return 参见‘RetValue’
*/
RetValue Run();
};
#endif /* TCPServer_hpp */

server.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
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
#include "TCPServer.hpp"
#define DEFAULT_QUEUE_NUM 5
TCPServer::TCPServer(std::string ipStr, unsigned int port)
{
if (true == ipStr.empty()
|| 0 >= ipStr.length())
{
std::cout << "Ip 地址不能为空!" << std::endl;
return;
}
// 创建 服务端 socket
serverSockfd = socket(PF_INET, SOCK_STREAM, 0);
if (0 > serverSockfd)
{
std::cout << "创建 socket 失败!" << std::endl;
return;
}
std::cout << "创建 socket 成功!" << std::endl;
int opt = 1;
// 让端口释放后立即就可以被再次使用。
setsockopt(serverSockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 设置 服务端 地址
serAddrLen = sizeof(serverAddr);
memset(&serverAddr, 0, serAddrLen); // 清空结构体
serverAddr.sin_family = AF_INET; // 协议族 类型
serverAddr.sin_port = htons(port); // 服务端 端口
serverAddr.sin_addr.s_addr = inet_addr(ipStr.c_str()); // 服务端 IP 地址
maxFd = serverSockfd; // 初始化 maxFd
FD_ZERO(&masterSet); // 情况 masterSet
FD_SET(serverSockfd, &masterSet); // 添加监听 fd
}
TCPServer::~TCPServer()
{
for (int sockfd = 0; sockfd <= maxFd; sockfd++)
{
if (FD_ISSET(sockfd, &masterSet))
{
std::cout << "关闭(sockfd = " << sockfd << ") !" << std::endl;
close(sockfd); // 关闭 sockfd
}
}
}
RetValue TCPServer::BindAddr()
{
// 绑定 IP 地址和端口号
int ret = bind(serverSockfd, (struct sockaddr*)&serverAddr, serAddrLen);
if (0 != ret)
{
std::cout << "绑定 IP 地址和端口失败!" << std::endl;
return Ret_failed;
}
std::cout << "绑定 IP 地址和端口成功!" << std::endl;
return Ret_success;
}
RetValue TCPServer::Listen(unsigned int queueNum)
{
// 监听端口(使 Socket 变为被动连接,等待客户端 Socket 连接)
int ret = listen(serverSockfd, 0 >= queueNum ? DEFAULT_QUEUE_NUM : queueNum);
if (0 != ret)
{
std::cout << "监听端口失败!" << std::endl;
return Ret_failed;
}
std::cout << "监听端口成功!" << std::endl;
return Ret_success;
}
RetValue TCPServer::Run()
{
pthread_t threadId;
// 创建心跳检测线程
int ret = pthread_create(&threadId, NULL, CheckHeartBeat, (void*)this);
if (0 != ret)
{
std::cout << "创建 心跳检测 线程失败!" << std::endl;
return Ret_failed;
}
while (1)
{
FD_ZERO(&workingSet); // 每次循环清空 working set
memcpy(&workingSet, &masterSet, sizeof(masterSet));
timeout.tv_sec = 30;
timeout.tv_usec = 0;
int nums = select(maxFd + 1, &workingSet, NULL, NULL, &timeout);
if (0 > nums) // 出错
{
std::cout << "select 出错!" << std::endl;
return Ret_error;
}
else if (0 == nums) // 超时,可以通过心跳检测判断是否断开
{
continue;
}
else // 有新内容可读写
{
if (FD_ISSET(serverSockfd, &workingSet)) // 有新客户端连接请求
{
AcceptConn();
}
else // 客户端有消息更新
{
RecvMsg();
}
}
}
return Ret_success;
}
RetValue TCPServer::AcceptConn()
{
// 客户端 地址
struct sockaddr_in clientAddr;
socklen_t cliAddrLen = sizeof(clientAddr);
// 阻塞式等待客户端连接
std::cout << "等待客户端连接。。。" << std::endl;
int clientSockfd = accept(serverSockfd, (struct sockaddr*)&clientAddr, &cliAddrLen);
if (0 > clientSockfd)
{
std::cout << "服务器接受客户端连接失败!" << std::endl;
return Ret_failed;
}
std::string ipStr = inet_ntoa(clientAddr.sin_addr);
std::cout << "服务器接受客户端(sockfd = " << clientSockfd << " ,ip = " << ipStr << ")连接成功!" << std::endl;
// 添加 客户端 sockfd
sockfdMap.insert(make_pair(clientSockfd, make_pair(ipStr, 0)));
// 将新建的连接 clientSockfd 加入 masterSet
FD_SET(clientSockfd, &masterSet);
// 更新 max sockfd
if (clientSockfd > maxFd)
{
maxFd = clientSockfd;
}
return Ret_success;
}
RetValue TCPServer::RecvMsg()
{
char recvBuf[BUFF_SIZE];
ssize_t recvLen = 0;
for(int sockfd = 0; sockfd <= maxFd; sockfd++)
{
if(FD_ISSET(sockfd, &workingSet)) // 有数据可读写
{
bool closeConn = false; // 标记当前连接是否断开了
memset(recvBuf, 0, BUFF_SIZE);
recvLen = recv(sockfd, &recvBuf, BUFF_SIZE, 0); // 接收消息
if (0 > recvLen) // 出错
{
std::cout << "接收消息出错(sockfd = " << sockfd << ")!" << std::endl;
closeConn = true;
}
else if (0 == recvLen) // 连接已断开
{
std::cout << "接收消息失败,(sockfd = " << sockfd << ")客户端连接已断开!" << std::endl;
closeConn = true;
}
else
{
MsgHead *msgHead = (MsgHead*)recvBuf;
if(Msg_heart == msgHead->msgType) // 心跳消息
{
sockfdMap[sockfd].second = 0; // 每次收到心跳包,count重置为0,标识连接中
}
else // 其他消息
{
// 这里直接返回客户端信息作为响应
ssize_t sendLen = send(sockfd, recvBuf, recvLen, 0);
if (0 > sendLen) // 出错
{
std::cout << "发送消息出错!" << std::endl;
closeConn = true;
}
else if (0 == sendLen) // 连接已断开
{
std::cout << "发送消息失败,连接已断开!" << std::endl;
closeConn = true;
}
else // 发送成功!
{
if (0 == strcmp(msgHead->data, "quit")) // 遇到 “quit” 则退出
{
std::cout << "客户端(sockfd = " << sockfd << ")已主动关闭" << std::endl;
closeConn = true;
}
}
}
std::cout << "接收客户端(sockfd = " << sockfd << ")的数据:" << msgHead->data << std::endl;
}
if(true == closeConn)
{
std::cout << "关闭客户端(sockfd = " << sockfd << ")" << std::endl;
close(sockfd); // 关闭套接字
FD_CLR(sockfd, &masterSet); // 从集合中清除相应 sockfd
sockfdMap.erase(sockfd); // 从map中移除该记录
if(sockfd == maxFd) // 需要更新 maxFd;
{
while(false == FD_ISSET(maxFd, &masterSet))
{
maxFd--;
}
}
}
}
}
return Ret_success;
}
#pragma mark -- 心跳检测 线程
void* CheckHeartBeat(void* arg)
{
std::cout << "心跳检测 线程启动!" << std::endl;
TCPServer* server = (TCPServer*)arg;
while(1)
{
std::cout << "当前已连接客户端数量:" << server->sockfdMap.size() << std::endl;
std::map<int, std::pair<std::string, int>>::iterator it = server->sockfdMap.begin();
for( ; it != server->sockfdMap.end(); ) // 循环检测所有客户端连接 sockfd 心跳
{
if(CHECK_HB_COUNT <= it->second.second) // (规定的时间内)没有收到心跳包,判定客户端掉线
{
int sockfd = it->first;
std::string ipStr = it->second.first;
std::cout << "客户端(sockfd = " << sockfd << "ipStr = " << ipStr << ")已经掉线" << std::endl;
close(sockfd); // 关闭该连接
FD_CLR(sockfd, &server->masterSet); // 从集合中清除相应 sockfd
if(sockfd == server->maxFd) // 需要更新 maxFd;
{
// 更新 maxFd
while(false == FD_ISSET(server->maxFd, &server->masterSet))
{
server->maxFd--;
}
}
server->sockfdMap.erase(it++); // 从map中移除该记录
}
else if(CHECK_HB_COUNT > it->second.second
&& 0 <= it->second.second)
{
it->second.second += 1;
it++;
}
else
{
it++;
}
}
sleep(HB_INTERVAL); // 定时检查
}
}

server.main

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
#include <iostream>
#include "TCPServer.hpp"
#define DEMO_PORT 5578
#define DEMO_IP "127.0.0.1"
#define MAX_LISTEN_QUEUE 10
int main()
{
TCPServer server(DEMO_IP, DEMO_PORT);
if (Ret_success != server.BindAddr())
{
exit(1);
}
if (Ret_success != server.Listen(MAX_LISTEN_QUEUE))
{
exit(1);
}
if (Ret_success != server.Run())
{
exit(1);
}
return 0;
}

Client

client.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
#ifndef TCPClient_hpp
#define TCPClient_hpp
#include <iostream> // cout
#include <sys/socket.h> // socket
#include <netinet/in.h> // sockaddr_in
#include <arpa/inet.h> // inet_addr
#include <unistd.h> // close
#define BUFF_SIZE 4096
#define HB_INTERVAL 10 // 心跳时间间隔(单位:秒)
/** 返回值 枚举 */
typedef enum __retValue {
Ret_error = -2, // 出错
Ret_failed = -1, // 失败
Ret_success = 0, // 成功
}RetValue;
/** 消息类型 枚举 */
typedef enum __msgType {
Msg_heart = 0, // 心跳包
Msg_other = 1, // 其他数据
}MsgType;
/** 消息头 结构体 */
typedef struct __msgHead {
MsgType msgType; // 消息类型
unsigned int msgLen; // 消息长度
char data[0]; // 消息实体
}MsgHead;
void* SendHeartBeat(void *arg); /** 发送心跳 */
void* SendMsg(void *arg); /** 发送消息 */
void* RecvMsg(void *arg); /** 接收消息 */
class TCPClient
{
private:
struct sockaddr_in serverAddr; // 服务端地址
socklen_t serAddrLen; // 地址长度
int clientSockfd; // 客户端 socket
/**
发送心跳
@param arg 线程参数(TCPClient实例指针)
*/
friend void* SendHeartBeat(void* arg);
/**
发送消息
@param arg 线程参数(TCPClient实例指针)
*/
friend void* SendMsg(void* arg);
/**
接收消息
@param arg 线程参数(TCPClient实例指针)
*/
friend void* RecvMsg(void* arg);
public:
TCPClient(std::string ipStr, unsigned int port);
~TCPClient();
/**
连接 服务端
@return 参见‘RetValue’
*/
RetValue Connect();
/**
运行 客户端
@return 参见‘RetValue’
*/
RetValue Run();
};
#endif /* TCPClient_hpp */

client.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
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
#include "TCPClient.hpp"
#include <pthread.h>
#include <sstream>
TCPClient::TCPClient(std::string ipStr, unsigned int port)
{
if (true == ipStr.empty()
|| 0 >= ipStr.length())
{
std::cout << "Ip 地址不能为空!" << std::endl;
return;
}
// 创建 客户端 socket
clientSockfd = socket(PF_INET, SOCK_STREAM, 0);
if (0 > clientSockfd)
{
std::cout << "创建 socket 失败!" << std::endl;
return;
}
std::cout << "创建 socket 成功!" << std::endl;
// 设置 服务端 地址
serAddrLen = sizeof(serverAddr);
memset(&serverAddr, 0, serAddrLen); // 清空结构体
serverAddr.sin_family = AF_INET; // 协议族 类型
serverAddr.sin_port = htons(port); // 服务端 端口
serverAddr.sin_addr.s_addr = inet_addr(ipStr.c_str()); // 服务端 IP 地址
}
TCPClient::~TCPClient()
{
if (0 < clientSockfd)
{
std::cout << "关闭客户端 sockfd !" << std::endl;
close(clientSockfd); // 关闭客户端 sockfd
}
}
RetValue TCPClient::Connect()
{
std::cout << "开始连接 服务端。。。" << std::endl;
// 连接 服务端
int ret = connect(clientSockfd, (struct sockaddr*)&serverAddr, serAddrLen);
if (0 != ret)
{
std::cout << "连接 服务端 失败!" << std::endl;
return Ret_failed;
}
std::cout << "连接 服务端 成功!" << std::endl;
return Ret_success;
}
RetValue TCPClient::Run()
{
pthread_t heartBeatP;
pthread_t sendP;
pthread_t recvP;
// 创建发送心跳线程
int ret = pthread_create(&heartBeatP, NULL, SendHeartBeat, (void*)this);
if(0 != ret)
{
std::cout << "创建发送心跳线程失败!" << std::endl;
return Ret_failed;
}
// 创建发送消息线程
ret = pthread_create(&sendP, NULL, SendMsg, (void*)this);
if (0 != ret)
{
std::cout << "创建发送消息线程失败!" << std::endl;
return Ret_failed;
}
// 创建接收消息线程
ret = pthread_create(&recvP, NULL, RecvMsg, (void*)this);
if (0 != ret)
{
std::cout << "创建接收消息线程失败!" << std::endl;
return Ret_failed;
}
// 等待线程结束
void *result;
if (0 != pthread_join(heartBeatP, &result))
{
perror("等待 发送心跳 线程失败");
return Ret_failed;
}
if (0 != pthread_join(sendP, &result))
{
perror("等待 发送消息 线程失败");
return Ret_failed;
}
if (0 != pthread_join(recvP, &result))
{
perror("等待 接收消息 线程失败");
return Ret_failed;
}
return Ret_success;
}
#pragma mark -- 心跳发送
void* SendHeartBeat(void* arg)
{
std::cout << "发送心跳线程启动.\n";
TCPClient* client = (TCPClient*)arg;
MsgHead *head = (MsgHead *)malloc(BUFF_SIZE);
static int count = 0;
std::string msgStr;
size_t msgLen;
while(1)
{
count++;
memset(head, 0, BUFF_SIZE);
std::stringstream msgSS;
msgSS << "客户端心跳:"<< count;
msgStr = msgSS.str();
msgLen = msgStr.length();
head->msgType = Msg_heart;
head->msgLen = (unsigned int)msgLen;
memcpy(head->data, msgStr.c_str(), msgLen);
ssize_t sendLen = send(client->clientSockfd, head, sizeof(head) + msgLen, 0);
if (0 > sendLen) // 出错
{
std::cout << "发送心跳出错!" << std::endl;
free(head);
exit(1);
}
else if (0 == sendLen) // 连接已断开
{
std::cout << "连接已断开,发送心跳失败!" << std::endl;
free(head);
exit(1);
}
else // 发送成功
{
// std::cout << "发送心跳成功!" << std::endl;
}
free(head);
sleep(HB_INTERVAL);
}
return NULL;
}
#pragma mark -- 发送消息
void* SendMsg(void *arg)
{
std::cout << "发送消息线程启动!" << std::endl;
TCPClient *client = (TCPClient *)arg;
MsgHead *head = (MsgHead *)malloc(BUFF_SIZE);
std::string msgStr;
size_t msgLen;
while(1)
{
memset(head, 0, BUFF_SIZE);
std::cin >> msgStr;
msgLen = msgStr.length();
head->msgType = Msg_other;
head->msgLen = (unsigned int)msgLen;
memcpy(head->data, msgStr.c_str(), msgLen);
ssize_t sendLen = send(client->clientSockfd, head, msgLen + sizeof(MsgHead), 0);
if (0 > sendLen) // 出错
{
std::cout << "发送消息出错!" << std::endl;
exit(1);
}
else if (0 == sendLen) // 连接已断开
{
std::cout << "发送消息失败,连接已断开!" << std::endl;
exit(1);
}
else // 发送成功!
{
if (msgStr == "quit") // 遇到 “quit” 则退出
{
break;
}
// std::cout << "发送消息成功!" << std::endl;
}
}
return NULL;
}
#pragma mark -- 接收消息
void* RecvMsg(void *arg)
{
std::cout << "接收消息线程启动!" << std::endl;
TCPClient *client = (TCPClient *)arg;
char recvBuf[BUFF_SIZE];
ssize_t recvLen;
while (1)
{
memset(recvBuf, 0, BUFF_SIZE);
recvLen = recv(client->clientSockfd, recvBuf, BUFF_SIZE, 0);
if (0 > recvLen) // 出错
{
std::cout << "接收消息出错!" << std::endl;
exit(1);
}
else if (0 == recvLen) // 连接已断开
{
std::cout << "接收消息失败,连接已断开!" << std::endl;
exit(1);
}
else // 接收成功
{
MsgHead *msgHead = (MsgHead*)recvBuf;
if(Msg_heart == msgHead->msgType) // 心跳消息
{
}
std::cout << "接收到服务端消息:" << msgHead->data << std::endl;
if (0 == strcmp(msgHead->data, "quit")) // 遇到 “quit” 则退出
{
close(client->clientSockfd);
exit(1);
}
}
}
return NULL;
}

client.main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include "TCPClient.hpp"
#define DEMO_PORT 5578
#define DEMO_IP "127.0.0.1"
int main()
{
TCPClient client(DEMO_IP, DEMO_PORT);
if (Ret_success != client.Connect())
{
exit(1);
}
if (Ret_success != client.Run())
{
exit(1);
}
return 0;
}

Demo下载

------ 本文结束 ------
欣赏此文?求鼓励,求支持!