目录

C++ select分析1

目录

服务端升级为select模型

Socket的select模型

客户端转为Select模型

服务端升级为select模型

https://img-blog.csdnimg.cn/20210109122030486.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhaWR1XzQxMzg4NTMz,size_16,color_FFFFFF,t_70

Socket的select模型

1
2
3
4
5
6
7
select(
    _In_           int nfds,
    _Inout_opt_    fd_set FAR * readfds,
    _Inout_opt_    fd_set FAR * writefds,
    _Inout_opt_    fd_set FAR * exceptfds,
    _In_opt_       const struct timeval FAR * timeout
);
  • fd_set
1
2
3
4
typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;
  • FD_ZERO
1
#define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=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
58
59
60
61
62
63
64
65
66
67
68
fd_set fdRead;
fd_set fdWrite;
fd_set fdExp;

//清空集合
FD_ZERO(&fdRead);
FD_ZERO(&fdWrite);
FD_ZERO(&fdExp);

//将socket加入集合
FD_SET(sock, &fdRead);
FD_SET(sock, &fdWrite);
FD_SET(sock, &fdExp);

//将存的通信符加入集合
for (size_t i = 0; i < g_clients.size(); i++)
{
    FD_SET(g_clients[i], &fdRead);
}

//将select设置为非阻塞模式
timeval timeout = { 0,0 };

//使用select
int ret = select(sock + 1, &fdRead, &fdWrite, &fdExp, &timeout);
if (ret < 0)
{
    printf("客户端已退出,任务结束\n");
    break;
}
if (FD_ISSET(sock, &fdRead))
{
    FD_CLR(sock, &fdRead);
    //4.accept 接收客户端连接
    sockaddr_in clientAddr = {};
    int clAddrLen = sizeof(sockaddr_in);

    SOCKET sockAccpt = INVALID_SOCKET;
    sockAccpt = accept(sock, (sockaddr*)&clientAddr, &clAddrLen);

    if (INVALID_SOCKET == sockAccpt)
    {
        printf("Accept Error\n");
    }
    else
    {
        printf("Accept Success\n");
    }
    printf("新客户端加入:Socket = %d,IP = %s \n", (int)sockAccpt, inet_ntoa(clientAddr.sin_addr));

    //将新加入的通信文件描述符加入
    g_clients.push_back(sockAccpt);
}

//处理集合中的文件描述符对应的通信信息
for (size_t i = 0; i <fdRead.fd_count ; i++)
{
    int ret = Processor(fdRead.fd_array[i]);
    if (-1 == ret)
    {
        //出现错误,从动态数组中删除
        auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[i]);
        if (iter != g_clients.end())
        {
            g_clients.erase(iter);
        }
    }
} 

【完整代码】

  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
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#include <cstdio>
#include <vector>

#pragma comment(lib,"ws2_32.lib")

enum CMD             //命令枚举
{
    CMD_LOGIN,
    CMD_LOGIN_RESULT,
    CMD_LOGOUT,
    CMD_LOGOUT_RESULT,
    CMD_NEW_USER_JOIN,
    CMD_ERROR
};

//DataHeader
struct DataHeader      //数据包头
{
    short dataLength;
    short cmd;
};

//DataPackage
struct Login:public DataHeader          //登录
{
    Login()
    {
        dataLength = sizeof(Login);
        cmd = CMD_LOGIN;
    }
    char UserName[32]{};
    char PassWord[32]{};
};
 
struct LoginResult : public DataHeader     //登录结果
{
    LoginResult()
    {
        dataLength = sizeof(LoginResult);
        cmd = CMD_LOGIN_RESULT;
        lgResult = 0;
    }
    int lgResult;
};

struct LogOut :public DataHeader        //退出登录
{
    LogOut()
    {
        dataLength = sizeof(LogOut);
        cmd = CMD_LOGOUT;
    }
    char UserName[32]{};
};


struct LogOutResult :public DataHeader  //退出结果
{
    LogOutResult()
    {
        dataLength = sizeof(LogOutResult);
        cmd = CMD_LOGOUT_RESULT;
        lgOutResult = 0;
    }
    int lgOutResult;
};

struct NewUserJoin :public DataHeader  //新加入用户
{
    NewUserJoin()
    {
        dataLength = sizeof(NewUserJoin);
        cmd = CMD_NEW_USER_JOIN;
        sockID = 0;
    }
    int sockID;
};


std::vector<SOCKET> g_clients;

