教你开发一个高效的服务器

教你开发一个高效的服务器

转载请注明作者信息
作者:lijiuwei
邮箱:lijiuwei0902@gmail.com

高效服务器的关键技术:
1.需要有固定数量的线程来去处理请求,而不可以每次收到请求都fork或者create_thread;
2.如果socket使用堵塞模式,那么read必须要有超时,如果使用非堵塞模式,那么read就必须要有数据缓冲;
3.使用epoll分别在不同的线程中监视可读和可写;
4.对于tcp长连接使用主线程分配连接,然后由任务线程处理,一个任务线程对应一个任务队列;对于tcp短连接和udp连接,使用竞争线程;
5.时刻检测并且关闭超时连接;
6.真正关闭socket的地方只能由一处,其他地方发现其socket需要关闭只能标识socket应该关闭了,然后由那一处安全的关闭socket;
7.当服务器需要不断的发送硬盘上的数据给客户端(比如说文件传输服务器和流媒体服务器),那么服务器端必须先从硬盘加载数据到数据缓存,再从数据缓冲发送数据给客户端;
8.内存分配必须成功,不然线程睡眠然后重新分配直到分配成功为止;

通用设计方案:
现在我们设计一个通用的服务器,这个服务器接受客户端的连接,然后不断读取客户端的请求,在某一时刻又转为不断的发送数据给客户端;下面我们看看怎么设计这样的一个服务器

注:
1.为了只关注设计方面并且让代码尽量简洁,对于一些错误处理我删除了。
2.must_malloc,must_calloc和其他前缀为must的函数封装了相应的内存分配函数,使得分配必须成功;
3.queue_t结构是一个通用的任务队列结构,queue_new是创建新的队列,queue_push是推数据到队列中,queue_pop是从队列中抛出数据
4.下面的代码只是一个良好的服务器架构,而不是一个真正能使用的服务器
5.欢迎讨论和优化!

 

 

