=Start=
缘由:
学习需要
正文:
参考解答:
/* Linux网络编程服务器模型选择之并发服务器(上) http://www.cnblogs.com/lizhenghn/p/3617666.html */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <time.h> #include <string.h> #include <stdio.h> #define BUFFLEN 1024 #define SERVER_PORT 12347 #define BACKLOG 5 static void handle_request(int s_c) { time_t now; char buff[BUFFLEN]; int n = 0; memset(buff, 0, BUFFLEN); n = recv(s_c, buff, BUFFLEN, 0); /*接收发送方数据*/ if(n > 0 && !strncmp(buff, "TIME", 4)) /*判断是否合法接收数据*/ { memset(buff, 0, BUFFLEN); now = time(NULL); sprintf(buff, "%24s\r\n",ctime(&now)); send(s_c, buff, strlen(buff),0); /*发送数据*/ } close(s_c); /*关闭客户端*/ } static void handle_connect(int s_s) { int s_c; /*客户端套接字文件描述符*/ struct sockaddr_in from; /*客户端地址*/ int len = sizeof(from); /*主处理过程*/ while(1) { /*接收客户端连接*/ s_c = accept(s_s, (struct sockaddr*)&from, &len); if(s_c > 0)/*客户端成功连接*/ { /*创建进程进行数据处理*/ if(fork() > 0)/*父进程*/ { // printf("Remote IP:Port is '%s:%d'\n", inet_ntoa(from.sin_addr), (int)ntohs(from.sin_port)); // Segmentation fault (why?) close(s_c);/*关闭父进程的客户端连接套接字*/ } else { handle_request(s_c);/*处理连接请求*/ // printf("Remote IP:Port is '%s:%d'\n", inet_ntoa(from.sin_addr), (int)ntohs(from.sin_port)); // Nothing happens (why?) return ; } } } } int main(int argc, char *argv[]) { int s_s; /*服务器套接字文件描述符*/ struct sockaddr_in local; /*本地地址*/ /*建立TCP套接字*/ s_s = socket(AF_INET, SOCK_STREAM, 0); /*初始化地址*/ memset(&local, 0, sizeof(local)); local.sin_family = AF_INET;/*AF_INET协议族*/ local.sin_addr.s_addr = htonl(INADDR_ANY);/*任意本地地址*/ local.sin_port = htons(SERVER_PORT);/*服务器端口*/ int err; /*将套接字文件描述符绑定到本地地址和端口*/ err = bind(s_s, (struct sockaddr*)&local, sizeof(local)); err = listen(s_s, BACKLOG);/*侦听*/ /*处理客户端连接*/ handle_connect(s_s); close(s_s); return 0; }
&
/* Linux网络编程服务器模型选择之IO复用循环并发服务器 http://www.cnblogs.com/lizhenghn/p/3619091.html http://man7.org/linux/man-pages/man3/pthread_create.3.html http://man7.org/linux/man-pages/man2/socket.2.html http://man7.org/linux/man-pages/man2/bind.2.html http://man7.org/linux/man-pages/man2/listen.2.html http://man7.org/linux/man-pages/man2/select.2.html */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <time.h> #include <string.h> #include <stdio.h> #include <pthread.h> #include <sys/select.h> #define BUFFLEN 1024 #define SERVER_PORT 12349 #define BACKLOG 5 /*挂起队列的最大长度*/ #define CLIENTNUM 1024 /*最大支持客户端数量*/ // 全局变量 int connect_host[CLIENTNUM]; /*可连接客户端的文件描述符数组*/ int connect_number = 0; static void *handle_request(void *argv) { time_t now; /*时间*/ char buff[BUFFLEN]; /*收发数据缓冲区*/ fd_set scanfd; /*侦听描述符集合*/ struct timeval timeout; /*超时*/ timeout.tv_sec = 1; /*阻塞1秒后超时返回*/ timeout.tv_usec = 0; int i = 0; int maxfd = -1; /*最大侦听文件描述符*/ int err = -1; int n = 0; for(;;) { /*最大文件描述符值初始化为-1*/ maxfd = -1; FD_ZERO(&scanfd); /*清零文件描述符集合*/ for(i=0; i<CLIENTNUM; i++) /*将文件描述符放入集合*/ { if(connect_host[i] != -1)/*合法的文件描述符*/ { FD_SET(connect_host[i], &scanfd); /*放入集合*/ if(maxfd < connect_host[i]) /*更新最大文件描述符值*/ { maxfd = connect_host[i]; } } } err = select(maxfd + 1, &scanfd, NULL, NULL, &timeout); /*select等待*/ switch(err) { case 0: /*超时*/ break; case -1: /*错误发生*/ break; default: /*有可读套接字文件描述符*/ if(connect_number <= 0) break; for(i = 0; i<CLIENTNUM; i++) { /*查找激活的文件描述符*/ if(connect_host[i] != -1 && FD_ISSET(connect_host[i], &scanfd)) { memset(buff, 0, BUFFLEN); /*清零*/ n = recv(connect_host[i], buff, BUFFLEN, 0); /*接收发送方数据*/ if(n > 0 && !strncmp(buff, "TIME", 4))/*判断是否合法接收数据*/ { memset(buff, 0, BUFFLEN); /*清零*/ now = time(NULL); /*当前时间*/ sprintf(buff, "%24s\r\n", ctime(&now)); /*将时间拷贝入缓冲区*/ send(connect_host[i], buff, strlen(buff), 0); /*发送数据*/ } /*关闭客户端*/ close(connect_host[i]); connect_host[i] = -1; /*更新文件描述符在数组中的值*/ connect_number--; /*客户端计数器减1*/ } } break; } } return NULL; } static void *handle_connect(void *argv) { int sock_fd = *((int*)argv); /*获得服务器侦听套接字文件描述符*/ int s_c = -1; /*连接客户端文件描述符*/ struct sockaddr_in from; int len = sizeof(from); /*接收客户端连接*/ for(;;) { int i = 0; s_c = accept(sock_fd, (struct sockaddr *)&from, &len); /*接收客户端的请求*/ // printf("a client connect, from:%s\n", inet_ntoa(from.sin_addr)); // Segmentation fault (why?) /*查找合适位置,将客户端的文件描述符放入*/ for(i=0; i<CLIENTNUM; i++) { if(connect_host[i] == -1)/*找到*/ { connect_host[i]= s_c; /*放入*/ connect_number++; /*客户端计数器加1*/ break; /*继续轮询等待客户端连接*/ } } } return NULL; } int main(int argc, char *argv[]) { memset(connect_host, -1, CLIENTNUM); int sock_fd; /*服务器套接字文件描述符*/ sock_fd = socket(AF_INET, SOCK_STREAM, 0); /*建立TCP套接字*/ struct sockaddr_in local; /*本地地址*/ /*初始化地址连接信息*/ memset(&local, 0, sizeof(local)); /*清零*/ local.sin_family = AF_INET; /*AF_INET协议族*/ local.sin_addr.s_addr = htonl(INADDR_ANY); /*任意本地地址*/ local.sin_port = htons(SERVER_PORT); /*服务器端口*/ int err; err = bind(sock_fd, (struct sockaddr *)&local, sizeof(local)); /*将套接字文件描述符绑定到本地地址和端口*/ // if(err == -1) perror("bind() error"); err = listen(sock_fd, BACKLOG); /*将 sock_fd 设置为监听模式,参数 BACKLOG 定义挂起队列的最大长度*/ // if(err == -1) perror("listen() error"); pthread_t thread_do[2]; /*线程ID*/ /*创建线程处理客户端连接*/ pthread_create(&thread_do[0], /*线程ID*/ NULL, /*属性*/ handle_connect, /*线程回调函数*/ (void*)&sock_fd); /*传递给线程回调函数的唯一线程参数*/ /*创建线程处理客户端请求*/ pthread_create(&thread_do[1], /*线程ID*/ NULL, /*属性*/ handle_request, /*线程回调函数*/ NULL); /*传递给线程回调函数的唯一线程参数*/ int i = 0; for(i=0; i<2; i++) pthread_join(thread_do[i], NULL); /*等待线程结束*/ close(sock_fd); return 0; }
参考链接:
Linux网络编程服务器模型选择之并发服务器(上)
http://www.cnblogs.com/lizhenghn/p/3617666.html
Linux网络编程服务器模型选择之IO复用循环并发服务器
http://www.cnblogs.com/lizhenghn/p/3619091.html
http://man7.org/linux/man-pages/man3/pthread_create.3.html
http://man7.org/linux/man-pages/man2/socket.2.html
http://man7.org/linux/man-pages/man2/bind.2.html
http://man7.org/linux/man-pages/man2/listen.2.html
http://man7.org/linux/man-pages/man2/select.2.html
=END=
《 “Linux网络编程服务器模型之并发服务器” 》 有 6 条评论
通过完整示例来理解如何使用 epoll
http://blog.jobbole.com/93566/
https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
`
Linux 的 epoll(7) 机制,它是 Linux 最好的「就绪通知机制」。
select(2) 一次可以监测 FD_SETSIZE 数量大小的描述符,FD_SETSIZE 通常是一个在 libc 编译时指定的数字(1024)。需要线性扫描所有通过描述符,时间复杂度为 O(n) 。
poll(2) 一次可以监测的描述符数量并没有限制,但撇开其它因素,我们每次都不得不检查就绪通知,线性扫描所有通过描述符,这样时间复杂度为 O(n) 而且很慢。
epoll 没有这些固定限制,也不执行任何线性扫描,能在O(1)时间内完成操作。因此它可以更高效地执行和处理大量事件。
`
如何实现一个Web Server
http://tinylcy.me/2017/%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAWeb-Server/
http://csapp.cs.cmu.edu/2e/ics2/code/netp/tiny/tiny.c
https://github.com/tinylcy/vino
并发模型比较
https://gobomb.github.io/post/high-concurrency-model/
`
一切始于 C10k
同步/异步?阻塞/非阻塞?
进程、线程、协程
并发模型
1. 单进(线)程·循环处理请求
2. 多进程
3. 多线程
4. 单线程·回调(callback)和事件轮询
Nginx
Node.js
5. 协程
Actor 和 CSP 模型
总结
update:函数式编程
`
http://www.kegel.com/c10k.html
[原]linux网络编程十三:I/O复用select
https://blog.csdn.net/jasonliuvip/article/details/22698751
[原]linux网络编程十四:I/O复用epoll
https://blog.csdn.net/jasonliuvip/article/details/22826273
[原]linux网络编程十六:I/O复用的应用-poll简单实现聊天室程序
https://blog.csdn.net/jasonliuvip/article/details/22961111
漫话:如何给女朋友解释什么是BIO、NIO和AIO?
https://mp.weixin.qq.com/s/HQd-PsnJI8TtEtAXMsjuPw
`
BIO (Blocking I/O):同步阻塞I/O模式。
NIO (New I/O):同步非阻塞模式。
AIO (Asynchronous I/O):异步非阻塞I/O模型。
以烧水为例进行简单说明:
同步阻塞模式:这种模式下,我们的工作模式是先来到厨房,开始烧水,并坐在水壶面前一直等着水烧开。
同步非阻塞模式:这种模式下,我们的工作模式是先来到厨房,开始烧水,但是我们不一直坐在水壶前面等,而是回到客厅看电视,然后每隔几分钟到厨房看一下水有没有烧开。
异步非阻塞I/O模型:这种模式下,我们的工作模式是先来到厨房,开始烧水,我们不一一直坐在水壶前面等,也不隔一段时间去看一下,而是在客厅看电视,水壶上面有个开关,水烧开之后他会通知我。
阻塞VS非阻塞:人是否坐在水壶前面一直等。
同步VS异步:水壶是不是在水烧开之后主动通知人。
`
爱奇艺网络协程编写高并发应用实践
https://mp.weixin.qq.com/s/mqpsOqc58D9StjlRoBIQug
https://github.com/iqiyi/libfiber