A função Select ainda é relativamente importante na programação de Socket, mas para aqueles que são novos no Socket, eles não gostam de escrever programas com Select.Eles são usados apenas para escrever programas de bloqueio, como conectar, aceitar, recv e recvfrom.
No entanto, o uso de Select pode concluir um programa que funciona de maneira não bloqueadora.Ele pode monitorar as mudanças nos descritores de arquivo que precisamos para monitorar leitura e gravação ou anormais.O chamado bloqueio de modo de bloqueio, como o nome indica, é que o processo ou thread deve aguardar a ocorrência de um evento quando essas funções são executadas. Se o evento não ocorrer, o processo ou thread será bloqueado e a função não poderá retornar imediatamente.
O chamado método non-blocking non-block significa que o processo ou thread não precisa esperar o evento ocorrer ao executar esta função. Uma vez que a execução retorna, a execução da função é refletida pela diferença no valor de retorno .Se o evento ocorrer, é igual ao método de bloqueio .Se o evento não ocorrer, um código é retornado para informar que o evento não ocorreu, e o processo ou thread continua em execução, portanto a eficiência é maior.
Explicação detalhada e análise de caso da função de seleção
Uso da função de seleção e análise de caso
Arquivo de cabeçalho
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>
OR
#include <sys/ioctl.h>
Definir função
int select(int n, fd_set * readfds,fd_set * writefds, fd_set * exceptfds, struct timeval * timeout);
// select函数会不断修改timeout的值,所以每次循环都应该重新赋值
A descrição da função
select () é usada para aguardar a mudança do status do descritor de arquivo. O parâmetro n representa o maior descritor de arquivo mais 1. Os parâmetros readfds, writefds e exceptfds são chamados de frases descritivas, que são usadas para retornar as condições de leitura, gravação ou exceção do descritor.
As macros a seguir fornecem maneiras de lidar com essas três frases de descrição:
FD_CLR(inr fd,fd_set* set);-----------用来清除描述词组 set 中相关 fd 的位
FD_ISSET(int fd,fd_set *set);---------用来测试描述词组 set 中相关 fd 的位是否为真
FD_SET(int fd,fd_set*set);------------用来设置描述词组 set 中相关 fd 的位 //添加描述符
FD_ZERO(fd_set *set); ---------------用来清除描述词组 set 的全部位 //每次循环都要清空集合,否则不能检测描述符变化
- O parâmetro timeout é a estrutura timeval, que é utilizada para definir o tempo de espera de select (). Sua estrutura é definida da seguinte forma:
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
struct timeval * timeout é o período de timeout de select, este parâmetro é muito importante, pode fazer select em três estados:
- Primeiro, se NULL é passado como um parâmetro formal, isto é, nenhuma estrutura de tempo é passada, ou seja, select é colocado em um estado de bloqueio e deve esperar até que um descritor de arquivo no conjunto de descritores de arquivo de monitoramento mude;
- Em segundo lugar, se o valor do tempo for definido como 0 segundos e 0 milissegundos, torna-se uma função pura sem bloqueio, não importa se o descritor do arquivo foi alterado, ele retornará imediatamente para continuar a execução, o arquivo retornará 0 se não houver mudança, e um valor positivo se houver mudança ;
- Terceiro, o valor do tempo limite é maior que 0, que é o tempo limite de espera, ou seja, seleciona blocos dentro do tempo limite e retorna quando um evento chega dentro do tempo limite, caso contrário, deve retornar após o tempo limite e o retorno o valor é igual ao anterior.
O seguinte é um fragmento de programa comum
fs_set readset;
FD_ZERO(&readset);
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset) {......}
Erro de código
- Se a execução for bem-sucedida, ele retornará o número de status alterados do descritor de arquivo;
- Se retornar 0, significa que o tempo limite passou antes que o status do descritor mudasse;
- Quando ocorre um erro, ele retorna -1. A causa do erro é armazenada em errno. Nesse momento, os valores dos parâmetros readfds, writefds, exceptfds e
timeout tornam-se imprevisíveis.
Compreensão aprofundada da função de seleção
A chave para uma compreensão profunda do modelo de seleção é entender fd_set. Para a conveniência da explicação, consideramos o comprimento de fd_set como 1 byte, e cada bit em fd_set pode corresponder a um descritor de arquivo fd. Então, um fd_set longo de 1 byte pode corresponder a 8 fd no máximo.
- Execute fd_set set; FD_ZERO (& set); então set é expresso em bits como 0000,0000.
- Se fd = 5, depois de executar FD_SET (fd, & set), set torna-se 0001, 0000 (a quinta posição é 1)
- Se fd = 2 e fd = 1 forem adicionados novamente, o conjunto torna-se 0001,0011
- Execute select (6, & set, 0,0,0) para bloquear a espera
- Se ocorrer um evento legível em fd = 1 e fd = 2, selecione retorna e o conjunto torna-se 0000, 0011 neste momento. Quando nenhum evento legível ocorre, fd = 5
é limpo.
Uso da função de seleção da porta serial
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> //文件控制定义
#include <termios.h>//终端控制定义
#include <errno.h>
char* device="/dev/ttyUSB0";
int serial_fd = 0;
struct termios options;
//default config with fd--ycc1
void defaultConfigWithFd()
{
//串口主要设置结构体termios <termios.h>
//struct termios options;
/**1. tcgetattr函数用于获取与终端相关的参数。
*参数fd为终端的文件描述符,返回的结果保存在termios结构体中
*/
tcgetattr(serial_fd, &options);
/**2. 修改所获得的参数*/
options.c_cflag |= (CLOCAL | CREAD);//设置控制模式状态,本地连接,接收使能
options.c_cflag &= ~CSIZE;//字符长度,设置数据位之前一定要屏掉这个位
options.c_cflag &= ~CRTSCTS;//无硬件流控
options.c_cflag |= CS8;//8位数据长度
options.c_cflag &= ~CSTOPB;//1位停止位
options.c_iflag |= IGNPAR;//无奇偶检验位
options.c_oflag = 0; //输出模式
options.c_lflag = 0; //不激活终端模式
cfsetospeed(&options, B115200);//设置波特率
/**3. 设置新属性,TCSANOW:所有改变立即生效*/
tcflush(serial_fd, TCIFLUSH);//溢出数据可以接收,但不读
tcsetattr(serial_fd, TCSANOW, &options);
}
//open serial--ycc1
int openSerial(char* device)
{
serial_fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
if (serial_fd < 0) {
perror("open err");
return -1;
}
return serial_fd;
}
//close
void uart_close()
{
close(serial_fd);
}
//打开串口并初始化设置
int init_serial(char* device)
{
if (openSerial(device) < 0) {
perror("open error");
return -1;
}
defaultConfigWithFd();
return 0;
}
//***ycc --bb
//set baudrate
void set_baudrate(int speed) {
int i;
int speed_arr[] = {B38400, B19200, B9600, B4800, B2400, B1200, B300,
B38400, B19200, B9600, B4800, B2400, B1200, B300};
int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 19200, 9600, 4800, 2400,
1200, 300};
//设置串口输入波特率和输出波特率
for (i = 0; i < sizeof(speed_arr) / sizeof(int); i++) {
if (speed == name_arr[i]) {
cfsetispeed(&options, speed_arr[i]);
cfsetospeed(&options, speed_arr[i]);
}
}
}
//set device
void set_device(char* device)
{
uart_close();
serial_fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
}
//set data bits
int set_data_bits(int databits)
{
//设置数据位
options.c_cflag &= ~CSIZE; //屏蔽其他标志位
switch (databits)
{
case 5 :
options.c_cflag |= CS5;
break;
case 6 :
options.c_cflag |= CS6;
break;
case 7 :
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
fprintf(stderr,"Unsupported data size/n");
return -1;
}
}
//set flow control
void set_flow_ctrl(int flow_ctrl)
{
//设置数据流控制
switch(flow_ctrl)
{
case 0 ://不使用流控制
options.c_cflag &= ~CRTSCTS;
break;
case 1 ://使用硬件流控制
options.c_cflag |= CRTSCTS;
break;
case 2 ://使用软件流控制
options.c_cflag |= IXON | IXOFF | IXANY;
break;
}
}
//set parity bits
void set_parity_bits(int parity)
{
//设置校验位
switch (parity)
{
case 'n':
case 'N': //无奇偶校验位。
options.c_cflag &= ~PARENB;
options.c_iflag &= ~INPCK;
break;
case 'o':
case 'O'://设置为奇校验
options.c_cflag |= (PARODD | PARENB);
options.c_iflag |= INPCK;
break;
case 'e':
case 'E'://设置为偶校验
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
options.c_iflag |= INPCK;
break;
case 's':
case 'S': //设置为空格
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
break;
default:
fprintf(stderr,"Unsupported parity/n");
}
}
//set stop bits
void set_stop_bits(int stopbits)
{
// 设置停止位
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
fprintf(stderr,"Unsupported stop bits/n");
}
}
//***ycc --ee
/**
*串口发送数据
*@fd:串口描述符
*@data:待发送数据
*@datalen:数据长度
*/
int uart_send(char *data, int datalen)
{
int len = 0;
len = write(serial_fd, data, datalen);//实际写入的长度
if(len == datalen) {
return len;
} else {
tcflush(serial_fd, TCOFLUSH);//TCOFLUSH刷新写入的数据但不传送
return -1;
}
return 0;
}
/**
*串口接收数据
*要求启动后,在pc端发送ascii文件
*/
int uart_recv(char *data, int datalen)
{
int len=0, ret = 0;
fd_set fs_read;
struct timeval tv_timeout;
FD_ZERO(&fs_read);
FD_SET(serial_fd, &fs_read);
tv_timeout.tv_sec =2; // (10*20/115200+5);
tv_timeout.tv_usec = 0;
while(FD_ISSET(serial_fd,&fs_read))
{
FD_ZERO(&fs_read);
FD_SET(serial_fd,&fs_read);
ret = select(serial_fd+1, &fs_read, NULL, NULL, &tv_timeout);
printf("ret = %d\n", ret);
//如果返回0,代表在描述符状态改变前已超过timeout时间,错误返回-1
if(FD_ISSET(serial_fd, &fs_read)) {
len = read(serial_fd, data, datalen);
printf("len = %d\n", len);
if(-1==len) {
return -1;
}
} else {
perror("select");
}
}
return 0;
}
void testSend()
{
char buf[]="yccyccyccy";
init_serial("/dev/tty");
uart_send(buf, sizeof(buf));
}
void testRsv()
{
init_serial("/dev/tty");
char buf1[11];
uart_recv(buf1, sizeof(buf1));
printf("uart receive %s\n", buf1);
}
int main(int argc, char **argv)
{
testSend();
//testRsv();
return 0;
}