Linux中的EAGAIN含义

http://blog.163.com/niuxiangshan@126/blog/static/1705965952010862259798/

在Linux环境下开发经常会碰到很多错误(设置errno),其中EAGAIN是其中比较常见的一个错误(比如用在非阻塞操作中)。

    从字面上来看,是提示再试一次。这个错误经常出现在当应用程序进行一些非阻塞(non-blocking)操作(对文件或socket)的时候。例如,以 O_NONBLOCK的标志打开文件/socket/FIFO,如果你连续做read操作而没有数据可读。此时程序不会阻塞起来等待数据准备就绪返回,read函数会返回一个错误EAGAIN,提示你的应用程序现在没有数据可读请稍后再试。

    又例如,当一个系统调用(比如fork)因为没有足够的资源(比如虚拟内存)而执行失败,返回EAGAIN提示其再调用一次(也许下次就能成功)。

Linux – 非阻塞socket编程处理EAGAIN错误

 在linux进行非阻塞的socket接收数据时经常出现Resource temporarily unavailable,errno代码为11(EAGAIN),这是什么意思?

 这表明你在非阻塞模式下调用了阻塞操作,在该操作没有完成就返回这个错误,这个错误不会破坏socket的同步,不用管它,下次循环接着recv就可以。对非阻塞socket而言,EAGAIN不是一种错误。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。

 另外,如果出现EINTR即errno为4,错误描述Interrupted system call,操作也应该继续。

 最后,如果recv的返回值为0,那表明连接已经断开,我们的接收操作也应该结束。

  iReadSizeOnce=read(iOpenCom,RxBuf+iReadSize,1024);

    if (iReadSizeOnce != ZERO)

    {

        if (iReadSizeOnce != EAGAIN)

        {

            continue;

        }

        else

        {

            //stCComApiLog.LogError("读串口操作错误");

            return(FUN_ERROR);

        }

    }

EPOLL 边缘触发学习

在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。

相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:

#define __FD_SETSIZE    1024

表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。



epoll的接口非常简单,一共就三个函数:

1. int epoll_create(int size);

创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。





2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fd到epfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:从epfd中删除一个fd;

第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

struct epoll_event {

  __uint32_t events;  /* Epoll events */

  epoll_data_t data;  /* User data variable */

};



events可以是以下几个宏的集合:

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

EPOLLOUT:表示对应的文件描述符可以写;

EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

EPOLLERR:表示对应的文件描述符发生错误;

EPOLLHUP:表示对应的文件描述符被挂断;

EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里





3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。



——————————————————————————————–



从man手册中,得到ET和LT的具体描述如下



EPOLL事件有两种模型:

Edge Triggered (ET)

Level Triggered (LT)



假如有这样一个例子:

1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符

2. 这个时候从管道的另一端被写入了2KB的数据

3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作

4. 然后我们读取了1KB的数据

5. 调用epoll_wait(2)……



Edge Triggered 工作模式:

如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用 epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。

   i    基于非阻塞文件句柄

   ii   只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。



Level Triggered 工作模式

相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。

tornado日志

define,options头文件

from tornado.options import define, options
tornado的define(在命令行中没有该定义的参数时时,使用这里的默认值)

define("port", default=8889, help="Run server on a specific port", type=int)

 

解析命令行参数

tornado.options.parse_command_line()

 

写log

import logging
logging.debug("debug .... ")

 

命令行执行和log格式

python myserver.py -port=8888 -log_file_prefix=var/log/test_log@8888.log
python myserver.py -port=8889 -log_file_prefix=var/log/test_log@8889.log
-------------
# -*- coding: utf-8 -*-
"""启动web server"""
import tornado.ioloop
from application import application
from tornado.options import define, options
import logging
define("port", 8889, help="port", type=int)
if __name__ == "__main__":
    options.parse_command_line()
    logging.debug("debug ...")
    options.parse_command_line()
    application.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

python socket 笔记

创建sock = socket.socket()

    sock.bind(sock_addr) #sock_addr = (host,port)

    sock.connect(sock_addr)   #客服端

    sock.listen(num) # num>=1  #监听

    connectsock,addr = sock.accept() #接收

    sock.settimeout(time) #超时

    sock.recv(size) #接收数据

    sock.send(data)#发送数据

    sock.sendall(data)#发送数据

    sock.dup()#返回一个新的套接字

    sock.setblocking(0)#设置sock为非阻塞模式

 

 

  • 表2. Socket 模块的类方法

类方法

说明

Socket

低层网络接口(每个 BSD API)

socket.socket(family, type)

创建并返回一个新的 socket 对象

socket.getfqdn(name)

