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类型了。

获取notification发送用户以及用户如何对应用户组

    今天因为 Ho Sean Shan又收不到邮件了所以我去找原因,还是比较懒散,不情愿去深读代码,其实要是深读了是很简单的,其实就是if else判断顺着理下去就好了,原因是这个notification的exclude_current_user 选项被打开了,所以排除了当前操作的用户,而Sean就是去Request这个action的人,所以当然看不见喽。

那通过今天一方面了解了notification是怎样获取到用户的
private function _notification_get_target( $notification_details, $key )
 {
  $this->load->model('acl/acl_aro');
  
  $key;//target, target_cc, target_bcc
  $type_key = $key . '_type';
  
  $targets = array();
  if($notification_details[$type_key] == 'role')
  {
   $role_names = explode(',', $notification_details[$key]);
   foreach($role_names as $role_name)
   {
    $role_name = trim($role_name);
    $all_users = $this->acl_aro->get_role_users_by_role_name($role_name);
    
    foreach($all_users as $user_details)
    {
     if($notification_details['exclude_current_user'] == 'N' || $user_details['user_id'] != $this->_user_id)
     {
      $targets = $this->_add_valid_email_into_target( $user_details['user_email'], $targets );
     }
    }
   }
  }
  elseif( $notification_details[$type_key] == 'email' )
  {
   $emails = explode(',', $notification_details[$key]);
   
   foreach($emails as $email)
   {
    $email = trim($email);
    $targets = $this->_add_valid_email_into_target( $email, $targets );
   }
  }
  elseif( $notification_details[$type_key] == 'user' )
  {
   $user_ids = explode(',', $notification_details[$key]);
   foreach($user_ids as $user_id)
   {
    $user_id = trim($user_id);
    $user_details = $this->acl_aro->get_user_info('', $user_id);
    $targets = $this->_add_valid_email_into_target( $user_details['user_email'], $targets );
   }
  }
  
  //add the current user to be in CC list.
  if($key == 'target_cc' && $notification_details['target_cc_current_user'] == 'Y')
  {
   $user_details = $this->acl_aro->get_user_info('', $this->_user_id);
   $targets = $this->_add_valid_email_into_target( $user_details['user_email'], $targets );
  }
  
  return $targets;
 }
另一方面是用户如何对应用户组
SELECT c.*
FROM  evo_central_config.acl_user_role a, evo_central_config.acl_aro_role b, evo_central_config.customer_user c
WHERE a.role_id = b.role_id
AND   b.role_name = 'CSM_VMS_CSA'
AND   a.user_id = c.user_id
AND   c.is_active = 'Y'

python网络编程学习笔记(1)

转载请注明:@小五义  http://www.cnblogs.com/xiaowuyi



学习用书:《python 网络编程基础》作者John Goerzen

第一部分底层网络学习

        Python提供了访问底层操作系统Socket接口的全部方法,需要的时候这些接口可以提供灵活而强有力的功能。

(1)基本客户端操作

        在《python 网络编程基础》一书中,作者列出了一个简单的Python客户端程序,具体如下:

复制代码
import socket,sys
port =70
host=sys.argv[1]

filename=sys.argv[2]

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((host,port))

s.sendall(filename+"\r\n")

while 1:
    buf=s.recv(2048)
    if not len(buf):
        break
    sys.stdout.write(buf)
复制代码

 

        该程序实现的是Gopher协议,实现从主机上请求相关文档的功能。(Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点。但在WWW出现后,Gopher失去了昔日的辉煌。现在它基本很少被使用。)

       于是,我按照书上的语句进行了一下测试,在dos下运行python gopherclient.py quux.org。但是系统提示为

Traceback (most recent call last):

File "gopherclient.py", line 5, i

filename=sys.argv[2]

IndexError: list index out of range

看了一下,sys.argv只有两个元素['gopherclient.py', 'quux.org/']所以filename=sys.argv[2]就超出下界了。可是为什么会出现这个原因呢?是书里面写错了吗,因为我也是初学socket,不是很了解,所以我也是没有找到原因,如果哪位大牛知道是什么原因,希望能给讲解一下。

