poll 的使用方法及代码

一、在谈epoll和poll网络编程代码之前,首先简单的介绍一下poll函数和epoll函数组

1、poll函数原型

使用此函数的头文件为

#include<poll.h>

poll函数原型如下:

#include<poll.h>

int poll(struct poll_fd *fds, nfds_t  nfds, int timeout)

参数解释

(1)fds:指向元素类型为struct poll_fd类型的首元素

(2)nfds:fds结构体数组的大小

(3)timeout:表示poll函数超时的时间限制

poll_fd结构

struct poll_fd
{
    int events;
    int revents;
    int fd;
}

变量说明

(1)events:告诉内核需要检测的事件(此为用户设置)

(2)revents:对文件描述符进行操作结果事件,在内核中进行操作(内核返回时的完成时间)

(3)fd:每个结构体中都会有一个被监视的文件描述符,内核可以处理多个结构体,就说明可以监测多个文件描述符

以下为events和revents的取值

2、poll函数实现的原理

内核将用户的fds结构体数组拷贝到内核中,当有时间发生时内核再将所有时间都返回到fds数组中,

polll函数只返回已就绪时间的个数,所以用户要操作就绪事件,就得用轮询的方法

二、poll网络编程实例

注意:poll函数是阻塞函数,当没有就绪文件描述符的时候,poll一直处于阻塞状态,知道有就绪文件描述符

程序代码(包含注释)

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>

#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>

#define NFDS 100//fds数组的大小

// 创建一个用于监听的socket  
int CreateSocket()
{
	int listenfd = socket(AF_INET, SOCK_STREAM, 0);
	assert(-1 != listenfd);

	struct sockaddr_in ser;
	memset(&ser, 0, sizeof(ser));
	ser.sin_family = AF_INET;
	ser.sin_port = htons(6000);
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res =  bind(listenfd, (struct sockaddr*)&ser, sizeof(ser));
	assert(-1 != res);

	listen(listenfd, 5);

	return listenfd;
}

// 初始化fds结构体数组
void InitFds(struct pollfd *fds)
{
	int i = 0;
	for(; i < NFDS; ++i)
	{
		fds[i].fd = -1;
		fds[i].events = 0;
		fds[i].revents = 0;
	}
}

// 向fds结构体数组中插入一个文件描述符
void InsertFd(struct pollfd *fds, int fd, int flag)//此处flag是为了判断是文件描述符c,还是listenfd,来设置events
{
	int i = 0;
	for(; i < NFDS; ++i)
	{
		if(fds[i].fd == -1)
		{
			fds[i].fd = fd;
			fds[i].events |= POLLIN; 
			if(flag)
			{
				fds[i].events |= POLLRDHUP;
			}

			break;
		}
	}
}

// 从fds结构体数组中删除一个文件描述符
void DeleteFd(struct pollfd *fds, int fd)
{
	int i = 0;
	for(; i < NFDS; ++i)
	{
		if(fds[i].fd == fd)
		{
			fds[i].fd = -1;
			fds[i].events = 0; 
			break;
		}
	}
}

// 获取一个已完成三次握手的连接
void GetClientLink(int fd, struct pollfd *fds)
{
	struct sockaddr_in cli;
	socklen_t len = sizeof(cli); 
	int c = accept(fd, (struct sockaddr*)&cli, &len);
	assert(c != -1);

	printf("one client link success\n");

	InsertFd(fds, c, 1);
}

// 断开一个用户连接
void UnlinkClient(int fd, struct pollfd *fds)
{
	close(fd);
	DeleteFd(fds, fd);
	printf("one client unlink\n");
}

// 处理客户端发送来的数据
void DealClientData(int fd, struct pollfd *fds)
{
	char  buff[128] = {0};

	int n = recv(fd, buff, 127, 0);
	if(n <= 0)
	{
		UnlinkClient(fd, fds);
		return;
	}

	printf("%s\n", buff);

	send(fd, "ok", 2, 0);
}

// poll返回后,处理就绪的文件描述符
void DealFinishFd(struct pollfd *fds, int listenfd)
{
	int i = 0;
	for(; i < NFDS; ++i)
	{
		if(fds[i].fd == -1)
		{
			continue;
		}

		int fd = fds[i].fd;
		if(fd == listenfd && fds[i].revents & POLLIN)
		{
			GetClientLink(fd, fds);
			//获取连接
		}
		else if(fds[i].revents & POLLRDHUP)
		{
			UnlinkClient(fd, fds);
			//断开连接
		}
		else if(fds[i].revents & POLLIN)
		{
			DealClientData(fd, fds);
			//处理客户端数据
		}
	}
}

int main()
{
	int listenfd = CreateSocket();

	struct pollfd *fds = (struct pollfd*)malloc(sizeof(struct pollfd) * NFDS);
	//malloc一个fds结构体数组
	assert(NULL != fds);

	InitFds(fds);
	//初始化fds结构体数组

	InsertFd(fds, listenfd, 0);
	//插入文件描述符listenfd


	while(1)
	{
		int n = poll(fds, NFDS, -1);
		if(n <= 0)
		{
			printf("poll error\n");
			continue;
		}

		DealFinishFd(fds, listenfd);
		//处理就绪的文件描述符
	}

	free(fds);
}

以上就是poll简单网络编程的代码,当然,如果要测试此代码的话,得写一个客户端,如果读者有兴趣的话,

可以自己写一个客户端测试一下。

三、poll的优缺点

1、优点

(1)poll() 不要求开发者计算最大文件描述符加一的大小。 
(2)poll() 在应付大数目的文件描述符的时候速度更快,相比于select。 
(3)它没有最大连接数的限制,原因是它是基于链表来存储的。 
(4)在调用函数时,只需要对参数进行一次设置就好了

2、缺点

(1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义(epoll可以解决此问题) 
(2)与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符,这样会使性能下降 
(3)同时连接的大量客户端在一时刻可能只有很少的就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降
 

 

 

 

 

 

 

 

 

 

  • 7
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Modbus RTU (Remote Terminal Unit) 是一种基于 ASCII 码的通信协议,广泛用于工业自动化设备之间的数据交换,如PLC(可编程逻辑控制器)和变频器等。以下是使用 Modbus RTU 进行通信的基本步骤: 1. **配置硬件接口**:首先,确保你的设备(如PLC)支持 Modbus RTU 并配置好串口或以太网接口。确定正确的波特率(通常为9600bps)、数据位数(8位)、停止位(1位)和校验方式(偶校验或无校验)。 2. **选择通信软件或库**:使用 Modbus RTU 的客户端库,如Python的`pymodbus`、Java的`modbus-rtu`等,或者专门的串口调试工具(如TLC5940 Modbus demo),这将负责发送和接收命令。 3. **初始化连接**:根据所选软件的文档,创建连接到设备的实例,并设置相应的参数,比如IP地址或串口号。 4. **发送请求**:使用 Modbus功能码指定你要执行的操作,如读取寄存器(0x03)或写入寄存器(0x06)。构建包含功能码、地址和数据的请求报文(如十六进制格式)。 5. **执行命令**:调用发送函数将请求发送出去,等待设备响应。对于读操作,你可能需要指定起始地址和数量;对于写操作,确认写入的数据已经正确。 6. **解析响应**:对于读取操作,从设备接收到的数据中提取你需要的信息。如果是写入响应,检查是否有错误码。 7. **处理错误**:检查响应中的错误代码,如无响应或非法请求,可能需要重试或者调整配置。 8. **断开连接**:完成数据交换后,记得关闭连接释放资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值