Cpp代码 
  1. typedef struct server_st *server_t;  
  2. typedef struct task_queue_st *task_queue_t;  
  3. typedef struct conn_st *conn_t;  
  4.   
  5. /** 服务器结构 */  
  6. struct server_st {  
  7.     /** 监听地址 */  
  8.     struct in_addr listen_addr;  
  9.   
  10.     /** 监听端口 */  
  11.     int port;  
  12.   
  13.     /** 监听描述符 */  
  14.     int listen_fd;  
  15.   
  16.     /**负责读取请求的任务队列,一个线程负责处理一个任务队列,一共五个*/  
  17.     queue_t readreq_task_queues[5];  
  18.   
  19.     /**负责写数据的任务队列,一个线程负责处理一个任务队列,一共五个*/  
  20.     queue_t writedata_task_queues[5];  
  21. };  
  22.   
  23. /** 任务队列结构 */  
  24. struct task_queue_st {  
  25.     /** 待添加的连接 */  
  26.     queue_t pending_conn_queue;  
  27.   
  28.     /** 互斥锁,用于多线程环境下对任务队列的访问控制 */  
  29.     pthread_mutex_t task_queue_mutex;  
  30.   
  31.     /** 链表结构,保存任务队列中的所有连接 */  
  32.     conn_t conn_head;  
  33.   
  34.     /** 链表的最后一个节点,用于方便在结尾插入新的节点 */  
  35.     conn_t conn_tail;  
  36.   
  37.     /** 总连接数*/  
  38.     int total_conns;  
  39. };  
  40.   
  41. /** 用户的阶段状态 */  
  42. typedef enum {  
  43.     status_ESTABLISHED,/** 客户端刚建立连接 */  
  44.     status_CONNECTED,/** 客户端正在连接 */  
  45.     status_START_RECV_DATA,/** 客户端开始接收数据,即表示服务器端开始发送数据 */  
  46.     status_CLOSED/** 客户端关闭连接 */  
  47. } client_status_t;  
  48.   
  49. /** 服务器的阶段状态 */  
  50. typedef enum {  
  51.     status_SERVER_CONNECTED,/** 与客户端建立连接 */  
  52.     status_SERVER_CLOSING /** 服务器关闭连接 */  
  53. } server_status_t;  
  54.   
  55. /** 连接结构 */  
  56. struct conn_st {  
  57.     /** 该连接的所属服务器 */  
  58.     server_t server;  
  59.   
  60.     /** 该连接的引用计数 */  
  61.     int ref_count;  
  62.   
  63.     /** 用户最后一次活动的时间*/  
  64.     time_t last_activity;  
  65.   
  66.     /** 客户端当前的状态阶段*/  
  67.     client_status_t client_status;  
  68.   
  69.     /** 服务器当前的状态阶段*/  
  70.     server_status_t server_status;  
  71.   
  72.     /** socket描述符*/  
  73.     int fd;  
  74.   
  75.     /** 数据缓冲,当服务器端需要不断的发送数据给客户端时被用到 */  
  76.     char *remain_data;  
  77.   
  78.     /** 数据缓冲剩余数据大小 */  
  79.     int remain_data_size;  
  80.   
  81.     /** 下一个连接 */  
  82.     conn_t readreq_task_queue_next;  
  83.   
  84.     /** 下一个连接 */  
  85.     conn_t writedata_task_queue_next;  
  86. };  
  87.   
  88. /** 主函数 */  
  89. int main(int argc, char **argv) {  
  90.     int idx, conn_fd;  
  91.     server_t server;  
  92.   
  93.     server = must_calloc(1, sizeof(struct server_st));  
  94.     server->port = 端口号;  
  95.     server->listen_addrs.s_addr = INADDR_ANY;  
  96.     server->listen_fd = 服务器监听(&server->listen_addr,server->port);  
  97.     设置成非堵塞(server->listen_fd);  
  98.   
  99.     /* 创建5个负责读取请求的任务线程 */  
  100.     for (idx = 0; idx < 5; idx++) {  
  101.         task_queue_t task_queue = server->readreq_task_queues[idx];  
  102.   
  103.         bzero(task_queue,sizeof(struct task_queue_st));  
  104.         task_queue->pending_conn_queue = queue_new();  
  105.         pthread_mutex_init(&task_queue->task_queue_mutex,NULL);  
  106.   
  107.         pthread_create(&task_queue->thread_tid, NULL, readreq_task_thread, task_queue);  
  108.     }  
  109.   
  110.     /* 创建5个负责写数据的任务线程 */  
  111.     for (idx = 0; idx < 5; idx++) {  
  112.         task_queue_t task_queue = server->writedata_task_queues[idx];  
  113.   
  114.         bzero(task_queue,sizeof(struct task_queue_st));  
  115.         task_queue->pending_conn_queue = queue_new();  
  116.         pthread_mutex_init(&task_queue->task_queue_mutex,NULL);  
  117.   
  118.         pthread_create(&task_queue->thread_tid, NULL, writedata_task_thread, task_queue);  
  119.     }  
  120.   
  121.     /* 无限循环的监听,等待客户端的连接请求 */  
  122.     while (1) {  
  123.         conn_fd = accept(server->listen_fd, 0, 0);  
  124.         if (conn_fd < 0) continue;  
  125.   
  126.         /* 和一个客户端建立了连接,下面开始把该连接分配给负责读取请求的任务线程*/  
  127.         assign_to_readreq_task_thread(server,conn_fd);  
  128.     }  
  129.   
  130.     return 0;  
  131. }  
  132.   
  133.   
  134. /*分配任务,原则为哪个线程的任务队列最少就分配给哪一个线程去处理*/  
  135. static void assign_to_readreq_task_thread(server_t server, int conn_fd) {  
  136.     int idx;  
  137.     task_queue_t task_queue, min_task_queue = NULL;  
  138.     conn_t conn;  
  139.   
  140.     /* 查找连接数最少的任务队列 */  
  141.     for (idx = 0; idx < 5; idx++) {  
  142.         task_queue = server->readreq_task_queues[idx];  
  143.   
  144.         if (min_task_queue == NULL)  
  145.             min_task_queue = task_queue;  
  146.         else if (min_task_queue->total_conns > task_queue->total_conns)  
  147.             min_task_queue = task_queue;  
  148.     }  
  149.   
  150.     if (min_task_queue) {  
  151.         conn = must_calloc(1, sizeof(struct conn_st));  
  152.   
  153.         conn->server = server;  
  154.         conn->client_status = status_ESTABLISHED;  
  155.         conn->server_status = status_SERVER_CONNECTED;  
  156.         conn->ref_count++;/*累加引用计数*/  
  157.   
  158.         /*添加到该任务队列中的待添加队列并且累加该任务队列里的连接数*/  
  159.         pthread_mutex_lock(&min_task_queue->task_queue_mutex);  
  160.         queue_push(min_task_queue->pending_conn_queue, conn, 1);  
  161.         min_task_queue->total_conns++;  
  162.         pthread_mutex_unlock(&min_task_queue->task_queue_mutex);  
  163.     }  
  164. }  
  165.   
  166. /** 
  167. *负责读取请求的任务线程处理主函数,负责管理,处理自己队列中的所有连接 
  168. */  
  169. static void *readreq_task_thread(void *arg) {  
  170.     int epfd = epoll_create(20480);  
  171.     struct epoll_event ev,happened_ev[128];  
  172.     task_queue_t task_queue = (task_queue_t) arg;  
  173.     int ready,i;  
  174.   
  175.     while (1) {  
  176.         time_t curtime = time(NULL);  
  177.         /*下面三个变量遍历链表时使用,conn是当前连接,prev_conn是先前的连接,discarded_conn是丢弃的连接*/  
  178.         conn_t conn = NULL, prev_conn = NULL, discarded_conn;  
  179.   
  180.         /*把待添加队列里的连接添加到任务队列中*/  
  181.         pthread_mutex_lock(&task_queue->task_queue_mutex);  
  182.         while ((conn = queue_pop(task_queue->pending_conn_queue))) {  
  183.             if (!task_queue->conn_head) {  
  184.                 task_queue->conn_head = conn;  
  185.             } else {  
  186.                 task_queue->conn_tail->readreq_task_queue_next = conn;  
  187.             }  
  188.             task_queue->conn_tail = conn;  
  189.         }  
  190.         pthread_mutex_unlock(&task_queue->task_queue_mutex);  
  191.   
  192.         if (task_queue->total_conns == 0) {  
  193.             thread_sleep(1);  
  194.             continue;  
  195.         }  
  196.   
  197.         conn = task_queue->conn_head;  
  198.         while (conn) {  
  199.             //清理已经关闭的连接,把已经关闭的连接和超时的连接从链表中删除掉  
  200.             boolean conn_timeout = conn->last_activity > 0 && curtime - conn->last_activity > CLIENT_TIMEOUT;  
  201.   
  202.             if ((conn->client_status == status_CLOSED || conn->server_status == status_SERVER_CLOSING || conn_timeout) && conn->ref_count == 1) {  
  203.                 task_queue->total_conns--;  
  204.   
  205.                 discarded_conn = conn;//先放入临时变量中  
  206.   
  207.                 //从链表中删除  
  208.                 if (prev_conn) {  
  209.                     if (!conn->readreq_task_queue_next) {  
  210.                         task_queue->conn_tail = prev_conn;  
  211.                         prev_conn->readreq_task_queue_next = NULL;  
  212.                     } else {  
  213.                         prev_conn->readreq_task_queue_next = conn->readreq_task_queue_next;  
  214.                     }  
  215.                 } else {  
  216.                     task_queue->conn_head = conn->readreq_task_queue_next;  
  217.                 }  
  218.                 conn = conn->readreq_task_queue_next;  
  219.   
  220.                 close(discarded_conn->fd);/*真正关闭连接的地方*/  
  221.                 free(discarded_conn);  
  222.             } else {  
  223.                 ev.data.fd = conn->fd;  
  224.                 ev.data.ptr = conn;  
  225.                 /*只负责监视读事件和出错事件*/  
  226.                 ev.events = EPOLLIN | EPOLLET;  
  227.                 if(conn->client_status == status_ESTABLISHED) {  
  228.                     设置超时时间或者设置非堵塞(conn->fd);/*两者必须选一个*/  
  229.                     epoll_ctl(epfd,EPOLL_CTL_ADD,conn->fd,&ev);  
  230.                     conn->client_status = status_CONNECTED;  
  231.                 } else {  
  232.                     epoll_ctl(epfd,EPOLL_CTL_MOD,conn->fd,&ev);  
  233.                 }  
  234.                 prev_conn = conn;  
  235.                 conn = conn->readreq_task_queue_next;  
  236.             }  
  237.         }  
  238.   
  239.         /** 堵塞等待,超时时间为POLL_TIMEOUT毫秒*/  
  240.         if ((ready = epoll_wait(epfd, happened_ev,sizeof(happened_ev) / sizeof(struct epoll_event), POLL_TIMEOUT)) < 0)  
  241.             continue;  
  242.   
  243.         /** 
  244.         * 循环查找向我们发送请求的客户端的连接 
  245.         */  
  246.         for (i = 0; i < ready; i++) {  
  247.             if (happened_ev[i].events & POLLIN) {  
  248.                 conn = (conn_t) happened_ev[i].data.ptr;  
  249.   
  250.                 conn->last_activity = time(NULL);/* 记录客户端的最后活动时间*/  
  251.   
  252.                 proc_protocol(conn);/*处理协议函数*/  
  253.             } else if(happened_ev[i].events & EPOLLHUP) {  
  254.                 conn = (conn_t) happened_ev[i].data.ptr;  
  255.   
  256.                 conn->client_status = status_CLOSED;  
  257.             }  
  258.         }  
  259.     }  
  260. }  
  261.   
  262. /* 协议处理函数 */  
  263. void proc_protocol(conn_t conn) {  
  264.     某一时刻客户端要求服务器端不断发送数据给客户端,然后设置了conn->client_status = status_START_RECV_DATA,并且调用了下面的move_to_writedata_task_thread(conn);  
  265. }  
  266.   
  267. /*分配任务,原则为哪个线程的任务队列最少就分配给哪一个线程去处理*/  
  268. static void move_to_writedata_task_thread(conn_t conn) {  
  269.     server_t server = conn->server;  
  270.     task_queue_t task_queue, min_task_queue= NULL;  
  271.     int idx;  
  272.   
  273.     /* 查找连接数最少的任务队列 */  
  274.     for (idx = 0; idx < 5; idx++) {  
  275.         task_queue = server->writedata_task_queues[idx];  
  276.   
  277.         if (min_task_queue == NULL)  
  278.             min_task_queue = task_queue;  
  279.         else if (min_task_queue->total_conns > task_queue->total_conns)  
  280.             min_task_queue = task_queue;  
  281.     }  
  282.   
  283.     if (min_task_queue) {  
  284.         conn->ref_count++;/*累加引用计数*/  
  285.   
  286.         /*添加到该任务队列中的待添加队列并且累加该任务队列里的连接数*/  
  287.         pthread_mutex_lock(&min_task_queue->task_queue_mutex);  
  288.         queue_push(min_task_queue->pending_conn_queue, conn, 1);  
  289.         min_task_queue->total_conns++;  
  290.         pthread_mutex_unlock(&min_task_queue->task_queue_mutex);  
  291.     }  
  292. }  
  293.   
  294. /** 
  295. *负责写数据的线程处理主函数,负责管理,处理自己队列中的所有连接 
  296. */  
  297. static void *writedata_task_thread(void *arg) {  
  298.     task_queue_t task_queue = (task_queue_t) arg;  
  299.     int epfd = epoll_create(20480);  
  300.     struct epoll_event ev,happened_ev[128];  
  301.     int ready,i,nwrite,nread,sndbuf_size = 10240;  
  302.     char buf[sndbuf_size];  
  303.   
  304.     while (1) {  
  305.         time_t curtime = time(NULL);  
  306.         /*下面三个变量遍历链表时使用,conn是当前连接,prev_conn是先前的连接,discarded_conn是丢弃的连接*/  
  307.         conn_t conn = NULL, prev_conn = NULL, discarded_conn;  
  308.   
  309.         /*把待添加队列里的连接添加到任务队列中*/  
  310.         pthread_mutex_lock(&task_queue->task_queue_mutex);  
  311.         while ((conn = queue_pop(task_queue->pending_conn_queue))) {  
  312.             if (!task_queue->conn_head) {  
  313.                 task_queue->conn_head = conn;  
  314.             } else {  
  315.                 task_queue->conn_tail->writedata_task_queue_next = conn;  
  316.             }  
  317.             task_queue->conn_tail = conn;  
  318.         }  
  319.         pthread_mutex_unlock(&task_queue->task_queue_mutex);  
  320.   
  321.         if (task_queue->total_conns == 0) {  
  322.             thread_sleep(1);  
  323.             continue;  
  324.         }  
  325.   
  326.         conn = task_queue->conn_head;  
  327.         while (conn) {  
  328.             //清理已经关闭的连接,把已经关闭的连接和超时的连接从链表中删除掉  
  329.             boolean conn_timeout = conn->last_activity > 0 && curtime - conn->last_activity > CLIENT_TIMEOUT;  
  330.   
  331.             if (conn->client_status == status_CLOSED || conn->server_status == status_SERVER_CLOSING || conn_timeout) {  
  332.                 task_queue->total_conns--;  
  333.   
  334.                 discarded_conn = conn;//放入临时变量中  
  335.   
  336.                 //从链表中删除  
  337.                 if (prev_conn) {  
  338.                     if (!conn->writedata_task_queue_next) {  
  339.                         task_queue->conn_tail = prev_conn;  
  340.                         prev_conn->writedata_task_queue_next = NULL;  
  341.                     } else {  
  342.                         prev_conn->writedata_task_queue_next = conn->writedata_task_queue_next;  
  343.                     }  
  344.                 } else {  
  345.                     task_queue->conn_head = conn->writedata_task_queue_next;  
  346.                 }  
  347.                 conn = conn->writedata_task_queue_next;  
  348.   
  349.                 discarded_conn->ref_count--;/*引用计数减一,并且该连接已经关闭了,那么真正关闭连接的任务就交给readreq_task_thread函数了*/  
  350.             } else {  
  351.                 ev.data.fd = conn->fd;  
  352.                 ev.data.ptr = conn;  
  353.                 /*只负责监视写事件和出错事件*/  
  354.                 ev.events = EPOLLOUT | EPOLLET;  
  355.                 if(conn->client_status == status_START_RECV_DATA) {  
  356.                     setsockopt(conn->fd, SOL_SOCKET, SO_SNDBUF, (char *)&sndbuf_size, sizeof(int));  
  357.                     epoll_ctl(epfd,EPOLL_CTL_ADD,conn->fd,&ev);  
  358.                     conn->client_status = status_CONNECTED;  
  359.                 } else {  
  360.                     epoll_ctl(epfd,EPOLL_CTL_MOD,conn->fd,&ev);  
  361.                 }  
  362.                 prev_conn = conn;  
  363.                 conn = conn->writedata_task_queue_next;  
  364.             }  
  365.         }  
  366.   
  367.         /**堵塞等待,超时时间为POLL_TIMEOUT毫秒*/  
  368.         if ((ready = epoll_wait(epfd, happened_ev,sizeof(happened_ev) / sizeof(struct epoll_event), POLL_TIMEOUT)) < 0)  
  369.             continue;  
  370.   
  371.         /** 
  372.         * 循环查找向我们发送请求的客户端的连接 
  373.         */  
  374.         for (i = 0; i < ready; i++) {  
  375.             if (happened_ev[i].events & EPOLLOUT) {  
  376.                 conn = (conn_t) happened_ev[i].data.ptr;  
  377.   
  378.                 conn->last_activity = time(NULL);/* 记录客户端的最后活动时间*/  
  379.                 if(conn->remain_data_size == 0) {/*如果数据缓存已经没有任何数据了的话*/  
  380.                     if((nread = read(文件描述符,buf,sizeof(buf))) > 0) {/*从硬盘读数据出来到数据缓存*/  
  381.                         if((nwrite = write(conn->fd,buf,nread)) < 0 && errno != EINTR) {  
  382.                             conn->client_status = status_CLOSED;  
  383.                             continue;  
  384.                         }  
  385.   
  386.                         if(nwrite < nread) {  
  387.                             conn->remain_data_size = nread - nwrite;  
  388.                             conn->remain_data = must_malloc(conn->remain_data_size);  
  389.                             memcpy(conn->remain_data,buf + nwrite, conn->remain_data_size);  
  390.                         }  
  391.                     } else if(nread == 0) {  
  392.                         conn->server_status = status_SERVER_CLOSING;  
  393.                     }  
  394.                 } else {  
  395.                     /*写数据缓存里的数据到客户端*/  
  396.                     if((nwrite = write(conn->fd,conn->remain_data,conn->remain_data_size)) < 0 && errno != EINTR) {  
  397.                         conn->client_status = status_CLOSED;  
  398.                         free(conn->remain_data);  
  399.                         continue;  
  400.                     }  
  401.   
  402.                     if(nwrite < conn->remain_data_size) {  
  403.                         conn->remain_data_size = conn->remain_data_size - nwrite;  
  404.                         memcpy(buf, conn->remain_data + nwrite, conn->remain_data_size);  
  405.                         conn->remain_data = must_realloc(conn->remain_data, conn->remain_data_size);  
  406.                         memcpy(conn->remain_data, buf, conn->remain_data_size);  
  407.                     } else {  
  408.                         free(conn->remain_data);  
  409.                         conn->remain_data = NULL;  
  410.                         conn->remain_data_size = 0;  
  411.                     }  
  412.                 }  
  413.             } else if(happened_ev[i].events & EPOLLHUP) {  
  414.                 conn = (conn_t) happened_ev[i].data.ptr;  
  415.   
  416.                 conn->client_status = status_CLOSED;  
  417.             }  
  418.         }  
  419.     }  
  420. }  
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值