IO复用技术常常用于网络服务器中,用来处理大量的客户端数据。Linux下有select,epoll,Unix下有kqueue,Windows下有IOCP。我们这里只讨论Linux环境下的。
提到IO复用,大部分人都会想到网络,TCP,socket等名词,很少跟嵌入式,串口等沾上边。但Linux有个非常好的特性,就是“一切皆文件”。线程对socket的监听,其实是对一个文件的监听。而串口也是文件,对线程来说,监听socket跟监听串口是没区别的,他们都是文件描述符。所以,我们也可以用IO复用技术来监听串口,处理串口数据。
Linux下有select和epoll这两个选择(还有一个poll,比较少人用)。Select会随着监听的文件数量增加而线性增加轮询时间,epoll则不会。当监听的数量到达一定程度的时候,select处理的速度就跟不上epoll了,所以服务器一般都用epoll。但在这里我们只用来监听串口,用epoll有点大材小用了,一般的板子都只留了几路串口,轮询时间基本可以忽略,用select来处理足够了。
先来构建一个串口基类,所有串口都通过这个基类派生:
-
class SERIAL
-
{
-
public:
-
SERIAL();
-
virtual ~SERIAL();
-
-
//打开串口
-
bool open_serial(const char* dev_name);
-
//设置串口波特率
-
void set_baudrate(int baud);
-
//设置数据位,停止位,校验位
-
bool set_parity(int databits,int stopbits,int parity);
-
//关闭串口
-
bool close_serial();
-
//虚函数,处理串口数据,由继承类实现
-
virtual void deal_data(const char* serial_data,int len);
-
-
int serial_fd;
-
};
注意,这个SERIAL基类的deal_data函数定义成了虚函数,它的实现是这样的:
-
void SERIAL::deal_data(const char* serial_data,int len)
-
{
-
return ;
-
}
里面是空的,是由于每个串口的数据格式不一样,比如串口1是GPS数据,串口1派生类的deal_data函数就要处理GPS数据,串口2是其它格式的数据,串口2派生类的deal_data就要能处理其它格式的数据。deal_data函数在派生类中实现,在监听的时候再利用虚函数的多态性来指定要用哪个。
我们顺手来派生两个串口类:
-
class serial_1:public SERIAL
-
{
-
public:
-
serial_1();
-
~serial_1();
-
-
//串口1数据处理函数,继承于串口基类
-
virtual void deal_data(const char* serial_data,int len);
-
};
-
class serial_2:public SERIAL
-
{
-
public:
-
serial_2();
-
~serial_2();
-
-
//串口2数据处理函数,继承于串口基类
-
virtual void deal_data(const char* serial_data,int len);
-
};
接下来构建一个IO复用的监听类LISTEN_SERIAL:
-
#define MAX_SERIAL 10 //最多监听的串口数量
-
class LISTEN_SERIAL
-
{
-
public:
-
LISTEN_SERIAL();
-
~LISTEN_SERIAL();
-
-
//开始监听串口线程
-
void start_listen();
-
static void* listen_serial(void* pointer);
-
//把串口加入到监听队列
-
void get_serial(SERIAL* s);
-
//比较两个文件描述符,返回较大的一个
-
int get_maxfd(int fd1,int fd2);
-
-
private:
-
SERIAL* s_a[MAX_SERIAL];
-
int counts;
-
pthread_t listen_td;
-
};
LISTEN_SERIAL实现如下:
-
#include "listen_serial.h"
-
-
LISTEN_SERIAL::LISTEN_SERIAL():counts(0)
-
{
-
for (int i = 0; i < 10; ++i)
-
{
-
s_a[i] = NULL;
-
}
-
}
-
-
LISTEN_SERIAL::~LISTEN_SERIAL()
-
{
-
}
-
-
//开始监听串口线程
-
void LISTEN_SERIAL::start_listen()
-
{
-
pthread_create(&listen_td,NULL,listen_serial,this);
-
}
-
-
void* LISTEN_SERIAL::listen_serial(void* pointer)
-
{
-
struct timeval timeout;
-
fd_set fdread;
-
LISTEN_SERIAL* pt = (LISTEN_SERIAL*)pointer;
-
if (pt->counts == 0)
-
{
-
return NULL;
-
}
-
while(!g_stop_listen_serial)
-
{
-
//set the param
-
FD_ZERO(&fdread);
-
for (int i = 0; i < pt->counts; ++i)
-
{
-
if(pt->s_a[i] != NULL)
-
{
-
FD_SET(pt->s_a[i]->serial_fd,&fdread);
-
}
-
}
-
int maxfd = 0;
-
if (pt->s_a[0] != NULL)
-
{
-
maxfd = pt->s_a[0]->serial_fd;
-
}
-
for (int i = 1; i < pt->counts; ++i)
-
{
-
maxfd = pt->get_maxfd(maxfd,pt->s_a[i]->serial_fd);
-
}
-
timeout.tv_sec = 10;
-
timeout.tv_usec = 0;
-
-
//start select
-
int ret = select(maxfd+1,&fdread,NULL,NULL,&timeout);
-
if(ret < 0)
-
{
-
perror("select:");
-
continue;
-
}
-
else if(ret == 0)
-
{
-
continue;
-
}
-
//select OK,ret>0
-
for (int i = 0; i < pt->counts; ++i)
-
{
-
if(FD_ISSET(pt->s_a[i]->serial_fd,&fdread))
-
{
-
char buf[1024];
-
ret = read(pt->s_a[i]->serial_fd,buf,sizeof(buf));
-
if(ret <= 0)
-
{
-
printf("串口%d已关闭", i);
-
//do not listen again
-
pt->s_a[i] = NULL;
-
}
-
else
-
{
-
//调用派生类中实现的deal_data来处理各自的数据
-
pt->s_a[i]->deal_data(buf,ret);
-
memset(buf,0,sizeof(buf));
-
}
-
}
-
}//end of for
-
}//end of while
-
return NULL;
-
}
-
-
//把串口加入到监听队列
-
void LISTEN_SERIAL::get_serial(SERIAL* s)
-
{
-
s_a[counts++] = s;
-
}
-
-
//比较两个文件描述符,返回较大的一个
-
int LISTEN_SERIAL::get_maxfd(int fd1,int fd2)
-
{
-
return (fd1>fd2?fd1:fd2);
-
}
上面我们构建了串口基类SERIAL,派生出两个串口类serial_1和serial_2。然后构建了监听串口的LISTEN_SERIAL类,现在我们用LISTEN_SERIAL类来监听这两个串口,然后让串口各自的deal_data来处理自己的数据。
-
void main()
-
{
-
//两个串口
-
serial_1 s1;
-
serial_2 s2;
-
//串口监听类
-
LISTEN_SERIAL ls;
-
-
if(s1.open_serial("/dev/ttyXXX"))
-
{
-
s1.set_baudrate(9600);
-
s1.set_parity(8,1,'N');
-
//加入监听队列
-
ls.get_serial(&s1);
-
}
-
if(s2.open_serial("/dev/ttyXXX"))
-
{
-
s2.set_baudrate(57600);
-
s2.set_parity(8,1,'N');
-
//加入监听队列
-
ls.get_serial(&s2);
-
}
-
-
//开始监听
-
ls.start_listen();
-
//等待监听线程返回
-
...
-
return;
-
}
这样就在ls中只创建了一个线程来监听两个串口。如果还要增加串口,就按同样的方法派生串口类,然后在mian中加入监听队列就行了。
转载请注明出处:
http://blog.chinaunix.net/uid-30008524-id-5575690.html
阅读(3440) | 评论(0) | 转发(0) |