Fork me on GitHub
0%

网络编程——C++实现socket通信(TCP)高并发之select模式.md

相关链接:TCP连接与释放网络编程——C++实现socket通信(TCP)

相关函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
服务端:
socket()
bind()
listen()
FD_ZERO()等辅助函数
select() 高并发select模式
accept()
read() 或 recv()等
write() 或 send()等
close()

客户端:
socket()
connect()
write() 或 send()等
read() 或 recv()等
close()

着重说明下==select函数==及==辅助函数==用法说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
调用select()函数之后,select()函数会清空它所检测的socket描述符集合,所以每次调用select()之前都必须把socket描述符重新加入到待检测的集合中。
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
-nfds: 监听的最大文件描述符值+1
-readfds: 监听socket可读事件的集合的指针 (经常用到的)
-writefds: 监听socket可写事件的集合的指针
-execptfds:监听socket异常事件的集合的指针
-timeout: 设置select监听的超时时间,NULL表示阻塞监听,0表示不阻塞立即返回,>0表示阻塞等待timeout时长


处理三个集合fd_set(实质是位图)的辅助函数:
void FD_CLR(int fd, fd_set *set); //清除集合set中指定fd的位
int FD_ISSET(int fd, fd_set *set); //判断set中指定fd的位是否为真(也就是fd是否在集合set中)
void FD_SET(int fd, fd_set *set); //设置集合set中指定fd的位
void FD_ZERO(fd_set *set); //清空集合set

注意:每当服务端连接断开后,进入TIME_WAIT状态,等待2msl时间之后才能重新使用IP和端口,否则在bind时就会报错。要解决这个问题可以在程序开始时调用端口复用函数setsockopt。原型如下:

1
2
3
4
5
6
7
8
9
10
11
//int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
/* sockfd:标识一个套接口的描述字。
   level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
   optname:需设置的选项。
   optval:指针,指向存放选项值的缓冲区
   optlen:optval缓冲区长度。
   返回值: 成功返回0,失败返回 -1. */
  

实际调用:
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

==废话不多说,上源码!==

实现的功能:客户端C向服务端S发送一串字符数据,S端会对字符串做转大写操作然后回发给C端。直接在咱们Tcp_Server.cpp基础上修改代码

服务端Select_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
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <ctype.h>
#include <sys/select.h> //select() 头文件

#define MAXSIZE 1024
#define IP_ADDR "127.0.0.1"
#define IP_PORT 8888

