免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: codechurch

[C] 寻求建议,关于异步编程 [复制链接]

论坛徽章:
44
15-16赛季CBA联赛之浙江
日期:2021-10-11 02:03:59程序设计版块每日发帖之星
日期:2016-07-02 06:20:0015-16赛季CBA联赛之新疆
日期:2016-04-25 10:55:452016科比退役纪念章
日期:2016-04-23 00:51:2315-16赛季CBA联赛之山东
日期:2016-04-17 12:00:2815-16赛季CBA联赛之福建
日期:2016-04-12 15:21:2915-16赛季CBA联赛之辽宁
日期:2016-03-24 21:38:2715-16赛季CBA联赛之福建
日期:2016-03-18 12:13:4015-16赛季CBA联赛之佛山
日期:2016-02-05 00:55:2015-16赛季CBA联赛之佛山
日期:2016-02-04 21:11:3615-16赛季CBA联赛之天津
日期:2016-11-02 00:33:1215-16赛季CBA联赛之浙江
日期:2017-01-13 01:31:49
发表于 2014-12-18 14:14 |显示全部楼层
回复 30# codechurch

哦,我明白你在说什么了,你是说针对一个session多次创建/销毁context的时候stack位置会变化,所以不能保存指向桟上的指针。

我觉得如果程序没写错,那一个context在销毁的时候stack应该已经清空了,如果此时还有指向桟上的指针,那一定是程序写错了。

论坛徽章:
0
发表于 2014-12-18 15:01 |显示全部楼层
回复 31# windoze


在那位(id太复杂)的设计里,session没有完成,栈也会销毁。

如:session在等待socket send完成,在socket send之后,将此session的栈回收,当send完成后,再恢复执行。

   

论坛徽章:
44
15-16赛季CBA联赛之浙江
日期:2021-10-11 02:03:59程序设计版块每日发帖之星
日期:2016-07-02 06:20:0015-16赛季CBA联赛之新疆
日期:2016-04-25 10:55:452016科比退役纪念章
日期:2016-04-23 00:51:2315-16赛季CBA联赛之山东
日期:2016-04-17 12:00:2815-16赛季CBA联赛之福建
日期:2016-04-12 15:21:2915-16赛季CBA联赛之辽宁
日期:2016-03-24 21:38:2715-16赛季CBA联赛之福建
日期:2016-03-18 12:13:4015-16赛季CBA联赛之佛山
日期:2016-02-05 00:55:2015-16赛季CBA联赛之佛山
日期:2016-02-04 21:11:3615-16赛季CBA联赛之天津
日期:2016-11-02 00:33:1215-16赛季CBA联赛之浙江
日期:2017-01-13 01:31:49
发表于 2014-12-18 15:55 |显示全部楼层
回复 32# codechurch

新创建的stack应该只包含“分岔点”之后的内容,之前的内容不应该在里面,这样就不会发生你说的问题了。
不过看 @yulihua49 的代码,他真的好像把整个stack都复制了一遍,这样真有可能出问题。

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
发表于 2014-12-18 16:54 |显示全部楼层
codechurch 发表于 2014-12-18 10:32
回复 22# yulihua49

我突然想到一个严重的问题。

我先前的那些程序都是错的,错的一塌糊涂,现在在调整,以后再说。

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
发表于 2014-12-18 16:56 |显示全部楼层
本帖最后由 yulihua49 于 2014-12-18 17:06 编辑
windoze 发表于 2014-12-18 11:42
回复 25# codechurch

不会崩溃的,尽管你切换到其它stack去了,但4001那块stack不是还在那儿吗。

会崩溃的,原来的地址被别人用了。
XXX()
{
char a[400];
char *p=a;
.....
}

这种东西肯定完蛋。
栈迁移后,a改变了地址,p还指向原来的地方。
我这里这样用的挺多,真是个问题。

我这个方案,整体来说可能不行。

