龙空技术网

Linux系统下poll的使用方式

一道闪电惊天雷 135

前言:

当前兄弟们对“linux中perror”都比较注重,同学们都需要分析一些“linux中perror”的相关内容。那么小编同时在网摘上收集了一些有关“linux中perror””的相关资讯,希望姐妹们能喜欢,咱们一起来学习一下吧!

简介

poll是linux的事件轮询机制函数,每个进程可以管理一个pollfd队列,由poll函数进行事件注册和查询。

pollfd数据结构:

struct pollfd {

int fd; /* file descriptor */

short events; /* requested events */

short revents; /* returned events */

};

fd是文件描述符,用来指示linux给当前pollfd分配的文件。编程时需要给events注册我们想要的事件,之后使用poll函数对pollfd队列进行轮询,轮询结束后,revents由内核设置为实际发生的事件。如果fd是负数,那么会忽略events,而且revents会置为0。事件的编码在poll.h头文件中定义了。

poll函数结构:

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

fds是队列的对头指针,nfds是队列的长度,timeout是时间控制机制,超时返回0,计时使用毫秒。

上述所有的参数参考:

代码实例

开发环境: Ubuntu 18.04 LTS

编译器:g++ 7.2

服务器

服务器接收客户端的请求,并向客户端返回客户端发来的消息。

/*

* 借助于poll实现I/O复用模型,

* poll与select最大的区别在于该模型基于事件驱动。

*/

#include <stdio.h>

#include <stdlib.h>

#include <sys/ioctl.h>

#include <sys/poll.h>

#include <sys/socket.h>

#include <sys/time.h>

#include <netinet/in.h>

#include <errno.h>

#include <string.h>

#include <unistd.h>

#define TRUE 1

#define FALSE 0

int main(int argc, char* argv[]) {

if (argc != 2) {

printf("Usage: %s <port of server>\n", argv[0]);

return -1;

}

int len, rc, on = 1;

int listen_sd = -1, new_sd = -1;

int end_server = FALSE, compress_array = FALSE;

int close_conn;

char buffer[80];

struct sockaddr_in addr;

int timeout;

struct pollfd fds[200]; // poll队列

int nfds = 1, current_size = 0;

int port = atoi(argv[1]);

if (port <= 1024) {

perror("port error\n");

return -1;

}

bzero(&addr, sizeof(addr));

addr.sin_family = AF_INET;

addr.sin_port = htons(port);

addr.sin_addr.s_addr = htonl(INADDR_ANY);

listen_sd = socket(AF_INET, SOCK_STREAM, 0);

if (listen_sd < 0) {

perror("socket() error\n");

return -1;

}

rc = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR,

(char*)&on, sizeof(on));

if (rc < 0) {

perror("setsockopt() failed\n");

close(listen_sd);

return -1;

}

rc = ioctl(listen_sd, FIONBIO, (char*)&on);

if (rc < 0) {

perror("setsockopt() failed\n");

close(listen_sd);

return -1;

}

rc = bind(listen_sd, (struct sockaddr*)&addr, sizeof(addr));

if (rc < 0) {

perror("listen() error\n");

close(listen_sd);

return -1;

}

memset(fds, 0, sizeof(fds));

fds[0].fd = listen_sd;

fds[0].events = POLLIN;

timeout = (3 * 60 * 1000);

rc = listen(listen_sd, 32);

if (rc < 0) {

perror("listen() failed\n");

close(listen_sd);

return -1;

}

