select例子:
服务器上用select来监听connfd是否就绪,如果是正常数据的话,则正常处理。如果是异常数据的话,则异常处理。服务器端代码:
/*************************************************************************
> File Name: practice_91.c
> Author:
> Mail:
> Created Time: 2016年02月24日 星期三 15时38分01秒
************************************************************************/
/*
能够同时接收普通数据和带外数据
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
int main(int argc, char *argv[])
{
if(argc <= 2){
printf("%s ip_address port_number\n", argv[0]);
return -1;
}
const char * ip = argv[1];
int port = atoi(argv[2]);
int ret = 0;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
//socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd < 0){
printf("socket error\n");
return -1;
}
//bind
ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
if(ret != 0){
printf("bind error\n");
return -1;
}
//listen
ret = listen(listenfd, 5);
if(ret != 0){
printf("listen error\n");
return -1;
}
//客户端地址
struct sockaddr_in client_addr;
socklen_t client_addrlen = sizeof(client_addr);
//accept
int connfd = accept(listenfd, (struct sockaddr *)&client_addr, &client_addrlen);
if(connfd < 0){
printf("accept error\n");
close(listenfd);
return -1;
}
char buf[1024];
fd_set read_fds;
fd_set exception_fds;
FD_ZERO(&read_fds);
FD_ZERO(&exception_fds);
while(1){
memset(buf, 0, sizeof(buf));
FD_SET(connfd, &read_fds);
FD_SET(connfd, &exception_fds);
ret = select(connfd+1, &read_fds, NULL, &exception_fds, NULL);//会一直阻塞,直到文件描述符就绪;每次事件发生之后,都要再重新设置文件描述符
if(ret < 0){
printf("select error\n");
break;
}
if(FD_ISSET(connfd, &read_fds)){//正常可读事件
ret = recv(connfd, buf, sizeof(buf)-1, 0);
if(ret <= 0){//ret == 0表示对方关闭了连接
break;
}
printf("get %d bytes of normal data: %s\n", ret, buf);
}
else if(FD_ISSET(connfd, &exception_fds)){//对于异常事件
ret = recv(connfd, buf, sizeof(buf)-1, MSG_OOB);
if(ret <= 0){//ret == 0表示对方关闭了连接
break;
}
printf("get %d bytes of oob data: %s\n", ret, buf);
}
}
close(connfd);
close(listenfd);
return 0;
}
客户端代码:
/*************************************************************************
> File Name: 53backlog.c
> Author:
> Mail:
> Created Time: 2016年01月16日 星期六 11时35分15秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>//包含bool类型,C语言是没有bool类型的,C++才有
#include <unistd.h>
#include <errno.h>
static bool stop = false;
static void handle_term(int sig)
{
stop = true;
}
int main(int argc, char *argv[])
{
signal(SIGTERM, handle_term);
if(argc <= 2){
printf("usage: %s ip_address port_number backlog\n", argv[0]);
return 1;
}
const char * ip = argv[1];
int port = atoi(argv[2]);
int backlog = 5;//atoi(argv[3]);
int sock = socket(AF_INET, SOCK_STREAM, 0);
assert(sock >= 0);
//创建ipv4的地址
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET, ip, (void *)&address.sin_addr);
int ret = connect(sock, (struct sockaddr *)&address, sizeof(address));
if(ret < 0)
printf("connect failed.\n");
else{
const char * oob_data = "abc";
const char * normal_data = "123";
printf("send the normal_data: %s\n", normal_data);
send(sock, normal_data, strlen(normal_data), 0);
sleep(1);//发送延迟1秒,如果没有延迟的话,太快有时候会导致收不到oob_data
printf("send the oob_data: %s\n", oob_data);
send(sock, oob_data, strlen(oob_data), MSG_OOB);
sleep(1);
printf("send the normal_data: %s\n", normal_data);
send(sock, normal_data, strlen(normal_data), 0);
}
printf("send over...,按任意键退出\n");
getchar();
close(sock);
return 0;
}
在终端下输入:./practice_server_91 192.168.0.127 12345 //启动了服务器
在另外一个终端下输入:./56senddata 192.168.0.127 12345 //启动了客户端
服务器终端输出为:
get 3 bytes of normal data: 123
get 2 bytes of normal data: ab
get 1 bytes of oob data: c
get 3 bytes of normal data: 123
send the normal_data: 123
send the oob_data: abc
send the normal_data: 123
send over...,按任意键退出
POLL实例
服务器和客户端用poll机制实现,服务器实现对客户端的响应的回显。服务器将客户端的输入直接回复。
服务器用poll机制实现对监听套接字和若干连接套接字进行I/O复用。
客户端用poll机制实现对连接套接字和标准输入文件描述符进行I/O复用。
服务器源代码:
/*************************************************************************
> File Name: practice_91.c
> Author:
> Mail:
> Created Time: 2016年02月24日 星期三 15时38分01秒
************************************************************************/
/*
能够同时接收普通数据和带外数据
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>
#include <poll.h>
#include <errno.h>
#define OPEN_MAX 1024
int do_poll(int listenfd)
{
int connfd, sockfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen = sizeof(cliaddr);
struct pollfd clientfds[1024];
int maxi;
char buf[1024];
int i, num;
int nready;
//添加监听文件描述符
clientfds[0].fd = listenfd;
clientfds[0].events = POLLIN;
//初始化客户连接文件描述符
for(i = 1; i < OPEN_MAX; ++i){
clientfds[i].fd = -1;
}
maxi = 0;
for(; ;){
//获取就绪的文件描述符的个数
nready = poll(clientfds, maxi+1, -1);
if(nready == -1){
printf("poll error\n");
return -1;
}
//测试监听文件描述符是否就绪好
if(clientfds[0].revents & POLLIN){
//接受新的连接
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddrlen);
if(connfd == -1){
if(errno == EINTR)
continue;
else{
printf("accept error\n");
return -1;
}
}
printf("接受新的客户连接:%s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
//将新的连接添加到数组中
for(i = 1; i < OPEN_MAX; ++i){
if(clientfds[i].fd < 0){
clientfds[i].fd = connfd;
break;
}
}
if(i == OPEN_MAX){
printf("too many clients\n");
return -1;
}
clientfds[i].events = POLLIN;
//记录客户连接套接字的个数
maxi = (i > maxi ? i : maxi);
if(--nready <= 0)
continue;
}
//不是监听套接字就绪
//处理客户连接,处理连接套接字
//handle_connectin(clientfds, maxi);
memset(buf, 0, 1024);
for(i = 1; i <= maxi; ++i){
if(clientfds[i].fd < 0)
continue;
//测试连接套接字是否就绪可读
if(clientfds[i].revents & POLLIN){
//有消息则读取到buf中
num = read(clientfds[i].fd, buf, 1024);
if(num == 0){
close(clientfds[i].fd);
clientfds[i].fd = -1;
continue;
}
printf("server read the data is : %s", buf);
write(clientfds[i].fd, buf, num);
}
}
}
}
int main(int argc, char *argv[])
{
if(argc <= 2){
printf("%s ip_address port_number\n", argv[0]);
return -1;
}
const char * ip = argv[1];
int port = atoi(argv[2]);
int ret = 0;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
//socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd < 0){
printf("socket error\n");
return -1;
}
//bind
ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
if(ret != 0){
printf("bind error\n");
return -1;
}
//listen
ret = listen(listenfd, 5);
if(ret != 0){
printf("listen error\n");
return -1;
}
do_poll(listenfd);
close(listenfd);
return 0;
}
客户端源代码:
/*************************************************************************
> File Name: poll_client.c
> Author:
> Mail:
> Created Time: 2016年02月26日 星期五 20时25分24秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
int handle_connection(int sockfd)
{
char sendbuf[1024], recvbuf[1024];
struct pollfd pfds[2];
int num;
//添加连接套接字
pfds[0].fd = sockfd;
pfds[0].events = POLLIN;
//添加标准输入文件描述符
pfds[1].fd = STDIN_FILENO;
pfds[1].events = POLLIN;
for(; ;){
poll(pfds, 2, -1);
//连接文件描述符准备好
if(pfds[0].revents & POLLIN){
num = read(sockfd, recvbuf, 1024);
if(num == 0){
printf("client: server closed.\n");
close(sockfd);
}
//将收到的内容输出到标准输出
write(STDOUT_FILENO, recvbuf, num);
}
//测试标准输入是否就绪
if(pfds[1].revents & POLLIN){
num = read(STDIN_FILENO, sendbuf, 1024);
if(num == 0){
continue;
}
write(sockfd, sendbuf, num);
}
}
return 0;
}
int main(int argc, char *argv[])
{
if(argc <= 2){
printf("input the: %s ipaddr port", argv[0]);
return -1;
}
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
const char * ip = argv[1];
int port = atoi(argv[2]);
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, ip, &servaddr.sin_addr);
servaddr.sin_port = htons(port);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
//处理连接套接字
handle_connection(sockfd);
return 0;
}
在终端1下输入:./poll_server 192.168.0.140 12345 //启动了服务器
在终端2下输入:./poll_client 192.168.0.140 12345 //启动了客户端1
client1 hello world
client1 hello world
client1 linux
client1 linux
在终端3下输入:./poll_client 192.168.0.140 12345 //启动了客户端2
client2 hello world
client2 hello world
client ^H
client
client 2
client 2
服务器终端输出为:
接受新的客户连接:192.168.0.140:59023
server read the data is : client1 hello world
server read the data is : client1 linux
接受新的客户连接:192.168.0.140:59279
server read the data is : client2 hello world
server read the data is : client
server read the data is : client 2
EPOLL实例
服务器源代码:
/*************************************************************************
> File Name: epoll_server.c
> Author:
> Mail:
> Created Time: 2016年02月27日 星期六 16时17分58秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <stdbool.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10
//设置文件描述符为非阻塞
int setnonblocking(int fd)
{
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
//将文件描述符fd添加到内核事件表中
void addfd(int epollfd, int fd, bool enable_et)
{
struct epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN;
if(enable_et)
event.events |= EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
setnonblocking(fd);
}
//LT模式的工作流程
void lt(struct epoll_event * events, int number, int epollfd, int listenfd)
{
char buf[BUFFER_SIZE];
int i;
for(i = 0; i < number; ++i){
int sockfd = events[i].data.fd;
if(sockfd == listenfd){//如果描述符是监听套接字
struct sockaddr_in clientaddr;
socklen_t clientaddr_len;
int connfd = accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddr_len);
addfd(epollfd, connfd, false);//对connfd禁用et模式
}
else if(events[i].events & EPOLLIN){//如果不是监听套接字,则是连接套接字,如果可读则读取数据,LT模式
printf("event trigger once\n");
memset(buf, 0, BUFFER_SIZE);
int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
if(ret <= 0){
close(sockfd);
continue;
}
printf("get %d bytes data: %s\n", ret, buf);
}
else{
printf("其它情况....\n");
}
}
}
//ET模式的工作流程
void et(struct epoll_event * events, int number, int epollfd, int listenfd)
{
char buf[BUFFER_SIZE];
int i;
for(i = 0; i < number; ++i){
int sockfd = events[i].data.fd;
if(sockfd == listenfd){//如果描述符是监听套接字
struct sockaddr_in clientaddr;
socklen_t clientaddr_len;
int connfd = accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddr_len);
addfd(epollfd, connfd, true);//对connfd开启et模式
}
else if(events[i].events & EPOLLIN){//如果不是监听套接字,则是连接套接字,如果可读则读取数据,ET模式,所以要重复的读完
printf("event trigger once");
while(1){
memset(buf, 0, BUFFER_SIZE);
int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
if(ret < 0){
if((errno==EAGAIN) || (errno==EWOULDBLOCK)){//此时表示读取完毕
printf("read later, 此时读取完毕\n");
break;
}
close(sockfd);
break;
}
else if(ret == 0){
close(sockfd);
}
else{
printf("\nget %d bytes data: %s", ret, buf);
}
}
}
else{
printf("其它情况....\n");
}
}
}
int main(int argc, char *argv[])
{
if(argc <= 3){
printf("input: %s ip_addr port_num et or lt", argv[0]);
return -1;
}
const char * ip = argv[1];
int port = atoi(argv[2]);
int ret = 0;
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, ip, &servaddr.sin_addr);
servaddr.sin_port = htons(port);
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd < 0){
printf("socket error\n");
return -1;
}
ret = bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
if(ret < 0){
printf("bind error\n");
close(listenfd);
return -1;
}
ret = listen(listenfd, 5);
if(ret < 0){
printf("listen error\n");
close(listenfd);
return -1;
}
struct epoll_event events[MAX_EVENT_NUMBER];
int epollfd = epoll_create(5);
if(epollfd < 0){
printf("epoll_create error\n");
close(listenfd);
}
addfd(epollfd, listenfd, true);//true表示开启ET模式
while(1){
int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if(ret < 0){
printf("epoll_wait error\n");
break;
}
if(strncmp(argv[3], "lt", 2) == 0)
lt(events, ret, epollfd, listenfd);
else if(strncmp(argv[3], "et", 2) == 0)
et(events, ret, epollfd, listenfd);
}
close(listenfd);
return 0;
}
客户端源代码:
/*************************************************************************
> File Name: 53backlog.c
> Author:
> Mail:
> Created Time: 2016年01月16日 星期六 11时35分15秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>//包含bool类型,C语言是没有bool类型的,C++才有
#include <unistd.h>
#include <errno.h>
static bool stop = false;
static void handle_term(int sig)
{
stop = true;
}
int main(int argc, char *argv[])
{
signal(SIGTERM, handle_term);
if(argc <= 2){
printf("usage: %s ip_address port_number backlog\n", argv[0]);
return 1;
}
const char * ip = argv[1];
int port = atoi(argv[2]);
int backlog = 5;//atoi(argv[3]);
int sock = socket(AF_INET, SOCK_STREAM, 0);
assert(sock >= 0);
//创建ipv4的地址
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET, ip, (void *)&address.sin_addr);
int ret = connect(sock, (struct sockaddr *)&address, sizeof(address));
if(ret < 0)
printf("connect failed.\n");
else{
char buf[1024];
int num = 0;
while(1){
num = read(STDIN_FILENO, buf, 1024);
if(num <= 0){
break;
}
write(sock, buf, num);
}
}
printf("send over...,按任意键退出\n");
getchar();
close(sock);
return 0;
}
以LT模式启动服务器:
linux
linuxeverreadwrite
haha
服务器输出:
event trigger once
get 6 bytes data: linux
event trigger once
get 9 bytes data: linuxever
event trigger once
get 9 bytes data: readwrite
event trigger once
get 1 bytes data:
event trigger once
get 5 bytes data: haha
可以发现第二次的数据比较多,一次不能读完,然后触发了该读事件3次。
以ET模式启动服务器:
客户端输入:
linux
linuxeverreadwrite
haha
服务器输出:
event trigger once
get 6 bytes data: linux
read later, 此时读取完毕
event trigger once
get 9 bytes data: linuxever
get 9 bytes data: readwrite
get 1 bytes data:
read later, 此时读取完毕
event trigger once
get 5 bytes data: haha
read later, 此时读取完毕
EPOLL使用EPOLLONESHOT事件:
服务器中对每个连接套接字可读就绪的时候创建新的工作线程去处理数据。如果在该线程读取完毕,处理数据过程中,如果该套接字上又有数据输入了,如果没有注册epolloneshot事件则还会创建新的线程去处理数据,那么就会有多个线程同时处理一个连接套接字,这样会出现错误的。所以在连接套接字上注册了epolloneshot事件,那么只能有一个线程在处理该套接字。当该线程处理完数据之后,重新注册该事件,再次检测到epollin事件的时候再创建一个线程去处理数据。
服务器源代码:
/*************************************************************************
> File Name: epoll_server.c
> Author:
> Mail:
> Created Time: 2016年02月27日 星期六 16时17分58秒
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <stdbool.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 1024
struct fds
{
int epollfd;
int sockfd;
};
//设置文件描述符为非阻塞
int setnonblocking(int fd)
{
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
//将文件描述符fd添加到内核事件表中
void addfd(int epollfd, int fd, bool oneshot)
{
struct epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET;
if(oneshot)
event.events |= EPOLLONESHOT;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
setnonblocking(fd);
}
void reset_oneshot(int epollfd, int fd)
{
struct epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}
//处理连接套接字上的数据
void* worker(void* arg)
{
int sockfd = ((struct fds*)arg)->sockfd;
int epollfd = ((struct fds*)arg)->epollfd;
printf("####start new thread to receive data on fd: %d\n", sockfd);
char buf[BUFFER_SIZE];
memset(buf, 0, BUFFER_SIZE);
int ret;
while(1){
ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
if(ret == 0){
close(sockfd);
printf("对端关闭了连接\n");
break;
}
else if(ret < 0){
if(errno == EAGAIN){
reset_oneshot(epollfd, sockfd);
//printf("read later\n");
break;
}
}
else{
printf("get %d bytes data: %s\n", ret, buf);
sleep(4);//模拟此时是正在处理数据
}
memset(buf, 0, BUFFER_SIZE);
}
printf("####end new thread receiving data on fd: %d\n", sockfd);
}
int main(int argc, char *argv[])
{
int i;
if(argc <= 2){
printf("input: %s ip_addr port_num\n", argv[0]);
return -1;
}
const char * ip = argv[1];
int port = atoi(argv[2]);
int ret = 0;
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, ip, &servaddr.sin_addr);
servaddr.sin_port = htons(port);
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd < 0){
printf("socket error\n");
return -1;
}
ret = bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
if(ret < 0){
printf("bind error\n");
close(listenfd);
return -1;
}
ret = listen(listenfd, 5);
if(ret < 0){
printf("listen error\n");
close(listenfd);
return -1;
}
struct epoll_event events[MAX_EVENT_NUMBER];
int epollfd = epoll_create(5);
if(epollfd < 0){
printf("epoll_create error\n");
close(listenfd);
}
addfd(epollfd, listenfd, false);
while(1){
int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if(ret < 0){
printf("epoll_wait error\n");
break;
}
for(i = 0; i < ret; ++i){
int sockfd = events[i].data.fd;
if(sockfd == listenfd){
struct sockaddr_in client_address;
socklen_t client_addresslen = sizeof(client_address);
int connfd = accept(sockfd, (struct sockaddr*)&client_address, &client_addresslen);
//对连接套接字注册oneshot事件
addfd(epollfd, connfd, true);
}
else if(events[i].events & EPOLLIN){
pthread_t thread;
struct fds fds_for_new_worker;
fds_for_new_worker.sockfd = sockfd;
fds_for_new_worker.epollfd = epollfd;
//启动新的进程为连接socket服务
pthread_create(&thread, NULL, worker, (void*)&fds_for_new_worker);
}
else{
printf("发生了其它情况\n");
}
}
}
close(listenfd);
return 0;
}
客户端源代码同上一样
linux
lin
lin
lin
linsadfasdf(输入之后等待超过4秒)
linuxever //此时会启动新的工作线程
liin
####start new thread to receive data on fd: 5
get 6 bytes data: linux
get 4 bytes data: lin
get 4 bytes data: lin
get 4 bytes data: lin
get 12 bytes data: linsadfasdf
####end new thread receiving data on fd: 5
####start new thread to receive data on fd: 5
get 10 bytes data: linuxever
get 5 bytes data: liin
####end new thread receiving data on fd: 5
参考文章:
http://www.cnblogs.com/Anker/p/3265058.html
http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html
http://www.cnblogs.com/Anker/archive/2013/08/15/3261006.html
http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html