int Processor(SOCKET sockAccpt)
{//缓冲区
    char szRecv[1024] = {};

    //读取包头数据
    int nLen = recv(sockAccpt, (char*)&szRecv, sizeof(DataHeader), 0);
    DataHeader* dbHeader = (DataHeader*)szRecv;
    if (nLen < 0)
    {
        printf("客户端<%d>已退出,任务结束\n", sockAccpt);
        return -1;
    }
    //if(nLen >= sizeof(DataHeader))
    switch (dbHeader->cmd)
    {
    case CMD_LOGIN:
    {
        recv(sockAccpt, szRecv + sizeof(DataHeader), dbHeader->dataLength - sizeof(DataHeader), 0);
        Login* login = (Login*)szRecv;
        printf("收到客户端<Socket%d>请求:CMD_LOGIN ,数据长度: %d, UserName = %s, \
                    PassWord = %s \n", sockAccpt, login->dataLength, login->UserName, login->PassWord);
        //忽略对用户密码进行判断
        LoginResult lgRet = {};
        send(sockAccpt, (char*)&lgRet, sizeof(LoginResult), 0);
    }
    break;
    case CMD_LOGOUT:
    {
        recv(sockAccpt, szRecv + sizeof(DataHeader), dbHeader->dataLength - sizeof(DataHeader), 0);
        LogOut* logout = (LogOut*)szRecv;
        printf("收到客户端<Socket%d>请求:CMD_LOGOUT ,数据长度: %d, UserName = %s, \
                    \n", sockAccpt, logout->dataLength, logout->UserName);
        //忽略对用户密码进行判断
        LogOutResult lgOutRet = {};
        send(sockAccpt, (char*)&lgOutRet, sizeof(LogOutResult), 0);
    }
    break;
    default:
        DataHeader HeaderError = { 0, CMD_ERROR };
        send(sockAccpt, (char*)&HeaderError, sizeof(HeaderError), 0);
        break;
    }
}

int main()
{
    WORD ver = MAKEWORD(2, 2);
    WSAData dat;
    WSAStartup(ver, &dat);

    //1.创建socket套接字
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    //2,bind 绑定用于接收客户端连接的端口
    sockaddr_in sinAddr = {};
    sinAddr.sin_family = AF_INET;
    sinAddr.sin_port = htons(5678); //host to net unsigned short
    sinAddr.sin_addr.S_un.S_addr = INADDR_ANY;  //inet_addr("127.0.0.1")
    if (SOCKET_ERROR == bind(sock, (sockaddr*)&sinAddr, sizeof(sockaddr_in)))
    {
        printf("Bind Error\n");
    }
    else
    {
        printf("Bind Success\n");
    }

    //3. listen 监听网络端口
    if (SOCKET_ERROR == listen(sock, 5))
    {
        printf("Listen Error\n");
    }
    else
    {
        printf("Listen Success\n");
    }

    while (true)
    {
        //伯克利 socket
        fd_set fdRead;
        fd_set fdWrite;
        fd_set fdExp;

        //清空集合
        FD_ZERO(&fdRead);
        FD_ZERO(&fdWrite);
        FD_ZERO(&fdExp);

        //将socket加入集合
        FD_SET(sock, &fdRead);
        FD_SET(sock, &fdWrite);
        FD_SET(sock, &fdExp);

        //将存的通信符加入集合
        for (size_t i = 0; i < g_clients.size(); i++)
        {
            FD_SET(g_clients[i], &fdRead);
        }

        //将select设置为非阻塞模式
        timeval timeout = { 0,0 };

        //使用select
        int ret = select(sock + 1, &fdRead, &fdWrite, &fdExp, &timeout);
        if (ret < 0)
        {
            printf("select任务结束\n");
            break;
        }
        if (FD_ISSET(sock, &fdRead))
        {
            FD_CLR(sock, &fdRead);
            //4.accept 接收客户端连接
            sockaddr_in clientAddr = {};
            int clAddrLen = sizeof(sockaddr_in);

            SOCKET sockAccpt = INVALID_SOCKET;
            sockAccpt = accept(sock, (sockaddr*)&clientAddr, &clAddrLen);

            if (INVALID_SOCKET == sockAccpt)
            {
                printf("Accept Error\n");
            }
            else
            {
                //群发消息
                for (size_t i = 0; i < g_clients.size(); i++)
                {
                    NewUserJoin userJoin;
                    send(g_clients[i], (const char*)&userJoin, sizeof(NewUserJoin), 0);
                }
                //将新加入的通信文件描述符加入
                g_clients.push_back(sockAccpt);
                printf("新客户端加入:Socket = %d,IP = %s \n", (int)sockAccpt, inet_ntoa(clientAddr.sin_addr));
            }
        }

        //处理集合中的文件描述符对应的通信信息
        for (size_t i = 0; i <fdRead.fd_count ; i++)
        {
            int ret = Processor(fdRead.fd_array[i]);
            if (-1 == ret)
            {
                //出现错误,从动态数组中删除
                auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[i]);
                if (iter != g_clients.end())
                {
                    g_clients.erase(iter);
                }
            }
        } 
    }

    //关闭通信文件描述符
    for (size_t i = 0; i < g_clients.size(); i++)
    {
        closesocket(g_clients[i]);
    }

    //closesocket 关闭套接字
    closesocket(sock);

    WSACleanup();

    printf("结束任务\n");
    getchar();
    return 0;
 }

客户端转为Select模型

【主要代码】

 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
fd_set fdRead;

//清空集合
FD_ZERO(&fdRead);

//将socket加入集合
FD_SET(sockCli, &fdRead);

//将select设置为非阻塞模式
timeval timeout = { 0,0 };

//使用select
int ret = select(sockCli + 1, &fdRead, NULL, NULL, &timeout);
if (ret < 0)
{
	printf("select任务结束\n");
	break;
}