do {

printf("Waiting on poll()...\n");

rc = poll(fds, nfds, timeout);

if (rc < 0) {

perror("poll() failed\n");

break;

}

if (rc == 0) {

printf("poll timed out. End porgram\n");

break;

}

current_size = nfds;

for (int i = 0; i < current_size; ++i) {

if (fds[i].revents == 0) // 没有事件的状态

continue;

if (fds[i].revents != POLLIN) { // 必须是写入事件!

printf("Error! revents = %d\n", fds[i].revents);

end_server = TRUE;

break;

}

if (fds[i].fd == listen_sd) { // 监听到新的信号

printf("Listening socket is readable\n");

do {

new_sd = accept(listen_sd, NULL, NULL);

if (new_sd < 0) {

if (errno != EWOULDBLOCK) {

perror("accept() failed\n");

end_server = TRUE;

}

break;

}

printf("New incomming connection - %d\n", new_sd);

fds[nfds].fd = new_sd;

fds[nfds].events = POLLIN;

nfds++;

} while (new_sd != -1);

} else { // 已经建立连接的socket收到消息

printf("Descriptor %d is readable\n", fds[i].fd);

close_conn = FALSE;

do {

// 处理接收到客户端的信息,死循环是为了接收完所有可能的数据

// 注意这里,recv本身是一个阻塞的函数,所以只要客户端不主动关闭连接,

// 那么服务器会一直阻塞在这里,又因为使用了while(TRUE)方式循环接收,

// 因此出现了如果使用多个客户端进行连接,只有当前面的关闭连接后,

// 后面的才会收到数据。在高性能的服务器编程中,客户端的连接应该使用

// 多线程或者多进程的方式处理。如果资源充足,应该给每个客户端一个进程

// 或者线程,当然这样可能也会出现资源不足的情况。更好的方式是多线程(进程)结合

// 心跳检测机制,把下面的send发送数据替换成心跳函数。如果收不到心跳,

// 就认定已经断线,此时把客户端的连接剔除即可。本例子中客户端主动断开

// 连接也会被剔除,因为send函数收不到回复了。

// 当然,这个例子只是一个示范poll的作用,没有那么复杂。

rc = recv(fds[i].fd, buffer, sizeof(buffer), 0);

if (rc < 0) {

if (errno != EWOULDBLOCK) {

perror("recv() failed\n");

close_conn = TRUE;

}

break;

}

if (rc == 0) {

printf("Connection closed\n");

close_conn = TRUE;

break;

}

len = rc;

printf("%d bytes received\n", len);

rc = send(fds[i].fd, buffer, len, 0);

if (rc < 0) {

perror("send() failed\n");

close_conn = TRUE;

break;

}

} while (TRUE);

if (close_conn) {

close(fds[i].fd);

fds[i].fd = -1;

compress_array = TRUE;

}

}

}

// 压缩poll队列,就是顺序表删除中间节点的方法

// 后边的数据依次覆盖前边的,时间复杂度是O(n)

if (compress_array) {

compress_array = FALSE;

for (int i = 0; i < nfds; ++i) {

if (fds[i].fd == -1) {

for (int j = i; j < nfds; ++j) {

fds[i].fd = fds[j + 1].fd;

}

--i;

--nfds;

}

}

}

} while(end_server == FALSE);

// 清理所有打开的socket

for (int i = 0; i < nfds; ++i) {

if (fds[i].fd >= 0) {

close(fds[i].fd);

}

}

return 0;

}

客户端

每隔2秒向服务器发送一次信息。

#include <stdio.h>

#include <sys/socket.h>

#include <sys/types.h>

#include <arpa/inet.h>

#include <string.h>

#include <stdlib.h>

#include <unistd.h>

int main(int argc, char* argv[]) {

if (argc != 3) {

printf("Usage: %s <ip of server> <port of server>\n", argv[0]);

return -1;

}

int port = atoi(argv[2]);

if (port < 1024) {

perror("port error\n");

return -1;

}

struct sockaddr_in serv_addr;

bzero(&serv_addr, sizeof(serv_addr));

serv_addr.sin_family = AF_INET;

serv_addr.sin_port = htons(port);

if (inet_pton(AF_INET, argv[1], &serv_addr.sin_addr.s_addr) < 0) {

perror("IP error\n");

return -1;

}

int socketfd = socket(AF_INET, SOCK_STREAM, 0);

if (socketfd < 0) {

perror("socket() error\n");

return -1;

}

if (connect(socketfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {

perror("connect() error\n");

return -1;

}

char buffer[80];

memset(buffer, 0, sizeof(buffer));

int i = 0;

while(1) {

int rc = send(socketfd, buffer, sizeof(buffer), 0);

if (rc < 0) {

perror("send() error\n");

return -1;

} else if (rc == 0) {

printf("send nothing !\n");

} else {

printf("send successfully!\n");

}

rc = recv(socketfd, buffer, sizeof(buffer), 0);

if (rc < 0) {

perror("recv() error\n");

return -1;

} else if (rc == 0) {

printf("receive nothing\n");

} else {

printf("receive %dth data: '%s'", i, buffer);

}

++i;

sleep(2);

}

return 0;

}

标签: #linux中perror