前言
本次是对网络部分知识的内容再次复习学习,同期文章可以看上篇Linux网络编程 ——Select机制
本文内容将以上课内容为导向,重新对其的学习。
一、什么是io
IO是输入input输出output的首字母缩写形式,直观意思是计算机输入输出,它描述的是计算机的数据流动的过程,因此IO第一大特征是有数据的流动;另外,对于一次IO,它究竟是输入还是输出,是针对不同的主体而言的,不同的主体有不同的描述。例如,甲乙两人交谈,甲将大脑中的想法通过声带震动,继而通过声波传入乙的耳朵,乙通过耳膜的震动再由神经将信号解析到大脑,这个数据流动的过程对甲而言是输出,对乙而言是输入。
下面,我们从三个层面来理解IO
从直观层面去理解IO
IO是计算机和外设之间的数据流动过程,电脑与外部设备如键盘和鼠标
从计算机架构的角度去理解IO
从计算机架构上来讲,任何涉及到计算机核心(CPU和内存)与其他设备间的数据转移的过程就是IO。本体就是计算机核心(CPU和内存)。例如从硬盘上读取数据到内存,是一次输入,将内存中的数据写入到硬盘就产生了输出。在计算机的世界里,这就是IO的本质。
从编程的角度去理解IO
此时,IO的主体是其应用程序的运行态,即进程,特别强调的是我们的应用程序其实并不存在实质的IO过程,真正的IO过程是操作系统的事情,这里把应用程序的IO操作分为两种动作:IO调用和IO执行。IO调用是由进程发起,IO执行是操作系统的工作。因此,更准确些来说,此时所说的IO是应用程序对操作系统IO功能的一次触发,即IO调用
以一个进程的输入类型的IO调用为例,它将完成或引起如下工作内容:
(1)进程向操作系统请求外部数据
(2)操作系统将外部数据加载到内核缓冲区
(3)操作系统将数据从内核缓冲区拷贝到进程缓冲区
(4)进程读取数据继续后面的工作
二、阻塞 与非阻塞
此学习以程序实例为导向:
阻塞程序如下:
#define MAXLNE 4096
int main() {
int listenfd,connfd;
struct sockaddr_in addr;
char buf[MAXLNE];
if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1) {
printf("create socket failed: %s(errno:%d)\n",strerror(errno),errno);
return 0;
}
memset(&addr,0,sizeof(&addr));
addr.sin_family=AF_INET;
//addr.sin_addr.s_addr=INADDR_ANY;
addr.sin_port=htons(9999);
inet_pton(AF_INET,"192.168.112.129",&addr.sin_addr.s_addr);
if(bind(listenfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
printf("bind failed:%s(errno:%d)\n",strerror(errno),errno);
return 0;
}
if(-1==listen(listenfd,128)) {
printf("listen failed:%s(errno:%d)\n",strerror(errno),errno);
}
struct sockaddr_in client;
socklen_t len=sizeof(client);
if((connfd=accept(listenfd,(struct sockaddr *)&client,&len))==-1) {
printf("accept failed:%s(errno:%d)\n",strerror(errno),errno);
return 0;
}
printf("========waiting for client's request========\n");
while(1) {
int n=recv(connfd,buf,MAXLNE,0);
printf("%d\n",n);
if(n>0) {
printf("recv msg:%s",buf);
buf[n]='\0';
send(connfd,buf,n,0);
printf("aa");
}
else if(n==0) {
close(connfd);
break;
}
}
}
非阻塞程序如下:
添加上即可
#include <fcntl.h> //需要包含的头文件
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
注意:
(1)IO函数是否阻塞取决于连接的 fd 是否阻塞
(2)阻塞与非阻塞的主要区别在与是否等待系统内核数据就绪
阻塞与非阻塞模型如下所示:
可以看到,在阻塞io模型中,即使内核数据未准备好,应用程序中的read函数也在等待;而在非阻塞io模型中,应用程序检测到系统内核数据未就绪,直接返回继续执行应用程序中的任务,但是会一直轮询查看内核中数据是否就绪。
但是在数据拷贝阶段,两者都是阻塞的。
针对以上程序明显有弊端,只能有一个客户端连接请求进行通信
为了解决这问题,有以下方法:
解法: a.多线程 多进程
b. io多路复用
接下来我们进行代码复现
三、多线程复现
代码如下:
void *fun(void *arg) {
int connfd=*(int *)arg;
char buff[MAXLNE];
while (1) {
int n = recv(connfd, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
send(connfd, buff, n, 0);
} else if (n == 0) {
close(connfd);
break;
}
}
return NULL;
}
while (1)
{
struct sockaddr_in cilent;
socklen_t len=sizeof(cilent);
if((connfd=accept(listenfd,(struct sockaddr *)&cilent,&len))==-1) {
printf("accept:failed %s(errno:%d)",strerror(errno),errno);
}
pthread_t tid;
int ret;
if((ret=pthread_create(&tid,NULL,fun,(void *)&connfd))==-1)
{
}
}
优点:逻辑简单
缺点:线程开销较大
四、io多路复用之select
原理看之前的文章。
这次直接上代码:
#define MAX_SIZE 1024
int main(int argc,char *argv[])
{
char buf[MAX_SIZE]; int n;
int sockfd=socket(AF_INET,SOCK_STREAM,0);
sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_addr.s_addr=0;
addr.sin_family=AF_INET;
addr.sin_port=htons(8888);
bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));
listen(sockfd,128);
fd_set rfds,rset,wfds,wset;
FD_ZERO(&rfds);
FD_SET(sockfd,&rfds);
FD_ZERO(&wfds);
int max_fd=sockfd;
while (1)
{
rset=rfds;
wset=wfds;
int ready=select(max_fd+1,&rset,&wset,NULL,NULL);
if(FD_ISSET(sockfd,&rset))
{
struct sockaddr_in clientaddr;
socklen_t len=sizeof(clientaddr);
int cfd=accept(sockfd,(struct sockaddr *)&clientaddr,&len);
FD_SET(cfd,&rfds);
/**/
if(cfd>max_fd) max_fd=cfd;
if(--ready==0) continue;
}
for(int i=sockfd+1;i<=max_fd;i++)
{
if(FD_ISSET(i,&rset))
{
n=recv(i,buf,MAX_SIZE,0);
if(n>0)
{
buf[n]='\0';
printf("recv msg from client: %s\n",buf);
FD_SET(i,&wfds);
}
else if(n==0)
{
close(i);
FD_CLR(i,&rfds);
continue;
}
if(--ready==0) break;
}
else if(FD_ISSET(i,&wset))
{
printf("n的个数 %d\n",n);
send(i,buf,n,0);
**FD_CLR(i,&wfds);**//非常重要
// FD_SET(i,&rfds);
}
}
}
close(sockfd);
return 0;
}