一、linux网络I/O模型
1、同步和异步、阻塞和非阻塞
1.1、同步和异步
关注的是消息的通知机制
- 同步
调用方主动等待结果的返回 - 异步
调用方不需要主动等待结果的返回,通过其他方式:回调函数、状态通知等通知
1.2 阻塞和非阻塞
关注的是等待结果返回调用方的状态
- 阻塞
结果返回之前,当前线程被挂起,不做任务事情 - 非阻塞
结果返回之前,当前线程不阻塞,可做其他事情
1.3 组合含义
- 同步阻塞
主动等待调用返回,期间不做任何事情,调用方被挂起 - 同步非阻塞
主动等待调用方返回,期间可以做其他事情,但是还需要轮训去查看调用结果是否返回 - 异步阻塞
不需要主动等待调用方返回,但是阻塞。很少用到 - 异步非阻塞
不需要主动等待调用方返回,期间可以做其他事情。
2、linux5中I/O模型
2.1 阻塞I/O
应用程序调用一个 IO 函数,导致应用程序阻塞,等待数据准备好。 如果数 据没有准备好,一直等待…数据准备好了,从内核拷贝到用户空间,IO 函数返回 成功指示。
2.2 非阻塞I/O
我们把一个 SOCKET 接口设置为非阻塞就是告诉内核,当所请求的 I/O 操作 无法完成时,不要将进程睡眠,而是返回一个错误。这样我们的 I/O 操作函数将 不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好 为止。在这个不断测试的过程中,会大量的占用 CPU 的时间
2.3 I/O复用
主要是 select 和 epoll 两个系统调用;对一个 IO 端口,两次调用,两 次返回,比阻塞 IO 并没有什么优越性;关键是能实现同时对多个 IO 端口进行监听;
当用户进程调用了 select,那么整个进程会被 block;而同时,kernel 会“监 视”所有 select 负责的 socket;当任何一个 socket 中的数据准备好了,select 就会返回
2.4 信号驱动I/O
首先我们允许套接口进行信号驱动 I/O,并安装一个信号处理函数,进程继续 运行并不阻塞。当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理 函数中调用 I/O 操作函数处理数据。
2.5 异步I/O
当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用 的部件在完成后,通过状态、通知和回调来通知调用者的输入输出操作
2.6 5中I/O模型比较
3、select poll epoll区别
3.1 所能打开的文件描述符个数(最大连接数)
- select
单个进程所能打开的最大连接数有 FD_SETSIZE 宏定义,其大 小是 32 个整数的大小(在 32 位的机器上,大小就是 3232,同 理 64 位机器上 FD_SETSIZE 为 3264) - poll
poll 本质上和 select 没有区别,但是它没有最大连接数的限 制,原因是它是基于链表来存储的 - epoll
能达到系统允许打开的最大文件描述符数,1G 内存的机器上可以打开 10 万左右的连接,2G 内存的机器可以打开 20 万左右的连接
3.2 性能方法
- 使用效率
select、poll每次都将所有的文件描述符(就绪的和未就绪的)返回,所以应用程序检索就绪文件描述符的时间复杂度为O(n),epoll通过events参数返回所有就绪的文件描述符,应用程序检索就绪文件描述符的时间复杂度为O(1) - 内核效率
select和poll采用轮询的方式:即每次都需要扫描整个注册的文件描述符集合,并将其中就绪的文件描述符返回给用户程序,因此,内核中检测就绪文件描述符的算法时间复杂度为O(n), epoll则采取回调的方式,内核检测到就绪文件描述符,就触发回调函数,将文件描述符及发生的事件插入内核就绪事件队列,因此,epoll在内核中检测就绪文件描述符的算法时间复杂度为O(1) - 数据拷贝
epoll 通过内核和用户空间共享一块内存来实现的
总结
- 表面上看 epoll 的性能最好,但是在连接数少并且连接都十分活跃的情况 下,select 和 poll 的性能可能比 epoll 好,毕竟 epoll 的通知机制需要很多函数回 调。
- select 低效是因为每次它都需要轮询。但低效也是相对的,视情况而定, 也可通过良好的设计改善
4、java语言下的网络编程demo(BIO)
4.1 Service
package cn.enjoyedu.bio;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
/*服务器必备*/
ServerSocket serverSocket = new ServerSocket();
/*绑定监听端口*/
serverSocket.bind(new InetSocketAddress(10001));
System.out.println("Server start.......");
while(true){
new Thread(new ServerTask(serverSocket.accept())).start();
}
}
private static class ServerTask implements Runnable{
private Socket socket = null;
public ServerTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
/*拿和客户端通讯的输入输出流*/
try(ObjectInputStream inputStream
= new ObjectInputStream(socket.getInputStream());
ObjectOutputStream outputStream
= new ObjectOutputStream(socket.getOutputStream())){
/*服务器的输入*/
String userName = inputStream.readUTF();
System.out.println("Accept clinet message:"+userName);
outputStream.writeUTF("Hello,"+userName);
outputStream.flush();
}catch (Exception e){
e.printStackTrace();
}
finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
4.2 Client
package cn.enjoyedu.bio;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
//客户端启动必备
Socket socket = null;
//实例化与服务端通信的输入输出流
ObjectOutputStream output = null;
ObjectInputStream input = null;
//服务器的通信地址
InetSocketAddress addr
= new InetSocketAddress("127.0.0.1",10001);
try{
socket = new Socket();
/*连接服务器*/
socket.connect(addr);
output = new ObjectOutputStream(socket.getOutputStream());
input = new ObjectInputStream(socket.getInputStream());
/*向服务器输出请求*/
output.writeUTF("Mark");
output.flush();
//接收服务器的输出
System.out.println(input.readUTF());
}finally{
if (socket!=null) socket.close();
if (output!=null) output.close();
if (input!=null) input.close();
}
}
}
5、BIO实现一个RPC框架
5.1 RPC实现需要的步骤
- 代理问题
动态代理-见代码 - 序列化问题
Serializable - 通讯问题
BIO通讯 - 登记的服务的实例化问题
反射
5.2 client
1、客户端通过动态代理调用
package cn.enjoyedu.rpc.client;
import cn.enjoyedu.rpc.service.SendSms;
import cn.enjoyedu.rpc.service.StockService;
import cn.enjoyedu.rpc.vo.UserInfo;
import cn.enjoyedu.rpc.client.rpc.RpcClientFrame;
/**
*@author 王乐
*
*类说明:rpc的客户端,调用远端服务
*/
public class RpcClient {
public static void main(String[] args) {
UserInfo userInfo
= new UserInfo("Mark","Mark@xiangxue.com");
SendSms sendSms = RpcClientFrame.getRemoteProxyObj(SendSms.class,
"127.0.0.1",9189);
System.out.println("Send mail: "+ sendSms.sendMail(userInfo));
}
}
2、客户端动态代理类
package cn.enjoyedu.rpc.client.rpc;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
*@author 王乐
*
*
*类说明:rpc框架的客户端代理部分
*/
public class RpcClientFrame {
//远程代理对象
public static <T> T getRemoteProxyObj(final Class<?> serviceInterface,
String hostname,int port){
final InetSocketAddress addr
= new InetSocketAddress(hostname,port);
return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(),
new Class<?>[]{serviceInterface}
,new DynProxy(serviceInterface,addr));
}
//动态代理类
private static class DynProxy implements InvocationHandler {
private final Class<?> serviceInterface;
private final InetSocketAddress addr;
public DynProxy(Class<?> serviceInterface, InetSocketAddress addr) {
this.serviceInterface = serviceInterface;
this.addr = addr;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Socket socket = null;
ObjectOutputStream output = null;
ObjectInputStream input = null;
try{
socket = new Socket();
socket.connect(addr);
output = new ObjectOutputStream(socket.getOutputStream());
output.writeUTF(serviceInterface.getName());//方法所在的类
output.writeUTF(method.getName());//方法的名
output.writeObject(method.getParameterTypes());//方法的入参类型
output.writeObject(args);
output.flush();
input = new ObjectInputStream(socket.getInputStream());
return input.readObject();
}finally{
if (socket!=null) socket.close();
if (output!=null) output.close();
if (input!=null) input.close();
}
}
}
}
5.3 服务端
package cn.enjoyedu.rpc.server;
import cn.enjoyedu.rpc.service.SendSms;
import cn.enjoyedu.rpc.service.impl.SendSmsImpl;
import cn.enjoyedu.rpc.server.rpc.RpcServerFrame;
/**
*@author 王乐
*
*类说明:rpc的服务端,提供服务
*/
public class SmsRpcServer {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
try{
RpcServerFrame serviceServer = new RpcServerFrame(9189);
serviceServer.registerSerive(SendSms.class.getName(),
SendSmsImpl.class);
serviceServer.startService();
}catch(Exception e){
e.printStackTrace();
}
}
}).start();
}
}
package cn.enjoyedu.rpc.server.rpc;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*@author 王乐
*
*类说明:rpc框架的服务端部分
*/
public class RpcServerFrame {
private static ExecutorService executorService
= Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
//服务的注册中心
private static final Map<String,Class> serviceHolder
= new HashMap<>();
//服务的端口号
private int port;
public RpcServerFrame(int port) {
this.port = port;
}
//服务注册
public void registerSerive(String className,Class impl){
serviceHolder.put(className,impl);
}
//处理服务请求任务
private static class ServerTask implements Runnable{
private Socket client = null;
public ServerTask(Socket client){
this.client = client;
}
public void run() {
try(ObjectInputStream inputStream =
new ObjectInputStream(client.getInputStream());
ObjectOutputStream outputStream =
new ObjectOutputStream(client.getOutputStream())){
//方法所在类名接口名
String serviceName = inputStream.readUTF();
//方法的名字
String methodName = inputStream.readUTF();
//方法的入参类型
Class<?>[] parmTypes = (Class<?>[]) inputStream.readObject();
//方法入参的值
Object[] args = (Object[]) inputStream.readObject();
Class serviceClass = serviceHolder.get(serviceName);
if (serviceClass == null){
throw new ClassNotFoundException(serviceName+" Not Found");
}
Method method = serviceClass.getMethod(methodName,parmTypes);
Object result = method.invoke(serviceClass.newInstance(),args);
outputStream.writeObject(result);
outputStream.flush();
}catch(Exception e){
e.printStackTrace();
}finally {
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//启动RPC服务
public void startService() throws IOException{
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(port));
System.out.println("RPC server on:"+port+":运行");
try{
while(true){
executorService.execute(new ServerTask(serverSocket.accept()));
}
}finally {
serverSocket.close();
}
}
}
client运行截图
service运行截图