《后台开发:核心技术与应用实践》第七章网络IO模型
标签: 《后台开发:核心技术与应用实践》第七章网络IO模型 Html/CSS博客 51CTO博客
2023-03-31 18:23:28 130浏览
《后台开发:核心技术与应用实践》第七章网络IO模型,文章目录一、基础知识1.IO的两种操作:同步IO和异步IO2.网络中IO的操作有4种情况:3.当一个网络IO(eg,read操作),会涉及哪两个系统对象和经历哪两个阶段?二、四种网络IO模型1.阻塞IO模型(1)阻塞和非阻塞在于用户进程调用内核IO操作方式下的区别(2)特点(3)几乎所有的IO接口(包括socket接口)都是阻塞型的(4)改进方案:多线程(多进程),线程池,连接池(5)为何一个s
文章目录
- 一、基础知识
- 1.IO的两种操作:同步IO和异步IO
- 2.网络中IO的操作有4种情况:
- 3.当一个网络IO(eg,read操作),会涉及哪两个系统对象和经历哪两个阶段?
- 二、四种网络IO模型
- 1.阻塞IO模型
- (1)阻塞和非阻塞在于用户进程调用内核IO操作方式下的区别
- (2)特点
- (3)几乎所有的IO接口(包括socket接口)都是阻塞型的
- (4)改进方案:多线程(多进程),线程池,连接池
- (5)为何一个 socket 可以 accept 多次?
- (6)总结
- 2.非阻塞IO模型
- 3.多路IO复用模型,即事件驱动IO
- (1)特点
- (2)多路IO复用和阻塞IO的区别
- (3)使用select()的效果与非阻塞IO类似
- (4)select()原型:探测多个文件句柄的状态变化
- (5)事件驱动模型
- 4.异步IO模型
- (1)特点
- (2)非阻塞IO和异步IO的区别
- 5.各个 IO 模型的比较
- 二、select——完成非阻塞方式工作的程序,监视需要被监视的文件描述符的变化情况:读、写或异常
- 1.select函数原型
- 2.使用select函数循环读取键盘输入
- 3.观察 select 超时
- 3.使用 select 函数提高服务器的处理能力
一、基础知识
1.IO的两种操作:同步IO和异步IO
- 同步IO:必须等待IO操作完成后,控制权才返回给用户进程
- 异步IO:无需等待IO操作完成,就将控制权返回给用户进程
2.网络中IO的操作有4种情况:
- 输入:等待数据到达套接字接收缓冲区
- 输出:等待套接字发送缓冲区有足够的空间容纳将要发送的数据
- 服务器接收连接请求:等待新的客户端连接请求的到来
- 客户端发送连接请求:等待服务器回送客户的发起的SYN所对应的ACK
3.当一个网络IO(eg,read操作),会涉及哪两个系统对象和经历哪两个阶段?
- 两个系统对象为:
(1)调用这个IO的进程
(2)系统内核 - 两个阶段是:
(1)等待数据准备
(2)将数据从内核拷贝到进程(实际是拷贝到内存中)
二、四种网络IO模型
1.阻塞IO模型
(1)阻塞和非阻塞在于用户进程调用内核IO操作方式下的区别
(2)特点
(3)几乎所有的IO接口(包括socket接口)都是阻塞型的
(4)改进方案:多线程(多进程),线程池,连接池
(5)为何一个 socket 可以 accept 多次?
(6)总结
多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞模型来尝试解决这个问题。
2.非阻塞IO模型
- 特点
- recv()函数的不同含义
- 如下的函数可以将某句柄归设为非阻塞状态 :fcntl( fd , F_SETFL , O_NONBLOCK );
3.多路IO复用模型,即事件驱动IO
(1)特点
- 它的基本原理就是有个函数(eg:select)会不断地轮询所负责的所有socket,当某个socket 有数据到达了,就通知用户进程
- 多路 IO 复用模型的流程如图 7-3 所示
(2)多路IO复用和阻塞IO的区别
(3)使用select()的效果与非阻塞IO类似
(4)select()原型:探测多个文件句柄的状态变化
(5)事件驱动模型
- 这种模型的特征在于每一个执行周期都会探测一次或一组事件,一个特定的事件会触发某个特定的响应,这里可以将这种模型归类为“事件驱动模型” 。
- select()事件驱动模型的优点
- select()事件驱动模型的缺点
4.异步IO模型
(1)特点
- 用户进程发起 read 操作之后,立刻就可以开始去做其他的事;而另一方面,从内核的角度,当它收到一个异步的 read 请求操作之后,首先会立刻返回,所以不会对用户进程产生任何阻塞。
- 然后,内核会等待数据准备完成,然后将数据拷贝到用户内存中,当这一切都完成之后,内核会给用户进程发送一个信号,返回 read 操作已完成的信息。
(2)非阻塞IO和异步IO的区别
- 非阻塞 IO 在执行 recvfrom 这个系统调用的时候,如果内核的数据没有准备好,这时候不会阻塞进程 。但是当内核中数据准备好时,recvfrom 会将数据从内核拷贝到用户内存中,这个时候进程则被阻塞。
- 异步 IO 则不一样,当进程发起 IO 操作之后,就直接返回,直到内核发送一个信号,告诉进程 IO 已完成,则在这整个过程中,进程完全没有被阻塞。
5.各个 IO 模型的比较
二、select——完成非阻塞方式工作的程序,监视需要被监视的文件描述符的变化情况:读、写或异常1.select函数原型
(1)结构体1: fd_set
(2)结构体2: timeval
结构体 timeval 是一个常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数 。(3)select 的各个参数所表示的含义
2.使用select函数循环读取键盘输入
#include <sys/time.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/select.h>
int main(){
int keyboard;
int ret,i;
char c;
fd_set readfd;
struct timeval timeout;
keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK);
assert(keyboard>0);
while(1){
timeout.tv_sec=1;
timeout.tv_usec=0;
FD_ZERO(&readfd); //将readfd清零
FD_SET(keyboard,&readfd);//将keyboard加入readfd
ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);
if(FD_ISSET(keyboard,&readfd))//如果keyboard在readfd中,则为真
{
i=read(keyboard,&c,1);
if('\n'==c)
continue;
printf("The input is %c\n",c);
if ('q'==c)
break;
}
}
return 0;
}
(1)执行
(2)分析
- open("/dev/tty",O_RDONLY | O_NONBLOCK)
- assert(keyboard>0)
- 函数解释
timeout.tv_sec=1;
timeout.tv_usec=0;
FD_ZERO(&readfd); //将readfd清零
FD_SET(keyboard,&readfd);//将keyboard加入readfd
ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);
- 函数解释
if(FD_ISSET(keyboard,&readfd))//如果keyboard在readfd中,则为真
{
i=read(keyboard,&c,1);
if('\n'==c)
continue;
printf("The input is %c\n",c);
if ('q'==c)
break;
}
3.观察 select 超时
#include <sys/time.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/select.h>
int main(){
int keyboard;
int ret,i;
char c;
fd_set readfd;
struct timeval timeout;
keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK);
assert(keyboard>0);
while(1) {
timeout.tv_sec=5;
timeout.tv_usec=0;
FD_ZERO(&readfd);
FD_SET(keyboard,&readfd);
ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);
if (ret == -1)
perror("select error");
else if (ret){
if(FD_ISSET(keyboard,&readfd)){
i=read(keyboard,&c,1);
if('\n'==c)
continue;
printf("hehethe input is %c\n",c);
if ('q'==c)
break;
}
}else if (ret == 0)
printf("time out\n");
}
return 0;
}
(1)执行
(2)解释
if (ret == -1)
perror("select error");
else if (ret){
if(FD_ISSET(keyboard,&readfd)){
i=read(keyboard,&c,1);
if('\n'==c)
continue;
printf("hehethe input is %c\n",c);
if ('q'==c)
break;
}
}else if (ret == 0)
printf("time out\n");
}
3.使用 select 函数提高服务器的处理能力
- server端代码:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#define DEFAULT_PORT 6666
int main( int argc, char ** argv){
int serverfd,acceptfd; /* 监听socket: serverfd,数据传输socket: acceptfd */
struct sockaddr_in my_addr; /* 本机地址信息 */
struct sockaddr_in their_addr; /* 客户地址信息 */
unsigned int sin_size, myport=6666, lisnum=10;
if ((serverfd = socket(AF_INET , SOCK_STREAM, 0)) == -1) {
perror("socket" );
return -1;
}
printf("socket ok \n");
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(DEFAULT_PORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(my_addr.sin_zero), 0);
if (bind(serverfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr )) == -1) {
perror("bind" );
return -2;
}
printf("bind ok \n");
if (listen(serverfd, lisnum) == -1) {
perror("listen" );
return -3;
}
printf("listen ok \n");
fd_set client_fdset; /*监控文件描述符集合*/
int maxsock; /*监控文件描述符中最大的文件号*/
struct timeval tv; /*超时返回时间*/
int client_sockfd[5]; /*存放活动的sockfd*/
bzero((void*)client_sockfd,sizeof(client_sockfd));
int conn_amount = 0; /*用来记录描述符数量*/
maxsock = serverfd;
char buffer[1024];
int ret=0;
while(1)
{
/*初始化文件描述符号到集合*/
FD_ZERO(&client_fdset);
/*加入服务器描述符*/
FD_SET(serverfd,&client_fdset);//把服务器描述符加入到集合中
/*设置超时时间*/
tv.tv_sec = 30; /*30秒*/
tv.tv_usec = 0;
/*把活动的句柄加入到文件描述符中*/
for(int i = 0; i < 5; ++i){
/*程序中Listen中参数设为5,故i必须小于5*/
if(client_sockfd[i] != 0){
FD_SET(client_sockfd[i], &client_fdset);
}
}
/*printf("put sockfd in fdset!\n");*/
/*select函数*/
ret = select(maxsock+1, &client_fdset, NULL, NULL, &tv);
if(ret < 0){
perror("select error!\n");
break;
}
else if(ret == 0){
printf("timeout!\n");
continue;
}
/*轮询各个文件描述符*/
for(int i = 0; i < conn_amount; ++i)
{
/*FD_ISSET检查client_sockfd是否可读写,>0可读写*/
if(FD_ISSET(client_sockfd[i], &client_fdset))
{
printf("start recv from client[%d]:\n",i);
ret = recv(client_sockfd[i], buffer, 1024, 0);
if(ret <= 0)
{
printf("client[%d] close\n", i);
close(client_sockfd[i]);
FD_CLR(client_sockfd[i], &client_fdset);
client_sockfd[i] = 0;
}
else
{
printf("recv from client[%d] :%s\n", i, buffer);
}
}
}
/*检查是否有新的连接,如果有,接收连接,加入到client_sockfd中*/
if(FD_ISSET(serverfd, &client_fdset))
{
/*接受连接*/
struct sockaddr_in client_addr;
size_t size = sizeof(struct sockaddr_in);
int sock_client = accept(serverfd, (struct sockaddr*)(&client_addr), (unsigned int*)(&size));
if(sock_client < 0)
{
perror("accept error!\n");
continue;
}
/*把连接加入到文件描述符集合中*/
if(conn_amount < 5)
{
client_sockfd[conn_amount++] = sock_client;
bzero(buffer,1024);
strcpy(buffer, "this is server! welcome!\n");
send(sock_client, buffer, 1024, 0);
printf("new connection client[%d] %s:%d\n", conn_amount, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
bzero(buffer,sizeof(buffer));
ret = recv(sock_client, buffer, 1024, 0);
if(ret < 0)
{
perror("recv error!\n");
close(serverfd);
return -1;
}
printf("recv : %s\n",buffer);
if(sock_client > maxsock)
{
maxsock = sock_client;
}
else
{
printf("max connections!!!quit!!\n");
break;
}
}
}
}
for(int i = 0; i < 5; ++i)
{
if(client_sockfd[i] != 0){
close(client_sockfd[i]);
}
}
close(serverfd);
return 0;
}
- client端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define DEFAULT_PORT 6666
int main( int argc, char * argv[]){
int connfd = 0;
int cLen = 0;
struct sockaddr_in client;
if(argc < 2){
printf(" Uasge: clientent [server IP address]\n");
return -1;
}
client.sin_family = AF_INET;
client.sin_port = htons(DEFAULT_PORT);
client.sin_addr.s_addr = inet_addr(argv[1]);
connfd = socket(AF_INET, SOCK_STREAM, 0);
if(connfd < 0){
perror("socket" );
return -1;
}
if(connect(connfd, (struct sockaddr*)&client, sizeof(client)) < 0){
perror("connect" );
return -1;
}
char buffer[1024];
bzero(buffer,sizeof(buffer));
recv(connfd, buffer, 1024, 0);
printf("recv : %s\n", buffer);
bzero(buffer,sizeof(buffer));
strcpy(buffer,"this is client!\n");
send(connfd, buffer, 1024, 0);
while(1){
bzero(buffer,sizeof(buffer));
scanf("%s",buffer);
int p = strlen(buffer);
buffer[p] = '\0';
send(connfd, buffer, 1024, 0);
printf("i have send buffer\n");
}
close(connfd);
return 0;
}
- makefile代码
all:server client
server:server.o
g++ -g -o server server.o
client:client.o
g++ -g -o client client.o
server.o:server.cpp
g++ -g -c server.cpp
client.o:client.cpp
g++ -g -c client.cpp
clean:all
rm all
(1)执行
(2)分析server代码
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
暂无评论,快来写一下吧
展开评论