├── .gitignore ├── README.md ├── client.py ├── client_thread.py ├── coroutine.c ├── coroutine.h ├── doc ├── flow.png ├── result.png ├── ucontext_coroutine1.md ├── ucontext_coroutine2.md └── ucontext_coroutine3.md ├── test_simple.c └── test_socket.c /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 协程的简单实现 2 | ## 目的 3 | 学习协程原理,并记录了学习过程,以供有相同需求的同学参考 4 | ## 以博客的形式描述了大概思路 5 | 1. [准备知识](doc/ucontext_coroutine1.md) 6 | 2. [实现简单协程库](doc/ucontext_coroutine2.md) 7 | 3. [使用协程库并实现简单非阻塞socket服务器](doc/ucontext_coroutine3.md) 8 | ## 文件说明 9 | - coroutine.h 协程头文件 10 | - coroutine.c 协程实现 11 | - test_simple.c 简单测试 12 | - test_socket.c 非阻塞socket测试 13 | - client.py 单线程socket,用户输入测试 14 | - client_thread.py 多线程socket测试 15 | -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | HOST = 'localhost' # The remote host 4 | PORT = 8888 # The same port as used by the server 5 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 6 | s.connect((HOST, PORT)) 7 | while True: 8 | msg = raw_input(">>:") 9 | s.sendall(msg) 10 | data = s.recv(1024) 11 | 12 | print('Received', repr(data)) 13 | s.close() -------------------------------------------------------------------------------- /client_thread.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | import socket 3 | import sys 4 | import threading 5 | from time import ctime,sleep 6 | 7 | threads = [] 8 | 9 | def connect_send(): 10 | count = 0 11 | t = threading.current_thread() 12 | tid = t.ident 13 | res = "count:"+str(count)+"__tid:"+str(tid) 14 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 15 | sock.connect(('127.0.0.1', 8888)) 16 | sock.send(res) 17 | szBuf = sock.recv(1024) 18 | print szBuf 19 | while(szBuf != 'exit'): 20 | count += 1 21 | res = "count:"+str(count)+"__tid:"+str(tid) 22 | sock.send(res) 23 | szBuf = sock.recv(1024) 24 | print("recv:" + szBuf) 25 | sleep(0.1) 26 | if(count > 10): 27 | break 28 | sock.close() 29 | 30 | for i in xrange(100): 31 | t = threading.Thread(target=connect_send, args=()) 32 | threads.append(t) 33 | 34 | if __name__ == '__main__': 35 | for t in threads: 36 | t.start() -------------------------------------------------------------------------------- /coroutine.c: -------------------------------------------------------------------------------- 1 | #include "coroutine.h" 2 | 3 | void main_func(schedule *s) 4 | { 5 | int id = s->running_coroutine; 6 | if(id != -1) 7 | { 8 | coroutine* c = s->coroutines[id]; 9 | c->func(s, c->args); 10 | c->state = DEAD; 11 | s->running_coroutine = -1; 12 | } 13 | 14 | } 15 | 16 | schedule* schedule_create() 17 | { 18 | schedule *s = (schedule *)malloc(sizeof(schedule)); 19 | s->running_coroutine = -1; 20 | s->max_index = 0; 21 | s->coroutines = (coroutine **)malloc(sizeof(coroutine *)*MAX_UTHREAD_SIZE); 22 | memset(s->coroutines, 0, sizeof(coroutine *)*MAX_UTHREAD_SIZE); 23 | s->max_index = 0; 24 | return s; 25 | } 26 | 27 | int coroutine_create(schedule *s, coroutine_func func, void *args) 28 | { 29 | coroutine *c = NULL; 30 | int i = 0; 31 | for(i=0; i < s->max_index; i++) 32 | { 33 | c = s->coroutines[i]; 34 | if(c->state == DEAD) 35 | { 36 | break; 37 | } 38 | } 39 | if(s->max_index == i || c == NULL) 40 | { 41 | s->coroutines[i] = (coroutine *)malloc(sizeof(coroutine)); 42 | } 43 | c = s->coroutines[i]; 44 | c->state = READY; 45 | c->func = func; 46 | c->args= args; 47 | s->max_index ++; 48 | getcontext(&(c->ctx)); 49 | 50 | c->ctx.uc_stack.ss_sp = c->stack; 51 | c->ctx.uc_stack.ss_size = STACK_SZIE; 52 | c->ctx.uc_stack.ss_flags = 0; 53 | c->ctx.uc_link = &(s->main); 54 | 55 | makecontext(&(c->ctx),(void (*)(void))main_func, 1, s); 56 | return i; 57 | } 58 | 59 | void coroutine_yield(schedule *s) 60 | { 61 | if(s->running_coroutine != -1) 62 | { 63 | coroutine* c = s->coroutines[s->running_coroutine]; 64 | c->state = SUSPEND; 65 | s->running_coroutine = -1; 66 | 67 | swapcontext(&(c->ctx), &(s->main)); 68 | } 69 | } 70 | 71 | void coroutine_resume(schedule * s, int id) 72 | { 73 | if(id >= 0 && id < s->max_index) 74 | { 75 | coroutine *c = s->coroutines[id]; 76 | if(c != NULL && c->state == SUSPEND) 77 | { 78 | c->state = RUNNING; 79 | s->running_coroutine = id; 80 | swapcontext(&(s->main), &(c->ctx)); 81 | } 82 | } 83 | } 84 | 85 | enum Coroutine_State coroutine_status(schedule *s, int id) 86 | { 87 | if(id < 0 || id >= s->max_index) 88 | { 89 | return DEAD; 90 | } 91 | coroutine* c = s->coroutines[id]; 92 | if(c == NULL) 93 | { 94 | return DEAD; 95 | } 96 | return c->state; 97 | } 98 | 99 | int schedule_finished(schedule *s) 100 | { 101 | int i = 0; 102 | if(s->running_coroutine != -1) 103 | { 104 | return 0; 105 | } 106 | for(i=0; i < s->max_index; i++) 107 | { 108 | coroutine* c = s->coroutines[i]; 109 | if(c != NULL && c->state != DEAD) 110 | { 111 | return 0; 112 | } 113 | } 114 | return 1; 115 | } 116 | 117 | int coroutine_running(schedule * s, int id) 118 | { 119 | enum Coroutine_State state = coroutine_status(s, id); 120 | if(state == DEAD) 121 | { 122 | return 0; 123 | } 124 | coroutine* c = s->coroutines[id]; 125 | c->state = RUNNING; 126 | s->running_coroutine = id; 127 | swapcontext(&(s->main), &(c->ctx)); 128 | } 129 | 130 | int delete_coroutine(schedule *s, int id) 131 | { 132 | if(id < 0 || id >= s->max_index) 133 | { 134 | return 0; 135 | } 136 | coroutine* c = s->coroutines[id]; 137 | if(c != NULL) 138 | { 139 | s->coroutines[id] = NULL; 140 | free(c); 141 | } 142 | return 1; 143 | } 144 | 145 | void schedule_close(schedule *s) 146 | { 147 | int i = 0; 148 | for( i = 0; i < s->max_index; i ++) 149 | { 150 | delete_coroutine(s, i); 151 | } 152 | free(s->coroutines); 153 | free(s); 154 | } -------------------------------------------------------------------------------- /coroutine.h: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_H 2 | #define COROUTINE_H 3 | 4 | #include 5 | 6 | #define STACK_SZIE (1024*128) 7 | #define MAX_UTHREAD_SIZE 1024 8 | #define NULL 0 9 | 10 | enum Coroutine_State{DEAD, READY, RUNNING, SUSPEND}; 11 | 12 | struct schedule_; 13 | 14 | typedef void (*coroutine_func)(struct schedule_* s, void *args); 15 | 16 | typedef struct coroutine_ 17 | { 18 | coroutine_func func; 19 | void *args; 20 | ucontext_t ctx; 21 | enum Coroutine_State state; 22 | char stack[STACK_SZIE]; 23 | }coroutine; 24 | 25 | typedef struct schedule_ 26 | { 27 | ucontext_t main; 28 | int running_coroutine; 29 | coroutine **coroutines; 30 | int max_index; 31 | }schedule; 32 | 33 | // 用于执行目标函数 34 | static void main_func(schedule *s); 35 | // 创建调度器 36 | schedule* schedule_create(); 37 | // 删除协程 38 | int delete_coroutine(schedule *s, int id); 39 | // 关闭调度器 40 | void schedule_close(schedule *s); 41 | // 查看状态 42 | enum Coroutine_State coroutine_status(schedule *s, int id); 43 | // 创建协程 44 | int coroutine_create(schedule *s, coroutine_func func, void *args); 45 | // 切换协程 46 | void coroutine_yield(schedule *s); 47 | // 恢复协程 48 | void coroutine_resume(schedule * s, int id); 49 | // 检查是否完全执行完成 50 | int schedule_finished(schedule *schedule); 51 | // 运行协程 52 | int coroutine_running(schedule * s, int id); 53 | #endif -------------------------------------------------------------------------------- /doc/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaobing94/coroutine/db610a906d2e9f1d61538f04fb71685e0db26a40/doc/flow.png -------------------------------------------------------------------------------- /doc/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaobing94/coroutine/db610a906d2e9f1d61538f04fb71685e0db26a40/doc/result.png -------------------------------------------------------------------------------- /doc/ucontext_coroutine1.md: -------------------------------------------------------------------------------- 1 | ## C语言实现协程(一) 2 | 3 | ### 引言 4 | 在使用socket编程时,我们会用到accept、connect、recv、send等函数,这些函数在没有数据到达时,会阻塞等待IO数据的到达。 5 | 这不利于我们处理多个连接并快速响应。一种方案是,服务端每accept一个连接,就创建一个新的线程用来处理这个连接。这会导致线程过多, 6 | 而且线程之前切换开销很大。这就可以使用到协程了。当然不止socket这种可以使用协程,IO密集型都可以使用协程,无论是网络IO还是其他IO。 7 | ### 协程 8 | 协程可以理解为用户态的轻量级的非抢占式的线程。 9 | 10 | ### 特点 11 | 用户态:协程是在用户态实现调度。 12 | 轻量级:协程不用内核调度,内核态与用户态之间切换。 13 | 非抢占:协程是由用户自己实现调度,并且同一时间只能有一个协程在执行,协程自己主动交出CPU的。 14 | 15 | ### 优缺点 16 | 优点: 17 | 1. 协程切换的时候开销小,用户态且轻量 18 | 2. 非抢占式,不用加很多锁,减小复杂度,不用很复杂的处理线程同步问题。 19 | 缺点: 20 | 协程不能利用多核,只能使用单核,因为同时只有一个协程在运行。 21 | 22 | ### 适用场景 23 | IO密集型。 24 | 在IO密集的情况下,协程的开销比线程小,能快速实现调度。 25 | 协程不适用于计算密集型,协程不能很好的利用多核cpu。 26 | 27 | ### ucontext组件 28 | linux下在头文件< ucontext.h >提供了getcontext(),setcontext(),makecontext(),swapcontext()四个函数和mcontext_t和ucontext_t结构体。 29 | 30 | 其中mcontext_t与机器相关。ucontext_t结构体如下(一般在/usr/include下): 31 | ``` 32 | typedef struct ucontext 33 | { 34 | unsigned long int uc_flags; 35 | struct ucontext *uc_link; 36 | stack_t uc_stack; 37 | mcontext_t uc_mcontext; 38 | __sigset_t uc_sigmask; 39 | struct _libc_fpstate __fpregs_mem; 40 | } ucontext_t; 41 | ``` 42 | 其中uc_link指向下文,及当前上下文(可以理解为执行状态)执行完了,恢复运行的上下文; 43 | uc_sigmask为该上下文中的阻塞信号集合; 44 | uc_stack为该上下文中使用的栈; 45 | uc_mcontext保存的上下文的特定机器表示,包括调用线程的特定寄存器等。 46 | 通过栈式计算机原理,我们可以知道,保存栈区和所有通用寄存器的值就可以保存程序运行的状态。这里uc_mcontext就是用来保存所有的寄存器的值的。而我们把栈设置到uc_stack所指向的内存, 47 | uc_stack就保存了栈的状态。 48 | 49 | ### ucontext的4个函数介绍 50 | 51 | ``` 52 | int getcontext(ucontext_t *ucp); 53 | ``` 54 | 获取当前上下文,初始化ucp结构体,将当前的上下文保存到ucp中。如果执行成功,返回0。执行失败返回-1。 55 | ``` 56 | int setcontext(const ucontext_t *ucp); 57 | ``` 58 | 设置当前上下文,设置当前的上下文为ucp, 恢复ucp的执行状态。如果ucp执行完了,会恢复到uc_link所指向的上下文,若uc_link为NULL,则线程退出。如果执行成功,不返回。执行失败返回-1。 59 | ``` 60 | void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...); 61 | ``` 62 | 创建上下文,修改通过getcontext取得的上下文ucp, 然后给该上下文指定一个栈空间ucp->stack,设置后继的上下文ucp->uc_link。 63 | ``` 64 | int swapcontext(ucontext_t *oucp, ucontext_t *ucp); 65 | ``` 66 | 切换上下文,保存当前上下文到oucp结构体中,然后激活upc上下文。 如果执行成功,不返回。执行失败返回-1。 67 | 68 | ### ucontext简单试用 69 | 下面一段简单代码体验一下获取和恢复上下文: 70 | ``` 71 | #include 72 | #include 73 | #include 74 | 75 | int main(int argc, const char *argv[]){ 76 | ucontext_t context; 77 | 78 | getcontext(&context); 79 | puts("Hello world"); 80 | sleep(1); 81 | setcontext(&context); 82 | return 0; 83 | } 84 | ``` 85 | 保存并编译,执行结果: 86 | ``` 87 | Hello world 88 | Hello world 89 | Hello world 90 | Hello world 91 | Hello world 92 | Hello world 93 | Hello world 94 | ^C 95 | ``` 96 | 如果不主动ctrl+c结束程序,会不断执行下去。第8行哪里getcontext保存了上下文,相当于按了一个暂停并保存起来放到了context变量里面,程序继续执行,到第11行的时候,恢复了原来的上下文,相当于把context变量里面保存的东西恢复了,恢复到第8行的状态。所以程序会不断执行下去。 97 | 98 | ### 函数之前切换 99 | 下面一段简单的代码体验一下不同函数间切换和恢复上下文: 100 | ``` 101 | #include 102 | #include 103 | #include 104 | 105 | 106 | void fun1() 107 | { 108 | printf("func1\n"); 109 | } 110 | void fun2(ucontext_t *ctx) 111 | { 112 | ucontext_t t_ctx; 113 | printf("func2\n"); 114 | swapcontext(&t_ctx, ctx); 115 | } 116 | 117 | int main(int argc, char *argv[]) { 118 | ucontext_t context, context2, main_ctx; 119 | char stack[1024]; 120 | getcontext(&context); 121 | getcontext(&context2); 122 | context.uc_stack.ss_sp = stack; 123 | context.uc_stack.ss_size = 1024; 124 | context.uc_link = &main_ctx; 125 | context2.uc_stack.ss_sp = stack; 126 | context2.uc_stack.ss_size = 1024; 127 | makecontext(&context,(void (*)(void))fun1, 0); 128 | swapcontext(&main_ctx, &context); 129 | puts("main"); 130 | makecontext(&context2,(void (*)(void))fun2,1,&main_ctx); 131 | swapcontext(&main_ctx, &context2); 132 | puts("main over"); 133 | return 0; 134 | } 135 | ``` 136 | 执行结果: 137 | ``` 138 | func1 139 | main 140 | func2 141 | main over 142 | ``` 143 | 这里第28行,先保存上下文到main_ctx,切换到context上下文执行,就调用了fun1函数,fun1函数执行完后恢复uc_link指向的上下文main_ctx。然后接着执行输出了main。第31行保存上下文到main_ctx恢复context2,执行fun2函数,并传一个函数main_ctx,执行到14行后保存上下文到gCtx,然后恢复ctx(ctx是通过指针传过来的mainc_ctx);即恢复到了32行的状态最后main函数执行完成。 144 | ### 总结 145 | linux系统,为我们提供了获取当前执行状态的函数、恢复当前上下文的函数和构造当前上下文的函数,所有栈的区域我们都可以自己分配一段空间来做协程的栈,每个协程都有自己独立的空间,我们就可以自己在用户态实现上线文切换,实现一个“用户态的线程”,即协程。 -------------------------------------------------------------------------------- /doc/ucontext_coroutine2.md: -------------------------------------------------------------------------------- 1 | ## C语言实现协程(二) 2 | ### 基本思路 3 | 上一篇已经提到如何使用ucontext组件来实现上下文切换。而我们要实现协程就需要实现一个调度器用来调度管理协程、协程恢复、协程切换等功能。用调度器来管理当前执行的协程,及并在调度器,并通过调度器管理所有协程的列表。 4 | 5 | ### 协程 6 | 协程需要执行目标函数的功能,并具备保存自身栈区、上下文和执行状态的功能。则协程结构体如下: 7 | ``` 8 | enum Coroutine_State{DEAD, READY, RUNNING, SUSPEND}; 9 | 10 | struct schedule_; 11 | 12 | typedef void (*coroutine_func)(struct schedule_* s, void *args); 13 | 14 | typedef struct coroutine_ 15 | { 16 | coroutine_func func; 17 | void *args; 18 | ucontext_t ctx; 19 | enum Coroutine_State state; 20 | char stack[STACK_SZIE]; 21 | }coroutine; 22 | ``` 23 | 其中coroutine_func func为被执行的函数, void*args为函数的参数struct schedule_* s为调度器,ucontext_t ctx为当前协程上下文,enum Coroutine_State state为协程状态, char stack[STACK_SZIE]为函数的栈区。大小为STACK_SZIE宏。 24 | 25 | ### 调度器 26 | 调度器需要具有管理所有协程的功能,并需要具有保存主逻辑上下文的功能,并且要能保存当前执行的协程。则结构体如下: 27 | ``` 28 | typedef struct schedule_ 29 | { 30 | ucontext_t main; 31 | int running_coroutine; 32 | coroutine **coroutines; 33 | int max_index; 34 | }schedule; 35 | 36 | ``` 37 | 其中main是重来保存主逻辑上下文的,running_coroutine指向当前运行的协程id,coroutines是协程指针列表,max_index,为当前使用的最大id。 38 | 39 | ### 协程函数执行 40 | 41 | 为了便于管理协程的状态,通过一个函数来执行协程。函数如下: 42 | ``` 43 | void main_func(schedule *s) 44 | { 45 | int id = s->running_coroutine; 46 | if(id != -1) 47 | { 48 | coroutine* c = s->coroutines[id]; 49 | c->func(s, c->args); 50 | c->state = DEAD; 51 | s->running_coroutine = -1; 52 | } 53 | 54 | } 55 | ``` 56 | 当func目标函数执行完成了协程也就结束了到DEAD状态,当前也没有在执行的协程了,running_coroutine设置为-1。 57 | ### 协程创建 58 | ``` 59 | int coroutine_create(schedule *s, coroutine_func func, void *args) 60 | { 61 | coroutine *c = NULL; 62 | int i = 0; 63 | for(i=0; i < s->max_index; i++) 64 | { 65 | c = s->coroutines[i]; 66 | if(c->state == DEAD) 67 | { 68 | break; 69 | } 70 | } 71 | if(s->max_index == i || c == NULL) 72 | { 73 | s->coroutines[i] = (coroutine *)malloc(sizeof(coroutine)); 74 | } 75 | c = s->coroutines[i]; 76 | c->state = READY; 77 | c->func = func; 78 | c->args= args; 79 | s->max_index ++; 80 | getcontext(&(c->ctx)); 81 | 82 | c->ctx.uc_stack.ss_sp = c->stack; 83 | c->ctx.uc_stack.ss_size = STACK_SZIE; 84 | c->ctx.uc_stack.ss_flags = 0; 85 | c->ctx.uc_link = &(s->main); 86 | 87 | makecontext(&(c->ctx),(void (*)(void))main_func, 1, s); 88 | return i; 89 | } 90 | ``` 91 | 获取并初始化上下文信息,并设置栈区等信息,且设置方法为main_func。 92 | ### 协程切换 93 | ``` 94 | void coroutine_yield(schedule *s) 95 | { 96 | if(s->running_coroutine != -1) 97 | { 98 | coroutine* c = s->coroutines[s->running_coroutine]; 99 | c->state = SUSPEND; 100 | s->running_coroutine = -1; 101 | 102 | swapcontext(&(c->ctx), &(s->main)); 103 | } 104 | } 105 | ``` 106 | 保存当前运行的协程上下文,并切回主调度逻辑。 107 | ### 协程恢复 108 | ``` 109 | void coroutine_resume(schedule * s, int id) 110 | { 111 | if(id >= 0 && id < s->max_index) 112 | { 113 | coroutine *c = s->coroutines[id]; 114 | if(c != NULL && c->state == SUSPEND) 115 | { 116 | c->state = RUNNING; 117 | s->running_coroutine = id; 118 | swapcontext(&(s->main), &(c->ctx)); 119 | } 120 | } 121 | } 122 | 123 | ``` 124 | 恢复指定id的协程,保存主调度逻辑,恢复对应id的协程。 125 | 126 | ### 简单测试 127 | ``` 128 | #include 129 | #include "coroutine.h" 130 | 131 | void func1(schedule *s, void *args) 132 | { 133 | puts("11"); 134 | coroutine_yield(s); 135 | int *a = (int *)args; 136 | printf("func1:%d\n", a[0]); 137 | 138 | } 139 | 140 | void func2(schedule *s, void *args) 141 | { 142 | puts("22"); 143 | coroutine_yield(s); 144 | puts("22"); 145 | int *a = (int *)args; 146 | printf("func2:%d\n", a[0]); 147 | } 148 | 149 | int main() 150 | { 151 | int args1[] = {1}; 152 | int args2[] = {2}; 153 | schedule *s = schedule_create(); 154 | int id1 = coroutine_create(s, func1, (void *)args1); 155 | int id2 = coroutine_create(s, func2, (void *)args2); 156 | coroutine_running(s, id2); 157 | coroutine_running(s, id1); 158 | while(!schedule_finished(s)){ 159 | coroutine_resume(s, id2); 160 | coroutine_resume(s, id1); 161 | } 162 | puts("main over"); 163 | schedule_close(s); 164 | return 0; 165 | } 166 | ``` 167 | 结果: 168 | ``` 169 | 22 170 | 11 171 | 22 172 | func2:2 173 | func1:1 174 | main over 175 | ``` 176 | ### 总结 177 | 本文主要介绍了C语言实现简单协程库的实现,主要用于学习协程,实际使用情况比较复杂,这里为了学习简化了很多。主要从协程函数执行、协程创建、协程恢复等功能介绍了协程的实现。 178 | 项目代码我已经上传到github:https://github.com/xiaobing94/coroutine -------------------------------------------------------------------------------- /doc/ucontext_coroutine3.md: -------------------------------------------------------------------------------- 1 | ## C语言实现协程(三) 2 | ### 回顾 3 | 协程常用于IO密集型,但是目前我们仅仅是实现协程的切换与调度,并没有实战检验我们的协程库。接下来我们就用我们自己实现的协程库,实现一个非阻塞的socket服务器。 4 | 5 | ### 实现思路 6 | 我们已经实现了协程的切换恢复等功能。 7 | 现在我们要实现的是,当协程一个IO没有数据时我们不等待,而切换到下一个IO进行处理。 8 | 如果做一个比喻的话,大概就是像上学的时候老师一个一个听学生背课文。学生一个一个排好队,第一个同学上来,老师询问会背了吗?会,那么就背诵;不会,到旁边去准备,然后继续下一个同学询问是否背诵,接着又会询问之前的同学是否会背诵。 9 | 这里的主循环就是不断接收同学并询问是否已经会背诵,而同学背诵的过程就是协程执行的过程,可以看到同时只能有一个同学在背诵。 10 | 这里的同学可以理解成同学,而老师就是主逻辑负责不断询问各个IO是否准备好,而不会背诵就是未准备好,就需要切换IO,而前面拿起课本走过来的动作就不需要做了,相当于保存了执行状态。 11 | 12 | ### 思路图 13 | ![思路图](https://github.com/xiaobing94/coroutine/blob/master/doc/flow.png) 14 | 15 | ### 实现 16 | 需要实现两个函数: 17 | 一、主循环接收连接并轮询连接的请求 18 | 主循环不断accept检测是否有新的连接,并不断恢复旧的协程。 19 | 二、连接的数据处理逻辑 20 | 连接处理会判断是否有数据,有就会根据业务情况进行处理,否则则进行协程的切换。 21 | ### 测试 22 | 这里编写两个简单的python脚本进行测试。 23 | 第一个用户输入数据并不断发送,可以同时开启多个脚本进行测试。 24 | ``` 25 | import socket 26 | 27 | HOST = 'localhost' # The remote host 28 | PORT = 8888 # The same port as used by the server 29 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 30 | s.connect((HOST, PORT)) 31 | while True: 32 | msg = raw_input(">>:") 33 | s.sendall(msg) 34 | data = s.recv(1024) 35 | 36 | print('Received', repr(data)) 37 | s.close() 38 | ``` 39 | 第二个多线程不断发送请求进行测试。 40 | ``` 41 | #coding: utf8 42 | import socket 43 | import sys 44 | import threading 45 | from time import ctime,sleep 46 | 47 | threads = [] 48 | 49 | def connect_send(): 50 | count = 0 51 | t = threading.current_thread() 52 | tid = t.ident 53 | res = "count:"+str(count)+"__tid:"+str(tid) 54 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 55 | sock.connect(('127.0.0.1', 8888)) 56 | sock.send(res) 57 | szBuf = sock.recv(1024) 58 | print szBuf 59 | while(szBuf != 'exit'): 60 | count += 1 61 | res = "count:"+str(count)+"__tid:"+str(tid) 62 | sock.send(res) 63 | szBuf = sock.recv(1024) 64 | print("recv:" + szBuf) 65 | sleep(0.1) 66 | if(count > 10): 67 | break 68 | sock.close() 69 | 70 | for i in xrange(100): 71 | t = threading.Thread(target=connect_send, args=()) 72 | threads.append(t) 73 | 74 | if __name__ == '__main__': 75 | for t in threads: 76 | t.start() 77 | ``` 78 | 可以看到该服务端程序能在单线程情况下响应多个连接。 79 | ![效果图](https://github.com/xiaobing94/coroutine/blob/master/doc/result.png) 80 | ### 总结 81 | 这里使用我们自己实现的协程,实现了非阻塞socket的服务端,了解了协程如何处理IO问题及处理IO的过程。实际使用过程会比这个复杂,但基本原理相同。 -------------------------------------------------------------------------------- /test_simple.c: -------------------------------------------------------------------------------- 1 | #include "coroutine.h" 2 | 3 | #include 4 | 5 | void func1(schedule *s, void *args) 6 | { 7 | puts("11"); 8 | coroutine_yield(s); 9 | int *a = (int *)args; 10 | printf("func1:%d\n", a[0]); 11 | 12 | } 13 | 14 | void func2(schedule *s, void *args) 15 | { 16 | puts("22"); 17 | 18 | coroutine_yield(s); 19 | puts("22"); 20 | int *a = (int *)args; 21 | printf("func2:%d\n", a[0]); 22 | } 23 | 24 | int main() 25 | { 26 | int args1[] = {1}; 27 | int args2[] = {2}; 28 | schedule *s = schedule_create(); 29 | int id1 = coroutine_create(s, func1, (void *)args1); 30 | int id2 = coroutine_create(s, func2, (void *)args2); 31 | coroutine_running(s, id2); 32 | coroutine_running(s, id1); 33 | while(!schedule_finished(s)){ 34 | coroutine_resume(s, id2); 35 | coroutine_resume(s, id1); 36 | } 37 | puts("main over"); 38 | schedule_close(s); 39 | return 0; 40 | } -------------------------------------------------------------------------------- /test_socket.c: -------------------------------------------------------------------------------- 1 | #include "coroutine.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #define MAXLINE 1024 10 | #define PORT 8888 11 | // typedef struct _ConnfdList 12 | // { 13 | // int len; 14 | // int max_index; 15 | // int *fds; 16 | // }ConnfdList; 17 | 18 | #define PEER_IP "0.0.0.0" 19 | 20 | int create_socket() 21 | { 22 | int sock_fd; 23 | if( (sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){ 24 | printf("create socket error: %s(errno: %d)\n",strerror(errno),errno); 25 | return -1; 26 | } 27 | int flags = fcntl(sock_fd, F_GETFL, 0); 28 | fcntl(sock_fd, F_SETFL, flags|O_NONBLOCK); 29 | struct sockaddr_in servaddr; 30 | memset(&servaddr, 0, sizeof(servaddr)); 31 | servaddr.sin_family = AF_INET; 32 | servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址设置成INADDR_ANY,让系统自动获取本机的IP地址。 33 | servaddr.sin_port = htons(PORT);//设置的端口为DEFAULT_PORT 34 | 35 | if( bind(sock_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){ 36 | printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno); 37 | exit(0); 38 | } 39 | //开始监听是否有客户端连接 40 | if( listen(sock_fd, 1024) == -1){ 41 | printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno); 42 | exit(0); 43 | } 44 | return sock_fd; 45 | } 46 | 47 | void accept_conn(int sock_fd, schedule *s, int corourine_ids[], coroutine_func handle) 48 | { 49 | int connfd = 0; 50 | while(1) 51 | { 52 | connfd = accept(sock_fd,(struct sockaddr*)NULL,NULL); 53 | if(connfd > 0) 54 | { 55 | int args[] = {sock_fd, connfd}; 56 | int id = coroutine_create(s, handle, (void *)args); 57 | int i=0; 58 | for(i=0; i