论坛徽章:
44
15-16赛季CBA联赛之浙江
日期:2021-10-11 02:03:59程序设计版块每日发帖之星
日期:2016-07-02 06:20:0015-16赛季CBA联赛之新疆
日期:2016-04-25 10:55:452016科比退役纪念章
日期:2016-04-23 00:51:2315-16赛季CBA联赛之山东
日期:2016-04-17 12:00:2815-16赛季CBA联赛之福建
日期:2016-04-12 15:21:2915-16赛季CBA联赛之辽宁
日期:2016-03-24 21:38:2715-16赛季CBA联赛之福建
日期:2016-03-18 12:13:4015-16赛季CBA联赛之佛山
日期:2016-02-05 00:55:2015-16赛季CBA联赛之佛山
日期:2016-02-04 21:11:3615-16赛季CBA联赛之天津
日期:2016-11-02 00:33:1215-16赛季CBA联赛之浙江
日期:2017-01-13 01:31:49
发表于 2014-12-18 17:44 |显示全部楼层
回复 35# yulihua49

我觉得直接给每个session创建一个context应该就行了,不需要搞的这么复杂。
如果你担心stack太大浪费内存,你可以用boost.context(这个东西说是C++写的,但其实就是一段几乎是纯C的代码加上些汇编,想避免C++自己拿来改成纯C花不了一个小时),stack的空间可以自己分配,在X86/X64上,最小可以是一个page,也就是4K,这样创建1M个fiber也不过只需要4G内存,对现有的64位系统来说真不是太大的开销,在Linux+GCC下,你还可以用split stack按需分配stack,这对现有的程序在源代码一级是透明的,不需要改动,代价是性能略有损失。

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
发表于 2014-12-22 14:05 |显示全部楼层
本帖最后由 yulihua49 于 2014-12-22 14:29 编辑
windoze 发表于 2014-12-18 17:44
回复 35# yulihua49

我觉得直接给每个session创建一个context应该就行了,不需要搞的这么复杂。

苦修了几天,终成正果。
“协程池”技术诞生了。
方案如下:
基于线程池,系统初始化,一堆空TCB,就是没有栈。
一个TCB,激活时,从线程获取栈,线程没有就分配。
完成任务后(一个service),归还栈给线程,归还不了就free。
其间,如果进入AIO未完成状态,就不归还栈。
这样,在do_work()期间,就不会发生跳栈。

系统的栈需求为:线程数*2 + AIO未完成的fiber数。
这样,我可以根据需求为应用定义更大的栈空间。
在空间方面,任何时候fiber空间不足,就给他线程栈用,并不影响fiber的运行,只是不能AIO,变成同步阻塞的而已。

另外,4K的应用栈肯定不够,将够系统异常事件处理用。(就是栈下的空间)。
我现在是2M,将来根据用户需求配置。

试验日志:
5 midsc:19720 12/22 13:54'29 thread_work:reuse fiber
5 DB2FIRST:127.0.0.1:3 12/22 13:54'29 do_work:call_back tid=7F216DA7A700,USEC=397637667
3 DB2FIRST:127.0.0.1:3 12/22 13:54'29 do_epoll:tid=7F216DA7A700 epoll_ctl fd[3]=-1,deleted,op=2
3 DB2FIRST:127.0.0.1:3 12/22 13:54'29 from_server:TCB:3,tid=139781550352128,Status Recv from server localhost PROTO_NUM=0X0009,ERRNO1=0,ERRNO2=6,PKG_LEN=4589,T_LEN=4589,t_cont=00000006,USEC=3628302869992435
5 DB2FIRST:127.0.0.1:3 12/22 13:54'29 thread_work:fiber yield
5 DB2FIRST:127.0.0.1:3 12/22 13:54'29 thread_work:send to fiber
5 DB2FIRST:127.0.0.1:3 12/22 13:54'29 thread_work:fiber yield
5 DB2FIRST:127.0.0.1:3 12/22 13:54'29 thread_work:send to fiber
5 DB2FIRST:127.0.0.1:3 12/22 13:54'29 thread_work:fiber yield
5 DB2FIRST:127.0.0.1:3 12/22 13:54'29 thread_work:send to fiber
5 DB2FIRST:127.0.0.1:3 12/22 13:54'29 thread_work:fiber yield
5 DB2FIRST:127.0.0.1:3 12/22 13:54'29 thread_work:send to fiber
5 midsc:19720 12/22 13:54'29 thread_work:回收 fiber

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
发表于 2014-12-22 14:32 |显示全部楼层
yulihua49 发表于 2014-12-22 14:05
苦修了几天,终成正果。
“协程池”技术诞生了。
方案如下:


