二、网络编程

一、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运行截图
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值