(2)基本服务器操作

        《python 网络编程基础》一书中同样给出了一个简单的服务器程序,具体如下:

复制代码
import socket
host=''
port=51423
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.bind((host,port))
s.listen(1)

print "Server is running on port %d;press Ctrl-C to terminate." %port
while 1:
    clientsock,clientaddr=s.accept()
    clientfile=clientsock.makefile('rw',0)
    clientfile.write("welcome,"+str(clientaddr)+'\n')
    clientfile.write("Please enter a string:")
    line=clientfile.readline().strip()
    clientfile.write("You entered %d characters.\n" %len(line))
    clientfile.close()
    clientsock.close()
复制代码

 

        该程序运行后,提示“Server is running on port 51423:press Ctrl-C to terminate”。此时,通过另一台机器telnet本机器的51423端口,如telnet 127.0.0.1:51423,此时会提示welcome 127.0.0.1 ****,please enter a string:。 然后输入几个字符后,会返回你输入字符的个数。

这里就该程序进行一下分析:

1、首先导入socket模块,给host和port赋值。

2、调用socket.socket()来建立一个socket赋值给s。socket.socket(domain, type, protocol).domain参数的值有AF_UNIX,AF_LOCAL,AF_INET,PF_UNIX,PF_LOCAL,PF_INET。这几个值中AF_UNIX=AF_LOCAL, PF_UNIX=PF_LOCAL, AF_LOCAL=PF_LOCAL, AF_INET=PF_INET。一般来说,AF 表示ADDRESS FAMILY 地址族,PF 表示PROTOCOL FAMILY 协议族,但这两个宏定义是一样的,所以使用哪个都没有关系。参数type指定socket的类型:SOCK_STREAM提供有序、可靠、双向及基于连接的字节流。SOCK_DGRAM支持数据报。SOCK_SEQPACKET提供有序、可靠、双向及基于连接的数据报通信。SOCK_RAW提供对原始网络协议的访问。SOCK_RDM提供可靠的数据报层,但是不保证有序性。protocol一般取0(为什么取0我也没搞清楚,放在以后明白了再写上吧)

3、s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)。setsockopt()函数用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。 这个函数中,第一个参数为协议层参数,指明了希望访问一个选项所在的协议栈。通常我们需要使用下面中的一个:

SOL_SOCKET来访问套接口层选项

SOL_TCP来访问TCP层选项

第二个参数是与第一个参数相对应的。第一个参数决定了协议层level,第二个参数决定了该协议层下选项组合。SOL_SOCKET的选项组合如下:

协议层 选项名字

SOL_SOCKET SO_REUSEADDR

SOL_SOCKET SO_KKEPALIVE

SOL_SOCKET SO_LINGER

SOL_SOCKET SO_BROADCAST

SOL_SOCKET SO_OOBINLINE

SOL_SOCKET SO_SNDBUF

SOL_SOCKET SO_RCVBUF

SOL_SOCKET SO_TYPE

SOL_SOCKET SO_ERROR

具体的一些组合用法可见:http://wenku.baidu.com/view/23013b7101f69e3143329402.html

第三个参数设为1,这里我也没很明白其中的意思,我试着把1换成50,结果是一样的。换成0也是可以的,没发现什么区别。希望大牛们给指点一下。

4、s.bind((host,port))绑定主机端口。

5、s.listen(1):listen函数使用主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。这里的参数涉及到一些网络的细节。在进程正理一个一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。毫无疑问,服务器进程不能随便指定一个数值,内核有一个许可的范围。这个范围是实现相关的。很难有某种统一,一般这个值会小30以内。这里设定为1表示每次最多只有一个等候处理的连接。

6、while循环从accept()函数开始。程序会在连接了一个客户端后关闭socket。当某个客户端连接的时,accept返回两个信息,一个新的连接客户端socket和客户端的ip地址、端口号。如在上面的例子中添加print语句输出clientsock和clientaddr,你会发现clientsock为socket.socketobject,clientaddr=('客户端Ip',端口)。后面的循环中使用了文件类对象,服务器接着显示出一些介绍性信息,从客户端读一个字符串,显示一个应答,最后关闭客户端socket。