=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