int main()
{
int i_listenfd, i_connfd;
struct sockaddr_in st_sersock;
char msg[MAXSIZE];
int nrecvSize = 0;

int maxfd = -1; //记录最大fd
fd_set readfds;
int allfds[MAXSIZE]; //存放当前所有可用的fd的数组
int index = 0; //记录fd数组中最大fd对应的下标

for(i : allfds)
{
i = -1;
}

if((i_listenfd = socket(AF_INET, SOCK_STREAM, 0) ) < 0) //建立socket套接字
{
printf("socket Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}

memset(&st_sersock, 0, sizeof(st_sersock));
st_sersock.sin_family = AF_INET; //IPv4协议
st_sersock.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。
st_sersock.sin_port = htons(IP_PORT);

if(bind(i_listenfd,(struct sockaddr*)&st_sersock, sizeof(st_sersock)) < 0) //将套接字绑定IP和端口用于监听
{
printf("bind Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}

if(listen(i_listenfd, 20) < 0) //设定可同时排队的客户端最大连接个数
{
printf("listen Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}

allfds[index] = maxfd = i_listenfd; //先赋值
printf("======waiting for client's request======\n");
//准备接受客户端连接
while(1)
{
FD_ZERO(&readfds);
for(int i = 0; i<= index; i++)
{
FD_SET(allfds[i], &readfds); //加入可读事件集合中
printf("----------allfds中的元素allfds[%d]:%d\n", i, allfds[i]);
}

int nCount = select(maxfd+1, &readfds, NULL, NULL, NULL); //select,返回共监听到有多少个fd上有事件
printf("----------select监听到可读事件计数:%d\n",nCount);

for(int i = 0; i < MAXSIZE; i++)
{
if(nCount == 0)
{
break;
}
if(!FD_ISSET(allfds[i], &readfds))
{
continue; //不在监听事件中则跳过
}
printf("----------即将处理监听到的 allfds[%d]: %d\n", i, allfds[i]);
if(allfds[i] == i_listenfd) //监听到有客户端连接
{
nCount--;
if((i_connfd = accept(i_listenfd, (struct sockaddr*)NULL, NULL)) < 0) //阻塞等待客户端连接
{
printf("accept Error: %s (errno: %d)\n", strerror(errno), errno);
// continue;
}
else
{
printf("Client[%d], welcome!\n", i_connfd);
}

for(int n = 0; n < MAXSIZE; n++)
{
if(allfds[n] == -1) //将新客户端fd加入数组中
{
allfds[n] = i_connfd;
maxfd < i_connfd ? maxfd = i_connfd : true ;
index < n ? index = n : true ;
printf("将新客户端fd加入数组中. fd:%d, maxfd:%d, index:%d\n", allfds[n], maxfd, index);
break;
}
}

}
else //监听到已连接的客户端发来的数据
{
nCount--;
//接受客户端发来的消息并作处理(小写转大写)后回写给客户端
memset(msg, 0 ,sizeof(msg));
if((nrecvSize = read(allfds[i], msg, MAXSIZE)) < 0)
{
printf("accept Error: %s (errno: %d)\n", strerror(errno), errno);
continue;
}
else if( nrecvSize == 0) //read返回0代表对方已close断开连接。
{
printf("client has disconnected!\n");
if(maxfd == allfds[i])
{
maxfd--;
}
if(index == i)
{
index--;
}
close(allfds[i]); //
FD_CLR(allfds[i], &readfds); //清除readfds中对它的监听事件
allfds[i] = -1; //清除数组中相应位置

continue;
}
else
{
printf("recvMsg:%s", msg);
for(int i=0; msg[i] != '\0'; i++)
{
msg[i] = toupper(msg[i]);
}
if(write(allfds[i], msg, strlen(msg)+1) < 0)
{
printf("accept Error: %s (errno: %d)\n", strerror(errno), errno);
}

}
}
}
}//while
close(i_listenfd);

return 0;
}

客户端Select_Client.cpp (直接用咱们Tcp_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
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <signal.h>
#include <arpa/inet.h>

#define MAXSIZE 1024
#define IP_ADDR "127.0.0.1"
#define IP_PORT 8888

int i_sockfd = -1;

void SigCatch(int sigNum) //信号捕捉函数(捕获Ctrl+C)
{
if(i_sockfd != -1)
{
close(i_sockfd);
}
printf("Bye~! Will Exit...\n");
exit(0);
}

int main()
{
struct sockaddr_in st_clnsock;
char msg[1024];
int nrecvSize = 0;

signal(SIGINT, SigCatch); //注册信号捕获函数

if((i_sockfd = socket(AF_INET, SOCK_STREAM, 0) ) < 0) //建立套接字
{
printf("socket Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}

memset(&st_clnsock, 0, sizeof(st_clnsock));
st_clnsock.sin_family = AF_INET; //IPv4协议
//IP地址转换(直接可以从物理字节序的点分十进制 转换成网络字节序)
if(inet_pton(AF_INET, IP_ADDR, &st_clnsock.sin_addr) <= 0)
{
printf("inet_pton Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}
st_clnsock.sin_port = htons(IP_PORT); //端口转换(物理字节序到网络字节序)

if(connect(i_sockfd, (struct sockaddr*)&st_clnsock, sizeof(st_clnsock)) < 0) //主动向设置的IP和端口号的服务端发出连接
{
printf("connect Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}

printf("======connect to server, sent data======\n");

while(1) //循环输入,向服务端发送数据并接受服务端返回的数据
{
fgets(msg, MAXSIZE, stdin);
printf("will send: %s", msg);
if(write(i_sockfd, msg, MAXSIZE) < 0) //发送数据
{
printf("write Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}

memset(msg, 0, sizeof(msg));
if((nrecvSize = read(i_sockfd, msg, MAXSIZE)) < 0) //接受数据
{
printf("read Error: %s (errno: %d)\n", strerror(errno), errno);
}
else if(nrecvSize == 0)
{
printf("Service Close!\n");
}
else
{
printf("Server return: %s\n", msg);
}

}
return 0;
}
Jeff wechat
------ 版权信息 ------

本文标题:网络编程——C++实现socket通信(TCP)高并发之select模式.md

文章作者:Jeff

发布时间:2020年06月19日 - 16:49

最后更新:2020年06月19日 - 16:52

原始链接:http://JeffCheng95.github.io/2020/06/19/网络编程——C-实现socket通信-TCP-高并发之select模式-md/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。