将使用点号分隔的 IP 地址字符串转换成一个完整的域名

socket.gethostbyname(hostname)

将主机名解析为一个使用点号分隔的 IP 地址字符串

socket.fromfd(fd, family, type)

从现有的文件描述符创建一个 socket 对象

  • 表3. Socket 模块的实例方法

实例方法

说明

sock.bind( (adrs, port) )

将 socket 绑定到一个地址和端口上

sock.accept()

返回一个客户机 socket(带有客户机端的地址信息)

sock.listen(backlog)

将 socket 设置成监听模式,能够监听 backlog 外来的连接请求

sock.connect( (adrs, port) )

将 socket 连接到定义的主机和端口上

sock.recv( buflen[, flags] )

从 socket 中接收数据,最多 buflen 个字符

sock.recvfrom( buflen[, flags] )

从 socket 中接收数据,最多 buflen 个字符,同时返回数据来源的远程主机和端口号

sock.send( data[, flags] )

通过 socket 发送数据

sock.sendto( data[, flags], addr )

通过 socket 发送数据

sock.close()

关闭 socket

sock.getsockopt( lvl, optname )

获得指定 socket 选项的值

sock.setsockopt( lvl, optname, val )

设置指定 socket 选项的值

struct的pack和unpack方法

[转]http://wwty.iteye.com/blog/401414 

我很诚实的表明转发地址哈哈 为了方便自己以后查找

---------------------------------------------

这两天做TCP协议,数据的传输都是二进制的,需要解释,于是用到了struct
看到这样一句代码:
  

Python代码  收藏代码

  1. length = struct.unpack('>I'self.buffer[:4])[0]  

 当时没有明白format=">I"是什么意思,从google找了一下,有人说这个东西,可都是比较笼统,没能让我明白,于是硬着头皮看API:
By default, C numbers are represented in the machine’s native format and byte order, and properly aligned by skipping pad bytes if necessary (according to the rules used by the C compiler).
通常,C语言下数字都是机器语言的格式并且按照字节排序,同时在需要的情况下会利用跳过填补的字节来进行适当的调整

 

Alternatively, the first character of the format string can be used to indicate the byte order, size and alignment of the packed data。
非此即彼:字符串的第一个字符要么被用于表示字符串的字节的排序,或者是字符串的size,还有就是数据是否对准。


 

Native byte order is big-endian or little-endian, depending on the host system. For example, Motorola and Sun processors are big-endian; Intel and DEC processors are little-endian.
计算机的字节序要么是高位顺序,要么是低位的,这依赖于主机本身。比如,摩托罗拉和sun的处理器是高位的,但是intel和DEC的是低位的。

 

这样子就明白了上面的format=">I"的意思,也就是说按照高位顺序来格式化取得一个int或long值。下面问题就又来了,你怎么知道读取的就是一个int或long值呢?

通过看struct的文档,可以看到struct通过两张表制定了一定的format规则,我按照自己的观察,给他归纳为两类,一个是和C当中类型的对照,另一个就是选择按照高位还是低位来解释字节。上面已经说了高低字节顺序,那么观察和C对照的表格,发现I 代表的就是integer or long ,详细的可以去看python的API。

 

下面是一些使用的例子,具体的使用,可以参考这些例子:
1. 设置fomat格式,如下: 
# 取前5个字符,跳过4个字符华,再取3个字符 
format = '5s 4x 3s'

2. 使用struck.unpack获取子字符串 
import struct 
print struct.unpack(format, 'Test astring') 
#('Test', 'ing') 
来个简单的例子吧,有一个字符串'He is not very happy',处理一下,把中间的not去掉,然后再输出。 
import struct 
theString = 'He is not very happy' 
format = '2s 1x 2s 5x 4s 1x 5s' 
print ' '.join(struct.unpack(format, theString)) 
输出结果: 
He is very happy

 

 

随后是关于网络字节的东东,从网上看来的,感觉有用:

Python的socket库采用string类型来发送和接收数据,这样当我们用
i = socket.recv(4)
来接收一个4字节的整数时,该整数实际上是以二进制的形式保存在字符串 i 的前4个字节中;大多数的时候我们需要的是一个真正的integer/long型,而不是一个用string型表示的整型。这时我们可以使用struct库:Interpret

strings as packed binary data. 对上面的情况,我们可以写
t = unpack("I", i)
第一个参数是格式化字符串,I指明字符串 i 包含的头一个数据项是一个以C语言的unsigned integer表示的整数,这里 i 只包含了一个数据项,实际上这个被解释的字符串也可以包含多个数据项,只要在格式化字符串里为每项数据指明一个格式即可;自然地,unpack返回的就是一个tuple类型了。