精华内容
下载资源
问答
  • 异步非阻塞串口通讯

    千次阅读 2011-01-09 20:58:00
    异步非阻塞串口通讯的实现步骤 2005.01.05 一,异步非阻塞串口通讯的优点 读写串行口时,既可以同步执行,也可以重叠(异步)执行。 在同步执行时,函数直到操作完成后才返回。这意味着在同步...

    目录:
    1. 异步非阻塞串口通讯的优点
    2. 异步非阻塞串口通讯的基本原理
    3. 异步非阻塞串口通讯的基础知识
    4. 异步非阻塞串口通讯的实现步骤
    2005.01.05

    一,异步非阻塞串口通讯的优点

    读写串行口时,既可以同步执行,也可以重叠(异步)执行。
    在同步执行时,函数直到操作完成后才返回。这意味着在同步执行时线程会被阻塞,从而导致效率下降。
    在重叠执行时,即使操作还未完成,调用的函数也会立即返回。费时的I/O操作在后台进行,这样线程就可以干别的事情。
    例如,线程可以在不同的句柄上同时执行I/O操作,甚至可以在同一句柄上同时进行读写操作。"重叠"一词的含义就在于此。

    二,异步非阻塞串口通讯的基本原理
    首先,确定要打开的串口名、波特率、奇偶校验方式、数据位、停止位,传递给CreateFile()函数打开特定串口;
    其次,为了保护系统对串口的初始设置,调用 GetCommTimeouts()得到串口的原始超时设置;
    然后,初始化DCB对象,调用SetCommState() 设置DCB,调用SetCommTimeouts()设置串口超时控制;
    再次,调用SetupComm()设置串口接收发送数据的缓冲区大小,串口的设置就基本完成,之后就可以启动读写线程了。

    三,异步非阻塞串口通讯的基础知识
    下面来介绍并举例说明一下编写异步非阻塞串口通讯的程序中将会使用到的几个关键函数

    CreateFile()
    功能:打开串口设备
    函数原型
    HANDLE CreateFile(
    LPCTSTR lpFileName, // 串口名称字符串;如: "COM1" 或 "COM2"
    DWORD dwDesiredAccess, // 设置读写属性(访问模式 );一般为 GENERIC_READ|GENERIC_WRITE,
    DWORD dwShareMode, // 共享模式;"必须"为 0, 即不能共享
    LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全属性;一般为NULL
    DWORD dwCreationDistribution, // 创建方式,串口设置必须设置此值; 在这里"必须"为 OPEN_EXISTING
    DWORD dwFlagsAndAttributes, // 文件属性和标志;在这里我们设置成FILE_FLAG_OVERLAPPED ,实现异步I/O
    HANDLE hTemplateFile // 临时文件的句柄,通常为NULL  
    );
    说明:
    如果调用成功,那么该函数返回文件的句柄,如果调用失败,则函数返回INVALID_HANDLE_VALUE。
    Forexample:

    Handle m_hComm = CreateFile(com1,GENERIC_READ||GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0);

    CloseHandle();
    功能:关闭串口
    BOOL CloseHandle(
    HANDLE hObject // handle to object to close

    这个,我想就不多说了吧!

    GetCommState()
    功能:获得串口状态
    BOOL GetCommState(
    HANDLE hFile, // handle of communications device
    LPDCB lpDCB // address of device-control block structure
    );

    SetCommState()
    功能:设置串口状态 
    BOOL SetCommState(
    HANDLE hFile, // handle of communications device
    LPDCB lpDCB // address of device-control block structure
    );
    说明:
    在打开通信设备句柄后,常常需要对串行口进行一些初始化工作。这需要通过一个DCB结构来进行。DCB结构包含了诸如波特率、每个字符的数据位数、奇偶校验和停止位数等信息。在查询或配置置串行口的属性时,都要用DCB结构来作为缓冲区。
    调用GetCommState函数可以获得串口的配置,该函数把当前配置填充到一个DCB结构中。一般在用CreateFile打开串行口后,可以调用GetCommState函数来获取串行口的初始配置。要修改串行口的配置,应该先修改DCB结构,然后再调用SetCommState函数用指定的DCB结构来设置串行口
    Forexample:
    DCB dcb;
    memset(&dec,0,dizeof(dcb));
    if(!GetCommState(HComm,&dcb))//获取当前DCB配置
     return FALSE; 
    dcb.BaudRate = CBR_9600;//修改数据传输率
    ............
    if(SetCommState(hComm,&dcb))//设置新参数
    ......    //错误处理

    BuildCommDCB()
    功能:初始化DCB结构
    BOOL BuildCommDCB(
    LPCTSTR lpDef, // pointer to device-control string
    LPDCB lpDCB // pointer to device-control block
    );
    Forexample:
    DCB dcb;
    memset(&dcb,0,sizeof(dcb));
    dcb.DCBlength = sizeof(dcb);
    if(!BuildCommDCb("9600,n,8,1",&dcb))//"baud=9600 parity=N data=8 stop=1"
    {
     ......  //参数修改错误
     return FALSE;
    }
    else
    {
    ......  //己准备就绪
    }
    说明:功能同上面的例子。

    SetupComm()
    功能:设置I/O缓冲区的大小
    函数原型:
    BOOL SetupComm(
      HANDLE hFile,     // handle to communications device
      DWORD dwInQueue,  // size of input buffer
      DWORD dwOutQueue  // size of output buffer
    );
    说明:
    除了在DCB中的设置外,程序一般还需要设置I/O缓冲区的大小和超时。Windows用I/O缓冲区来暂存串行口输入和输出的数据,如果通信的速率较高,则应该设置较大的缓冲区。调用SetupComm函数可以设置串行口的输入和输出缓冲区的大小。

     

    先介绍一个结构:COMMTIMEOUTS

    typedef struct _COMMTIMEOUTS {  
    DWORD ReadIntervalTimeout; // 读间隔超时 
    DWORD ReadTotalTimeoutMultiplier; // 读时间系数 
    DWORD ReadTotalTimeoutConstant; // 读时间常量 
    DWORD WriteTotalTimeoutMultiplier; // 写时间系数
    DWORD WriteTotalTimeoutConstant; // 写时间常量 
    } COMMTIMEOUTS,*LPCOMMTIMEOUTS;

     

    再介绍两个函数
    GetCommTimeouts
    功能:读取TimeOut的值
    函数原型:
    BOOL GetCommTimeouts(
    HANDLE hFile, // handle of communications device
    LPCOMMTIMEOUTS lpCommTimeouts // address of comm. time-outs structure
    );
    SetCommTimeouts
    功能:设置TimeOUt的值
    函数原型:
    BOOL SetCommTimeouts(
    HANDLE hFile, // handle of communications device
    LPCOMMTIMEOUTS lpCommTimeouts // address of communications time-out structure
    );

    这里顺便介绍一下TimeOut机制的两个性质:

    超时函数
    说明:
    在用ReadFile和WriteFile读写串行口时,需要考虑超时问题。如果在指定的时间内没有读出或写入指定数量的字符,那么ReadFile或WriteFile的操作就会结束。要查询当前的超时设置应调用GetCommTimeouts函数,该函数会填充一个COMMTIMEOUTS结构。调用SetCommTimeouts可以用某一个COMMTIMEOUTS结构的内容来设置超时。

      有两种超时:间隔超时和总超时。间隔超时是指在接收时两个字符之间的最大时延,总超时是指读写操作总共花费的最大时间。写操作只支持总超时,而读操作两种超时均支持。用COMMTIMEOUTS结构可以规定读/写操作的超时,该结构的定义为: COMMTIMEOUTS结构的成员都以毫秒为单位。总超时的计算公式是:

    总超时=时间系数×要求读/写的字符数 + 时间常量

      例如,如果要读入10个字符,那么读操作的总超时的计算公式为:

    读总超时=ReadTotalTimeoutMultiplier×10 + ReadTotalTimeoutConstant

      可以看出,间隔超时和总超时的设置是不相关的,这可以方便通信程序灵活地设置各种超时。

      如果所有写超时参数均为0,那么就不使用写超时。如果ReadIntervalTimeout为0,那么就不使用读间隔超时,如果ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都为0,则不使用读总超时。如果读间隔超时被设置成MAXDWORD并且两个读总超时为0,那么在读一次输入缓冲区中的内容后读操作就立即完成,而不管是否读入了要求的字符。

      在用重叠方式读写串行口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是ReadFile和WriteFile的返回时间。

    Forexample:
    COMMTIMEOUTS timeOver;
    memset(&&timeOver.0.sizeof(timeOver));
    DWORDtimeMultiplier,timeConstant;
    timeOver.ReadTotalTimeoutMultiplier=timeMultiplier;
    timeOver.ReadTotalTimeoutConstant=timeConstant;
    SetCommTimeouts(hComport,&&timeOver);

    ReadFile()
    功能:读取数据
    函数原型:
    BOOL ReadFile(
    HANDLE hFile, // 串口名称字符串(文件句柄 )
    LPVOID lpBuffer, // 读缓冲区 
    DWORD nNumberOfBytesToRead, // 要求读入的字节数
    LPDWORD lpNumberOfBytesRead, // 实际读入的字节数 
    LPOVERLAPPED lpOverlapped // 指向一个OVERLAPPED结构
    ); //若返回TRUE则表明操作成功

    Forexample:
    char *pReciveBuf;
    DWORD nWantRead = 100,
     nReadRead;
    LPOVERLAPPED m_OverlappedRead;
    BOOL bReadStatus = ReadFile( m_hComm, preciveBuf,nWantRead, &&nReadRead, &&m_OverlappedRead );

    WriteFile() 
    功能:来将资料写入Serial port. 
    函数原型:
    BOOL WriteFile(
    HANDLE hFile, // handle to file to write to
    LPCVOID lpBuffer, // pointer to data to write to file
    DWORD nNumberOfBytesToWrite, // number of bytes to write
    LPDWORD lpNumberOfBytesWritten, // pointer to number of bytes written
    LPOVERLAPPED lpOverlapped // pointer to structure needed for overlapped I/O
    );

    说明:
     ReadFile函数只要在串行口输入缓冲区中读入指定数量的字符,就算完成操作。
    而WriteFile函数不但要把指定数量的字符拷入到输出缓冲中,而且要等这些字符从串行口送出去后才算完成操作。

    当ReadFile和WriteFile返回FALSE时,不一定就是操作失败,线程应该调用GetLastError函数分析返回的结果。例如,在重叠操作时如果操作还未完成函数就返回,那么函数就返回FALSE,而且GetLastError函数返回ERROR_IO_PENDING。

    如果GetLastError函数返回ERROR_IO_PENDING,则说明重叠操作还为完成,线程可以等待操作完成。
    有两种等待办法:一种办法是用象WaitForSingleObject这样的等待函数来等待OVERLAPPED结构的hEvent成员,
    可以规定等待的时间,在等待函数返回后,调用GetOverlappedResult。
    另一种办法是调用GetOverlappedResult函数等待,如果指定该函数的bWait参数为TRUE,
    那么该函数将等待OVERLAPPED结构的hEvent 事件。
    GetOverlappedResult可以返回一个OVERLAPPED结构来报告包括实际传输字节在内的重叠操作结果。

    如果规定了读/写操作的超时,那么当超过规定时间后,hEvent成员会变成有信号的。因此,在超时发生后,WaitForSingleObject和GetOverlappedResult都会结束等待。WaitForSingleObject的dwMilliseconds参数会规定一个等待超时,该函数实际等待的时间是两个超时的最小值。注意GetOverlappedResult不能设置等待的时限,因此如果hEvent成员无信号,则该函数将一直等待下去

    ClearCommError() 
    功能: 从字面上的意思看来, 它是用来清除错误情况用的, 但是实际上它还可以拿来取得目前通讯设备的一些信息.
    函数原型:
    BOOL ClearCommError(
    HANDLE hFile, // handle to communications device
    LPDWORD lpErrors, // pointer to variable to receive error codes
    LPCOMSTAT lpStat // pointer to buffer for communications status
    );
    说明:
     在调用ReadFile和WriteFile之前,线程应该调用ClearCommError函数清除错误标志。
    该函数负责报告指定的错误和设备的当前状态。


    PurgeComm()
    功能:终止目前正在进行的读或写的动作
    函数原型:
    BOOL PurgeComm(
    HANDLE hFile, // handle of communications resource
    DWORD dwFlags // action to perform
    );
    参数说明:
    HANDLE hFile,//串口名称字符串
    dwFlags 共有四种 flags:

      PURGE_TXABORT: 终止目前正在进行的(背景)写入动作
      PURGE_RXABORT: 终正目前正在进行的(背景)读取动作
      PURGE_TXCLEAR: flush 写入的 buffer
      PURGE_TXCLEAR: flush 读取的 buffer
    调用PurgeComm函数可以终止正在进行的读写操作,该函数还会清除输入或输出缓冲区中的内容。

    GetCommMask() 
    功能:得到设置的通信事件的掩码
    函数原型:
    BOOL GetCommMask(
    HANDLE hFile, // handle of communications device
    LPDWORD lpEvtMask // address of variable to get event mask
    );

    SetCommMask() 
    功能:设置想要得到的通信事件的掩码
    函数原型:
    BOOL SetCommMask(
    HANDLE hFile, // handle of communications device
    DWORD dwEvtMask // mask that identifies enabled events
    );
    说明:
    可设置的通信事件标志(即SetCommMask()函数所设置的掩码)
    可以有EV_BREAK、EV_CTS、EV_DSR、 EV_ERR、EV_RING、EV_RLSD、EV_RXCHAR、EV_RXFLAG、EV_TXEMPTY。

    注:若对端口数据的响应时间要求较严格,可采用事件驱动I/O读写,Windows定义了9种串口通信事件,较常用的有:

      EV_RXCHAR: 接收到一个字节,并放入输入缓冲区。

      EV_TXEMPTY: 输出缓冲区中的最后一个字符发送出去。

      EV_RXFLAG: 接收到事件字符(DCB结构中EvtChar成员),放入输入缓冲区。

    下面是MSDN上的解释:
    EV_BREAK A break was detected on input. 
    EV_CTS The CTS (clear-to-send) signal changed state. 
    EV_DSR The DSR (data-set-ready) signal changed state. 
    EV_ERR A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY. 
    EV_RING A ring indicator was detected. 
    EV_RLSD The RLSD (receive-line-signal-detect) signal changed state. 
    EV_RXCHAR A character was received and placed in the input buffer. 
    EV_RXFLAG The event character was received and placed in the input buffer. The event character is specified in the device's DCB structure, which is applied to a serial port by using the SetCommState function. 
    EV_TXEMPTY The last character in the output buffer was sent.

    WaitCommEvent()
    功能:等待设定的通讯事件的发生
    函数原型:
    BOL WaitCommEvent(
    HANDLE hFile, // handle of communications device
    LPDWORD lpEvtMask, // address of variable for event that occurred
    LPOVERLAPPED lpOverlapped, // address of overlapped structure
    );
    说明:
    WaitCommEvent() 会一直 block(阻塞) 到你所设定的通讯事件发生为止. 
    所以当 WaitCommEvent() 返回时, 你可以由 lpEvtMask 取得究竟是那一事件发生, 再来决定要如何处理.

    WaitForSingleObject()
    功能:保证线程同步的等待函数
    函数原型:
    DWORD WaitForSingleObject(HANDLE hHandle,//同步对象的句柄
         DWORD dwMilliseconds//以毫秒为单位的超时间隔,如果设为INFINITE,则超时间隔是无限的
        );
    说明:
    返回值    含义  
    WAIT_FAILED   函数失败 
    WAIT_OBJECT_0  指定的同步对象处于有信号的状态 
    WAIT_ABANDONED  拥有一个mutex的线程已经中断了,但未释放该MUTEX 
    WAIT_TIMEOUT   超时返回,并且同步对象无信号

    WaitForMultipleObjects()
    功能:可以同时监测多个同步对象
    函数原型:
    DWORD WaitForMultipleObjects(DWORD nCount,//句柄数组中句柄的数目
         CONST HANDLE *lpHandles,//代表一个句柄数组
         BOOL bWaitAll, //说明了等待类型(),如果为TRUE,那么函数在所有对象都有信号后才返回,
          //如果为FALSE,则只要有一个对象变成有信号的,函数就返回
        DWORD dwMilliseconds//以毫秒为单位的超时间隔
         );
    说明: 
     返回值        含义 
    WAIT_OBJECT_0到WAIT_ OBJECT_0+nCount-1    若bWaitAll为TRUE,则返回值表明所有对象都是有信号的。
           如果bWaitAll为FALSE,则返回值减去WAIT_OBJECT_0就是数组中有信号对       象的最小索引。 
     
    WAIT_ABANDONED_0到WAIT_ ABANDONED_ 0+nCount-1  若bWaitAll为TRUE,则返回值表明所有对象都有信号,但有一个mutex被       放弃了。若bWaitAll为FALSE,则返回值减去WAIT_ABANDONED_0就是被放弃       mutex在对象数组中的索引。 
     WAIT_TIMEOUT      超时返回

    杜思波 湖南永州
    展开全文
  • linux下串口的开发之非阻塞异步

    千次阅读 2017-08-16 09:59:34
    linux下串口的开发 读取串口数据使用文件操作read函数读取,如果设置为原始模式(Raw Mode)传输数据,那么read函数返回的字符数是实际串口收到...可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操作

    linux下串口的开发

    读取串口数据使用文件操作read函数读取,如果设置为原始模式(Raw Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数。

    char  buff[1024];

    int    Len;

    int  readByte = read(fd,buff,Len);

    可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操作。

    void SERIAL_RX(void)

    {

          //  read(fd, RXBUF , RX_len);

    #if 1

           int ret,n,pos,retval;

           fd_set rfds;

           struct timeval tv ;

           pos = 0;//指向接收缓冲

     

           for(n = 0; n < RX_len; n++)

           {

                  RXBUF[n] = 0xFF;

           }

     

           FD_ZERO(&rfds);// 清空串口接收端口集

           FD_SET(fd,&rfds);// 设置串口接收端口集   

           tv.tv_sec = 2;

           tv.tv_usec = 0;

     

           while(FD_ISSET(fd,& rfds)) // 检测串口是否有读写动作

           {    

    // 每次循环都要清空,否则不会检测到有变化

                  FD_ZERO(&rfds);// 清空串口接收端口集

                  FD_SET(fd,&rfds);// 设置串口接收端口集   

     

                  retval = select(fd+1,&rfds,NULL,NULL,&tv);  

                  if(retval == -1)

                  {

                         perror("select()");

                         break;

                  }                             

                  else if(retval)

                  {  //判断是否还有数据

                         //sleep(2);                             

                         ret = read(fd, RXBUF, RX_len);

                         pos += ret;

                         //printf("ret = %d /n",ret);

                         if((RXBUF[pos-2] == '/r') & (RXBUF[pos-1] == '/n')) // 确实接收到了数据,并打印出来

                         {

                                FD_ZERO(&rfds);

                                FD_SET(fd,&rfds);      

                                retval = select(fd+1,&rfds,NULL,NULL,&tv);  

                                if(!retval)//no datas

                                {

                                       break;

                                }      

                         }

                  }

                  else

                  {

                         break;

                  }

           }

    }

    Linux下直接用read读串口可能会造成堵塞,或数据读出错误。然而用select先查询com口,再用read去读就可以避免,并且当com口延时时,程序可以退出,这样就不至于由于com口堵塞,程序就死了。

    Select的函数格式(我所说的是Unix系统下的伯克利socket编程,和windows下的有区别,一会儿说明):

    int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

    先说明两个结构体:

    l        struct fd_set

    可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如:

    ?        FD_ZERO(fd_set *set):清除一个文件描述符集;

    ?        FD_SET(int fd, fd_set *set):将一个文件描述符加入文件描述符集中;

    ?        FD_CLR(int fd, fd_set *set):将一个文件描述符从文件描述符集中清除;

    ?        FD_ISSET(int fd, fd_set *set): 检查集合中指定的文件描述符是否可以读写。

    一会儿举例说明。

    l        struct timeval

    是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。

    struct timeval{

                  long tv_sec;

                 long tv_unsec;

    }

    具体解释select的参数:

    l        int maxfdp

    是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。

    l        fd_set *readfds

    是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

    l        fd_set *writefds

    是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

    l        fd_set *errorfds

    同上面两个参数的意图,用来监视文件错误异常。

    l        struct timeval* timeout

    是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

    l        返回值:

    负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件

    在有了select后可以写出像样的网络程序来!举个简单的例子,就是从网络上接受数据写入一个文件中。

    一般来说,在使用select函数之前,首先使用FD_ZERO和FD_SET来初始化文件描述符集,在使用了select函数时,可循环使用FD_ISSET测试描述符集,在执行完对相关的文件描述符后,使用FD_CLR来清除描述符集。

    2.2      写串口

    读写串口设置好串口之后,读写串口就很容易了,把串口当作文件读写就是。发送数据:

    char  buffer[1024];

    int    Length;

    int    nByte;

    nByte = write(fd, buffer ,Length);

    2.3      关闭串口

    close(fd);

    展开全文
  • 这是最近需要实现一个基于TCP的一个与设备通信...Qt的串口通信,当然也有阻塞和非阻塞,调用waitForXX的函数就会挂起当前caller线程,那么就阻塞了。 http://blog.csdn.net/chenlong12580/article/details/8976176 ...

    这是最近需要实现一个基于TCP的一个与设备通信比较复杂的协议以前没有太搞明白的问题

    之后看了这些,豁然开朗

    这是Qt的TCP相关讲解:

    http://www.bogotobogo.com/Qt/Qt5_Asynchronous_QTcpServer_QThreadPool.php

    http://blog.csdn.net/chenlong12580/article/details/7431864

    http://blog.csdn.net/chenlong12580/article/details/9003139

     

    Qt的串口通信,当然也有阻塞和非阻塞,调用waitForXX的函数就会挂起当前caller线程,那么就阻塞了。

    http://blog.csdn.net/chenlong12580/article/details/8976176

    http://blog.csdn.net/chenlong12580/article/details/9003139

     

    注意:如果main函数没有进入Qt的主事件循环,那么connect函数将没有作用。

    转载于:https://www.cnblogs.com/foohack/p/4713900.html

    展开全文
  • 同步和异步 同步 就是你知道你什么时候在做什么,做完一件事情再做下一件事情,因此主动权在自己手里。...阻塞和非阻塞 阻塞:一个调用过程必须完成才返回。对于IO操作,如果IO没有准备好,读取或...

    同步和异步

    同步 就是你知道你什么时候在做什么,做完一件事情再做下一件事情,因此主动权在自己手里。比如通过等待或轮询,你在某个时间点总是知道结果是怎样的(有数据还是没数据等)。
    异步 就是你不知道什么时候会发生什么。比如你注册了多个回调函数,你不知道什么时候会被调用以及被调用的是哪一个回调函数。

    阻塞和非阻塞

    阻塞:一个调用过程必须完成才返回。对于IO操作,如果IO没有准备好,读取或者写入等函数将一直等待。
    非阻塞:一个调用过程会立即返回,无论结果怎样。对于IO操作,读取或者写入函数总会立即返回一个状态,要么读取成功,要么读取失败(没有数据或被信号中断等)。
    看微博上有人(屈春河的微博)说还有部分阻塞:整个调用过程分为C1,C2,…,Cn步,调用者完成C1,…,Cj步后就返回,而不是等待完成整个调用过程。

    组合方式

    通常有三种组合方式:
    同步阻塞:所有动作依次顺序执行。单线程可完成。
    同步非阻塞:调用者一般通过轮询方式检测处理是否完成。单线程可完成。
    对于IO操作来讲,我们熟悉的select/epoll,即IO多路复用,可以认为属于这种工作方式。不过有人说select/epoll是异步阻塞的方式,这是为啥呢?明明select/epoll过程在用户态看来类似于轮询,而读写过程可以非阻塞的。实际上select/epoll过程是阻塞的,但它的好处是可以同时监听多个fd且可以设置超时,并且利用select/epoll的阻塞换取了读写的非阻塞。
    异步非阻塞:Callback模式,注册回调,等待其他线程利用回调执行后续处理。Linux kernel里面有个aio就是异步非阻塞IO,但好像很多坑。

    IO操作中的同步和异步

    需要再重复一下几种IO模型:
    1.阻塞I/O (blocking I/O):recv和recvfrom是阻塞的,即没有数据到来之前本线程不能做其他事情。
    2.非阻塞I/O (nonblocking I/O):recv和recvfrom没有数据时也立即返回,但要一直进行recv/recvfrom并轮询返回值,浪费CPU。可以用fcntl来设置非阻塞:
    int fcntl(int fd, int cmd, long arg);
    例如:
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(sockfd, F_SETFL, O_NONBLOCK);
    3.I/O多路复用 (I/O multiplexing):select/poll/epoll,没数据时会阻塞,有数据的时候返回就可以进行recv了,它和阻塞I/O的区别是可以同时监听多个套接字描述符上的数据,有一个有数据就返回。而阻塞I/O是阻塞在recv那个地方,而且recv的时候已经指定某一个套接字了。
    4.信号驱动I/O (signal driven I/O (SIGIO)):通过注册SIGIO的信号处理函数,来监听和接收数据(我没用过)。
    5.异步I/O (asynchronous I/O):利用aio机制,设置存放数据的缓存、回调函数以及回调的方式(线程或信号等),并调用aio_read()读数据并立即返回。内核中准备好数据并拷贝完成后,通知用户态去读。

    可见,这里并不是按照同步异步和阻塞非阻塞的组合给出的,我们也不必去纠结。但是异步IO模型和其他4种模型的一个显著区别在于:前4种模型最终调用recv去读,读的过程包括数据拷贝并返回状态,交给用户态处理;而异步IO在内核中默默地拷贝数据,记录状态,用户态省去了读操作,而是直接处理数据,这也是aio为什么需要预先分配读缓存的原因。
    如果非要套用同步和异步的概念,那我认为是按照recv(将数据拷贝到用户态)的时机来区分的:对于异步IO,用户态并不知道什么时候recv,因为kernel已经帮忙做好了,而其他4中模型都是用户程序自己主动去recv的。

    类比总线协议?

    这让我想到了同步总线和异步总线,他俩一个明显区别是:
    同步总线的通信双方总是通过一个统一的时钟来进行同步,比如串口,你首先要设置一个波特率,并且通信双方的这个值必须一致。也就是说两端都知道什么时候发送数据,什么时候能够期望收到对端的数据。某一方的时钟频率相差很大都会导致数据传输混乱。
    而异步总线的通信协议中会要求某一端提供时钟(通常是master),例如I2C和SMI,并且并不要求时钟频率恒定。Master想发送数据了,才开始产生时钟,时钟间隔可以不固定。那么slave(可能有多个slaves,通过片选来确定数据要发送到哪个slave)只能在收到时钟以及一段opcode后才知道有数据来以及要做什么操作。
    这实际上和软件上的同步、异步的概念类似。

    再说IO模型

    再说说各种IO模型的适用场景。

    由于现实中场景复杂,因此需要根据不同场景选择合适的IO模型。IO过程主要是硬件上操作时间比较长,数据传输通常又有DMA,所以IO过程中CPU消耗并不大。所以线程做IO时CPU可以做别的事情,因而不适宜让IO将CPU阻塞,我们选择IO模型的目标就是在保证IO正常的前提下尽量提高CPU利用率。

    上面的几种IO模型中:阻塞、非阻塞、异步IO(aio),通常用于块设备读写,IO传输效率和CPU利用率是主要要评估的点。而多路复用(包括事件通知机制libevent等)、signal IO则常用于字符设备或网络socket。因为它们更多的是监听事件,考虑是否能及时收到并正确处理事件。

    例如网络发包时,如果组包、发包的过程放在一个线程里串行的做,那么如果发包buffer满了就只能等待buffer可用才能把新的包放下去,这样就没办法完全利用CPU。可以用一个线程组包以及做其他事情,另一个线程发包,或者发包用非阻塞方式,看到buffer满就先返回做一些其他事情,再尝试重发。

    阻塞IO:内核程序在等待一个IO时,如果读不到东西,就阻塞了(内核会通过set_current_state(TASK_INTERRUPTIBLE)让线程设置为可中断,表示你要等待某事件或资源而睡眠,然后调用schedule放弃CPU),直到IO准备好了读到东西返回,或者被信号中断返回。如果是被信号中断返回的,会伴随错误码EINTR

    阻塞IO对于在一个线程中存在并发IO操作或者需要监听多个设备时就不适合。当然你可以通过多线程或多进程来分别处理每种IO操作或每个socket,编程也简单,但创建线程本身有资源开销,并且存在任务切换和任务通信的开销,效率不高。

    非阻塞IO:纯碎的非阻塞一般不用,因为每次去无脑地轮询看起来很傻。所以都是和select/epoll结合用的,等到数据ready了再用非阻塞去读。
    用非阻塞时(在读时设置MSG_DONTWAIT或者用fcntl设置F_SETFL为O_NONBLOCK),如果没有数据了你也要任性去读的话,会直接返回EAGAIN,即Resource temporarily unavailable。

    多路复用:就是一个线程中处理多个IO,例如select和epoll,它们可同时监听多个IO事件,有一个事件发生就返回,处理完这个事件后继续监听。实际上监听的过程是阻塞的,但比阻塞IO的好处是可以在一个线程里同时监听多个IO事件。

    select把所有fd都加到一个集合(set)里面,然后监控这个set,有一个fd发生事件就返回,这个监控的过程在内核中是通过遍历set,并且select返回后,你在用户态也要遍历所有fd列表来查找是listenfd还是接入的fd产生的事件以及哪个接入fd的事件,因此整个过程要两次遍历。另外,每次select都要重新设置整个set到内核里,前期准备工作也耗时。select在Linux里的fd数量限制由__FD_SETSIZE定义,一般是1024。

    epoll解决了select处理fd集合的上述两个问题,它把设置set和等待事件的api分离(epoll_ctl和epoll_wait),不用每次等待前都设置set;另外epoll_wait的第二个参数会保存哪些fd产生事件了,因此不用扫描整个set列表。这样就可以更高效地监听更多的事件。
    epoll_wait等到事件后不要做太多事情,防止又有新的fd准备好了。所以epoll也会结合线程池等设计,将事件的处理放到任务队列里去,然后尽快重新epoll_wait。

    但是和select相比,为了解决上述问题epoll引入了监控fd的红黑树,和ready事件的list,这些是额外的开销。

    这里也说一下多路复用时对信号的处理:
    select从内核返回到用户态之前会检查是否有未处理的signal,如果有signal pending,也就是被信号中断了,就会返回-ERESTARTSYS到标准库(我实际看到的是ERESTARTNOHAND)。有人给你kill了信号,你当然要处理这个信号啊,因此会执行信号处理函数,然后会确定该系统调用是返回到用户程序还是重新执行这个被打断的系统调用。
    这是根据这个信号的处理是否设置了SA_RESTART标记来决定的,如果设置了SA_RESTART,则系统调用被信号打断,CPU转而执行信号处理函数完成后,系统调用会重新执行;而如果不设置SA_RESTART标记,则执行完信号处理函数后就返回到用户程序了。至于信号是否默认设置了SA_RESTART,可以通过strace去跟一下,例如对于信号SIGINT默认不设置这个标记。

    例如阻塞的方式调用read()/recv()的时候,假如sigaction的时候对信号设置了SA_RESTART标记,那么如果read过程中被该信号打断,执行完信号处理函数后,read会继续进入内核执行继续阻塞而不是返回。而如果信号没设置SA_RESTART的话,在调用完信号处理函数之后,read立即返回,read返回-1并设置出错码为EINTR

    在select和read的时候都需要注意检查这个错误码看是不是被信号中断而返回的。

    自动忽略SA_RESTART的系统调用
    然后对于一些对超时时间敏感的系统调用,如select/usleep,会忽略信号的SA_RESTART标记,无论是否设置该标记,总是返回到用户程序。例如sleep,被信号中断后就返回,不管你是否给这个信号设置了SA_RESTART标记。因为你sleep(5)想睡5s,结果睡2s后被中断,不可能自动重发再睡5s。

    另外说一下,每次select完之后,参数tv会被赋值为尚未等待的时长,例如设置5s,但select等了2s就返回了,那tv中保存3s。nanosleep()也是一样,它可能在睡眠过程中被信号打断而返回,但你可以通过检查nanosleep没睡够的时间让睡眠更精确一些。

    signal()函数的man page中说明了有哪些系统调用,即使你设置了自动重发(SA_RESTART)也不会自动重发。

    The following interfaces are never restarted after being interrupted by
    a signal handler, regardless of the use of SA_RESTART; they always fail
    with the error EINTR when interrupted by a signal handler:
    
       * Socket interfaces, when a timeout has  been  set  on  the  socket
         using   setsockopt(2):   accept(2),   recv(2),  recvfrom(2),  and
         recvmsg(2), if a receive timeout (SO_RCVTIMEO) has been set; con‐
         nect(2),  send(2),  sendto(2),  and sendmsg(2), if a send timeout
         (SO_SNDTIMEO) has been set.
    
       * Interfaces used to wait  for  signals:  pause(2),  sigsuspend(2),
         sigtimedwait(2), and sigwaitinfo(2).
    
       * File    descriptor    multiplexing   interfaces:   epoll_wait(2),
         epoll_pwait(2), poll(2), ppoll(2), select(2), and pselect(2).
    
       * System V IPC interfaces: msgrcv(2), msgsnd(2), semop(2), and sem‐
         timedop(2).
    
       * Sleep    interfaces:    clock_nanosleep(2),   nanosleep(2),   and
         usleep(3).
    
       * read(2) from an inotify(7) file descriptor.
    
       * io_getevents(2).
    
    The sleep(3) function is also never restarted if interrupted by a  han
    dler,  but  gives  a success return: the number of seconds remaining to
    sleep.

    signal IO:现在基本没人用了,因为用它的程序架构不好看,有点过度设计了。它就是设定一个特定的信号SIGIO的处理函数。内核发现事件(具体什么fd什么事件内核里面自己实现)后就kill一个SIGIO信号给用户程序,然后在信号处理函数中做事情。

    异步IO:即aio。aio有两个版本,glibc的aio,以及kernel里的纯碎的异步aio机制。即发起一个IO后,我不需要原地等,后台会在IO ready之后帮你读到指定的数据区,当我在某一个点上需要同步等待它读完的时候,就调用xxx_suspend。也就是说,数据的读取是内核或glib帮你做的,并且你可以在任何你需要数据的时候再去等待IO。

    glibc的aio,你调用aio_read会立即返回,glib开一个后台线程帮你同步去读,当你运行到某个点确实需要等待数据读完时,你调用一个aio_suspend来等待就可以了(当然如果这时数据已经读出来了,aio_suspend就立即返回了)。

    kernel里的aio机制,用户态接口为io_setup/io_submit/io_getevents/io_destroy,这几个API分别用来准备上下文、类似aio_read发布IO请求的过程、类似aio_suspend的同步等待过程、销毁上下文。使用时要加-laio。
    kernel的aio通常跟O_DIRECT配合用,例如有些人想在用户态自己做数据缓存,而不用内核的page cache,就用O_DIRECT打开硬盘,用aio读数据。使用kernel的aio时,由于kernel里面的aio还不是很完善,进化的比较慢,目前只用O_DIRECT打开文件才能用kernel的aio

    glibc_aio和kernel_aio接口的用法见man page。

    事件触发: libevent利用epoll做了一层封装,做成基于异步事件通知的IO模型,它实际上就是让你先event_add注册事件处理函数,然后dispatch里面不断的去epoll_wait,有事件发生就调用对应的callback函数。我觉得就类似minigui里面的按钮事件的proc函数一样,你点了按钮就调用预先注册好的按钮事件处理函数。并且libevent是跨平台的,防止epoll在别的系统上没有。

    展开全文
  • 本文档,是我本人翻译的一篇介绍linux系统同步异步阻塞非阻塞的知识的,你在网上看到的百分之八十的知识,可能都以讹传讹,让你看的一知半解,因为网上很多该类博客,没有讲解清楚,举得例子:如老王烧水,小王银行...
  • 异步io是kernel帮你的线程盯着该线程所要的数据是否可用,而线程可以去做别的事情。当数据可用时kernel通知你的线程。需要利用事件等机制来完成。 同步io是你的线程自己去向内核查询所要的数据是否可用。在查询的...
  • 使用C++撰写,非阻塞是的读取UDP,串口方式串口数据,UDP数据一般采用阻塞式的方式,很多时候,整个程序如果没有接收到新的数据,那么程序就会一直等待接收数据,造成程序等待接收数据的状态,blockingread,...
  • 怎样理解阻塞非阻塞与同步异步的区别?
  • linux设备驱动中的阻塞非阻塞IO异步通知 阻塞操作 等待队列的驱动编程在已有的设备驱动基本框架上继续 非阻塞操作 实现过程 驱动编程实现 编程典型模板 异步通知 编程实现 使用场景linux设备驱动中的阻塞,非阻塞I/O,...
  • 基于API 异步串口编程源码

    热门讨论 2013-04-25 17:52:16
    本例程采用 windows API编写,属于串口异步非阻塞操作,采用线程监视、windows事件、自定义消息,其中数据接收一块分别采用SetCommMask、WaitCommEvent 设置等待串口通信事件,是初学者不可多得的学习资料,该历程...
  • 假设现在办公室开会,就缺一个人了,在他没有来的时候,大家都在聊天。如果这个人不来,会议就没法召开,这就是阻塞。会议这个线程就被挂起了。...如果你不来,会议照常可以进行,这就是非阻塞。尽管你不
  • 一、概述 Linux串口非常灵活,可以根据需要配置成标准串口和自定义串口模式,就Linux 串口读取数据来说,有有两种主要方式:阻塞与非阻塞。...非阻塞方式,及时返回当前完整数据包。 固定200ms的时间等...
  • 1. 异步非阻塞串口通讯的优点 2. 异步非阻塞串口通讯的基本原理 3. 异步非阻塞串口通讯的基础知识 4. 异步非阻塞串口通讯的实现步骤2005.01.05 一,异步非阻塞串口通讯的优点 读写串行口时,既可以同步执行,...
  • 之前一直觉得串口编程很简单,这两天仔细研究后发现串口里的各种参数还挺复杂,稍不注意就容易出错,这里总结一下网上的各种文章及自己的理解与实践。 open 函数 功能描述:用于打开或创建文件,成功则返回文件...
  • 1. 异步非阻塞串口通讯的优点 2. 异步非阻塞串口通讯的基本原理 3. 异步非阻塞串口通讯的基础知识 4. 异步非阻塞串口通讯的实现步骤 2005.01.05 一,异步非阻塞串口通讯的优点 读写串行口时,既可以...
  • 使用Win32API实现Windows下异步串口通讯(上.... 收藏 ...4. 异步非阻塞串口通讯的实现步骤2005.01.05 一,异步非阻塞串口通讯的优点 读写串行口时,既可以同步执行,...
  • 使用Win32API实现Windows下异步串口通讯 2011年04月10日  目录:  1. 异步非阻塞串口通讯的优点 ... [b]一,异步非阻塞串口通讯的优点[/b]  读写串行口时,既可以同步执行,也可以重...
  • python:非阻塞异步编程

    千次阅读 2013-04-08 07:53:12
    例如,对于一个聊天室来说,因为有多个连接需要同时被处理,所以很显然,阻塞或同步的方法是不合适的,这就像买票只开了一个窗口,佷多人排队等一样。那么我们如何解决这个问题呢?主要有三种方法:forking、...
  • FILE_ATTRIBUTE_NORMAL,//|FILE_FLAG_OVERLAPPED, //异步方式打开 NULL ); if (m_hCom!=INVALID_HANDLE_VALUE) { m_pWnd = pWnd; m_IsOpen = true; flag = true; } return flag; } bool ...
  • 1. 异步非阻塞串口通讯的优点 2. 异步非阻塞串口通讯的基本原理 3. 异步非阻塞串口通讯的基础知识 4. 异步非阻塞串口通讯的实现步骤 2005.01.05 一,异步非阻塞串口通讯的优点 读写串行口时,既可以同步...
  • 异步事件触发操作无疑是最快的,但其逻辑复杂,调试困难不适合于新手或小型软件;现提供一种简单的代码实现,欢迎优化. 一.串口初始化 private SerialPort serialPort; private object seriaLocker = new object();...
  • 异步非阻塞串口通讯的实现步骤2005.01.05一,异步非阻塞串口通讯的优点读写串行口时,既可以同步执行,也可以重叠(异步)执行。在同步执行时,函数直到操作完成后才返回。这意味着在同步执行时线程会被阻塞,从而...
  • 一、硬件层次上的同步与异步 1.异步通信 在异步通信中,CPU与外设之间有两项约定: (1)字符格式---字符的编码形式及规定,每个串行字符由以下四个部分组成: ⑴1个起始位,低电平; ⑵5--8个数据位; ⑶1个...
  • 异步非阻塞串口通讯的实现步骤2005.01.05一,异步非阻塞串口通讯的优点读写串行口时,既可以同步执行,也可以重叠(异步)执行。在同步执行时,函数直到操作完成后才返回。这意味着在同步执行时线程会被阻塞,从而...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,680
精华内容 1,472
关键字:

串口异步非阻塞方式