任务调度器:
  1. static void *thread_work(void *param)
  2. {
  3. resource *rs=(resource *)param;
  4. int ret,fds;
  5. TCB *task;
  6. struct epoll_event event;

  7.         rs->tid=pthread_self();
  8.         while(1) {
  9. //从就绪队列取一个任务
  10.                 pthread_mutex_lock(&rpool.mut);
  11.                 while(!(task=rdy_get())) {
  12.                         if(rpool.flg >= tpool.rdy_num) break;
  13.                         rpool.flg++;
  14.                         ret=pthread_cond_wait(&rpool.cond,&rpool.mut); //没有任务,等待
  15.                         rpool.flg--;
  16.                 }
  17.                 pthread_mutex_unlock(&rpool.mut);
  18.                 if(!task) {
  19.                         fds = epoll_wait(g_epoll_fd, &event, 1 , -1);
  20.                         if(fds < 0){
  21.                                 ShowLog(1,"%s:epoll_wait err=%d,%s",__FUNCTION__,errno,strerror(errno));
  22.                                 sleep(30);
  23.                                 continue;
  24.                         }
  25.                         task = (TCB *)event.data.ptr;
  26.                         if(task->events == 0X10||task->fd==-1) {
  27.                                 ShowLog(1,"%s:task already timeout",__FUNCTION__);
  28.                                 continue;//已经进入超时状态
  29.                         }
  30.                         task->events=event.events;
  31.                 }
  32.                 rs->timestamp=now_usec();

  33.                 task->uc.uc_link=&rs->tc;
  34.                 if(task->AIO_flg) {//fiber task
  35.                         set_showid(task->ctx);//Showid 应该在会话上下文结构里
  36.                         pthread_mutex_lock(&task->lock);//防止其他线程提前闯入
  37. ShowLog(5,"%s:send to fiber",__FUNCTION__);
  38.                         setcontext(&task->uc);  //== longjmp()
  39.                         continue;//no action,logic only
  40.                 }
  41.                 while(task->uc.uc_stack.ss_size>0)
  42.                         ShowLog(2,"%s:wait pre fiber exit!",__FUNCTION__);
  43.                 if(!rs->tc.uc_stack.ss_size) {
  44. ShowLog(5,"%s:create fiber",__FUNCTION__);
  45.                         task->uc.uc_stack.ss_sp=malloc(use_stack_size);
  46.                         if(!task->uc.uc_stack.ss_sp) {//create fiber fault
  47.                                 do_work(task->sv.TCB_no); //进行你的服务,不使用AIO
  48.                                 if(task->status == -2) {
  49.                                         client_del(task);
  50.                                 }
  51.                                 continue;
  52.                         }
  53.                 } else {
  54. ShowLog(5,"%s:reuse fiber",__FUNCTION__);
  55.                         task->uc.uc_stack.ss_sp=rs->tc.uc_stack.ss_sp;
  56.                         rs->tc.uc_stack.ss_sp=NULL;
  57.                         rs->tc.uc_stack.ss_size=0;
  58.                 }
  59.                 task->uc.uc_stack.ss_size=use_stack_size;
  60.                 makecontext(&task->uc,do_work,1,task->sv.TCB_no);

  61.                 ret=swapcontext(&rs->tc,&task->uc);
  62.                 if(!task->AIO_flg) {//service complate
  63.                         if(!rs->tc.uc_stack.ss_size) {//回收fiber stack
  64. ShowLog(5,"%s:回收 fiber",__FUNCTION__);
  65.                                 rs->tc.uc_stack.ss_size=use_stack_size;
  66.                                 rs->tc.uc_stack.ss_sp=task->uc.uc_stack.ss_sp;
  67.                         } else {
  68. ShowLog(5,"%s:free fiber",__FUNCTION__);
  69.                                 free(task->uc.uc_stack.ss_sp);
  70.                         }
  71.                         task->uc.uc_stack.ss_sp=NULL;
  72.                         task->uc.uc_stack.ss_size=0;//mark fiber cpmplate
  73.                         if(task->status == -2) {
  74.                                 client_del(task);
  75.                         }
  76.                 } else {
  77.                         pthread_mutex_unlock(&task->lock);
  78. ShowLog(5,"%s:fiber yield",__FUNCTION__);
  79.                         mthr_showid_del(rs->tid);
  80.                 }
  81.         }
  82.         ShowLog(1,"%s:tid=%lX canceled",__FUNCTION__,pthread_self());
  83.         mthr_showid_del(rs->tid);
  84.         rs->timestamp=now_usec();
  85.         rs->status=0;
  86.         rs->tid=0;
  87.         if(rs->tc.uc_stack.ss_sp) {
  88.                 free(rs->tc.uc_stack.ss_sp);
  89.                 rs->tc.uc_stack.ss_sp=NULL;
  90.                 rs->tc.uc_stack.ss_size=0;
  91.         }
  92.         return NULL;
  93. }
  94. //yield to schedle
  95. int do_event(int TCBno,int sock,int flg)
  96. {
  97. TCB *task;
  98. int save_fd;
  99. int ret;

  100.         if(TCBno<0 ||TCBno>client_q.max_client) return -1;
  101.         task=&client_q.pool[TCBno];
  102.         if(task->uc.uc_stack.ss_size == 0) return MEMERR;
  103.         save_fd=task->fd;
  104.         task->AIO_flg=flg+1;
  105.         task->fd=sock;
  106.         pthread_mutex_lock(&task->lock);//防止其他线程提前闯入
  107.         ret=do_epoll(task,0,flg);
  108.         if(ret<0) {
  109.                 pthread_mutex_unlock(&task->lock);
  110.                 task->fd=save_fd;
  111.                 task->AIO_flg=0;
  112.                 return FORMATERR;
  113.         }
  114.         ret=swapcontext(&task->uc,task->uc.uc_link);
  115.         pthread_mutex_unlock(&task->lock);
  116.         task->fd=save_fd;
  117.         task->AIO_flg=0;
  118.         return ret;
  119. }
