add lots file of APUE

This commit is contained in:
geekard
2012-10-30 20:31:20 +08:00
parent 05e8ae5877
commit 6642e173f9
113 changed files with 4954 additions and 181 deletions

View 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;
}

View 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。相比于selectepoll最大的好处在于它**不会随着监听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-connectionepoll的效率并不会比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;
}

View File

@@ -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之后,保证重新建立的所得到的数据绝对不会是发往就连接的数据.

View File

@@ -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) 任务队列:线程池的概念具体到实现则可能是队列,链表之类的数据结构,其中保存执行线程。
  我们实现的通用线程池框架由五个重要部分组成CThreadManageCThreadPoolCThreadCJobCWorkerThread除此之外框架中还包括线程同步使用的类CThreadMutex和CCondition。

View File

@@ -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值非00 = 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值非0l_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中,是允许的.

View File

@@ -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
以上解释了测试12的现象,write一个已经接受到RST的socket系统内核会发送SIGPIPE给发送进程如果进程catch/ignore这个信号write都返回EPIPE错误.
因此,UNP建议应用根据需要处理SIGPIPE信号至少不要用系统缺省的处理方式处理这个信号系统缺省的处理方式是退出进程这样你的应用就很难查处处理进程为什么退出。
原文地址 http://blog.chinaunix.net/u/31357/showart_242605.html

View 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)

View File

@@ -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为非0l_linger为0则套接口关闭时TCP夭折连接TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个__RST__给对方而不是通常的四分组终止序列这避免了**TIME_WAIT**状态;
3、设置 l_onoff 为非0l_linger为0套接口关闭时内核将拖延一段时间由l_linger决定。如果套接口缓冲区中仍残留数据**进程将处于睡眠状态(注意close调用不是立即返回)**,直 到a所有数据发送完且被对方确认之后进行正常的终止序列描述字访问计数为0b延迟时间到。此种情况下应用程序检查close的返回值是非常重要的如果在数据发送完并被确认前时间到(超时)close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。close的成功返回仅告诉我们发送的数据和FIN已由对方TCP确认它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的它将不等待close完成。
2、设置 l_onoff为非0l_linger为0套接口关闭时TCP夭折连接TCP将**丢弃保留**在套接口发送缓冲区中的任何数据并发送一个__RST__给对方而不是通常的四分组终止序列这避免了**TIME_WAIT**状态;
3、设置 l_onoff 为非0l_linger为非0当套接口关闭时内核将拖延一段时间由l_linger决定。如果套接口缓冲区中仍残留数据**进程将处于睡眠状态(注意close调用不是立即返回)**,直 到a所有数据发送完且__被对方确认__之后进行正常的终止序列描述字访问计数为0b延迟时间到。此种情况下应用程序__检查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 非零 优雅 是

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB