Fork me on GitHub
0%

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

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

相关函数:

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

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

着重说明下==poll函数==用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
跟select功能类似,可以设置的同时监听上限会更多,poll效率更高,调用完poll函数之后不会清空监听的事件集合.
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
-fds: 是一个struct pollfd结构类型的数组,用于存放需要检测其状态的socket描述符。结构体类型定义如下:
struct pollfd {
int fd; //文件描述符
short events; //等待的需要监听的事件类型,常用取值为POLLIN(监听读)/POLLOUT(写)/POLLERR(异常)。如fds[0].events = POLLIN
short revents; //实际发生了的事件,也就是返回结果。值的范围同events: POLLIN/POLLOUT/POLLERR
};

-nfds: nfds_t类型的参数,用于标记数组fds中的结构体元素的总数量;
-timeout: 是poll函数调用阻塞的时间,单位:毫秒。传值-1表示阻塞监听,0表示不阻塞立即返回,>0表示阻塞等待timeout的时间

返回值:>0:数组fds中准备好读、写或出错状态的那些socket描述符的总数量,fds数组中有状态的fd的revents被赋值传出,可以通过跟POLLIN/POLLOUT/POLLERR等标志通过位与&来判断,如if(fds[n].revents & POLLIN)
=0:数组fds中没有任何socket描述符准备好读、写,或出错,revents会被清空
=-1:poll函数调用失败,同时会自动设置全局变量errno.

注意:每当服务端连接断开后,进入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基础上修改代码

服务端Poll_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
#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 <poll.h> //poll头文件

#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 index = 0; //记录fd数组中最大fd对应的下标
struct pollfd pofds[MAXSIZE]; //结构体数组

for(n : pofds) //将所有数组中的fd设为-1,方便以后填充
{
n.fd = -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);
}
printf("listen fd: %d\n", i_listenfd);

pofds[index].fd = i_listenfd; //先赋值
pofds[index].events = POLLIN;
printf("======waiting for client's request======\n");
//准备接受客户端连接
while(1)
{
int nCount = poll(pofds, index+1, -1); //阻塞监听
printf("----------poll监听到可读事件计数:%d\n",nCount);

for(int i = 0; i < MAXSIZE; i++)
{
if(nCount == 0)
{
break;
}
if(!(pofds[i].revents & POLLIN))
{
continue; //不在监听事件中则跳过
}
printf("----------即将处理监听到的 pofds[%d]: %d\n", i, pofds[i].fd);
nCount--; //每处理一次就自减1
if(pofds[i].fd == i_listenfd) //监听到有客户端连接
{
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(pofds[n].fd == -1) //将新客户端fd加入数组中
{
pofds[n].fd = i_connfd;
pofds[n].events = POLLIN;
index < n ? index = n : true ;
printf("将新客户端fd加入数组中. fd:%d, index:%d\n", pofds[n].fd, index);
break;
}
}

}
else //监听到已连接的客户端发来的数据
{
//接受客户端发来的消息并作处理(小写转大写)后回写给客户端
memset(msg, 0 ,sizeof(msg));
if((nrecvSize = read(pofds[i].fd, 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(index == i) //如果是最大的下标的客户端退出,则index-1
{
index--;
}
pofds[i].fd = -1; //清除数组中相应位置
close(pofds[i].fd);

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

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

return 0;
}

客户端Poll_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)高并发之poll模式.md

文章作者:Jeff

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

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

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

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