复制代码

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
发表于 2014-12-22 14:37 |显示全部楼层
本帖最后由 yulihua49 于 2014-12-23 16:16 编辑
yulihua49 发表于 2014-12-22 14:32
任务调度器:


AIO_tcp.c:
  1. /*@(#) SDBC 7.1 TCP SERVER Tools                      *
  2. * to suport Fiberized.IO
  3. **********************************************/
  4. #include <sys/socket.h>
  5. #include <sys/time.h>
  6. #include <unistd.h>
  7. #include <fcntl.h>
  8. #include <scsrv.h>

  9. #ifndef MIN
  10. #define MIN(a,b) ((a)<(b))?(a):(b)
  11. #endif

  12. //timeout for second
  13. int RecvNet(int socket,char *buf,int n,int timeout,int TCB_no)
  14. {
  15. int bcount=0,br,ret;
  16. int i;
  17. int fflag;

  18.         if(socket<0) return SYSERR;
  19.         if(!buf && n<0) return 0;
  20.         fflag=fcntl(socket,F_GETFL,0);
  21.         if(TCB_no>=0) {
  22.                 fcntl(socket,F_SETFL,fflag|O_NONBLOCK); //异步操作
  23.         } else {
  24.                 struct timeval tmout;
  25.                 tmout.tv_sec=timeout;
  26.                 tmout.tv_usec=0;
  27.                 ret=setsockopt(socket,SOL_SOCKET,SO_RCVTIMEO,(char *)&tmout,sizeof(tmout));
  28.                 if(ret) {
  29.                         ShowLog(5,"%s:setsockopt err=%d,%s",__FUNCTION__,
  30.                                 errno,strerror(errno));
  31.                 }
  32.         }

  33.         *buf=0;
  34.         br=0;

  35.         while(bcount<n){
  36.                 if((br=read(socket,buf,n-bcount))>0){
  37.                         bcount+=br;
  38.                         buf+=br;
  39.                         continue;
  40.                 }
  41.                 if(errno==EAGAIN) return TIMEOUTERR;
  42.                 if(br<0){
  43.                     if(errno!=ECONNRESET)
  44.                         ShowLog(1,"%s:br=%d,err=%d,%s",__FUNCTION__,br,errno,strerror(errno));
  45.                     break;
  46.                 }
  47. //ShowLog(5,"RecvNet:read br=0,errno=%d,%s",errno,strerror(errno));
  48.                 if(bcount < n && TCB_no>=0) { //切换任务
  49.                         i=do_event(TCB_no,socket,0);//yield by EPOOLIN
  50.                         if(i<0) {
  51.                                 struct timeval tmout;
  52.                                 tmout.tv_sec=timeout;
  53.                                 tmout.tv_usec=0;
  54.                                 ret=setsockopt(socket,SOL_SOCKET,SO_RCVTIMEO,(char *)&tmout,sizeof(tmout));
  55.                                 fcntl(socket,F_SETFL,fflag);
  56.                                 TCB_no=-1;
  57.                         }
  58.                 }
  59.         }
  60.         if(TCB_no >= 0) fcntl(socket,F_SETFL,fflag);
  61.         return bcount==0?-1:bcount;
  62. }

  63. int SendNet(int socket,char *buf,int n,int MTU,int TCB_no)
  64. {
  65. int bcount,bw;
  66. int sz,i=0;
  67. int fflag;
  68. size_t SendSize;

  69.         if(socket<0) return SYSERR;
  70.         fflag=fcntl(socket,F_GETFL,0);
  71.         if(TCB_no >= 0) {
  72.                 fcntl(socket,F_SETFL,fflag|O_NONBLOCK); //异步操作
  73.         }
  74.         bcount=0;
  75.         bw=0;
  76.         if(MTU>500) SendSize=MTU;
  77.         else SendSize=n;
  78.         while(bcount<n){
  79.                 sz=MIN(n-bcount,SendSize);
  80.                 if((bw=write(socket,buf,sz))>0){
  81.                         bcount+=bw;
  82.                         buf+=bw;
  83.                 }
  84.                 if(bw<0) break;
  85.                 if(bcount < n && TCB_no >= 0) { //切换任务
  86.                     i=do_event(TCB_no,socket,1); //yield by EPOLLOUT
  87.                     if(i<0) {
  88.                                  fcntl(socket,F_SETFL,fflag);
  89.                                  TCB_no=-1;
  90.                      }
  91.                 }
  92.         }
  93.         if(TCB_no >= 0) {
  94.                 fcntl(socket,F_SETFL,fflag);
  95.         }
  96.         return bcount==0?-1:bcount;
  97. }
复制代码
比先前的简单了许多。

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
发表于 2014-12-24 14:59 |显示全部楼层
本帖最后由 yulihua49 于 2014-12-24 15:15 编辑
codechurch 发表于 2014-12-17 12:01
本人用C语言在x86上实现了类似于C#的async await异步操作。

(windows使用setthreadcontext\getthreadco ...

这就是我最担心的。
每个fiber至少1M内存,10000个,就是10G.有这些资源,留给应用,用作内存数据库好不好?
况且这些内存,绝大多数是休眠或空闲的。

还有,你们的那个说法,fiber找个线程执行,我特不理解。如果一堆线程,没人找,那它在干啥?
我的说法是,线程,收到一个事件,得到TCB,为他生成一个运行栈,并运行它。
没事时都在epoll_wait。

线程是执行主体,fiber是客体,所以是线程运行fiber,而非fiber运行线程。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP