网络编程面试题

1.linux高并发之IO多路复用select,poll和epoll的区别

IO多路复用:
就是假设现在要设计一个高性能服务器,需要多个客户端与之连接,采用多线程的话多线程切换带来的开销太大。所以就会考虑采用单线程的方式,解决方案就是IO多路复用,IO多路复用一般有select(),poll(),epoll()方式,他们都是对连接进服务端的客户端socket进行监控,例如现在有100个客户端socket,那么就监控这100个,如果这100个socket有信息进入,则IO多路复用就会返回,否则,就阻塞。即IO多路复用可以同时阻塞多个I/O操作,而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写。

正因为阻塞I/O只能阻塞一个I/O操作,而I/O复用模型能够阻塞多个I/O操作,所以才叫多路复用。

  • select()
//首先创建文件描述符数组fds[5],这部分代码省略,总之就是一个线程监听了5个套接字
while(1){
FD_ZERO(&ret);
for(int i=0;i<5;i++){
	FD_SET(fds[i],&rset);//rset是一个bitmap,0,1代表哪个文件描述符是被监听的,1024个坑位
}
puts("round again");
select(max+1,&rset,NULL,NULL,NULL);
//rset完全从用户态拷贝到了内核态,完全交由内核态来判断是否有是数据到来,
//如果没有数据到来,select函数就一直处于阻塞状态
//如果有数据到来,内核需要做两件事情,
//第一件是有数据的套接字的FD置1,这里FD指的是rset中的位
//第二件是select函数返回
for(int i=0;i<5;i++){
	if(FD_ISSET(fd[i],&rset)){
		memset(buffer,0,MAXBUF);
		read(fds[i],buffer,MAXBUF);
		puts(buffer);
		}
}
}

缺点:

  1. bitmap大小有上限
  2. rset不可重用,重复更新,消耗时间复杂度
  3. rset从用户态拷贝到内核态时间仍有时间开销
  4. rset虽然被置位,但是不知道具体的置位点,最后需要O(n)的时间复杂度来找到具体位置
  • poll
struct pollfd{
	int fd;//文件描述符
	short events;//需要在意的事件
	short revents;//对events的一个回馈
}

//初始化pollfds
while(1){
	puts("round again");
	poll(pollfds,5,5000);
	//同样把pollfds拷贝到内核态,内核空间监听套接字的数据
	//如果有数据到来需要做两件事:
	//1.pollfds.revents置位
	//2.poll 返回
	for(int i=0;i<5;i++){
		if(pollfds[i].revents&POLLIN){
			pollfds[i].revents=0;
			memset(buffer,0,MAXBUF);
			read(pollfds[i].fd,buffer,MAXBUF);
			puts(buffer);
		}
	}
}

相比于select,poll解决1,2两点,3,4点还是保留,工作原理一样。

  • epoll
while(1){
	puts("round again");
	nfds=epoll_wait(epfd,events,5,10000);
	//内核空间和用户空间共享epfd
	//有数据到来时:
	//1.“置位”,重排
	//2.返回有多少套接字有数据到来
	for(i=0;i<nfds;i++){
		memset(buffer,0,MAXBUF);
		read(events[i].data.fd,buffer,MABUF);
		puts(buffer);
	}
}

select的问题全部解决

select的适用场合

2.手撕一个最简单的server端服务器

  • 基本流程
  1. 创建套接字
  2. 配置服务器地址相关参数
  3. 将两者绑定
  4. 监听套接字上的端口
  5. 在上面创建的套接字上等待连接,并打开一个新的套接字用于与请求之间的交互
  6. 在发送缓冲区中写入响应
  7. 关闭连接
#include<iostream>
#include<sys/socket.h>
#include<netinet/in.h>
#include <strings.h>
#include <assert.h>
#include <zconf.h>
#include <cstring>

#define PORT 8099
#define LISTENQ 5

int main(){
    int listenfd,connfd;
    struct sockaddr_in servaddr;
    listenfd=socket(PF_INET,SOCK_STREAM,0);
    assert(listenfd!=-1);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
    servaddr.sin_port=htons(PORT);

    int bind_ok=bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    assert(bind_ok!=-1);
    int listen_ok=listen(listenfd,LISTENQ);
    assert(listenfd!=-1);

    std::string buffer="hello";
    int write_ok;
    while(1) {
        connfd = accept(listenfd, NULL, NULL);
        assert(connfd != -1);
        write_ok = write(connfd, buffer.c_str(), strlen(buffer.c_str()));


        if (write_ok < 0) {
            std::cout << "failed" << std::endl;
            close(connfd);
            return 0;
        }
        close(connfd);
    }
}

3.线程池

1.线程池的概念
线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传递给线程池,线程池就会启动一条线程来执行这个任务,执行任务结束后,该线程不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
2.使用线程池的原因
多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过度消耗系统资源,以及过度切换线程的危险,从而可能导致系统资源的崩溃。这时线程池就是最好的选择了。
3.线程池的c++实现

线程池的主要组成成分有三个部分:

  • 任务队列(Task Queue)
  • 线程池(Thread Pool)
  • 完成队列(Completed Tasks)

4.基于事件驱动的reactor模式

Reactor模式是处理并发I/O比较常见的一种模式,用于同步I/O,中心思想是将所有要处理的I/O事件注册到一个中心I/O多路复用器上,同时主线程/进程阻塞在多路复用器上;一旦有I/O事件到来或是准备就绪(文件描述符或socket可读、写),多路复用器返回并将事先注册的相应I/O事件分发到对应的处理器中。

Reactor是一种事件驱动机制,和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的事件发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。用“好莱坞原则”来形容Reactor再合适不过了:不要打电话给我们,我们会打电话通知你。

5.边沿触发与水平触发的区别

边缘触发(edge-triggered)
简称:ET,它只支持非阻塞socket。你可以设定一个值,当达到这个值时才会触发。它只通知一次。如果不对事件进行处理,它将会将其丢弃。
水平触发(level-triggered)
简称:LT,它只支持阻塞和非阻塞两种模式,它是一有事件发生触发,如果你不将其进行处理,它将不会将事件丢弃,它将会一直提示。

6.阻塞IO与非阻塞IO的区别

阻塞IO,指的是需要内核IO操作彻底完成后,才返回用户空间执行的操作。阻塞指的是用户空间程序的执行状态。
非阻塞IO,指的是用户空间的程序不需要等待内核IO彻底完成,可以立即返回用户空间执行用户操作,即处于非阻塞的状态,与此同时内核会立即返回给用户的一个状态值。

简单来说:阻塞是指用户空间(调用线程)一直在等待,而不能干别的事情。非阻塞是指用户空间(调用线程)拿到内核返回的状态值就返回自己的空间,IO操作可以干就干,不可以干,就去干别的事情。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值