if (FD_ISSET(sockCli, &fdRead))
{
	FD_CLR(sockCli, &fdRead);
	if (-1 == Processor(sockCli))
	{
		printf("select任务结束2\n");
		break;
	}
}

【客户端源码】

  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
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define  _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#include <cstdio>
#pragma comment(lib,"ws2_32.lib")

enum CMD             //命令枚举
{
	CMD_LOGIN,
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,
	CMD_LOGOUT_RESULT,
	CMD_NEW_USER_JOIN,
	CMD_ERROR
};

//DataHeader
struct DataHeader      //数据包头
{
	short dataLength;
	short cmd;
};

//DataPackage
struct Login :public DataHeader          //登录
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char UserName[32]{};
	char PassWord[32]{};
};

struct LoginResult : public DataHeader     //登录结果
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		lgResult = 0;
	}
	int lgResult;
};

struct LogOut :public DataHeader        //退出登录
{
	LogOut()
	{
		dataLength = sizeof(LogOut);
		cmd = CMD_LOGOUT;
	}
	char UserName[32]{};
};


struct LogOutResult :public DataHeader  //退出结果
{
	LogOutResult()
	{
		dataLength = sizeof(LogOutResult);
		cmd = CMD_LOGOUT_RESULT;
		lgOutResult = 0;
	}
	int lgOutResult;
};

struct NewUserJoin :public DataHeader  //新加入用户
{
	NewUserJoin()
	{
		dataLength = sizeof(NewUserJoin);
		cmd = CMD_NEW_USER_JOIN;
		sockID = 0;
	}
	int sockID;
};


int Processor(SOCKET sockAccpt)
{//缓冲区
	char szRecv[1024] = {};

	//读取包头数据
	int nLen = recv(sockAccpt, (char*)&szRecv, sizeof(DataHeader), 0);
	DataHeader* dbHeader = (DataHeader*)szRecv;
	if (nLen < 0)
	{
		printf("与服务断开,任务结束\n");
		return -1;
	}
	//if(nLen >= sizeof(DataHeader))
	switch (dbHeader->cmd)
	{
		case CMD_LOGIN:
		{
			recv(sockAccpt, szRecv + sizeof(DataHeader), dbHeader->dataLength - sizeof(DataHeader), 0);
			Login* login = (Login*)szRecv;
			printf("收到服务端返回数据:CMD_LOGIN ,数据长度: %d,\n", dbHeader->dataLength);
		}
		break;
		case CMD_LOGOUT:
		{
			recv(sockAccpt, szRecv + sizeof(DataHeader), dbHeader->dataLength - sizeof(DataHeader), 0);
			LogOut* logout = (LogOut*)szRecv;
			printf("收到服务端返回数据:CMD_LOGOUT ,数据长度: %d \n", dbHeader->dataLength);
		}
		break; 
		case CMD_NEW_USER_JOIN:
		{
			recv(sockAccpt, szRecv + sizeof(DataHeader), dbHeader->dataLength - sizeof(DataHeader), 0);
			NewUserJoin* newJoin = (NewUserJoin*)szRecv;
			printf("收到服务端返回数据:CMD_NEW_USER_JOIN ,数据长度: %d \n", dbHeader->dataLength);
		}
		break;
		default:
			break;
	}
}

int main()
{
	WORD ver = MAKEWORD(2, 2);
	WSAData dat;
	WSAStartup(ver, &dat);

	//1.建立一个socket
	SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockCli)
	{
		printf("Socket Error\n");
	}
	else
	{
		printf("Socket Success\n");
	}

	//2. connect连接服务器
	sockaddr_in servAddr = {};
	servAddr.sin_family = AF_INET;
	servAddr.sin_port = htons(5678);
	servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	int ret = connect(sockCli, (sockaddr*)&servAddr, sizeof(sockaddr_in));

	if (SOCKET_ERROR == ret)
	{
		printf("Connect Error\n");
	}
	else
	{
		printf("Connect Success\n");
	}

	while (true)
	{
		fd_set fdRead;
	
		//清空集合
		FD_ZERO(&fdRead);

		//将socket加入集合
		FD_SET(sockCli, &fdRead);

		//将select设置为非阻塞模式
		timeval timeout = { 0,0 };

		//使用select
		int ret = select(sockCli + 1, &fdRead, NULL, NULL, &timeout);
		if (ret < 0)
		{
			printf("select任务结束\n");
			break;
		}

		if (FD_ISSET(sockCli, &fdRead))
		{
			FD_CLR(sockCli, &fdRead);
			if (-1 == Processor(sockCli))
			{
				printf("select任务结束2\n");
				break;
			}
		}

		printf("空闲时间,处理其他业务\n");
		Login lgin{};
		strcpy(lgin.UserName, "喜羊羊");
		strcpy(lgin.PassWord, "123456");
		send(sockCli, (const char*)&lgin, sizeof(Login), 0);
		Sleep(1000);
	}

	//7.关闭套接字 closesocket
	closesocket(sockCli);

	WSACleanup();
	printf("结束任务\n");
	getchar();
	return 0;
}