mirror of
https://github.com/beyondx/Notes.git
synced 2026-02-04 02:43:32 +08:00
add lots file of APUE
This commit is contained in:
346
Zim/Programme/APUE/Linux_Epoll介绍和程序实例.txt
Normal file
346
Zim/Programme/APUE/Linux_Epoll介绍和程序实例.txt
Normal file
@@ -0,0 +1,346 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-15T20:39:55+08:00
|
||||
|
||||
====== Linux Epoll介绍和程序实例 ======
|
||||
Created Monday 15 October 2012
|
||||
http://blog.csdn.net/sparkliang/article/details/4770655
|
||||
|
||||
|
||||
===== Epoll 是何方神圣? =====
|
||||
|
||||
|
||||
Epoll 可是当前在 Linux 下开发**大规模并发网络程序**的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 相似,其实都 **I/O 多路复用**技术而已 ,并没有什么神秘的。其实在 Linux 下设计并发网络程序,向来不缺少方法,比如典型的 Apache 模型( Process Per Connection ,简称 __PPC__ ), __TPC__ ( Thread Per Connection )模型,以及 select 模型和 poll 模型,那为何还要再引入 Epoll 这个东东呢?那还是有得说说的 …
|
||||
|
||||
|
||||
===== 常用模型的缺点 =====
|
||||
|
||||
|
||||
如果不摆出来其他模型的缺点,怎么能对比出 Epoll 的优点呢。
|
||||
|
||||
==== 2.1 PPC/TPC 模型 ====
|
||||
这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我 。只是 PPC 是为它开了一个进程,而 TPC 开了一个线程。可是别烦我是有代价的,它要**时间和空间**啊,连接多了之后,那么多的__进程 / 线程切换开销__就上来了;因此这类模型能接受的最大连接数都不会高,一般在几百个左右。
|
||||
|
||||
==== 2.2 select 模型 ====
|
||||
1. 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Select 模型的最大并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧 …
|
||||
2. 效率问题, select 每次调用都会**线性扫描全部**的 FD 集合,这样效率就会呈现线性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了??!!
|
||||
3. 内核 / 用户空间 **内存拷贝问题**,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了**内存拷贝**方法。
|
||||
|
||||
==== 2.3 poll 模型 ====
|
||||
基本上效率和 select 是相同的, select 缺点的 2 和 3 它都没有改掉。
|
||||
|
||||
===== 3. Epoll 的提升 =====
|
||||
把其他模型逐个批判了一下,再来看看 Epoll 的改进之处吧,其实把 select 的缺点反过来那就是 Epoll 的优点了。
|
||||
|
||||
3.1. Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 察看。
|
||||
3.2. 效率提升, Epoll 最大的优点就在于它只管你__“活跃”__的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll 的效率就会远远高于 select 和 poll 。
|
||||
3.3. 内存拷贝, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。
|
||||
|
||||
===== 4. Epoll 为什么高效 =====
|
||||
Epoll 的高效和其数据结构的设计是密不可分的,这个下面就会提到。
|
||||
|
||||
首先回忆一下 select 模型,当有 I/O 事件到来时, select 通知应用程序有事件到了快去处理,而应用程序必须__轮询所有的 FD 集合__,测试每个 FD 是否有事件发生,并处理事件;代码像下面这样:
|
||||
|
||||
int res = select(maxfd+1, &readfds, NULL, NULL, 120);
|
||||
|
||||
if (res > 0)
|
||||
|
||||
{
|
||||
|
||||
for (int i = 0; i < MAX_CONNECTION; i++)
|
||||
|
||||
{
|
||||
|
||||
if (FD_ISSET(allConnection[i], &readfds))
|
||||
|
||||
{
|
||||
|
||||
handleEvent(allConnection[i]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if(res == 0) handle timeout, res < 0 handle error
|
||||
|
||||
Epoll 不仅会告诉应用程序有I/0 事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而__不必遍历整个FD 集合__。
|
||||
|
||||
int res = epoll_wait(epfd, events, 20, 120);
|
||||
for (int i = 0; i < res;i++)
|
||||
{
|
||||
handleEvent(events[n]);
|
||||
}
|
||||
|
||||
===== 5. Epoll 关键数据结构 =====
|
||||
前面提到 Epoll 速度快和其数据结构密不可分,其关键数据结构就是:
|
||||
|
||||
struct epoll_event {
|
||||
__uint32_t events; // Epoll events
|
||||
epoll_data_t data; // User data variable
|
||||
};
|
||||
|
||||
typedef union epoll_data {
|
||||
void *ptr;
|
||||
int fd;
|
||||
__uint32_t u32;
|
||||
__uint64_t u64;
|
||||
} epoll_data_t;
|
||||
|
||||
可见 epoll_data 是一个 union 结构体 , 借助于它应用程序可以__保存很多类型的信息__ :fd 、指针等等。有了它,应用程序就可以直接定位目标了。
|
||||
|
||||
6. ===== 使用 Epoll =====
|
||||
|
||||
既然 Epoll 相比 select 这么好,那么用起来如何呢?会不会很繁琐啊 … 先看看下面的三个函数吧,就知道 Epoll 的易用了。
|
||||
|
||||
int epoll_create(int size);
|
||||
生成一个 Epoll 专用的文件描述符,其实是**申请一个内核空间**,用来存放你想关注的 socket fd 上是否发生以及发生了什么事件。 size 就是你在这个 Epoll fd 上能关注的最大 socket fd 数,大小自定,只要内存足够。
|
||||
|
||||
int epoll_ctl(int epfd, int __op__, int fd, struct epoll_event *event );
|
||||
控制某个 Epoll 文件描述符上的事件:注册、修改、删除。其中参数 epfd 是 epoll_create() 创建 Epoll 专用的文件描述符。相对于 select 模型中的 FD_SET 和 FD_CLR 宏。
|
||||
|
||||
int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);
|
||||
等待 I/O 事件的发生;参数说明:
|
||||
epfd: 由 epoll_create() 生成的 Epoll 专用的文件描述符;
|
||||
epoll_event: 用于__回传__代处理事件的数组;
|
||||
maxevents: 每次能处理的事件数;
|
||||
timeout: 等待 I/O 事件发生的超时值;
|
||||
返回发生事件数。相对于 select 模型中的 select 函数。
|
||||
|
||||
7. 例子程序
|
||||
下面是一个简单 Echo Server 的例子程序,麻雀虽小,五脏俱全,还包含了一个简单的超时检查机制,简洁起见没有做错误处理。
|
||||
//
|
||||
// a simple echo server using epoll in linux
|
||||
//
|
||||
// 2009-11-05
|
||||
// by sparkling
|
||||
//
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
#define MAX_EVENTS 500
|
||||
|
||||
struct myevent_s
|
||||
{
|
||||
int fd;
|
||||
int events;
|
||||
void *arg;
|
||||
void (*call_back)(int fd, int events, void *arg); //call_back函数的参数都从结构体成员中得到。
|
||||
int status; // __1: in epoll wait list, 0 not in__
|
||||
char buff[128]; // recv data buffer
|
||||
int len;
|
||||
long last_active; // last active time
|
||||
};
|
||||
|
||||
// set event
|
||||
void EventSet(myevent_s *ev, int fd, void (*call_back)(int, int, void*), void *arg)
|
||||
{
|
||||
ev->fd = fd;
|
||||
ev->call_back = call_back;
|
||||
**ev->events = 0;**
|
||||
**ev->arg = arg; //arg其实就是struct myevent_s的指针。**
|
||||
ev->status = 0;
|
||||
ev->last_active = time(NULL);
|
||||
}
|
||||
|
||||
// add/mod an event to epoll
|
||||
void EventAdd(int epollFd, int events, myevent_s *ev)
|
||||
{
|
||||
__struct epoll_event epv = {0, {0}};__
|
||||
int op;
|
||||
__epv.data.ptr = ev; //myevent_s被封装到epoll_event的ptr成员中。__
|
||||
__epv.events = ev->events = events;__
|
||||
if(ev->status == 1){ //ev->fd已经处于epoll wait list中
|
||||
op = EPOLL_CTL_MOD;
|
||||
}
|
||||
else{
|
||||
op = EPOLL_CTL_ADD;
|
||||
ev->status = 1;
|
||||
}
|
||||
if(__epoll_ctl__(epollFd, op, ev->fd, **&epv**) < 0)
|
||||
printf("Event Add failed[fd=%d]/n", ev->fd);
|
||||
else
|
||||
printf("Event Add OK[fd=%d]/n", ev->fd);
|
||||
}
|
||||
|
||||
// delete an event from epoll
|
||||
void EventDel(int epollFd, myevent_s *ev)
|
||||
{
|
||||
struct epoll_event epv = {0, {0}};
|
||||
if(ev->status != 1) return;
|
||||
epv.data.ptr = ev;
|
||||
ev->status = 0;
|
||||
__epoll_ctl__(epollFd, EPOLL_CTL_DEL, ev->fd, &epv);
|
||||
}
|
||||
|
||||
int g_epollFd;
|
||||
myevent_s g_Events[MAX_EVENTS+1]; // g_Events[MAX_EVENTS] is used by __socket listen fd__
|
||||
void RecvData(int fd, int events, void *arg);
|
||||
void SendData(int fd, int events, void *arg);
|
||||
|
||||
// accept new connections from clients
|
||||
void AcceptConn(int fd, int events, void *arg) //arg参数在该函数中并没有被使用。
|
||||
{
|
||||
__struct sockaddr_in__ sin;
|
||||
socklen_t len = sizeof(struct sockaddr_in);
|
||||
int nfd, i;
|
||||
// accept
|
||||
if((__nfd__ = accept(fd, (struct sockaddr*)&sin, &len)) == -1)
|
||||
{
|
||||
if(errno != EAGAIN && errno != EINTR)
|
||||
{
|
||||
printf("%s: bad accept", __func__);
|
||||
}
|
||||
return;
|
||||
}
|
||||
do
|
||||
{
|
||||
for(i = 0; i < MAX_EVENTS; i++)
|
||||
{
|
||||
if(g_Events[i].status == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i == MAX_EVENTS)
|
||||
{
|
||||
printf("%s:max connection limit[%d].", __func__, MAX_EVENTS);
|
||||
break;
|
||||
}
|
||||
__// set nonblocking__
|
||||
if(fcntl(nfd, F_SETFL, O_NONBLOCK) < 0) break;
|
||||
// add a read event for receive data
|
||||
EventSet(&g_Events[i], nfd, __RecvData__, &g_Events[i]); //注册一个**回调函数**
|
||||
EventAdd(g_epollFd, **EPOLLIN|EPOLL**__ET__, &g_Events[i]);
|
||||
printf("new conn[%s:%d][time:%d]/n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), g_Events[i].last_active);
|
||||
}while(0);
|
||||
}
|
||||
|
||||
// receive data
|
||||
void RecvData(int fd, int events, void *arg) //当fd可读时,envents和arg来源于__struct epoll_event 的 epv.data.ptr和epv.events 成员。__
|
||||
{
|
||||
struct myevent_s *ev = __(struct myevent_s*)arg__;
|
||||
int len;
|
||||
// receive data
|
||||
len = recv(fd, ev->buff, sizeof(ev->buff)-1, 0);
|
||||
EventDel(g_epollFd, ev);
|
||||
if(len > 0)
|
||||
{
|
||||
ev->len = len;
|
||||
ev->buff[len] = '/0';
|
||||
printf("C[%d]:%s/n", fd, ev->buff);
|
||||
// change to **send** event
|
||||
EventSet(ev, fd, __SendData__, ev);
|
||||
EventAdd(g_epollFd, __EPOLLOUT|EPOLLET,__ ev);
|
||||
}
|
||||
else if(len == 0)
|
||||
{
|
||||
close(ev->fd);
|
||||
printf("[fd=%d] closed gracefully./n", fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
close(ev->fd);
|
||||
printf("recv[fd=%d] error[%d]:%s/n", fd, errno, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
// send data
|
||||
void SendData(int fd, int events, void *arg)
|
||||
{
|
||||
struct myevent_s *ev = (struct myevent_s*)arg;
|
||||
int len;
|
||||
// send data
|
||||
len = send(fd, ev->buff, ev->len, 0);
|
||||
ev->len = 0;
|
||||
EventDel(g_epollFd, ev);
|
||||
if(len > 0)
|
||||
{
|
||||
// change to **receive** event
|
||||
EventSet(ev, fd, __RecvData__, ev);
|
||||
EventAdd(g_epollFd, EPOLLIN|EPOLLET, ev);
|
||||
}
|
||||
else
|
||||
{
|
||||
close(ev->fd);
|
||||
printf("recv[fd=%d] error[%d]/n", fd, errno);
|
||||
}
|
||||
}
|
||||
|
||||
void InitListenSocket(int epollFd, short port)
|
||||
{
|
||||
int listenFd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
fcntl(listenFd, F_SETFL, O_NONBLOCK); // set non-blocking
|
||||
printf("server listen fd=%d/n", listenFd);
|
||||
EventSet(&g_Events[MAX_EVENTS], __listenFd, AcceptConn__, &g_Events[MAX_EVENTS]); //注册一个**回调函数**
|
||||
// add listen socket
|
||||
EventAdd(epollFd, __EPOLLIN|EPOLLET__, &g_Events[MAX_EVENTS]);
|
||||
// bind & listen
|
||||
sockaddr_in sin;
|
||||
bzero(&sin, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_addr.s_addr = INADDR_ANY;
|
||||
sin.sin_port = htons(port);
|
||||
bind(listenFd, (const sockaddr*)&sin, sizeof(sin));
|
||||
listen(listenFd, 5);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
short port = 12345; // default port
|
||||
if(argc == 2){
|
||||
port = atoi(argv[1]);
|
||||
}
|
||||
**// create epoll**
|
||||
g_epollFd = epoll_create(MAX_EVENTS);
|
||||
if(g_epollFd <= 0) printf("create epoll failed.%d/n", g_epollFd);
|
||||
// create & bind listen socket, and add to epoll, set non-blocking
|
||||
InitListenSocket(**g_epollFd**, port);
|
||||
__// event loop__
|
||||
struct epoll_event events[MAX_EVENTS];
|
||||
printf("server running:port[%d]/n", port);
|
||||
int checkPos = 0;
|
||||
while(1){
|
||||
// a simple timeout check here, every time 100, better to use a mini-heap, and add timer event
|
||||
long now = time(NULL);
|
||||
for(int i = 0; i < 100; i++, checkPos++) // __doesn't check listen fd__
|
||||
{
|
||||
if(checkPos == MAX_EVENTS) checkPos = 0; // recycle
|
||||
if(g_Events[checkPos].status != 1) continue;
|
||||
long duration = now - g_Events[checkPos].last_active;
|
||||
if(duration >= 60) // 60s timeout
|
||||
{
|
||||
close(g_Events[checkPos].fd);
|
||||
printf("[fd=%d] timeout[%d--%d]./n", g_Events[checkPos].fd, g_Events[checkPos].last_active, now);
|
||||
EventDel(g_epollFd, &g_Events[checkPos]);
|
||||
}
|
||||
}
|
||||
// wait for events to happen
|
||||
int fds = __epoll_wait__(g_epollFd, __events__, MAX_EVENTS, 1000);
|
||||
if(fds < 0){
|
||||
printf("epoll_wait error, exit/n");
|
||||
break;
|
||||
}
|
||||
for(int i = 0; i < fds; i++){
|
||||
**myevent_s *ev = (struct myevent_s*)events[i].data.ptr ;**
|
||||
if((events[i].events&**EPOLLIN**)&&(ev->events&EPOLLIN)) // read event
|
||||
{
|
||||
ev->call_back(ev->fd, events[i].events, ev->arg);
|
||||
}
|
||||
if((events[i].events&**EPOLLOUT**)&&(ev->events&EPOLLOUT)) // write event
|
||||
{
|
||||
ev->call_back(ev->fd, events[i].events, ev->arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
// free resource
|
||||
return 0;
|
||||
}
|
||||
138
Zim/Programme/APUE/Linux_Epoll介绍和程序实例/epoll精髓.txt
Normal file
138
Zim/Programme/APUE/Linux_Epoll介绍和程序实例/epoll精髓.txt
Normal file
@@ -0,0 +1,138 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-15T21:09:07+08:00
|
||||
|
||||
====== epoll精髓 ======
|
||||
Created Monday 15 October 2012
|
||||
http://www.cnblogs.com/OnlyXP/archive/2007/08/10/851222.html
|
||||
|
||||
在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队列里
|
||||
|
||||
typedef union epoll_data {
|
||||
void *ptr;
|
||||
int fd;
|
||||
__uint32_t u32;
|
||||
__uint64_t u64;
|
||||
} epoll_data_t;
|
||||
|
||||
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),并且无论后面的数据是否被使用,因此他们具有同样的职能(**如果epoll_wait返回的fd数据没有读完,下次再调用epoll_wait时继续返回该fd可读事件。**)。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。
|
||||
|
||||
|
||||
然后详细解释ET, LT:
|
||||
LT(level triggered)是__缺省的工作方式__,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,__内核还是会继续通知你的(下一次调用epoll_wait会继续立即返回)__,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
|
||||
|
||||
ET(edge-triggered)是高速工作方式,__只支持no-block socket__。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知(__即使下一次调用epoll_wait时该fd仍然就绪),直到你做了某些操作导致那个文件描述符不再为就绪状态了(__比如**读该fd直到将缓冲区中的数据读完,或写该fd直到将缓冲区填满。**)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。
|
||||
|
||||
在许多测试中我们会看到如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当我们遇到大量的idle- connection(例如WAN环境中存在大量的慢速连接),就会发现epoll的效率大大高于select/poll。
|
||||
|
||||
另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以__还需要再次读取(直到对到的数据量小于请求的数据量,或返回EAGAIN或EWOULDBLOCK错误)__:
|
||||
while(rs)
|
||||
{
|
||||
buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);
|
||||
if(buflen < 0)
|
||||
{
|
||||
// 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读
|
||||
// 在这里就当作是该次事件已处理处.
|
||||
if(errno == EAGAIN)
|
||||
break;
|
||||
else
|
||||
return;
|
||||
}
|
||||
else if(buflen == 0)
|
||||
{
|
||||
// 这里表示对端的socket已正常关闭.
|
||||
}
|
||||
if(buflen == sizeof(buf)
|
||||
rs = 1; // **需要再次读取**
|
||||
else
|
||||
rs = 0;
|
||||
}
|
||||
|
||||
还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快), 由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回-1表示出错。在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send()内部,但暂没有更好的办法.
|
||||
ssize_t socket_send(int sockfd, const char* buffer, size_t buflen)
|
||||
{
|
||||
ssize_t tmp;
|
||||
size_t total = buflen;
|
||||
const char *p = buffer;
|
||||
|
||||
while(1)
|
||||
{
|
||||
tmp = send(sockfd, p, total, 0);
|
||||
if(tmp < 0)
|
||||
{
|
||||
// 当send收到信号时,可以继续写,但这里返回-1.
|
||||
if(errno == EINTR)
|
||||
return -1;
|
||||
|
||||
// 当socket是非阻塞时,如返回EAGAIN错误,表示写缓冲队列已满,
|
||||
// 在这里做延时后再重试.
|
||||
if(errno == EAGAIN)
|
||||
{
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if((size_t)tmp == total)
|
||||
return buflen;
|
||||
|
||||
total -= tmp;
|
||||
p += tmp;
|
||||
}
|
||||
|
||||
return tmp;
|
||||
}
|
||||
@@ -5,7 +5,9 @@ Creation-Date: 2011-06-03T21:32:39+08:00
|
||||
====== Linux 网络编程之TIME WAIT状态 ======
|
||||
Created 星期五 03 六月 2011
|
||||
http://blog.csdn.net/feiyinzilgd/archive/2010/09/19/5894446.aspx
|
||||
|
||||
{{./TIME_WAIT.jpg}}
|
||||
|
||||
刚刚开始看TCP socket的4次握手终止流程图的时候,对于最后的TIME_WAIT状态不是很理解.现在在回过头来研究,发现TIME_WAIT状态是一个很微妙状态.之所以设计TIME_WAIT状态的原因有2个原因:
|
||||
|
||||
* 使得TCP的全双工连接能够可靠的终止.
|
||||
@@ -13,14 +15,14 @@ http://blog.csdn.net/feiyinzilgd/archive/2010/09/19/5894446.aspx
|
||||
|
||||
在具体详解这两个原因之前,我们需要理解MSL(maxinum segment lifetime)这个概念.
|
||||
|
||||
每一个TCP 都必须有一个MSL值.这个值一般是2分钟,但也不是固定的,不同的系统不一样.无论是否出错或者连接被断开,总之,一个数据包在网路上能停留的最大时间是MSL.也就是说MSL是数据包的生命周期时间.操作这个时间,该数据包将会被丢弃而不被发送.而TIME_WAIT状态持续的时间是MSL的两倍,也就是2MSL时间.
|
||||
每一个TCP 都必须有一个MSL值.这个值一般是2分钟,但也不是固定的,不同的系统不一样.无论是否出错或者连接被断开,总之,一个数据包在网路上能停留的最大时间是MSL.也就是说MSL是**数据包的生命周期时间.**操作这个时间,该数据包将会被丢弃而不被发送.而TIME_WAIT状态持续的时间是MSL的两倍,也就是2MSL时间.
|
||||
|
||||
TCP的全双工连接能够被可靠终止
|
||||
|
||||
TCP的可靠终止需要经过4次握手终止.如上图所示:首先,client 主动close,导致FIN发送给server,server接收到FIN后,给client回复一个ACK,之后,server会关闭和client的连接,即向client发送一个FIN,client接收到FIN之后,会发送一个ACK给server.此时client就进入TIME_WAIT状态.如果server没有收到ACK,server会重新发送一个FIN信息给client,client会重发ACK,server然后继续等待client发送一个ACK.这样保证了双方的可靠终止.2端都知道对方已经终止了.那么,在这个TIME_WAIT时间中,可以重发ACK,如果client没有收到FIN信息,则TCP会向server发送一个RST信息,这个信息会被server解释成error.
|
||||
TCP的可靠终止需要经过4次握手终止.如上图所示:首先,client 主动close,导致FIN发送给server,server接收到FIN后,给client回复一个ACK,之后,server会关闭和client的连接,即向client发送一个FIN,client接收到FIN之后,会发送一个ACK给server. 此时client就进入TIME_WAIT状态.**如果server没有收到ACK,server会重新发送一个FIN信息给client,client会重发ACK**,server然后继续等待client发送一个ACK.这样保证了双方的可靠终止.2端都知道对方已经终止了.那么,**在这个TIME_WAIT时间中**,可以重发ACK,如果client没有收到FIN信息,则TCP会向server发送一个RST信息,这个信息会被server解释成error.
|
||||
|
||||
连接终止后网络上任然残留的发送到该连接的数据被丢弃而不至于被新连接接收.
|
||||
|
||||
举个例子:
|
||||
|
||||
在10.12.24.48 port:21和206.8.16.32 port:23(不必关心哪一端是server哪一端是client)之间建立了一个TCP连接A.然后此链接A被close掉了.然后此时又在10.12.24.48 port:21和206.8.16.32 port:23(不必关心哪一端是server哪一端是client)之间建立了一个新的TCP连接B.很可能A和B连接是有不同的应用程序建立的.那么,当我们close掉A之后,网络上很有可能还有属于A连接两端的数据m正在网路上被传送.而此时A被close掉了,重新建立了B连接,由于A和B连接的地址和端口都是一样的.这样,m数据就会被最终发送到B连接的两端.这样就造成了混乱,B接收到了原本数据A的数据.处于TIME_WAIT状态的连接会禁止新的同样的连接(如A,B)连接被建立.除非等到TIME_WAIT状态结束,也就是2MSL时间之后.其中,一个MSL时间是为了网络上的正在被发送到该链接的数据被丢弃,另一个MSL使得应答信息被丢弃.这样,2MSL之后,保证重新建立的所得到的数据绝对不会是发往就连接的数据.
|
||||
在10.12.24.48 port:21和206.8.16.32 port:23(不必关心哪一端是server哪一端是client)之间建立了一个TCP连接A.然后此链接A被close掉了.然后此时又在10.12.24.48 port:21和206.8.16.32 port:23(不必关心哪一端是server哪一端是client)之间建立了一个新的TCP连接B.很可能A和B连接是有不同的应用程序建立的.那么,当我们close掉A之后,网络上很有可能还有属于A连接两端的数据m正在网路上被传送.而此时A被close掉了,重新建立了B连接,由于A和B连接的地址和端口都是一样的.这样,m数据就会被最终发送到B连接的两端.这样就造成了混乱,**B接收到了原本数据A的数据**.__处于TIME_WAIT状态的连接会禁止新的同样的连接(如A,B)连接被建立.除非等到TIME_WAIT状态结束,也就是2MSL时间之后__.其中,一个MSL时间是为了网络上的正在被发送到该链接的数据被丢弃,另一个MSL使得应答信息被丢弃.这样,2MSL之后,保证重新建立的所得到的数据绝对不会是发往就连接的数据.
|
||||
|
||||
@@ -17,9 +17,7 @@ http://www.cnblogs.com/xuyuan77/archive/2008/09/06/1285800.html
|
||||
我们将传统方案中的线程执行过程分为三个过程:T1、T2、T3。
|
||||
|
||||
T1:线程创建时间
|
||||
|
||||
T2:线程执行时间,包括线程的同步等时间
|
||||
|
||||
T3:线程销毁时间
|
||||
|
||||
那么我们可以看出,线程本身的开销所占的比例为(T1+T3) / (T1+T2+T3)。如果线程执行的时间很短的话,这比开销可能占到20%-50%左右。如果任务执行时间很频繁的话,这笔开销将是不可忽略的。
|
||||
@@ -35,11 +33,8 @@ http://www.cnblogs.com/xuyuan77/archive/2008/09/06/1285800.html
|
||||
一般线程池都必须具备下面几个组成部分:
|
||||
|
||||
1) 线程池管理器:用于创建并管理线程池
|
||||
|
||||
2) 工作线程: 线程池中实际执行的线程
|
||||
|
||||
2) 工作线程: 线程池中实际执行的线程
|
||||
3) 任务接口: 尽管线程池大多数情况下是用来支持网络服务器,但是我们将线程执行的任务抽象出来,形成任务接口,从而是的线程池与具体的任务无关。
|
||||
|
||||
4) 任务队列:线程池的概念具体到实现则可能是队列,链表之类的数据结构,其中保存执行线程。
|
||||
|
||||
我们实现的通用线程池框架由五个重要部分组成CThreadManage,CThreadPool,CThread,CJob,CWorkerThread,除此之外框架中还包括线程同步使用的类CThreadMutex和CCondition。
|
||||
|
||||
@@ -7,13 +7,10 @@ Created 星期五 03 六月 2011
|
||||
|
||||
Linux网络编程中,socket的选项很多.其中几个比较重要的选项有:SO_LINGER(仅仅适用于TCP,SCTP), SO_REUSEADDR.
|
||||
|
||||
SO_LINGER
|
||||
|
||||
__SO_LINGER__
|
||||
在默认情况下,当调用close关闭socke的使用,close会立即返回,但是,如果send buffer中还有数据,系统会试着先把send buffer中的数据发送出去,然后close才返回.
|
||||
|
||||
SO_LINGER选项则是用来修改这种默认操作的.于SO_LINGER相关联的一个结构体如下:
|
||||
|
||||
view plaincopy to clipboardprint?
|
||||
SO_LINGER选项则是用来修改这种默认操作的. SO_LINGER相关联的一个结构体如下:
|
||||
|
||||
#include <sys/socket.h>
|
||||
struct linger {
|
||||
@@ -23,71 +20,66 @@ view plaincopy to clipboardprint?
|
||||
|
||||
当调用setsockopt之后,该选项产生的影响取决于linger结构体中 l_onoff和l_linger的值:
|
||||
|
||||
0 = l_onoff
|
||||
|
||||
**l_onoff = 0**
|
||||
当l_onoff被设置为0的时候,将会关闭SO_LINGER选项,即TCP或则SCTP保持默认操作:close立即返回.l_linger值被忽略.
|
||||
|
||||
l_lineoff值非0,0 = l_linger
|
||||
|
||||
当调用close的时候,TCP连接会立即断开.send buffer中未被发送的数据将被丢弃,并向对方发送一个RST信息.值得注意的是,由于这种方式,是非正常的4中握手方式结束TCP链接,所以,TCP连接将不会进入TIME_WAIT状态,这样会导致新建立的可能和就连接的数据造成混乱。具体原因详见我的上一篇文章《linux 网络编程之TIME_WAIT状态》
|
||||
|
||||
l_onoff和l_linger都是非0
|
||||
|
||||
在这种情况下,回事的close返回得到延迟。调用close去关闭socket的时候,内核将会延迟。也就是说,如果send buffer中还有数据尚未发送,该进程将会被休眠直到一下任何一种情况发生:
|
||||
**l_lineoff值非0,l_linger = 0**
|
||||
**当调用close的时候,TCP连接会立即断开.send buffer中未被发送的数据将被丢弃,并向对方发送一个**__RST信息__**.**值得注意的是,由于这种方式,是非正常的4中握手方式结束TCP链接,所以,TCP连接将不会进入TIME_WAIT状态,这样会导致新建立的可能和就连接的数据造成混乱。具体原因详见我的上一篇文章《linux 网络编程之TIME_WAIT状态》
|
||||
|
||||
**l_onoff和l_linger都是非0**
|
||||
在这种情况下调用close去关闭socket的时候,内核将会延迟。也就是说,如果send buffer中还有数据尚未发送,该进程将会被休眠直到一下任何一种情况发生:
|
||||
1) send buffer中的所有数据都被发送并且得到对方TCP的应答消息(这种应答并不是意味着对方应用程序已经接收到数据,在后面shutdown将会具体讲道)
|
||||
2) 延迟时间消耗完。在延迟时间被消耗完之后,send buffer中的所有数据都将会被丢弃。
|
||||
2) 延迟时间消耗完。在延迟时间被消耗完之后,send buffer中的所有数据都将会被__丢弃__。
|
||||
|
||||
上面1),2)两种情况中,如果socket被设置为O_NONBLOCK状态,程序将不会等待close返回,send buffer中的所有数据都将会被丢弃。所以,需要我们判断close的返回值。在send buffer中的所有数据都被发送之前并且延迟时间没有消耗完,close返回的话,close将会返回一个EWOULDBLOCK的error.
|
||||
|
||||
下面用几个实例来说明:
|
||||
|
||||
|
||||
A. Close默认操作:立即返回
|
||||
A. __Close默认操作:立即返回__
|
||||
{{./1.jpg}}
|
||||
|
||||
|
||||
此种情况,close立即返回,如果send buffer中还有数据,close将会等到所有数据被发送完之后之后返回。由于我们并没有等待对方TCP发送的ACK信息,所以我们只能保证数据已经发送到对方,我们并不知道对方是否已经接受了数据。由于此种情况,TCP连接终止是按照正常的4次握手方式,需要经过TIME_WAIT。
|
||||
此种情况,__close立即返回,如果send buffer中还有数据,close将会等到所有数据被发送完之后之后返回__。由于我们并没有等待对方TCP发送的ACK信息,所以__我们只能保证数据已经发送到对方,我们并不知道对方是否已经接受了数据。__由于此种情况,TCP连接终止是按照正常的4次握手方式,需要经过TIME_WAIT。
|
||||
|
||||
B. l_onoff非0,并且使之l_linger为一个整数
|
||||
B. l_onoff非0,并且使之l_linger为一个整数
|
||||
|
||||
{{./2.jpg}}
|
||||
|
||||
在这种情况下,close会在接收到对方TCP的ACK信息之后才返回(l_linger消耗完之前)。但是这种ACK信息只能保证对方已经接收到数据,并不保证对方应用程序已经读取数据。
|
||||
在这种情况下,__close会在接收到对方TCP的ACK信息之后才返回(l_linger消耗完之前)。但是这种ACK信息只能保证对方已经接收到数据,并不保证对方应用程序已经读取数据。__
|
||||
|
||||
C. l_linger设置值太小
|
||||
C. l_linger设置值太小
|
||||
|
||||
{{./3.jpg}}
|
||||
|
||||
这种情况,由于l_linger值太小,在send buffer中的数据都发送完之前,close就返回,此种情况终止TCP连接,更l_linger = 0类似,TCP连接终止不是按照正常的4步握手,所以,TCP连接不会进入TIME_WAIT状态,那么,client会向server发送一个RST信息.
|
||||
这种情况,__由于l_linger值太小,在send buffer中的数据都发送完之前,close就返回,此种情况终止TCP连接,与l_linger = 0类似,TCP连接终止不是按照正常的4步握手,所以,TCP连接不会进入TIME_WAIT状态,那么,client会向server发送一个RST信息.__
|
||||
|
||||
D. Shutdown,等待应用程序读取数据
|
||||
D. Shutdown,等待应用程序读取数据
|
||||
|
||||
{{./4.jpg}}
|
||||
|
||||
同上面的B进行对比,调用shutdown后紧接着调用read,此时read会被阻塞,直到接收到对方的FIN,也就是说read是在server的应用程序调用close之后才返回的。当server应用程序读取到来自client的数据和FIN之后,server会进入一个叫CLOSE_WAIT,关于CLOSE_WAIT,详见我的博客《 Linux 网络编程 之 TCP状态转换》 。那么,如果server端要断开该TCP连接,需要server应用程序调用一次close,也就意味着向client发送FIN。这个时候,说明server端的应用程序已经读取到client发送的数据和FIN。read会在接收到server的FIN之后返回。所以,shutdown 可以确保server端应用程序已经读取数据了,而不仅仅是server已经接收到数据而已。
|
||||
同上面的B进行对比,调用shutdown后紧接着调用read,此时read会被阻塞,直到接收到对方的FIN,也就是说read是在server的应用程序调用close之后才返回的。当server应用程序读取到来自client的数据和FIN之后,server会进入一个叫CLOSE_WAIT,关于CLOSE_WAIT,详见我的博客《 Linux 网络编程 之 TCP状态转换》 。那么,如果server端要断开该TCP连接,需要server应用程序调用一次close,也就意味着向client发送FIN。这个时候,说明server端的应用程序已经读取到client发送的数据和FIN。read会在接收到server的FIN之后返回。所以,__shutdown 可以确保server端应用程序已经读取数据了,而不仅仅是server已经接收到数据而已。__
|
||||
|
||||
shutdown参数如下:
|
||||
|
||||
SHUT_RD:调用shutdown的一端receive buffer将被丢弃掉,无法接受数据,但是可以发送数据,send buffer的数据可以被发送出去
|
||||
|
||||
SHUT_WR:调用shutdown的一端无法发送数据,但是可以接受数据.该参数表示不能调用send.但是如果还有数据在send buffer中,这些数据还是会被继续发送出去的.
|
||||
|
||||
SO_REUSEADDR和SO_REUSEPORT
|
||||
|
||||
最近,看到CSDN的linux版块,有人提问,说为什么server程序重启之后,无法连接,需要过一段时间才能连接上.我想对于这个问题,有两种可能:一种可能就是该server一直停留在TIME_WAIT状态.这个时候,需要等待2MSL的时间才能重新连接上,具体细节原因请见我的另一篇文章《linux 网络编程之TIME_WAIT状态》
|
||||
SO_REUSEADDR和SO_REUSEPORT
|
||||
最近,看到CSDN的linux版块,有人提问,说为什么server程序重启之后,无法连接,需要过一段时间才能连接上.我想对于这个问题,有两种可能:
|
||||
|
||||
一种可能就是__该server一直停留在TIME_WAIT状态__.这个时候,需要等待2MSL的时间才能重新连接上,具体细节原因请见我的另一篇文章《linux 网络编程之TIME_WAIT状态》
|
||||
另一种可能就是SO_REUSEADDR参数设置问题.关于TIME_WAIT的我就不在这里重述了,这里我讲一讲SO_REUSEADDR.
|
||||
|
||||
SO_REUSEADDR允许一个server程序listen监听并bind到一个端口,既是这个端口已经被一个正在运行的连接使用了.
|
||||
SO_REUSEADDR允许一个server程序listen监听并bind到一个端口,即使这个端口已经被一个正在运行的连接使用了.
|
||||
|
||||
我们一般会在下面这种情况中遇到:
|
||||
|
||||
一个监听(listen)server已经启动
|
||||
当有client有连接请求的时候,server产生一个子进程去处理该client的事物.
|
||||
server主进程终止了,但是子进程还在占用该连接处理client的事情.虽然子进程终止了,但是由于子进程没有终止,该socket的引用计数不会为0,所以该socket不会被关闭.
|
||||
server程序重启.
|
||||
* 一个监听(listen)server已经启动
|
||||
* 当有client有连接请求的时候,server产生一个子进程去处理该client的事物.
|
||||
* server主进程终止了,但是子进程还在占用该连接处理client的事情.虽然父进程终止了,但是由于子进程没有终止,该socket的引用计数不会为0,所以该socket不会被关闭.
|
||||
* server程序重启.
|
||||
|
||||
默认情况下,server重启,调用socket,bind,然后listen,会失败.因为该端口正在被使用.如果设定SO_REUSEADDR,那么server重启才会成功.因此,所有的TCP server都必须设定此选项,用以应对server重启的现象.
|
||||
默认情况下,server重启,调用socket,bind,然后listen,会失败.因为该端口正在被使用.如果设定SO_REUSEADDR,那么server重启才会成功.因此,__所有的TCP server都必须设定此选项,用以应对server重启的现象.__
|
||||
|
||||
SO_REUSEADDR允许同一个端口上绑定多个IP.只要这些IP不同.另外,还可以在绑定IP通配符.但是最好是先绑定确定的IP,最后绑定通配符IP.一面系统拒绝.简而言之,SO_REUSEADDR允许多个server绑定到同一个port上,只要这些server指定的IP不同,但是SO_REUSEADDR需要在bind调用之前就设定.在TCP中,不允许建立起一个已经存在的相同的IP和端口的连接.但是在UDP中,是允许的.
|
||||
SO_REUSEADDR允许同一个端口上绑定多个IP.只要这些IP不同.另外,还可以在绑定IP通配符.但是最好是先绑定确定的IP,最后绑定通配符IP.以免系统拒绝.简而言之,SO_REUSEADDR允许多个server绑定到同一个port上,只要这些server指定的IP不同,__但是SO_REUSEADDR需要在bind调用之前就设定__.在TCP中,不允许建立起一个已经存在的相同的IP和端口的连接.但是在UDP中,是允许的.
|
||||
|
||||
@@ -4,10 +4,9 @@ Creation-Date: 2011-06-04T14:36:51+08:00
|
||||
|
||||
====== Socket数据发送中信号SIGPIPE及相关errno的研究 ======
|
||||
Created 星期六 04 六月 2011
|
||||
Socket数据发送中信号SIGPIPE及相关errno的研究
|
||||
好久没做过C开发了,最近重操旧业。
|
||||
听说另外一个项目组socket开发遇到问题,发送端和接受端数据大小不一致。建议他们采用writen的重发机制,以避免信号中断错误。采用后还是有问题。PM让我帮忙研究下。
|
||||
UNP n年以前看过,很久没做过底层开发,手边也没有UNP vol1这本书,所以做了个测试程序,研究下实际可能发生的情况了。
|
||||
原文地址 http://blog.chinaunix.net/u/31357/showart_242605.html
|
||||
|
||||
听说另外一个项目组socket开发遇到问题,发送端和接受端数据大小不一致。建议他们采用writen的重发机制,以避免信号中断错误。采用后还是有问题。PM让我帮忙研究下。UNP n年以前看过,很久没做过底层开发,手边也没有UNP vol1这本书,所以做了个测试程序,研究下实际可能发生的情况了。
|
||||
|
||||
测试环境:AS3和redhat 9(缺省没有nc)
|
||||
|
||||
@@ -67,12 +66,9 @@ main(int argc, char **argv)
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
为了方便观察错误输出,lib/writen.c也做了修改,加了些日志:
|
||||
|
||||
|
||||
|
||||
/* include writen */
|
||||
#include "unp.h"
|
||||
|
||||
@@ -112,20 +108,14 @@ Writen(int fd, void *ptr, size_t nbytes)
|
||||
}
|
||||
|
||||
|
||||
|
||||
client.c放在tcpclieserv目录下,修改了Makefile,增加了client.c的编译目标
|
||||
|
||||
|
||||
|
||||
|
||||
client: client.c
|
||||
${CC} ${CFLAGS} -o $@ $< ${LIBS}
|
||||
|
||||
|
||||
|
||||
接着就可以开始测试了。
|
||||
测试1 忽略SIGPIPE信号,writen之前,对方关闭接受进程
|
||||
|
||||
测试1 忽略SIGPIPE信号,writen之前,对方关闭接受进程
|
||||
本机服务端:
|
||||
nc -l -p 30000
|
||||
|
||||
@@ -141,7 +131,8 @@ Already write 40960, left 0, errno=0
|
||||
Begin send 40960 data
|
||||
Begin Writen 40960
|
||||
writen error: Broken pipe(32)
|
||||
结论:可见write之前,对方socket中断,发送端write会返回-1,errno号为EPIPE(32)
|
||||
结论:__可见write之前,对方socket中断,发送端write会返回-1,errno号为EPIPE(32)__
|
||||
|
||||
测试2 catch SIGPIPE信号,writen之前,对方关闭接受进程
|
||||
|
||||
修改客户端代码,catch sigpipe信号
|
||||
@@ -167,7 +158,7 @@ Begin send 40960 data
|
||||
Begin Writen 40960
|
||||
Signal is 13
|
||||
writen error: Broken pipe(32)
|
||||
结论:可见write之前,对方socket中断,发送端write时,会先调用SIGPIPE响应函数,然后write返回-1,errno号为EPIPE(32)
|
||||
结论:__可见write之前,对方socket中断,发送端write时,会先调用SIGPIPE响应函数,然后write返回-1,errno号为EPIPE(32)__
|
||||
|
||||
测试3 writen过程中,对方关闭接受进程
|
||||
|
||||
@@ -186,7 +177,7 @@ Already write 589821, left 3506179, errno=0
|
||||
Begin Writen 3506179
|
||||
writen error: Connection reset by peer(104)
|
||||
|
||||
结论:可见socket write中,对方socket中断,发送端write会先返回已经发送的字节数,再次write时返回-1,errno号为ECONNRESET(104)
|
||||
结论:__可见socket write中,对方socket中断,发送端write会先返回已经发送的字节数,再次write时返回-1,errno号为ECONNRESET(104)__
|
||||
|
||||
为什么以上测试,都是对方已经中断socket后,发送端再次write,结果会有所不同呢。从后来找到的UNP5.12,5.13能找到答案
|
||||
|
||||
@@ -203,6 +194,3 @@ If the process either catches the signal and returns from the signal handler, or
|
||||
以上解释了测试1,2的现象,write一个已经接受到RST的socket,系统内核会发送SIGPIPE给发送进程,如果进程catch/ignore这个信号,write都返回EPIPE错误.
|
||||
|
||||
因此,UNP建议应用根据需要处理SIGPIPE信号,至少不要用系统缺省的处理方式处理这个信号,系统缺省的处理方式是退出进程,这样你的应用就很难查处处理进程为什么退出。
|
||||
|
||||
原文地址 http://blog.chinaunix.net/u/31357/showart_242605.html
|
||||
|
||||
|
||||
182
Zim/Programme/APUE/pthreads_-_POSIX_threads.txt
Normal file
182
Zim/Programme/APUE/pthreads_-_POSIX_threads.txt
Normal file
@@ -0,0 +1,182 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-23T16:27:29+08:00
|
||||
|
||||
====== pthreads - POSIX threads ======
|
||||
Created Tuesday 23 October 2012
|
||||
http://linux.die.net/man/7/pthreads
|
||||
|
||||
===== Name =====
|
||||
pthreads - POSIX threads
|
||||
|
||||
===== Description =====
|
||||
POSIX.1 specifies __a set of interfaces__ (functions, header files) for threaded programming commonly known as **POSIX threads, or Pthreads**. A single process can contain multiple threads, all of which are executing the same program. These threads share **the same global memory** (data and heap segments), but each thread has **its own stack** (automatic variables).
|
||||
|
||||
POSIX.1 also requires that threads share a range of other attributes (i.e., these attributes are __process-wide__ rather than per-thread):
|
||||
- process ID
|
||||
- parent process ID
|
||||
- process group ID and session ID
|
||||
- controlling terminal
|
||||
- user and group IDs
|
||||
- open file descriptors
|
||||
- record locks (see fcntl(2))
|
||||
- signal dispositions
|
||||
- file mode creation mask (umask(2))
|
||||
- current directory (chdir(2)) and root directory (chroot(2))
|
||||
- interval timers (**setitimer**(2)) and POSIX timers (timer_create(2))
|
||||
- nice value (setpriority(2))
|
||||
- resource limits (setrlimit(2))
|
||||
- measurements of the consumption of CPU time (times(2)) and resources (getrusage(2))
|
||||
|
||||
As well as the stack, POSIX.1 specifies that various other attributes are distinct for each thread, including:
|
||||
- thread ID (the **pthread_t** data type)
|
||||
- signal mask (**pthread_sigmask**(3))
|
||||
- the errno variable
|
||||
- alternate signal stack (__sigaltstack__(2))
|
||||
- real-time scheduling policy and priority (sched_setscheduler(2) and sched_setparam(2))
|
||||
|
||||
The following Linux-specific features are also per-thread:
|
||||
- capabilities (see capabilities(7))
|
||||
- CPU affinity (sched_setaffinity(2))
|
||||
|
||||
===== Pthreads function return values =====
|
||||
Most pthreads functions return **0 on success, and an error number of failure**. Note that the pthreads functions __do not set errno__. For each of the pthreads functions that can return an error, POSIX.1-2001 specifies that the function can never fail with the error EINTR.
|
||||
|
||||
===== Thread IDs =====
|
||||
Each of the threads in a process has a unique thread identifier (stored in the type pthread_t). This identifier is returned to the caller of **pthread_create(3)**, and a thread can obtain its own thread identifier using **pthread_self(3)**. Thread IDs are only __guaranteed to be unique within a process__. A thread ID may be reused after a terminated thread has been joined, or a detached thread has terminated. In all pthreads functions that accept a thread ID as an argument, that ID by definition refers to a thread in the same process as the caller.
|
||||
|
||||
===== Thread-safe functions =====
|
||||
A thread-safe function is one that can be safely (i.e., it will deliver the same results regardless of whether it is) called from multiple threads at the same time.
|
||||
POSIX.1-2001 and POSIX.1-2008 require that all functions specified in the standard shall be thread-safe, __except__ for the following functions:
|
||||
|
||||
asctime()
|
||||
basename()
|
||||
catgets()
|
||||
crypt()
|
||||
ctermid() if passed a non-NULL argument
|
||||
ctime()
|
||||
........
|
||||
|
||||
===== Async-cancel-safe functions =====
|
||||
An async-cancel-safe function is one that can **be safely called** in an application where asynchronous cancelability is enabled (see **pthread_setcancelstate**(3)).
|
||||
Only the following functions are required to be async-cancel-safe by POSIX.1-2001 and POSIX.1-2008:
|
||||
|
||||
pthread_cancel()
|
||||
pthread_setcancelstate()
|
||||
pthread_setcanceltype()
|
||||
|
||||
===== Cancellation Points =====
|
||||
POSIX.1 specifies that certain functions must, and certain other functions may, be cancellation points. If a thread is __cancelable__, its cancelability type is __deferred__, and a cancellation request is __pending__ for the thread, then the thread is canceled when it calls a function that is a cancellation point.
|
||||
The following functions are required to be cancellation points by POSIX.1-2001 and/or POSIX.1-2008:
|
||||
|
||||
accept()
|
||||
aio_suspend()
|
||||
clock_nanosleep()
|
||||
.........
|
||||
|
||||
The following functions __may be cancellation points__ according to POSIX.1-2001 and/or POSIX.1-2008:
|
||||
access()
|
||||
asctime()
|
||||
..........
|
||||
An implementation may also mark other functions not specified in the standard as cancellation points. In particular, an implementation is likely to mark any nonstandard function that may block as a cancellation point. (This includes most functions that can touch files.)
|
||||
|
||||
===== Compiling on Linux =====
|
||||
On Linux, programs that use the Pthreads API should be compiled using cc __-pthread__.
|
||||
|
||||
===== Linux Implementations of POSIX Threads =====
|
||||
Over time, two threading implementations have been provided by the GNU C library on Linux:
|
||||
* LinuxThreads
|
||||
This is the original Pthreads implementation. Since glibc 2.4, this implementation is **no longer supported**.
|
||||
* NPTL (Native POSIX Threads Library)
|
||||
This is the modern Pthreads implementation. By comparison with LinuxThreads, NPTL provides __closer conformance__ to the requirements of the POSIX.1 specification and better performance when creating large numbers of threads. NPTL is available since glibc 2.3.2, and requires features that are present in the Linux 2.6 kernel(**futex和实时信号**).
|
||||
|
||||
Both of these are so-called __1:1 implementations__, meaning that each thread maps to a kernel scheduling entity. Both threading implementations employ the Linux clone(2) system call. In NPTL, thread synchronization primitives (mutexes, thread joining, etc.) are implemented using the Linux __futex__(2) system call.
|
||||
|
||||
==== LinuxThreads ====
|
||||
The notable features of this implementation are the following:
|
||||
|
||||
- In addition to the main (initial) thread, and the threads that the program creates using pthread_create(3), the implementation creates __a "manager" thread__. This thread handles thread creation and termination. (Problems can result if this thread is inadvertently killed.注意,管理线程是函数库**内部自动生成**的,而不是指调用进程。)
|
||||
|
||||
- Signals are used internally by the implementation. On Linux 2.2 and later, **the first three real-time signals** are used (see also signal(7)). On older Linux kernels, SIGUSR1 and SIGUSR2 are used. Applications must avoid the use of whichever set of signals is employed by the implementation.
|
||||
|
||||
- Threads __do not__ share process IDs. (In effect, LinuxThreads threads are **implemented as processes** which share more information than usual, but which do not share a common process ID.) LinuxThreads threads (including the manager thread) are **visible as separate processes** using ps(1).
|
||||
|
||||
The LinuxThreads implementation deviates from the POSIX.1 specification in a number of ways, including the following:
|
||||
下面是LinuxThreads实现的与POSIX.1规范__不符__的地方:
|
||||
|
||||
- Calls to getpid(2) return a different value in each thread.
|
||||
|
||||
- Calls to getppid(2) in threads other than the main thread return **the process ID of the manager thread**; instead getppid(2) in these threads should return the same value as getppid(2) in the main thread. 这里的main thread指的是最开始的(initial)线程。
|
||||
|
||||
- When one thread creates a new child process using fork(2), any thread should be able to wait(2) on the child. However, the implementation only allows the thread that created the child to wait(2) on it.
|
||||
|
||||
- When a thread calls execve(2), all other threads are terminated (as required by POSIX.1). However, the resulting process has the same PID as the thread that called execve(2): it should have the same PID as the main thread.
|
||||
|
||||
- Threads do not share user and group IDs. This can cause complications with set-user-ID programs and can cause failures in Pthreads functions if an application changes its credentials using seteuid(2) or similar.
|
||||
|
||||
- Threads do not share a common session ID and process group ID.
|
||||
|
||||
- Threads do not share record locks created using fcntl(2).
|
||||
|
||||
- The information returned by times(2) and getrusage(2) is per-thread rather than process-wide.
|
||||
|
||||
- Threads do not share semaphore undo values (see semop(2)).
|
||||
|
||||
- Threads do not share interval timers.
|
||||
|
||||
- Threads do not share a common nice value.
|
||||
|
||||
- POSIX.1 distinguishes the notions of signals that are directed to the process as a whole and signals that are directed to individual threads. According to POSIX.1, __a process-directed signal__ (sent using kill(2), for example) should be handled by a single, arbitrarily selected thread within the process. LinuxThreads does not support the notion of process-directed signals: signals may only be sent to specific threads.
|
||||
|
||||
- Threads have distinct alternate signal stack settings. However, a new thread's alternate signal stack settings are copied from the thread that created it, so that the threads initially share an alternate signal stack. (A new thread should start with no alternate signal stack defined. If two threads handle signals on their shared alternate signal stack at the same time, unpredictable program failures are likely to occur.)
|
||||
|
||||
==== NPTL ====
|
||||
With NPTL, all of the threads in a process are placed in **the same thread group**; all members of a thread group share the same PID. NPTL does not employ a manager thread. NPTL makes internal use of the first two real-time signals (see also signal(7)); these signals cannot be used in applications.
|
||||
|
||||
NPTL still has at least __one nonconformance__ with POSIX.1:
|
||||
|
||||
- Threads do not share a common nice value.
|
||||
|
||||
Some NPTL nonconformances only occur with older kernels:
|
||||
- The information returned by times(2) and getrusage(2) is per-thread rather than process-wide (fixed in kernel 2.6.9).
|
||||
- Threads do not share resource limits (fixed in kernel 2.6.10).
|
||||
- Threads do not share interval timers (fixed in kernel 2.6.12).
|
||||
- Only the __main thread__ is permitted to start a new session using setsid(2) (fixed in kernel 2.6.16).
|
||||
- Only the main thread is permitted to make the process into a process group leader using setpgid(2) (fixed in kernel 2.6.16).
|
||||
- Threads have distinct alternate signal stack settings. However, a new thread's alternate signal stack settings are copied from the thread that created it, so that the threads initially share an alternate signal stack (fixed in kernel 2.6.16).
|
||||
|
||||
Note the following further points about the NPTL implementation:
|
||||
-
|
||||
If the stack size soft resource limit (see the description of RLIMIT_STACK in setrlimit(2)) is set to a value other than unlimited, then this value defines the default stack size for new threads. To be effective, this limit must be set before the program is executed, perhaps using the ulimit -s shell built-in command (limit stacksize in the C shell).
|
||||
|
||||
===== Determining the Threading Implementation =====
|
||||
Since glibc 2.3.2, the __getconf(1)__ command can be used to determine the system's threading implementation, for example:
|
||||
bash$ getconf GNU_LIBPTHREAD_VERSION
|
||||
NPTL 2.3.4
|
||||
|
||||
With older glibc versions, a command such as the following should be sufficient to determine the default threading implementation:
|
||||
bash$ $( ldd /bin/ls | grep libc.so | awk '{print $3}' ) | \
|
||||
egrep -i 'threads|nptl'
|
||||
Native POSIX Threads Library by Ulrich Drepper et al
|
||||
|
||||
[geekard@kb310 ~]$ __/lib/libpthread.so.0__
|
||||
Native POSIX Threads Library by Ulrich Drepper et al
|
||||
Copyright (C) 2006 Free Software Foundation, Inc.
|
||||
This is free software; see the source for copying conditions.
|
||||
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE.
|
||||
Forced unwind support included.
|
||||
[geekard@kb310 ~]$
|
||||
|
||||
===== Selecting the Threading Implementation: LD_ASSUME_KERNEL =====
|
||||
On systems with a glibc that supports both LinuxThreads and NPTL (i.e., glibc 2.3.x), the LD_ASSUME_KERNEL environment variable can be used to override the dynamic linker's default choice of threading implementation. **This variable tells the dynamic linker to assume that it is running on top of a particular kernel version.** __By specifying a kernel version that does not provide the support required by NPTL, we can force the use of LinuxThreads.__ (The most likely reason for doing this is to run a (broken) application that depends on some nonconformant behavior in LinuxThreads.) For example:
|
||||
bash$ $( **LD_ASSUME_KERNEL=2.2.5** ldd /bin/ls | grep libc.so | \
|
||||
awk '{print $3}' ) | egrep -i 'threads|ntpl'
|
||||
linuxthreads-0.10 by Xavier Leroy
|
||||
|
||||
===== See Also =====
|
||||
clone(2), futex(2), gettid(2), proc(5), futex(7), sigevent(7), signal(7),
|
||||
and various Pthreads manual pages, for example: pthread_attr_init(3), pthread_atfork(3), pthread_cancel(3), pthread_cleanup_push(3), pthread_cond_signal(3), pthread_cond_wait(3), pthread_create(3), pthread_detach(3), pthread_equal(3), pthread_exit(3), pthread_key_create(3), pthread_kill(3), pthread_mutex_lock(3), pthread_mutex_unlock(3), pthread_once(3), pthread_setcancelstate(3), pthread_setcanceltype(3), pthread_setspecific(3), pthread_sigmask(3), pthread_sigqueue(3), and pthread_testcancel(3)
|
||||
|
||||
===== Referenced By =====
|
||||
core(5), intro(3), pthread_attr_getdetachstate(3), pthread_attr_getscope(3), pthread_attr_getstackaddr(3), pthread_attr_setaffinity_np(3), pthread_attr_setguardsize(3), pthread_attr_setinheritsched(3), pthread_attr_setschedparam(3), pthread_attr_setschedpolicy(3), pthread_attr_setstack(3), pthread_attr_setstacksize(3), pthread_cleanup_push_defer_np(3), pthread_getattr_np(3), pthread_getcpuclockid(3), pthread_join(3), pthread_kill_other_threads_np(3), pthread_setaffinity_np(3), pthread_setconcurrency(3), pthread_setschedparam(3), pthread_setschedprio(3), pthread_tryjoin_np(3), pthread_yield(3), sem_overview(7), vfork(2), xfs_copy(8)
|
||||
@@ -6,7 +6,7 @@ Creation-Date: 2011-06-03T21:37:25+08:00
|
||||
Created 星期五 03 六月 2011
|
||||
http://blog.csdn.net/factor2000/archive/2009/02/23/3929816.aspx
|
||||
|
||||
此选项指定函数close对面向连接的协议如何操作(如TCP)。内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。
|
||||
此选项指定函数close对面向连接的协议如何操作(如TCP)。内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。
|
||||
|
||||
SO_LINGER选项用来改变此缺省设置。使用如下结构:
|
||||
|
||||
@@ -19,12 +19,13 @@ struct linger {
|
||||
有下列三种情况:
|
||||
|
||||
1、设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,**close调用会立即返回**给调用者,如果可能将会传输任何未发送的数据(调用进程立即结束,但进程对应的TCP发送缓冲区中可能还有未发送完的数据,所以TCP连接可能会延迟一段时间后关闭,这个是正常的TIME_WAIT状态);
|
||||
2、设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个__RST__给对方,而不是通常的四分组终止序列,这避免了**TIME_WAIT**状态;
|
||||
3、设置 l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,**进程将处于睡眠状态(注意close调用不是立即返回)**,直 到(a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序检查close的返回值是非常重要的,如果在数据发送完并被确认前时间到(超时),close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。
|
||||
|
||||
2、设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将**丢弃保留**在套接口发送缓冲区中的任何数据并发送一个__RST__给对方,而不是通常的四分组终止序列,这避免了**TIME_WAIT**状态;
|
||||
|
||||
3、设置 l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,**进程将处于睡眠状态(注意close调用不是立即返回)**,直 到(a)所有数据发送完且__被对方确认__,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序__检查close的返回值__是非常重要的,如果在数据发送完并被确认前时间到(超时),close将__返回EWOULDBLOCK错误__且套接口发送缓冲区中的任何数据都__丢失__。close的成功返回仅告诉我们发送的数据(和FIN)__已由对方TCP确认__,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。
|
||||
|
||||
注释:l_linger的单位依赖于实现: 4.4BSD假设其单位是时钟滴答(百分之一秒),但Posix.1g规定单位为秒。
|
||||
|
||||
|
||||
下面的代码是一个使用SO_LINGER选项的例子,使用30秒的超时时限:
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
@@ -42,15 +43,15 @@ z = setsockopt(s,
|
||||
if ( z )
|
||||
perror("setsockopt(2)");
|
||||
|
||||
下面的例子显示了如何设置SO_LINGER的值来中止套接口s上的当前连接:
|
||||
下面的例子显示了如何设置SO_LINGER的值来**中止套接口s上的当前连接**:
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
int z; /* Status code */
|
||||
int s; /* Socket s */
|
||||
struct linger so_linger;
|
||||
...
|
||||
so_linger.l_onoff = TRUE;
|
||||
so_linger.l_linger = 0;
|
||||
__so_linger.l_onoff = TRUE;__
|
||||
__so_linger.l_linger = 0;__
|
||||
z = setsockopt(s,
|
||||
SOL_SOCKET,
|
||||
SO_LINGER,
|
||||
@@ -60,45 +61,7 @@ if ( z )
|
||||
perror("setsockopt(2)");
|
||||
**close(s); /* Abort connection */**
|
||||
|
||||
|
||||
在上面的这个例子中,当调用close函数时,套接口s会立即中止。__中止的语义是通过将超时值设置为0来实现的__。
|
||||
正常情况下,TCP收到不在已有连接中的数据(但不包括序号是将来收到的哪些)时会自动发送RST给对方,应用进程是不知晓的。
|
||||
但应用进程可以通过将l_linger设为0然后调用close的方法来异常终止(不是通常的四次握手,而是通过RST)与对方的通信。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/********** WINDOWS **********/
|
||||
|
||||
|
||||
|
||||
/* 当连接中断时,需要延迟关闭(linger)以保证所有数据都被传输,所以需要打开SO_LINGER这个选项;
|
||||
* //注:大致意思就是说SO_LINGER选项用来设置当调用closesocket时是否马上关闭socket;
|
||||
* linger的结构在/usr/include/linux/socket.h中定义://注:这个结构就是SetSocketOpt中的Data的数据结构
|
||||
* struct linger
|
||||
* {
|
||||
* int l_onoff; /* Linger active */ //低字节,0和非0,用来表示是否延时关闭socket
|
||||
* int l_linger; /* How long to linger */ //高字节,延时的时间数,单位为秒
|
||||
* };
|
||||
* 如果l_onoff为0,则延迟关闭特性就被取消。
|
||||
|
||||
* 如果非零,则允许套接口延迟关闭; l_linger字段则指明延迟关闭的时间
|
||||
*/
|
||||
|
||||
|
||||
更具体的描述如下:
|
||||
1、若设置了SO_LINGER(亦即linger结构中的l_onoff域设为非零),并设置了零超时间隔,则closesocket()不被阻塞立即执行,不论是否有排队数据未发送或未被确认。这种关闭方式称为“强制”或“失效”关闭,因为套接口的虚电路立即被复位,且丢失了未发送的数据。在远端的recv()调用将以WSAECONNRESET出错。
|
||||
|
||||
2、若设置了SO_LINGER并确定了非零的超时间隔,则closesocket()调用阻塞进程,直到所剩数据发送完毕或超时。这种关闭称为“优雅”或“从容”关闭。请注意如果套接口置为非阻塞且SO_LINGER设为非零超时,则closesocket()调用将以WSAEWOULDBLOCK错误返回。
|
||||
|
||||
3、若在一个流类套接口上设置了SO_DONTLINGER(也就是说将linger结构的l_onoff域设为零),则closesocket()调用立即返回。但是,如果可能,排队的数据将在套接口关闭前发送。请注意,在这种情况下WINDOWS套接口实现将在一段不确定的时间内保留套接口以及其他资源,这对于想用所以套接口的应用程序来说有一定影响。
|
||||
|
||||
|
||||
SO_DONTLINGER 若为真,则SO_LINGER选项被禁止。
|
||||
SO_LINGER延迟关闭连接 struct linger上面这两个选项影响close行为;
|
||||
|
||||
|
||||
选项 间隔 关闭方式 等待关闭与否
|
||||
SO_DONTLINGER 不关心 优雅 否
|
||||
SO_LINGER 零 强制 否
|
||||
SO_LINGER 非零 优雅 是
|
||||
|
||||
72
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能.txt
Normal file
72
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能.txt
Normal file
@@ -0,0 +1,72 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-23T10:13:38+08:00
|
||||
|
||||
====== 使用sendfile() 提升网络文件发送性能 ======
|
||||
Created Tuesday 23 October 2012
|
||||
http://hily.me/blog/2011/01/use-sendfile-accelerate-file-sending/
|
||||
|
||||
偶见一好文,清楚地阐述了什么是零拷贝(Zero Copy)以及 sendfile 的由来,不复述下实感不快。
|
||||
原文见:http://www.linuxjournal.com/article/6345
|
||||
|
||||
文章中列出了我们平时通过网络发送文件时会用到的两个系统调用:
|
||||
read(file, tmp_buf, len);
|
||||
write(socket, tmp_buf, len);
|
||||
|
||||
调用过程示意图如下:
|
||||
{{./1.jpg}}
|
||||
|
||||
在用户空间调用 read() 读取文件时发生两次内存拷贝:
|
||||
|
||||
* DMA引擎将文件读取到内核的文件缓冲区
|
||||
* 调用返回用户空间时将内核的文件缓冲区的数据复制到用户空间的缓冲区
|
||||
|
||||
接着调用 write() 把数据写入 socket 时,又发生了两次内存拷贝:
|
||||
|
||||
* 将用户空间的缓冲区的数据复制到内核的 socket 缓冲区
|
||||
* 将内核 socket 缓冲区的数据复制到网络协议引擎
|
||||
|
||||
也就是说,在整个文件发送的过程中,发生了四次内存拷贝。
|
||||
然后,数据读取到用户空间后并没有做过任何加工处理,因此通过网络发送文件时,
|
||||
根本没有必要把文件内容复制到用户空间。
|
||||
|
||||
于是引入了 mmap():
|
||||
tmp_buf = mmap(file, len);
|
||||
write(socket, tmp_buf, len);
|
||||
|
||||
调用过程示意图:
|
||||
{{./2.jpg}}
|
||||
|
||||
* 调用 mmap() 时会将文件直接读取到内核缓冲区,并把内核缓冲区直接__共享__到用户空间
|
||||
* 调用 write() 时,直接将内核缓冲区的数据复制到 socket 缓冲区
|
||||
|
||||
这样一来,就少了用户空间和内核空间之间的内存复制了。这种方式会有个问题,当前进程
|
||||
在调用 write() 时,另一个进程把文件清空了,程序就会报出 SIGBUS 类型错误。
|
||||
|
||||
Linux Kernel __2.1__ 引进了 sendfile(),只需要一个系统调用来实现文件发送。
|
||||
sendfile(socket, file, len);
|
||||
|
||||
调用过程示意图:
|
||||
{{./3.jpg}}
|
||||
* 调用 sendfile() 时会直接在内核空间把文件读取到内核的文件缓冲区
|
||||
* 将内核的文件缓冲区的数据复制到内核的 socket 缓冲区中
|
||||
* 将内核的 socket 缓冲区的数据复制到网络协议引擎
|
||||
|
||||
从性能上看,这种方式只是少了一个系统调用而已,还是做了3次拷贝操作。
|
||||
|
||||
Linux Kernel __2.4__ 改进了 sendfile(),调用接口没有变化:
|
||||
sendfile(socket, file, len);
|
||||
|
||||
调用过程示意图:
|
||||
{{./4.jpg}}
|
||||
|
||||
* 调用 sendfile() 时会直接在内核空间把文件读取到内核的文件缓冲区
|
||||
* 内核的 socket 缓冲区中保存的是当前要发送的数据在内核的文件缓冲区中的位置和偏移量
|
||||
* DMA gather copy 将内核的文件缓冲区的数据复制到网络协议引擎
|
||||
|
||||
这样就只剩下2次拷贝啦。
|
||||
|
||||
在许多 http server 中,都引入了 sendfile 的机制,如 nginx、lighttpd 等,它们正是利用
|
||||
sendfile() 这个特性来实现高性能的文件发送的。
|
||||
|
||||
– EOF –
|
||||
BIN
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能/1.jpg
Normal file
BIN
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能/1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
BIN
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能/2.jpg
Normal file
BIN
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能/2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
BIN
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能/3.jpg
Normal file
BIN
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能/3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
BIN
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能/4.jpg
Normal file
BIN
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能/4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
@@ -6,8 +6,6 @@ Creation-Date: 2011-10-07T18:42:52+08:00
|
||||
Created Friday 07 October 2011
|
||||
http://yibin.us/archives/6817
|
||||
|
||||
在一些python讨论版里,经常会见到一些“月经帖”,类似于“我用python读取一个文件乱码”,然后就会抱怨python的编码很麻烦,其实不是python编码难搞定,而是没有真正理解python的编码。
|
||||
|
||||
如在windows环境下的以下示例代码:
|
||||
|
||||
#!/usr/bin/env python
|
||||
@@ -21,9 +19,10 @@ if __name__=='__main__':
|
||||
do()
|
||||
|
||||
此时的ansi.txt编码为ansi,我们在cmd窗口执行,看到如下结果:
|
||||
{{~/sync/notes/zim/python/python中的编码/1.png}}
|
||||
{{./1.png}}
|
||||
|
||||
此时一切正常,但,如果还是用上面的脚本去读取utf8.txt,文件是utf8编码,就会得到下面的结果:
|
||||
{{~/sync/notes/zim/python/python中的编码/2.png}}
|
||||
{{./2.png}}
|
||||
经典的“乱码”出现了,有朋友可能会说了,我在python脚本里指定编码应该就解决了,于是:
|
||||
|
||||
#!/usr/bin/env python
|
||||
@@ -40,7 +39,7 @@ if __name__=='__main__':
|
||||
do()
|
||||
|
||||
再次运行:
|
||||
{{~/sync/notes/zim/python/python中的编码/3.png}}
|
||||
{{./3.png}}
|
||||
OMG,还是乱码。。。。
|
||||
|
||||
能不能正常输出中文不取决于#coding=utf-8,也不取决于目标文件的编码,而是取决于你的__终端输出设备__,这里就是CMD窗口,CMD窗口是不支持UTF-8的,它只支持__GBK__,所以,我们要转码。
|
||||
@@ -64,7 +63,7 @@ if __name__=='__main__':
|
||||
do()
|
||||
|
||||
结果:
|
||||
{{~/sync/notes/zim/python/python中的编码/4.png}}
|
||||
{{./4.png}}
|
||||
正常输出。
|
||||
|
||||
做一个小结:
|
||||
|
||||
@@ -122,13 +122,3 @@ Base64编码可用于在HTTP环境下传递较长的__标识信息__。例如,
|
||||
MIME::Base64 Perl module
|
||||
Firefox extension
|
||||
emacs函数
|
||||
|
||||
===== 参见 =====
|
||||
|
||||
Radix-64
|
||||
ASCII85
|
||||
Quoted-printable
|
||||
uuencode
|
||||
yEnc
|
||||
8BITMIME
|
||||
URL
|
||||
|
||||
@@ -9,10 +9,13 @@ Created Friday 14 October 2011
|
||||
Python 2.7.2 (default, Jun 29 2011, 11:17:09)
|
||||
[GCC 4.6.1] on linux2
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> '张俊' #python2 会自动将字符串转换为合适的编码字节字符串
|
||||
|
||||
>>> '张俊' #python2 会自动将字符串转换为合适编码的字节字符串
|
||||
'\xe5\xbc\xa0\xe4\xbf\x8a' #自动转换为utf-8编码的字节字符串
|
||||
|
||||
>>> u'张俊' #显式指定字符串类型为unicode类型, 此类型字符串没有编码,保存的是字符在unicode**字符集中的代码点(序号)**
|
||||
u'\u5f20\u4fca'
|
||||
|
||||
>>> '张俊'.encode('utf-8') #python2 已经自动将其转化为utf-8类型编码,因此再次编码(python2会将该字符串当作用ascii或unicode编码过)会出现错误。
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
|
||||
7
Zim/Programme/python/python笔记.txt
Normal file
7
Zim/Programme/python/python笔记.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:20:28+08:00
|
||||
|
||||
====== python笔记 ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
40
Zim/Programme/python/python笔记/dict.txt
Normal file
40
Zim/Programme/python/python笔记/dict.txt
Normal file
@@ -0,0 +1,40 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:21:05+08:00
|
||||
|
||||
====== dict ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
Help on class dict in module __builtin__:
|
||||
|
||||
class dict(object)
|
||||
| dict() -> new empty dictionary
|
||||
| dict(mapping) -> new dictionary initialized from **a mapping object**'s #mapping为__一个__pairs对象
|
||||
| (key, value) pairs
|
||||
| dict(iterable) -> new dictionary initialized as if via:
|
||||
| d = {}
|
||||
| for **k, v** in iterable: #迭代器对象每次返回的元素必须是一个容器类型,__容器中元素的个数为2__.**如[a,b], "ab",(a,b)**
|
||||
| d[k] = v
|
||||
| dict(__**kwargs)__ -> new dictionary initialized with the name=value pairs
|
||||
| in the keyword argument list. For example: dict(one=1, two=2)
|
||||
|
|
||||
| Methods defined here:
|
||||
|
||||
|
||||
>>> dict(__[('sape', 4139), ('guido', 4127), ('jack', 4098)]__)
|
||||
{'sape': 4139, 'jack': 4098, 'guido': 4127}
|
||||
|
||||
>>> dict([(x, x**2) for x in (2, 4, 6)]) # use a list comprehension
|
||||
{2: 4, 4: 16, 6: 36}
|
||||
|
||||
>>> dict(sape=4139, guido=4127, jack=4098)
|
||||
{'sape': 4139, 'jack': 4098, 'guido': 4127}
|
||||
tel = {'jack': 4098, 'sape': 4139}
|
||||
|
||||
>>> dc=dict(["df","12"]);dc #["df","12"]为一科迭代对象,每次返回的元素为两个字符的str,所以可以被unpack给key,value
|
||||
{'1': '2', 'd': 'f'}
|
||||
>>> dc=dict(["df",__"123"__]);dc
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
ValueError: dictionary update sequence element __#1 has length 3; 2 is required__
|
||||
>>>
|
||||
20
Zim/Programme/python/python笔记/float.txt
Normal file
20
Zim/Programme/python/python笔记/float.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:20:56+08:00
|
||||
|
||||
====== float ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
>>> float("0xff")
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
ValueError: invalid literal for float(): 0xff
|
||||
>>>
|
||||
|
||||
>>> __float.fromhex("0xfff")__
|
||||
4095.0
|
||||
>>>
|
||||
|
||||
>>> float("0.111")
|
||||
0.111
|
||||
>>>
|
||||
37
Zim/Programme/python/python笔记/int_long.txt
Normal file
37
Zim/Programme/python/python笔记/int_long.txt
Normal file
@@ -0,0 +1,37 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:20:52+08:00
|
||||
|
||||
====== int long ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
>>> 1&2 #按__位与__
|
||||
0
|
||||
|
||||
>>> 0xff&0xf1 #按位与
|
||||
241
|
||||
>>> 0xff&0xf0
|
||||
240
|
||||
>>> __hex__(0xff&0xf0) #返回的__字符串__
|
||||
'0xf0'
|
||||
__与hex()类似, bin(), oct()等返回的都是int或long型的字符串代表__
|
||||
|
||||
>>> 1&&2 __#python没有&&, ||, !逻辑运算符,但是有and, or, not,而且这三个逻辑运算符返回的是最后一个元素的内容__
|
||||
File "<stdin>", line 1
|
||||
1&&2
|
||||
^
|
||||
SyntaxError: invalid syntax
|
||||
|
||||
>>> 1 and 2 __#返回的是最后一个元素的内容而不是True或False,这里为2__
|
||||
2
|
||||
|
||||
>>> 'fff' & 'dfad' __#str类型没有定义__and__方法,所以没有位运算__
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: unsupported operand type(s) for &: 'str' and 'str'
|
||||
>>> help(str)
|
||||
|
||||
>>> 'fff' and 'dfad'
|
||||
'dfad'
|
||||
>>>
|
||||
|
||||
25
Zim/Programme/python/python笔记/list.txt
Normal file
25
Zim/Programme/python/python笔记/list.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:21:01+08:00
|
||||
|
||||
====== list ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
>>> l=range(1,10)
|
||||
>>> l
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
>>> __l[1:8:-1]__
|
||||
[]
|
||||
>>> l[8:1:-1]
|
||||
[9, 8, 7, 6, 5, 4, 3]
|
||||
>>> __l[::-1]__
|
||||
[9, 8, 7, 6, 5, 4, 3, 2, 1]
|
||||
>>> l[-1::-1]
|
||||
[9, 8, 7, 6, 5, 4, 3, 2, 1]
|
||||
>>> __l[-1:-9:-1]__
|
||||
[9, 8, 7, 6, 5, 4, 3, 2]
|
||||
>>> l[:]
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
>>> l[::]
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
|
||||
18
Zim/Programme/python/python笔记/set.txt
Normal file
18
Zim/Programme/python/python笔记/set.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:21:08+08:00
|
||||
|
||||
====== set ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
>>> set1=set(1,2,3)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: set expected __at most 1 arguments,__ got 3
|
||||
|
||||
>>> set1=set((1,2,3))
|
||||
>>> set1
|
||||
set([1, 2, 3])
|
||||
>>>
|
||||
|
||||
|
||||
40
Zim/Programme/python/python笔记/str.txt
Normal file
40
Zim/Programme/python/python笔记/str.txt
Normal file
@@ -0,0 +1,40 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:20:41+08:00
|
||||
|
||||
====== str ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
| join(...)
|
||||
| S.join(iterable) -> string
|
||||
|
|
||||
| Return a string which is the concatenation of __the strings in the__
|
||||
__ | iterable__. The separator between elements is S.
|
||||
|
||||
iterable迭代器对象每次返回的__必须是字符串对象__。
|
||||
|
||||
>>> ":".join("abcd")
|
||||
'a:b:c:d'
|
||||
|
||||
>>> ":".join(['a','b','c','d'])
|
||||
'a:b:c:d'
|
||||
|
||||
>>> ":".join(['a',__123__,'c'])
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: sequence item 1: __expected string__, int found
|
||||
|
||||
>>> ":".join(['a',['ab'],'c'])
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: sequence item 1: expected string, list found
|
||||
>>>
|
||||
|
||||
| rsplit(...)
|
||||
| S.rsplit([sep [,maxsplit]]) -> list of strings
|
||||
|
|
||||
| Return a list of the words in the string S, using sep as the
|
||||
| delimiter string, starting at the end of the string and working
|
||||
| to the front. If maxsplit is given, at most maxsplit splits are
|
||||
| done. If sep is not specified or is __None__, any whitespace string
|
||||
| is a separator.
|
||||
38
Zim/Programme/python/python笔记/unpack.txt
Normal file
38
Zim/Programme/python/python笔记/unpack.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:47:32+08:00
|
||||
|
||||
====== unpack ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
>>> for k,v in ["fdf",23,"dfdf",33]:
|
||||
... print k,v
|
||||
...
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
ValueError: __too many__ values to unpack
|
||||
|
||||
顺序容器类型如str, list, tuple__每次迭代时只能返回其中的一个元素__。
|
||||
所以第一次返回循环返回**"fdf"**,但是它有三个元素最多只能赋值给两个
|
||||
变量。
|
||||
|
||||
>>> for k,v in "dfdf":
|
||||
... print k,v
|
||||
...
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
ValueError: __need more than 1 value__ to unpack
|
||||
|
||||
字符串迭代时,每次返回其中的一个字符。所以最多只能unpack给一个变量。
|
||||
|
||||
>>> k,v="dfdf"
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
ValueError: __too many values to unpack__
|
||||
|
||||
unpack一个顺序容器类型时,左边变量的数目必须要与容器中元素的个数相同。
|
||||
|
||||
>>> k,v="df"
|
||||
>>> print k,v
|
||||
d f
|
||||
>>>
|
||||
7
Zim/Programme/python/python笔记/内置函数.txt
Normal file
7
Zim/Programme/python/python笔记/内置函数.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:21:30+08:00
|
||||
|
||||
====== 内置函数 ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
121
Zim/Programme/python/编写_Unix_管道风格的_Python_代码.txt
Normal file
121
Zim/Programme/python/编写_Unix_管道风格的_Python_代码.txt
Normal file
@@ -0,0 +1,121 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T22:01:26+08:00
|
||||
|
||||
====== 编写 Unix 管道风格的 Python 代码 ======
|
||||
Created Thursday 04 October 2012
|
||||
http://www.oschina.net/question/54100_11910
|
||||
|
||||
先推荐一份幻灯片,David Beazley ("Python essiential reference", PLY 的作者) 在 PyCon’2008 上报告的幻灯片,强烈推荐!!这篇文章的很多内容都来自或者受这份幻灯片的启发而来。
|
||||
|
||||
在上一篇文章里介绍了 Unix 管道的好处,那可不可以在写程序时也使用这样的思想呢?当然可以。看过 SICP 就知道,其实函数式编程中的 __map, filter__ 都可以看作是管道思想的应用。但其实管道的思想不仅可以在函数式语言中使用,只要语言支持定义函数,有能够存放一组数据的数据结构,就可以使用管道的思 想。
|
||||
|
||||
一个日志处理任务
|
||||
|
||||
这里直接以前面推荐的幻灯片里的例子来说明,应用场景如下:
|
||||
|
||||
某个目录及子目录下有一些 web 服务器的日志文件,日志文件名以 access-log 开头
|
||||
日志格式如下
|
||||
81.107.39.38 - ... "GET /ply/ply.html HTTP/1.1" 200 97238
|
||||
81.107.39.38 - ... "GET /ply HTTP/1.1" 304 -
|
||||
其中最后一列数字为发送的字节数,若为 ‘-’ 则表示没有发送数据
|
||||
|
||||
目标是算出总共发送了多少字节的数据,实际上也就是要把日志记录的没一行的最后一列数值加起来
|
||||
我不直接展示如何用 Unix 管道的风格来处理这个问题,而是先给出一些“不那么好”的代码,指出它们的问题,最后再展示管道风格的代码,并介绍如何使用 generator 来避免效率上的问题。想直接看管道风格的,点这里。
|
||||
|
||||
问题并不复杂,几个 for 循环就能搞定:
|
||||
|
||||
sum = 0
|
||||
for path, dirlist, filelist in __os.walk(top)__:
|
||||
for name in __fnmatch.filter__(filelist, "access-log*"):
|
||||
# 对子目录中的每个日志文件进行处理
|
||||
with open(name) as f:
|
||||
for line in f:
|
||||
if line[-1] == '-':
|
||||
continue
|
||||
else:
|
||||
sum += int(line__.rsplit(None, 1)__[1])
|
||||
|
||||
利用 os.walk 这个问题解决起来很方便,由此也可以看出 python 的 for 语句做遍历是多么的方便,不需要额外控制循环次数的变量,省去了设置初始值、更新、判断循环结束条件等工作,相比 C/C++/Java 这样的语言真是太方便了。看起来一切都很美好。
|
||||
|
||||
然而,设想以后有了新的统计任务,比如:
|
||||
|
||||
1. 统计某个特定页面的访问次数
|
||||
2. 处理另外的一些日志文件,日志文件名字以 error-log 开头
|
||||
|
||||
完成这些任务直接拿上面的代码过来改改就可以了,文件名的 pattern 改一下,处理每个文件的代码改一下。其实每次任务的处理中,找到特定名字为特定 pattern 的文件的代码是一样的,直接修改之前的代码其实就引入了重复。
|
||||
|
||||
如果重复的代码量很大,我们很自然的会注意到。然而 python 的 for 循环实在太方便了,像这里找文件的代码一共就两行,哪怕重写一遍也不会觉得太麻烦。for 循环的方便使得我们会忽略这样简单代码的重复。然而,再怎么方便好用,for 循环无法重用,只有把它放到函数中才能进行重用。
|
||||
|
||||
(先考虑下是你会如何避免这里的代码的重复。下面马上出现的代码并不好,是“误导性”的代码,我会在之后再给出“更好”的代码。)
|
||||
|
||||
因此,我们__把上面代码中不变的部分提取成一个通用的函数,可变的部分以参数的形式传入__,得到下面的代码。
|
||||
|
||||
def generic_process(topdir, filepat, processfunc):
|
||||
for path, dirlist, filelist in os.walk(top):
|
||||
for name in fnmatch.filter(filelist, filepat):
|
||||
with open(name) f:
|
||||
processfunc(f)
|
||||
|
||||
sum = 0
|
||||
# 很遗憾,python 对 closure 中的变量不能进行赋值操作,
|
||||
# 因此这里只能使用全局变量
|
||||
def add_count(f):
|
||||
global sum
|
||||
for line in f:
|
||||
if line[-1] == '-':
|
||||
continue
|
||||
else:
|
||||
sum += int(line.rsplit(None, 1)[1])
|
||||
|
||||
generic_process('logdir', 'access-log*', add_count)
|
||||
|
||||
看起来不变和可变的部分分开了,然而 generic_process 的设计并不好。它除了寻找文件以外还调用了日志文件处理函数,因此在其他任务中很可能就无法使用。另外 add_count 的参数必须是 file like object,因此测试时不能简单的直接使用字符串。
|
||||
|
||||
===== 管道风格的程序 =====
|
||||
下面考虑用 Unix 的工具和管道我们会如何完成这个任务:
|
||||
|
||||
find logdir -name "access-log*" | \
|
||||
xargs cat | \
|
||||
grep '[^-]$' | \
|
||||
awk '{ total += $NF } END { print total }'
|
||||
|
||||
find 根据文件名 pattern 找到文件,cat 把所有文件内容合并输出到 stdout,grep 从 stdin 读入,过滤掉行末为 ‘-’ 的行,awk 提取每行最后一列,将数值相加,最后打印出结果。(省掉 cat 是可以的,但这样一来 grep 就需要直接读文件而不是只从标准输入读。)
|
||||
|
||||
我们可以在 python 代码中模拟这些工具,__Unix 的工具通过文本来传递结果,在 python 中可以使用 list__。
|
||||
|
||||
def find(topdir, filepat, processfunc):
|
||||
files = []
|
||||
for path, dirlist, filelist in os.walk(top):
|
||||
for name in fnmatch.filter(filelist, filepat):
|
||||
files.append(name)
|
||||
return files
|
||||
|
||||
def cat(files):
|
||||
lines = []
|
||||
for file in files:
|
||||
with open(file) as f:
|
||||
for line in f:
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def grep(pattern, lines):
|
||||
result = []
|
||||
import re
|
||||
pat = re.compile(pattern)
|
||||
for line in lines:
|
||||
if pat.search(line):
|
||||
result.append(line)
|
||||
resurn result
|
||||
|
||||
lines = grep('[^-]$', cat(find('logdir', 'access-log*')))
|
||||
col = (line.rsplit(None, 1)[1] for line in lines)
|
||||
print sum(int(c) for c in col)
|
||||
|
||||
有了 find, cat, grep 这三个函数,只需要连续调用就可以像 Unix 的管道一样将这些函数组合起来。数据在管道中的变化如下图(简洁起见,过滤器直接标在箭头上 ):
|
||||
|
||||
{{./1.gif}}
|
||||
|
||||
看起来现在的代码行数比最初直接用 for 循环的代码要多,但现在的代码就像 Unix 的那些小工具一样,每一个都更加可能被用到。我们可以把更多常用的 Unix 工具用 Python 来模拟,从而在 Python 代码中以 Unix 管道的风格来编写代码。
|
||||
|
||||
不过上面的代码性能很差,多个临时的 list 被创建。解决的办法是用 generator,因为篇幅比较长,具体做法放到下一篇文章中。
|
||||
BIN
Zim/Programme/python/编写_Unix_管道风格的_Python_代码/1.gif
Normal file
BIN
Zim/Programme/python/编写_Unix_管道风格的_Python_代码/1.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -177,7 +177,7 @@ This manual is meant as a brief introduction to features found in Bash. The Bash
|
||||
|
||||
===== 1 Introduction =====
|
||||
|
||||
* What is Bash?: A short description of Bash.
|
||||
* What is Bash? :A short description of Bash.
|
||||
* What is a shell?: A brief introduction to shells.
|
||||
|
||||
===== 1.1 What is Bash[bæʃ]? =====
|
||||
@@ -275,9 +275,9 @@ This chapter briefly summarizes the shell's `__building blocks__': commands, con
|
||||
|
||||
When the shell reads input, it proceeds through a sequence of operations. If the input indicates the beginning of a comment, the shell ignores the comment symbol (‘#’), and the rest of that line.
|
||||
|
||||
Otherwise, roughly speaking, the shell reads its input and divides the input into __words and operators__, employing the __quoting rules __to select which meanings to assign various words and characters.
|
||||
Otherwise, roughly speaking, the shell reads its input and divides the input into __words(words是一组连续的字符,用metacharacter分割。) and operators(具有特定功能的字符序列,由control operator和重定向符组成)__, employing the __quoting rules __to select which meanings to assign various words and characters.
|
||||
|
||||
The shell then parses these tokens into commands and other constructs, removes the **special meaning** of certain words or characters, expands others, redirects input and output as needed, executes the specified command, waits for the command's exit status, and makes that exit status available for further inspection or processing.
|
||||
The shell then parses these tokens(由word和operator组成。) into commands and other constructs, removes the **special meaning** of certain words or characters, expands others, redirects input and output as needed, executes the specified command, waits for the command's exit status, and makes that exit status available for further inspection or processing.
|
||||
|
||||
==== 3.1.1 Shell Operation ====
|
||||
|
||||
@@ -299,7 +299,7 @@ The following is a brief description of the shell's operation when it reads and
|
||||
* ANSI-C Quoting: How to expand ANSI-C sequences in quoted strings.
|
||||
* Locale Translation: How to translate strings into different languages.
|
||||
|
||||
Quoting is used to __remove the special meaning of certain characters or words__ to the shell. Quoting can be used to disable special treatment for special characters, to prevent reserved words from being recognized as such, and to prevent parameter expansion.
|
||||
Quoting is used to __remove the special meaning of certain characters or words__ to the shell. Quoting can be used to disable special treatment for special characters, to prevent reserved words from being recognized as such, and to prevent parameter expansion.
|
||||
|
||||
Each of the shell metacharacters (see Definitions) has special meaning to the shell and must be quoted if it is to__ represent itself__. When the command history expansion facilities are being used (see History Interaction), the history expansion character, usually__ ‘!’__, must be quoted to prevent history expansion. See Bash History Facilities, for more details concerning history expansion.
|
||||
|
||||
@@ -312,7 +312,8 @@ A non-quoted backslash ‘\’ is the Bash escape character. It preserves the __
|
||||
|
||||
=== 3.1.2.2 Single Quotes(单引号中的任何字符都无特殊含义,因此单引号中的\'是无效的) ===
|
||||
|
||||
Enclosing characters in single quotes (‘'’) preserves the__ literal value of each character__ within the quotes. A single quote may **not occur **between single quotes, even when preceded by a backslash.单引号中的\无特殊含义。
|
||||
Enclosing characters in single quotes (‘'’) preserves the__ literal value of each character__ within the quotes. A single quote may **not occur **between single quotes, even when preceded by a backslash.
|
||||
单引号中的\无特殊含义。
|
||||
|
||||
=== 3.1.2.3 Double Quotes(除$ ` \和!外,其它任何字符都无特殊含义。其中\只有当后接$ ` \ 或换行时才有特殊含义,否则为正常字符) ===
|
||||
|
||||
@@ -320,9 +321,7 @@ Enclosing characters in double quotes (‘"’) preserves the literal value of a
|
||||
|
||||
The backslash retains its special meaning__ only when followed by one of the following characters: ‘$’, ‘`’, ‘"’, ‘\’, or newline__. Within double quotes, backslashes that are followed by one of these characters are **removed**. Backslashes preceding characters without a special meaning are** left unmodified**. A double quote may be quoted within double quotes by preceding it with a backslash. If enabled, history expansion will be performed unless an ‘!’ appearing in double quotes is escaped using a backslash. The backslash preceding the ‘!’ is not removed.
|
||||
|
||||
单独列出的
|
||||
|
||||
===== 特殊规则 =====
|
||||
=== 特殊规则 ===
|
||||
:__The special parameters ‘*’ and ‘@’ have special meaning when in double quotes__ (see Shell Parameter Expansion).
|
||||
|
||||
|
||||
@@ -369,7 +368,7 @@ __The expanded result is single-quoted__, as if the dollar sign had not been pre
|
||||
[geekard@geekard ~]$ set |grep IFS
|
||||
__IFS=$' \t\n'__
|
||||
[geekard@geekard ~]$
|
||||
原来是因为值中含有ANSI-C的转义字符,所以使用了$'...'形式。
|
||||
原来是因为值中含有ANSI-C的转义字符,所以使用了 **$'...' **形式。
|
||||
[geekard@geekard ~]$ echo ' \t\n' #正常情况下单引号中的内容**无任何特殊含义**,所以,\t\n并__不是__转义字符。
|
||||
\t\n
|
||||
[geekard@geekard ~]$ echo $' \t\n' #但是,如果使用$' \t\n'形式,bash将其解释为__含有转义字符序列的特殊字符串__
|
||||
@@ -385,7 +384,7 @@ cd
|
||||
|
||||
=== 3.1.2.5 Locale-Specific Translation ===
|
||||
|
||||
A double-quoted string preceded by a dollar sign (‘$’) will cause the string to be translated according to the c**urrent locale**. If the current locale is__ C or POSIX__, the dollar sign is ignored. If the string is translated and replaced, the replacement is **double-quoted**.
|
||||
A double-quoted string preceded by a dollar sign (‘$’) will cause the string to be translated according to the **current locale**. If the current locale is__ C or POSIX__, the dollar sign is ignored. If the string is translated and replaced, the replacement is **double-quoted**.
|
||||
|
||||
Some systems use the message catalog selected by the LC_MESSAGES shell variable. Others create the name of the message catalog from the value of the TEXTDOMAIN shell variable, possibly adding a suffix of ‘.mo’. If you use the TEXTDOMAIN variable, you may need to set the TEXTDOMAINDIR variable to the location of the message catalog files. Still others use both variables in this fashion: TEXTDOMAINDIR/LC_MESSAGES/LC_MESSAGES/TEXTDOMAIN.mo.
|
||||
|
||||
@@ -407,32 +406,31 @@ More complex shell commands are composed of simple commands __arranged together
|
||||
* Compound Commands: Shell commands for control flow.
|
||||
* Coprocesses: Two-way communication between commands.
|
||||
最后四个都是将简单命令组合为复杂命令的方法,它们__对外是一个整体__,可以被重定向。
|
||||
3.2.1 Simple Commands
|
||||
|
||||
==== 3.2.1 Simple Commands ====
|
||||
A simple command is the kind of command encountered most often. It's just a** sequence of words separated by **__blanks__**, terminated by one of the shell's **__control operators__ (see Definitions). The first word generally specifies a command to be executed, with the rest of the words being that command's arguments.
|
||||
|
||||
The return status (see Exit Status) of a simple command is its exit status as provided by the posix 1003.1 waitpid function, or __128+n__ if the command was terminated by **signal n**.
|
||||
|
||||
|
||||
==== 3.2.2 Pipelines ====
|
||||
|
||||
A pipeline is a sequence of simple commands separated by one of the control operators ‘|’ or ‘|&’.
|
||||
|
||||
The format for a pipeline is
|
||||
|
||||
[time [-p]] [!] command1 [ [| or |&] command2 ...]
|
||||
[time [-p]] [!] command1 [ [| or __|& __] command2 ...]
|
||||
|
||||
The output(一般是标准输出) of each command in the pipeline is connected via a pipe to the input of the next command. That is, each command reads the previous command's output. This connection is performed before any redirections specified by the command. 管道在任何重定向操作之前完成。
|
||||
The output (一般是标准输出) of each command in the pipeline is connected via a pipe to the input of the next command. That is, each command reads the previous command's output. This connection is performed before any redirections specified by the command. 管道在任何重定向操作之前完成。
|
||||
|
||||
If ‘|&’ is used, the __standard error__ of command1 is connected to command2's standard input through the pipe; it is shorthand for __2>&1 |__. This implicit redirection of the standard error is performed after any redirections specified by the command.
|
||||
|
||||
The reserved word time causes timing statistics to be printed for the pipeline once it finishes. The statistics currently consist of __elapsed (wall-clock) time and user and system time__ consumed by the command's execution. The -p option changes the output format to that specified by posix. The TIMEFORMAT variable may be set to a format string that specifies how the timing information should be displayed. See Bash Variables, for a description of the available formats. The use of time as a reserved word permits the timing of shell builtins, shell functions, and pipelines. An external time command cannot time these easily.
|
||||
The reserved word __time__ causes timing statistics to be printed for the pipeline once it finishes. The statistics currently consist of __elapsed (wall-clock) time and user and system time__ consumed by the command's execution. The -p option changes the output format to that specified by posix. The TIMEFORMAT variable may be set to a format string that specifies how the timing information should be displayed. See Bash Variables, for a description of the available formats. The use of time as a reserved word permits the timing of shell builtins, shell functions, and pipelines. An external time command cannot time these easily.
|
||||
|
||||
If the pipeline is not executed asynchronously (see Lists), the shell waits for all commands in the pipeline to complete.
|
||||
|
||||
Each command in a pipeline is executed __in its own subshell __(see Command Execution Environment). The exit status of a pipeline is the exit status of the__ last command__ in the pipeline, unless the** pipefail option** is enabled (see The Set Builtin). If pipefail is enabled, the pipeline's return status is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands exit successfully. If the reserved word ‘!’ precedes the pipeline, the exit status is the logical negation of the exit status as described above. The shell waits for all commands in the pipeline to terminate before returning a value.
|
||||
|
||||
管道线中各命令都在**各自的subshell**中执行,只有当最后一个命令执行完毕时,整个命令才结束,同时整个管道线命令的退出值为**最后一个命令**的退出值(而非中间某个命令的退出值)。如果打开了pipefail选项,则管道线中任何一个命令执行失败(退出吗非0),则整个管道线退出。
|
||||
管道线中各命令都在__各自__**的subshell**中执行,只有当最后一个命令执行完毕时(不管中间的命令是否执行成功),整个命令才结束,同时整个管道线命令的退出值为**最后一个命令**的退出值(而非中间某个命令的退出值)。如果打开了pipefail选项,则管道线中任何一个命令执行失败(退出码非0),则整个管道线退出。
|
||||
|
||||
|
||||
==== 3.2.3 Lists of Commands ====
|
||||
@@ -506,7 +504,7 @@ Note that wherever a ‘;’ appears in the description of a command's syntax, i
|
||||
An alternate form of the for command is also supported: 借鉴了C的语法规则
|
||||
|
||||
for __(( expr1 ; expr2 ; expr3 )) __; do commands ; done
|
||||
|
||||
#这里的expr值得是算术表达式。
|
||||
First, the arithmetic expression expr1 is evaluated according to the rules described below (see Shell Arithmetic). The arithmetic expression expr2 is then evaluated repeatedly __until it evaluates to zero__. Each time expr2 evaluates to a non-zero value, commands are executed and the arithmetic expression expr3 is evaluated. If any expression is omitted, it behaves as if it evaluates to 1. The return value is the exit status of the last command in list that is executed, or false if any of the expressions is invalid.
|
||||
|
||||
The** break** and **continue** builtins (see Bourne Shell Builtins) may be used to control loop execution.
|
||||
@@ -532,7 +530,7 @@ The** break** and **continue** builtins (see Bourne Shell Builtins) may be used
|
||||
case will selectively execute the command-list corresponding to the **first** pattern that matches word. If the shell option nocasematch (see the description of shopt in The Shopt Builtin) is enabled, the match is performed without regard to the case of alphabetic characters. The __‘|’__ is used to separate multiple patterns, and the ‘)’ operator** terminates a pattern list**. A list of patterns and an associated command-list is known as a__ clause__.
|
||||
|
||||
Each clause must be terminated with **‘;;’, ‘;&’, or ‘;;&’.** The word undergoes tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal **before** matching is attempted. Each pattern undergoes tilde expansion, parameter expansion, command substitution, and arithmetic expansion.
|
||||
pattern使用的是bash的匹配模式,多个pattern只能使用|相连,表示**或关系**__。bash会对模式和command-list中的内容进行“~”扩展、参数扩展、命令替换和算术扩展__。
|
||||
pattern使用的是__bash的匹配模式__,多个pattern只能使用|相连,表示**或关系**__。bash会对模式和command-list中的内容进行“~”扩展、参数扩展、命令替换和算术扩展__。
|
||||
|
||||
There may be an arbitrary number of case clauses, each terminated by a ‘;;’, ‘;&’, or ‘;;&’. The first pattern that matches determines the command-list that is executed.
|
||||
|
||||
@@ -551,7 +549,6 @@ pattern使用的是bash的匹配模式,多个pattern只能使用|相连,表
|
||||
If the ‘;;’ operator is used, no subsequent matches are attempted after the first pattern match. Using ‘__;&__’ in place of ‘;;’ causes execution to continue with the command-list associated with the **next clause**, if any. Using ‘;;&’ in place of ‘;;’ causes the shell to __test__ the patterns in the next clause, if any, and execute any associated command-list on a successful match.
|
||||
使用;;终止符号时,bash只会执行其前面的command-list然后退出case结构;使用;&结构时,bash还会__接着执行下一个__pattern对应的commandlist;使用;;&时,bash会__先判断__下一个pattern是否匹配,若是就执行对应的commandlist。
|
||||
|
||||
|
||||
The return status is zero if no pattern is matched. Otherwise, the return status is the exit status of the command-list executed.
|
||||
* select
|
||||
The select construct allows the __easy generation of menus__. It has almost the same syntax as the for command:
|
||||
@@ -573,9 +570,9 @@ The **PS3 **prompt is then displayed and a line is read from the standard input.
|
||||
done
|
||||
|
||||
|
||||
* ((...))
|
||||
* ((...)) #会被shell当作命令来执行,返回结果为true或false。
|
||||
|
||||
(( expression ))
|
||||
(( expression )) #expression必须为算术表达式,不能是命令,不会被扩展。
|
||||
|
||||
The__ arithmetic expression__ is evaluated according to the rules described below (see Shell Arithmetic). If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1. This is exactly equivalent to
|
||||
|
||||
@@ -587,7 +584,7 @@ The **PS3 **prompt is then displayed and a line is read from the standard input.
|
||||
|
||||
Return a status of 0 or 1 depending on the evaluation of the __conditional expression__ **expression**. Expressions are composed of the primaries described below in Bash Conditional Expressions. __Word splitting and filename expansion are not performed __on the words between the ‘[ [’ and ‘] ]’; tilde expansion, parameter and variable expansion, arithmetic expansion, command substitution, process substitution, and **quote removal** are performed. Conditional operators such as ‘-f’ must be unquoted to be recognized as primaries.
|
||||
|
||||
When used with ‘[[’, The __‘<’__** and **__‘>’__ operators __sort lexicographically__ using the current locale.
|
||||
When used with ‘[[’, The __‘<’__** and **__‘>’__ operators __sort lexicographically(对字符串而言,不能用于数字)__ using the current locale.
|
||||
|
||||
When the__ ‘==’ and ‘!=’__ operators are used, the string to the right of the operator is __considered a pattern__ and matched according to the rules described below in Pattern Matching. If the shell option nocasematch (see the description of shopt in The Shopt Builtin) is enabled, the match is performed without regard to the case of alphabetic characters. The return value is 0 if the string matches (‘==’) or does not match (‘!=’)the pattern, and 1 otherwise. Any part of the pattern may be quoted to force it to be matched as a string.
|
||||
|
||||
@@ -606,6 +603,7 @@ The **PS3 **prompt is then displayed and a line is read from the standard input.
|
||||
|
||||
The && and || operators do not evaluate expression2 if the value of expression1 is sufficient to determine the return value of the entire conditional expression.
|
||||
|
||||
对于[...]或test命令而言,其中不能有&&或||操作符,它们必须要放在[..]或test的外面;但是[[可以在内部使用它们。
|
||||
|
||||
=== 3.2.4.3 Grouping Commands ===
|
||||
|
||||
@@ -613,9 +611,9 @@ Bash provides two ways to group a list of commands to be __executed as a unit__.
|
||||
|
||||
()
|
||||
|
||||
( list ) #在一个__子shell中__执行list中的命令。括号是运算符,因此它与list之间可以没有空格。
|
||||
( list ) #在同一个__子shell中__执行list中的命令。括号是运算符,因此它与list之间可以没有空格。
|
||||
|
||||
Placing a list of commands between parentheses causes __a subshell environment __to be created (see Command Execution Environment), and each of the commands in list to be executed in **that subshell**. Since the list is executed in a subshell, variable assignments do not remain in effect after the subshell completes.
|
||||
Placing a list of commands between parentheses causes __a subshell environment __to be created (see Command Execution Environment), and each of the commands in list to be executed in **that subshell(正常情况下,list中的每个命令都是在不同的subshell中执行的。)**. Since the list is executed in a subshell, variable assignments do not remain in effect after the subshell completes.
|
||||
{}
|
||||
|
||||
{ list__; __} #在__当前shell__中执行list中的命令,注意__最后的分号不可少。大括号是关键字,因此,它与list之间必须要有空格。__
|
||||
@@ -632,7 +630,7 @@ A coprocess is a shell command preceded by the __coproc reserved word__. A copro
|
||||
|
||||
The format for a coprocess is:
|
||||
|
||||
coproc [NAME] **com{** #注意是**单条命令**,如果使用符合命令则必须用{}引住。
|
||||
coproc [NAME] **com{ } ** #注意是**单条命令**,如果使用复合命令则必须用{}引住。
|
||||
|
||||
This creates a coprocess named NAME. If NAME is not supplied, the default name is __COPROC__. NAME must not be supplied if command is a simple command (see Simple Commands); otherwise, it is interpreted as the first word of the simple command.
|
||||
|
||||
@@ -1335,7 +1333,8 @@ The exit status of an executed command is the value returned by the **waitpid**
|
||||
|
||||
For the shell's purposes, a command which exits with __a zero exit status has succeeded__. A non-zero exit status indicates failure. This seemingly counter-intuitive scheme is used so there is one well-defined way to indicate success and a variety of ways to indicate various failure modes. When a command __terminates on a fatal signal whose number is N, Bash uses the value 128+N as the exit status__.
|
||||
|
||||
* If a command is not found, the child process created to execute it returns a status of __127__. If a command is found but is not executable, the return status is __126__.
|
||||
* If a command is not found, the child process created to execute it returns a status of
|
||||
* If a command is found but is not executable, the return status is
|
||||
* If a command fails because of an error **during expansion or redirection**, the exit status is greater than zero.
|
||||
* The exit status is used by the Bash __conditional commands__ (see Conditional Constructs) and some of the __list constructs__ (see Lists).
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@ ls: cannot access thisfiledoesntexist: No such file or directory #coproc的出
|
||||
#let the output of the coprocess go to stdout
|
||||
$ __{ coproc mycoproc { awk '{print "foo" $0;fflush()}' ;} >&3 ;} 3>&1__
|
||||
|
||||
|
||||
|
||||
{....} 分组命令代表在**当前shell中**执行其中的命令,所以3>&1,表示将{...}中使用的3描述符与__当前shell__的1描述符相连接。
|
||||
而 {...}中的>&3是在coproc与当前shell的pipe建立之后执行的,其标准输出的描述符其环境中的3相连后,实际上是与当前shell的标准输出相连。
|
||||
|
||||
|
||||
@@ -113,7 +113,6 @@ What happens if someone kills your script while critical-section is running? The
|
||||
|
||||
if [ ! -e $lockfile ]; then
|
||||
**trap "rm -f $lockfile; exit" INT TERM EXIT #在捕获信号前需安装信号及其执行的命令**
|
||||
~~ #上述命令有一个bug,存在重复触发的死循环。~~
|
||||
touch $lockfile
|
||||
critical-section
|
||||
rm $lockfile
|
||||
|
||||
@@ -57,7 +57,7 @@ exit 0
|
||||
**********竞争处理
|
||||
if ( __set -o noclobber__; echo "$$" > "$lockfile") 2> /dev/null; #如果lockfile存在,则**含有重定向的命令**出错返回
|
||||
then
|
||||
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
|
||||
trap 'rm -f "$lockfile"; __exit $?__' INT TERM EXIT
|
||||
critical-section
|
||||
rm -f "$lockfile"
|
||||
trap __-__ INT TERM EXIT
|
||||
@@ -69,7 +69,6 @@ fi
|
||||
|
||||
if [ ! -e $lockfile ]; then
|
||||
**trap "rm -f $lockfile; exit" INT TERM EXIT #在捕获信号前需安装信号及其执行的命令**
|
||||
#上述命令有一个bug,存在重复触发的死循环。
|
||||
touch $lockfile
|
||||
critical-section
|
||||
rm $lockfile
|
||||
@@ -78,7 +77,7 @@ else
|
||||
echo "critical-section is already running"
|
||||
fi
|
||||
|
||||
****************Counted loops
|
||||
****************Counted loops---------------
|
||||
# Three expression for loop:
|
||||
for__ (( i = 0; i < 20; i++ ))__
|
||||
do
|
||||
|
||||
@@ -28,7 +28,7 @@ Created Friday 23 December 2011
|
||||
**bar,** i=
|
||||
[geekard@geekard ~]$ echo ${i:=bar}, i=$i #如果i变量未初始化或初始化为空,则参数扩展为__字符串bar__,i变量__设置为字符串bar__。
|
||||
**bar**, i=**bar**
|
||||
[geekard@geekard ~]$ unset i; echo ${i:?bar}, i=$i; date #如果变量i未初始化或初始化为空,则显示**i: =bar**同时__停止当前命令和后续命令的执行__。
|
||||
[geekard@geekard ~]$ unset i; echo ${i:?bar}, i=$i; date #如果变量i未初始化或初始化为空,则显示**i: bar**同时__停止当前命令和后续命令的执行__。
|
||||
bash:** i: bar**
|
||||
[geekard@geekard ~]$ echo ${i:+bar}, i=$i #如果变量i存在且不为空,则参数扩展为字符串__bar__的值,否则扩展为__空值__。
|
||||
, i=
|
||||
|
||||
@@ -97,14 +97,14 @@ done
|
||||
|
||||
|
||||
注意:
|
||||
1. <$pipe放在while的里或外面是有区别的,前者每次loop都重新打开$pipe文件,后者在整个loop中只打开文件一次。
|
||||
1. <$pipe放在while的里或外面是有区别的,前者每次loop都重新打开$pipe文件,后者在整个loop中只打开文件一次。
|
||||
2. __bash对pipe是行缓冲__。
|
||||
|
||||
-----------------------------
|
||||
You should __explicitly exit from the trap command__, otherwise the script will continue running past it. You should also catch a few other signals.
|
||||
|
||||
So: trap "rm -f $pipe" EXIT
|
||||
Becomes: trap "rm -f $pipe; exit" INT TERM EXIT
|
||||
Becomes: trap "rm -f $pipe; exit" INT TERM EXIT
|
||||
|
||||
Excellent article. Thank you!
|
||||
----------
|
||||
@@ -113,4 +113,3 @@ Not really necessary in this case
|
||||
__The EXIT trap gets executed when the shell exits regardless of why it exits so trapping INT and TERM aren't really necessary in this case.__
|
||||
|
||||
However, your point about "exit" is good: trapping a signal removes the default action that occurs when a signal occurs, so if the default action is to exit the program and you want that to happen in addition to executing your code, you need to include an exit statement in your code.
|
||||
|
||||
|
||||
@@ -67,12 +67,10 @@ dfs! s
|
||||
[geekard@geekard ~]$
|
||||
|
||||
|
||||
|
||||
|
||||
*********** 字符串中若含有空格、换行、TAB键,则必须要用引号包围:
|
||||
[geekard@geekard ~]$ echo "dfd\df"
|
||||
dfd\df
|
||||
[geekard@geekard ~]$ echo 'df #字符串中可以包含换行,但是C语言的字符串中不行(必须要使用转义字符)。
|
||||
[geekard@geekard ~]$ echo 'df #__Shell字符串中可以包含换行__,但是C语言的字符串中不行(必须要使用转义字符)。
|
||||
> df'
|
||||
df
|
||||
df
|
||||
|
||||
@@ -48,7 +48,8 @@ http://molinux.blog.51cto.com/2536040/469296
|
||||
4
|
||||
|
||||
[root@localhost ~]#
|
||||
[root@localhost ~]# A[0]=9 [root@localhost ~]# A[10]=1
|
||||
[root@localhost ~]# A[0]=9
|
||||
[root@localhost ~]# A[10]=1
|
||||
[root@localhost ~]# echo ${A[*]}
|
||||
9 1
|
||||
[root@localhost ~]# echo ${#A[*]}
|
||||
|
||||
@@ -8,7 +8,7 @@ Created Thursday 22 December 2011
|
||||
===== 条件测试命令: =====
|
||||
* test 和[ 是**等价**的条件测试命令,都可以单独执行但__无输出值,只有退出状态,0表示测试为真,非0表示测试为假。__
|
||||
|
||||
* 和 为**关键字**而非命令,是[ ]的**增强版本**,里面还可以用&&、||、<、>等逻辑和关系运算符(但__不能有算术运算符__),类似C语言的语法,~~因此返回值0表示测试为假,返回值1表示测试为真,这与test和[的返回状态值意义恰好相反,~~一般只用在条件测试中。
|
||||
* [ [ 和 ] ] 为**关键字**而非命令,是[ ]的**增强版本**,里面还可以用&&、||、<、>等逻辑和关系运算符(但__不能有算术运算符__),类似C语言的语法,一般只用在条件测试中。
|
||||
|
||||
* __((...)):比较特殊,先对__**算术、关系、逻辑表达式**__计算(求值),如果结果非空或非零,则返回状态为真(0),否则返回假(1);注意:没有输出值,只有退出状态值。__
|
||||
|
||||
@@ -16,7 +16,7 @@ Created Thursday 22 December 2011
|
||||
|
||||
|
||||
==== 注意: ====
|
||||
* 前三个是__专门用来做条件测试__的(因为它们作为命令执行时,没有输出值),而最后一个是shell用于__命令替换的语法__。
|
||||
* 前三个是__专门用来做条件测试__的(因为它们可以作为命令来执行,执行结果表明是真还是假),而最后一个是shell用于__算术替换的语法__。
|
||||
* 前两个__只能对__数值、字符串、文件做测试,而第三个__只能对__算术、关系、逻辑表达式做条件测试。
|
||||
|
||||
[geekard@geekard ~]$ **((0))**; echo $?
|
||||
@@ -38,7 +38,7 @@ Created Thursday 22 December 2011
|
||||
[geekard@geekard ~]$ __((i=2+3))__; echo i=$i, $? # 对表达式求值,将结果5赋给变量i,__ 5非0__故双括号返回真。
|
||||
i=5, 0
|
||||
[geekard@geekard ~]$
|
||||
[geekard@geekard ~]$ unset i; __$((i=2+3))__; echo i=$i,$? #__ shell__对$((...))中的表达式进行计算,把__执行计算结果当作命令来运行__。
|
||||
[geekard@geekard ~]$ unset i; __$((i=2+3))__; echo i=$i,$? #__ shell__对$((...))中的表达式进行计算,把__执行计算结果当作命令来运行__。也就是说__$(())不能作为命令来执行__。
|
||||
bash: 5: command not found
|
||||
i=5,127
|
||||
[geekard@geekard ~]$
|
||||
@@ -67,7 +67,7 @@ bash: 5: command not found
|
||||
5
|
||||
0
|
||||
0
|
||||
[geekard@geekard ~]$ ((2+3)); echo $?; __echo ((2+3))__** #shell不会自动对((..))求值替换**
|
||||
[geekard@geekard ~]$ ((2+3)); echo $?; __echo ((2+3))__** #shell不会自动对((..))求值替换,可以使用$(())**
|
||||
bash: syntax error near unexpected token `('
|
||||
[geekard@geekard ~]$
|
||||
[geekard@geekard ~]$ echo `((2+3))` #双括号没有输出,只有退出状态,故echo命令输出一空行
|
||||
@@ -90,14 +90,14 @@ bash: syntax error near unexpected token `('
|
||||
(2)字符串测试
|
||||
= 等于
|
||||
!= 不相等
|
||||
-z字符串 字符串长度伪则为真
|
||||
-n字符串 字符串长度不伪则为真
|
||||
-z字符串 字符串长度为0则为真
|
||||
-n字符串 字符串长度不为0则为真
|
||||
(3)文件测试
|
||||
-e文件名 文件存在为真
|
||||
-r文件名 文件存在且为只读时为真
|
||||
-w文件名 文件存在且可写时为真
|
||||
-x文件名 文件存在且可执行为真
|
||||
-s文件名 如果文件存在且长度不了0
|
||||
**-s**文件名 如果文件存在且长度不为0
|
||||
-d文件名 文件存在且为目录时为真
|
||||
-f文件名 文件存在且为普通文件为真
|
||||
-c文件名 文件存在且为字符类型特殊文件为真
|
||||
|
||||
Reference in New Issue
Block a user