Java 网络编程 —— 实现非阻塞式的服务器

创建阻塞的服务器

ServerSocketChannelSockelChannel 采用默认的阻塞模式时,为了同时处理多个客户的连接,必须使用多线程

public class EchoServer {
    
	private int port = 8000;
    private ServerSocketChannel serverSocketChannel = null;
    private ExecutorService executorService; //线程池
    private static final int POOL_MULTIPLE = 4; //线程池中工作线程的数目
    
    public EchoServer() throws IOException {
        //创建一个线程池
        executorService = Executors.newFixedThreadPool(
            Runtime.getRuntime().availableProcessors() * POOL_MULTIPLE);
        //创建一个ServerSocketChannel对象
        serverSocketChannel = ServerSocketChannel.open();
        //使得在同一个主机上关闭了服务器程序,紧接着再启动该服务器程序时,可以顺利绑定相同的端口
        serverSocketChannel.socket().setReuseAddress(true);
        //把服务器进程与一个本地端口绑定
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        System.out.println("服务器启动");
    }
    
    public void service() {
        while (true) {
            SocketChannel socketChannel = null;
            try {
                socketChannel = serverSocketChannel.accept();
                //处理客户连接
                executorService.execute(new Handler(socketChannel));
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String args[])throws IOException {
        new EchoServer().service();
    }
    
    //处理客户连按
    class Handler implements Runnable {

        private SocketChannel socketChannel;
		
        public Handler(SocketChannel socketChannel) {
            this.socketChannel = socketChannel;
        }
        
        public void run() {
            handle(socketChannel);
        }
        
        public void handle(SocketChannel socketChannel) {
            try {
                //获得与socketChannel关联的Socket对象
                Socket socket = socketChannel.socket();
                System.out.println("接收到客户连接,来自:" + socket.getInetAddress() + ":" + socket.getPort());
                
                BufferedReader br = getReader(socket);
                PrintWriter pw = getWriter(socket);
                
                String msg = null;
                while ((msg = br.readLine()) != null) {
                    System.out.println(msg);
                    pw.println(echo(msg));
                    if (msg.equals("bye")) {
                        break;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if(socketChannel != null) {
                        socketChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    } 
    
    private PrintWriter getWriter(Socket socket) throws IOException {
        OutputStream socketOut = socket.getOutputStream();
        return new PrintWriter(socketOut,true);
    }
    
    private BufferedReader getReader(Socket socket) throws IOException {
        InputStream socketIn = socket.getInputStream();
        return new BufferedReader(new InputStreamReader(socketIn));
    }
    
    public String echo(String msg) {
        return "echo:" + msg;
    }
}

创建非阻塞的服务器

在非阻塞模式下,EchoServer 只需要启动一个主线程,就能同时处理三件事:

  • 接收客户的连接
  • 接收客户发送的数据
  • 向客户发回响应数据

EchoServer 委托 Selector 来负责监控接收连接就绪事件、读就绪事件和写就绪事件如果有特定事件发生,就处理该事件

// 创建一个Selector对象
selector = Selector.open();
//创建一个ServerSocketChannel对象
serverSocketChannel = ServerSocketChannel.open();
//使得在同一个主机上关闭了服务器程序,紧接着再启动该服务器程序时
//可以顺利绑定到相同的端口
serverSocketChannel.socket().setReuseAddress(true);
//使ServerSocketChannel工作于非阻塞模式
serverSocketChannel.configureBlocking(false):
//把服务器进程与一个本地端口绑定
serverSocketChannelsocket().bind(new InetSocketAddress(port));

EchoServer 类的 service() 方法负责处理本节开头所说的三件事,体现其主要流程的代码如下:

public void service() throws IOException {
    serverSocketChannel.reqister(selector, SelectionKey.OP_ACCEPT);
    //第1层while循环
    while(selector.select() > 0) {
        //获得Selector的selected-keys集合
        Set readyKeys = selector.selectedKeys();
        Iterator it = readyKeys.iterator();
        //第2层while循环
        while (it.hasNext()) {
            SelectionKey key = null;
            //处理SelectionKey
            try {
                //取出一个SelectionKey
                key = (SelectionKey) it.next();
                //把 SelectionKey从Selector 的selected-key 集合中删除
                it.remove();
                1f (key.isAcceptable()) { 处理接收连接就绪事件; }
                if (key.isReadable()) { 处理读就绪水件; }
                if (key.isWritable()) { 处理写就绪事件; }
            } catch(IOException e) {
                e.printStackTrace();
                try {
                    if(key != null) {
                        //使这个SelectionKey失效
                        key.cancel();
                        //关闭与这个SelectionKey关联的SocketChannel
                        key.channel().close();
                    }
                } catch(Exception ex) { 
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 首先由 ServerSocketChannelSelector 注册接收连接就绪事件,如果 Selector 监控到该事件发生,就会把相应的 SelectionKey 对象加入 selected-keys 集合
  • 第一层 while 循环,不断询问 Selector 已经发生的事件,select() 方法返回当前相关事件已经发生的 SelectionKey 的个数,如果当前没有任何事件发生,该方法会阻塞下去,直到至少有一个事件发生。SelectorselectedKeys() 方法返回 selected-keys 集合,它存放了相关事件已经发生的 SelectionKey 对象
  • 第二层 while 循环,从 selected-keys 集合中依次取出每个 SelectionKey 对象并从集合中删除,,然后调用 isAcceptable()isReadable()isWritable() 方法判断到底是哪种事件发生了,从而做出相应的处理

1. 处理接收连接就绪事件

if (key.isAcceptable()) {
    //获得与SelectionKey关联的ServerSocketChannel
    ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
    //获得与客户连接的SocketChannel
    SocketChannel socketChannel = (SocketChannel) ssc.accept();
    //把Socketchannel设置为非阻塞模式
    socketChannel.configureBlocking(false);
    //创建一个用于存放用户发送来的数据的级冲区
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    //Socketchannel向Selector注册读就绪事件和写就绪事件
    socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE, buffer);
}

2. 处理读就绪事件

public void receive(SelectionKey key) throws IOException {
    //获得与SelectionKey关联的附件
    ByteBuffer buffer = (ByteBuffer) key.attachment();
    //获得与SelectionKey关联的Socketchannel
    SocketChannel socketChannel = (SocketChannel)key.channel();
    //创建一个ByteBuffer用于存放读到的数据
    ByteBuffer readBuff = ByteBuffer.allocate(32);
    socketChannel.read(readBuff);
    readBuff.flip();
    //把buffer的极限设为容量
    buffer.limit(buffer.capacity());
    //把readBuff中的内容拷贝到buffer
    buffer.put(readBuff);
}

3. 处理写就绪事件

public void send(SelectionKey key) throws IOException {
    //获得与SelectionKey关联的ByteBuffer
    ByteBuffer buffer = (ByteBuffer) key.attachment();
    //获得与SelectionKey关联的SocketChannel
    SocketChannel socketChannel = (SocketChannel) key.channel();
    buffer.flip();
    //按照GBK编码把buffer中的字节转换为字符串
    String data = decode(buffer);
    //如果还没有读到一行数据就返回
    if(data.indexOf("\r\n") == -1)
        return;
    //截取一行数据
    String outputData = data.substring(0, data.indexOf("\n") + 1);
    //把输出的字符串按照GBK编码转换为字节,把它放在outputBuffer中
    ByteBuffer outputBuffer = encode("echo:" + outputData);
    //输出outputBuffer的所有字节
    while(outputBuffer,hasRemaining())
        socketChannel.write(outputBuffer);
    //把outputData字符审按照GBK编码,转换为字节,把它放在ByteBuffer
    ByteBuffer temp = encode(outputData);
    //把buffer的位置设为temp的极限
    buffer.position(temp.limit()):
    //删除buffer已经处理的数据
    buffer.compact();
    //如果已经输出了字符串“bye\r\n”,就使SelectionKey失效,并关闭SocketChannel
    if(outputData.equals("bye\r\n")) {
        key.cancel();
        socketChannel.close();
    }
}

完整代码如下:

public class EchoServer {
    
	private int port = 8000;
    private ServerSocketChannel serverSocketChannel = null;
    private Selector selector;
    private Charset charset = Charset.forName("GBK");

	public EchoServer() throws IOException {
        // 创建一个Selector对象
        selector = Selector.open();
        //创建一个ServerSocketChannel对象
        serverSocketChannel = ServerSocketChannel.open();
        //使得在同一个主机上关闭了服务器程序,紧接着再启动该服务器程序时
        //可以顺利绑定到相同的端口
        serverSocketChannel.socket().setReuseAddress(true);
        //使ServerSocketChannel工作于非阻塞模式
        serverSocketChannel.configureBlocking(false):
        //把服务器进程与一个本地端口绑定
        serverSocketChannelsocket().bind(new InetSocketAddress(port));
    }
    
    public void service() throws IOException {
        serverSocketChannel.reqister(selector, SelectionKey.OP_ACCEPT);
        //第1层while循环
        while(selector.select() > 0) {
            //获得Selector的selected-keys集合
            Set readyKeys = selector.selectedKeys();
            Iterator it = readyKeys.iterator();
            //第2层while循环
            while (it.hasNext()) {
                SelectionKey key = null;
                //处理SelectionKey
                try {
                    //取出一个SelectionKey
                    key = (SelectionKey) it.next();
                    //把 SelectionKey从Selector 的selected-key 集合中删除
                    it.remove();
                    1f (key.isAcceptable()) {
                         //获得与SelectionKey关联的ServerSocketChannel
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        //获得与客户连接的SocketChannel
                        SocketChannel socketChannel = (SocketChannel) ssc.accept();
                        //把Socketchannel设置为非阻塞模式
                        socketChannel.configureBlocking(false);
                        //创建一个用于存放用户发送来的数据的级冲区
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //Socketchannel向Selector注册读就绪事件和写就绪事件
                        socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE, buffer);
                    }
                    if (key.isReadable()) { receive(key); }
                    if (key.isWritable()) { send(key); }
                } catch(IOException e) {
                    e.printStackTrace();
                    try {
                        if(key != null) {
                            //使这个SelectionKey失效
                            key.cancel();
                            //关闭与这个SelectionKey关联的SocketChannel
                            key.channel().close();
                        }
                    } catch(Exception ex) { 
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    public void receive(SelectionKey key) throws IOException {
        //获得与SelectionKey关联的附件
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        //获得与SelectionKey关联的Socketchannel
        SocketChannel socketChannel = (SocketChannel)key.channel();
        //创建一个ByteBuffer用于存放读到的数据
        ByteBuffer readBuff = ByteBuffer.allocate(32);
        socketChannel.read(readBuff);
        readBuff.flip();
        //把buffer的极限设为容量
        buffer.limit(buffer.capacity());
        //把readBuff中的内容拷贝到buffer
        buffer.put(readBuff);
    }
    
    public void send(SelectionKey key) throws IOException {
        //获得与SelectionKey关联的ByteBuffer
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        //获得与SelectionKey关联的SocketChannel
        SocketChannel socketChannel = (SocketChannel) key.channel();
        buffer.flip();
        //按照GBK编码把buffer中的字节转换为字符串
        String data = decode(buffer);
        //如果还没有读到一行数据就返回
        if(data.indexOf("\r\n") == -1)
            return;
        //截取一行数据
        String outputData = data.substring(0, data.indexOf("\n") + 1);
        //把输出的字符串按照GBK编码转换为字节,把它放在outputBuffer中
        ByteBuffer outputBuffer = encode("echo:" + outputData);
        //输出outputBuffer的所有字节
        while(outputBuffer,hasRemaining())
            socketChannel.write(outputBuffer);
        //把outputData字符审按照GBK编码,转换为字节,把它放在ByteBuffer
        ByteBuffer temp = encode(outputData);
        //把buffer的位置设为temp的极限
        buffer.position(temp.limit()):
        //删除buffer已经处理的数据
        buffer.compact();
        //如果已经输出了字符串“bye\r\n”,就使SelectionKey失效,并关闭SocketChannel
        if(outputData.equals("bye\r\n")) {
            key.cancel();
            socketChannel.close();
        }
    }
    
    //解码
    public String decode(ByteBuffer buffer) {
        CharBuffer charBuffer = charset.decode(buffer);
        return charBuffer.toStrinq();
    }
    
    //编码
    public ByteBuffer encode(String str) {
        return charset.encode(str);
    }
    
    public static void main(String args[])throws Exception {
        EchoServer server = new EchoServer();
        server.service();
    }
}

阻塞模式与非阻塞模式混合使用

使用非阻塞模式时,ServerSocketChannel 以及 SocketChannel 都被设置为非阻塞模式,这使得接收连接、接收数据和发送数据的操作都采用非阻塞模式,EchoServer 采用一个线程同时完成这些操作

假如有许多客户请求连接,可以把接收客户连接的操作单独由一个线程完成,把接收数据和发送数据的操作由另一个线程完成,这可以提高服务器的并发性能

负责接收客户连接的线程按照阻塞模式工作,如果收到客户连接,就向 Selector 注册读就绪和写就绪事件,否则进入阻塞状态,直到接收到了客户的连接。负责接收数据和发送数据的线程按照非阻塞模式工作,只有在读就绪或写就绪事件发生时,才执行相应的接收数据和发送数据操作

public class EchoServer {
    
	private int port = 8000;
    private ServerSocketChannel serverSocketChannel = null;
    private Selector selector = null;
    private Charset charset = Charset.forName("GBK");

	public EchoServer() throws IOException {
        selector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().setReuseAddress(true);
        serverSocketChannelsocket().bind(new InetSocketAddress(port));
    }
    
    public void accept() {
        while(true) {
            try {
                SocketChannel socketChannel = serverSocketChannel.accept();
                socketChannel.configureBlocking(false);
                
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                synchronized(gate) {
                    selector.wakeup();
                    socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE, buffer);
                }
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    private Object gate=new Object();
    
    public void service() throws IOException {
        while(true) {
            synchronized(gate){}
            int n = selector.select();
            if(n == 0) continue;
            Set readyKeys = selector.selectedKeys();
            Iterator it = readyKeys.iterator();
            while (it.hasNext()) {
                SelectionKey key = null;
                try {
    				it.remove();
                    if (key.isReadable()) {
                        receive(key);
                    }
                    if (key.isWritable()) {
                        send(key);
                    }
                } catch(IOException e) {
                    e.printStackTrace();
                    try {
                        if(key != null) {
                            key.cancel();
                            key.channel().close();
                        }
                    } catch(Exception ex) { e.printStackTrace(); }
                }
            }
        }
    }
    
    public void receive(SelectionKey key) throws IOException {
        ...
    }
    
    public void send(SelectionKey key) throws IOException {
        ...
    }
    
    public String decode(ByteBuffer buffer) {
        ...
    }
    
    public ByteBuffer encode(String str) {
        ...
    }
    
    public static void main(String args[])throws Exception {
        final EchoServer server = new EchoServer();
        Thread accept = new Thread() {
            public void run() {
                server.accept();
            }
        };
        accept.start();
		server.service();
    }
}

注意一点:主线程的 selector select() 方法和 Accept 线程的 register(...) 方法都会造成阻塞,因为他们都会操作 Selector 对象的共享资源 all-keys 集合,这有可能会导致死锁

导致死锁的具体情形是:Selector 中尚没有任何注册的事件,即 all-keys 集合为空,主线程执行 selector.select() 方法时将进入阻塞状态,只有当 Accept 线程向 Selector 注册了事件,并且该事件发生后,主线程才会从 selector.select() 方法返回。然而,由于主线程正在 selector.select() 方法中阻塞,这使得 Acccept 线程也在 register() 方法中阻塞。Accept 线程无法向 Selector 注册事件,而主线程没有任何事件可以监控,所以这两个线程将永远阻塞下去

为了避免对共享资源的竞争,同步机制使得一个线程执行 register() 时,不允许另一个线程同时执行 select() 方法,反之亦然


本文转载于网络 如有侵权请联系删除

相关文章

  • Tomcat调优总结

    大家好,又见面了,我是你们的朋友全栈君。Tomcat优化分为系统优化,Java虚拟机调优,Tomcat本身的优化。Tomcat如何起停./catalina.shstop./catalina.shstart /sbin/servicetomcatrestart/webagme/tomcat/bin/catalina.shstart1.如何调整tomcat的占用内存A:方法如下:1.linux下编辑tomcat安装目录下的bin目录下的catalina.sh文件,windows下为catalina.batvicatalina.sh2.查找到tomcat内存参数一行:/JAVA_OPTS,如果找不到则在第一行写上JAVA_OPTS=”-Xms384m-Xmx384m-Xmn192m-XX:MaxDirectMemorySize=64m-XX:-OmitStackTraceInFastThrow-XX:+UseParNewGC-XX:+UseConcMarkSweepGC-XX:+DeallocateHeapPages-XX:+CMSParallelFullGC”3.将JAVA_OPTS=”

  • 【阿信ABAQUS子程序(7)】USDFLD

    ABAQUS子程序USDFLD(Usersubroutinetoredefinefieldvariablesatamaterialpoint.),提供了用户自定义场变量的功能。USDFLD所有功能均可采用UMAT实现,但是相对而言其应用方式较UMAT更为简单,并不需要用户去重新开发材料本构模型,可以借助于ABAQUS自带的本构,通过读取计算过程中积分点上的场变量信息,在经过一定的计算之后上传新的自定义场变量结果即可,同时也可以通过这种机制反作用自带本构模型。ABAQUS子程序USDFLD的接口如下:SUBROUTINEUSDFLD(FIELD,STATEV,PNEWDT,DIRECT,T,CELENT, 1TIME,DTIME,CMNAME,ORNAME,NFIELD,NSTATV,NOEL,NPT,LAYER, 2KSPT,KSTEP,KINC,NDI,NSHR,COORD,JMAC,JMATYP,MATLAYO,LACCFLA) C INCLUDE'ABA_PARAM.INC' C CHARACTER*80CMNAME,ORNAME CHARACTER*3FLG

  • xpath定位深入学习(一)

    xpath定位的集中方法第一种方法:通过绝对路径做定位(相信大家不会使用这种方式)By.xpath("html/body/div/form/input")复制By.xpath("//input")复制第三种方法:通过元素索引定位By.xpath("//input[4]")复制第四种方法:使用xpath属性定位(结合第2、第3中方法可以使用)By.xpath("//input[@id='kw1']")复制By.xpath("//input[@type='name'and@name='kw1']")复制第五种方法:使用部分属性值匹配(最强大的方法)By.xpath("//input[start-with(@id,'nice')复制By.xpath("//input[ends-with(@id,'很漂亮')复制By.xpath("//input[contains(@

  • 用于机器人导航辅助的6自由度姿态估计的平面辅助视觉惯性里程计

    Plane-AidedVisual-InertialOdometryfor6-DOFPoseEstimationofaRoboticNavigationAid 用于机器人导航辅助的6自由度姿态估计的平面辅助视觉惯性里程计HEZHANGANDCANGYE(SeniorMember,IEEE)【论文速看】1、解决了什么问题?很多论文通过使用视觉里程计的输出来更新IMU的偏置等,但现在还没有很可靠的算法来评估视觉里程计输出的可靠性。2、提出了什么方法?创新点体现在哪些方面?(1)提出新的VIO算法,使用3DTOF相机辅助导航;(2)从当前相机视场的3D点云中提取平面信息,并进行跟踪(3)提出评估视觉里程计输出精度的算法3、有哪些需要补充的知识点(1)3DTOF相机(2)因子图【摘要】经典视觉惯性里程计(VIO)算法通过融合视觉里程计(VO)估计的相机自运动和惯性测量单元(IMU)测量的运动来估计运动相机的6自由度位姿。VIO试图通过在每一步使用VO的输出来更新IMU偏置的估计,从而提高IMU的测量精度。这种方法只有在准确的VO输出能被识别和使用时才有效。然而,没有可靠的方法可以用来进行在线评

  • 纠错码简介

    引出网络中的通信基于TCP和UDP两个通信协议,这大家都知道的,什么TCP的三次握手等等,面试经常被问到.三次握手是为了保证连接的正确建立.但是,在通信的时候,你如何保证你的消息正确送达了呢?有人说了,有收到请求的响应包.但我说的不是这个,比如说,你发送了一个数字1,你如何保证接受方收到的数字也是1呢?毕竟,在网络中环境如此复杂,就算是物理上也不能保证数据一定是不变的啊.比如有一个机房在上海,你在北京访问,那数据是要途径一千多公里的,在这个传输的过程中会受到各种干扰,很难保证数据不会失真.这个时候,纠错码出现了.简单介绍一下,其中所有有关数学的内容的去掉了,毕竟太高深,咱也不懂.思考因为计算机传输中只存在0和1,所以可以简单将其类比为数字.想象一个场景,你需要将一组数字发送给B,在发送的过程中,每个数字都有20%的概率变成其他数字(途中收到干扰导致失真).你们应该如何保证接收到的数字与发送的数字一致呢?假定这组数字是:123456789方案一根据概率论,每个数字20%的概率会错乱,也就是有80%是正确的.那只要样本足够多,那出现次数最多的就是正确的.比如,发送了5次,收到的内容是:12

  • SpringBoot 去除"No MyBatis mapper was found in '[com.pollyduan.cms]' package. " 警告

    springboot项目,集成了mybatis。偶然发现,项目每次启动都警告:NoMyBatismapperwasfoundin'[com.pollyduan.cms]'package.Pleasecheckyourconfiguration.复制我在com.pollyduan.cms.CmsApplication主类上使用@MapperScan(basePackages="com.pollyduan.cms.mapper")扫描指定包里的mapper接口,没有配置其他。莫名其妙警告'com.pollyduan.cms'找不到Mapper,真是莫名了奇妙。仔细观察发现,这个包恰恰是Application主类的包,看样子是默认扫描了。强迫症犯了,虽然不影响使用,但心里膈应,怎么去掉这个警告呢?网上搜了半天,都是瞎扯。跟源码也可以找到doScan()方法里扫描Mapper注解。却没有找到怎么让他不扫描。偶然想到一个思路,既然你要找主类包里的mapper,我就给你一个mapper。packagecom.pollyduan.cms;

  • k8s实战之部署PHP/Java网站

    梦想可以天花乱坠, 理想,是我们一步一个脚印踩出来的坎坷道路对k8s刚入门的朋友而言,光搭建k8s集群是不够的,我们需要更多的理论加实战,才能更好的掌握k8s的好处,当我们成功部署一个k8s集群之后,我们需要在实际项目中进行应用,本文简单的介绍了当前比较主流的PHP/Java网站的部署传统部署与K8S部署区别通常使用传统的部署的时候,我们一个web项目,网站的搭建,往往使用的如下的一种整体架构,可能有的公司在某一环节使用的东西是不一样,但是大体的框架流程是都是差不多的 使用K8S部署,便于弹性伸缩,节约资源,发布周期快,整体框架如下: 准备环境192.168.73.138k8s-Master192.168.73.139k8s-node01192.168.73.140k8s-node02192.168.73.136Harbor镜像仓库1php项目部署流程当我们把项目迁移到K8S平台上时,首先我们需要了解的是整个部署的流程,按照这个流程部署,才能避免出现问题,也方便大家理解 github项目地址https://github.com/zhangdongdong7/php-demo.git

  • 为什么选择 Intellij IDEA 作为日常开发工具

    作为一个从事Java开发的程序员,每天离不开编辑器的帮助。还记得刚开始学习Java编程的时候,使用Eclipse作为日常开发工具。后来工作以后,需要使用IntellijIDEA,刚开始其实并不想怎么用。毕竟Eclipse已经足够强大,可以满足日常开发的需求,何必再花时间再去学习其他工具那。刚开始改变是困难的。但是没办法,公司强制使用,不得不去了解去使用。后来用了一段时间才发现IDEA是的真的强大。如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。真香啊~下面就来介绍一下本人觉得IDEA一些强大的功能。文中提到的快捷键只适用于Windows平台更加智能的协助开发我们使用编辑器的目的就是在于简化开发难度,加快开发速度。IDEA就有许多功能,可以更加智能的、更加快速的帮你完成代码开发。代码提示下面先介绍最基本的代码提示功能。一般编辑器都会提供基本提示功能,可以快速提供可用的方法,变量等。当然IDEA也存在这

  • ftp自动下载

    #!/bin/bash  #author: QingFeng #qq: 530035210 #blog: https://my.oschina.net/pwd/blog  #自动添加秘钥认证用户 #缺省的配置如下   logdir=/data/log/shell          #日志路径 log=$logdir/shell.log            #日志文件  is_font=1                #终端是否打印日志: 1打印 0不打印  is_log=1                 #是否记录日志: 1记录 0不记录 basedir="/data/ehuzhu" yes=$(date -d yesterday +%Y-%m-%d) yesmonth=$(date -d yesterday +%Y-%m) host=x.x.x.x user=admin pass=xxxxxxx   datef(){ date "+%Y-%m-%d %H:%M:%S" }   print_log(){ if [[ $is_log -eq 

  • 二值图像分析:案例实战(文本分离+硬币计数)

    图像的二值化图像二值化就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的黑白效果。 将256个亮度等级的灰度图像通过适当的阈值选取而获得仍然可以反映图像整体和局部特征的二值化图像。在数字图像处理中,二值图像占有非常重要的地位,首先,图像的二值化有利于图像的进一步处理,使图像变得简单,而且数据量减小,能凸显出感兴趣的目标的轮廓。其次,要进行二值图像的处理与分析,首先要把灰度图像二值化,得到二值化图像。 在实际应用中,很多图像的分析最终都转换为二值图像的分析,比如:医学图像分析、前景检测、字符识别,形状识别。二值化+数学形态学能解决很多计算机识别工程中目标提取的问题。开操作演示---文本分离与切割开操作是先腐蚀后膨胀的过程。用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。跟开操作相对应的是闭操作。另外,腐蚀和膨胀在下文中有介绍。在cv4j中,我们封装好了这些形态学的常用操作,比如开闭操作、腐蚀和膨胀等等。其中,开操作的代码如下:publicclassMorphOpen{ /** *inordertoremovelitternoiseb

  • visual studio code -- python

    录:前提:已安装python在vsc中安装pthon模块快速入门打开控制台(ctrl+shift+P):Python:SelectInterpreter,选择python解释器或者在软件的下方点击选择python环境智能补全:智能补全在python标准库和你设置的python环境中的包下工作;在软件下方有终端,可以根据需要添加或删除终端;调试工具: 编辑python自动补全和智能感知可以在当前工作环境和python标准安装的库(pip安装的库)下工作,如果需要它们在其他包也可以工作的话,需要把这个包的路径放入设置文件中的python.autoComplete.extraPaths中:"python.autoComplete.extraPaths":[ "C:/ProgramFiles(x86)/Google/google_appengine", "C:/ProgramFiles(x86)/Google/google_appengine/lib"]复制类似的,python.autoComplete.preloadModules设

  • webview接入HttpDNS实践

    本文是对去年做的webview接入HttpDNS工作的一个总结,拖的时间有点久了。主要分享了GOTHookwebview中域名解析函数的方法。HttpDNS简介首先简单介绍下移动App接入HttpDNS后有什么好处,这里直接引用腾讯云文档中的说明:HttpDNS是通过将移动APP及桌面应用的默认域名解析方式,替换为通过Http协议进行域名解析,以规避由运营商LocalDNS服务异常所导致的用户网络接入异常。减少用户跨网访问,降低用户解析域名时延。 更详细的内容可以参考这篇文章:【鹅厂网事】全局精确流量调度新思路-HttpDNS服务详解移动端的实现原理域名的解析工作将在HttpDNS服务器上完成,客户端只要把待解析的域名作为参数发起一个HTTP请求,HttpDNS服务器就会把解析结果下发给客户端了。 在客户端,默认的域名解析是系统的getaddrinfo()库函数实现的,默认的域名解析请求会走到LocalDNS。 所以域名解析的工作必须要交给app应用层来实现。下面介绍几种实现方案。1、okhttpokhttp的实现是建立在socket之上的,并且实现了HTTP协议。对于客户端发起的ht

  • URL短链接实现方法

    最近项目开发中,需要实现URL长链接转短链接的需求,于是在网上找了一些资料,顺便整理了下,欢迎有想法的童鞋踊跃留言,我们共同探讨。一.短链接的好处1.内容需要(比如短信,微博中链接字数的限制)2.便于管理(方便后台跟踪点击量,便于统计) 3.用户友好(看起来很Cool,提升用户体验) 大致思路是定义一个URL映射算法,将长的URL映射到短的URL,使用数据库或者redis缓存存储映射关系,实现映射算法。其中关键部分在于映射算法,接下来我们就详细说下映射算法。二.映射算法1.进制转化 多数方案是使用不同进制进行相互转换,比如十进制转十六进制,十进制转六十二进制,即使我们记录了一亿条数据,一亿的64进制为F9eEa同样适合做短链接的参数,将自增长的ID转化为短链接的字符串,长链接短链接以key,value的映射关系存储到数据库或者缓存中,为了更方便的存取。缺点:没有办法保证转化的短链接字符串的长度,在高并发的情况下,如何保证能够快速分发是个问题。 2.固定算法我们使用6个字符来表示短链接,使用ASCII字符中的'a'-'z','0'

  • 2017年度盘点:15个最流行的GitHub机器学习项目

    在本文中,作者列出了2017年GitHub平台上最为热门的知识库,囊括了数据科学、机器学习、深度学习中的各种项目,希望能对大家学习、使用有所帮助。GitHub是计算机科学领域最为活跃的社区,在GitHub上,来自不同背景的人们分享越来越多的软件工具和资源库。在其中,你不仅可以获取自己所需的工具,还可以观看代码是如何写成并实现的。作为一名机器学习爱好者,作者在本文中列出了2017年GitHub平台上最为热门的知识库,其中包含了学习资料与工具。希望对你的学习和研究有所帮助。目录1.学习资源1.AwesomeDataScience2.MachineLearning/DeepLearningCheatSheet3.OxfordDeepNaturalLanguageProcessingCourseLectures4.PyTorch–Tutorial5.ResourcesofNIPS20172.开源工具1.TensorFlow 2.TuriCreate–ASimplifiedMachineLearningLibrary3.OpenPose4.DeepSpeech5.MobileDeepLearni

  • 13-RabbitMQ高级特性-消费端限流

    13-RabbitMQ高级特性-消费端限流消费端限流当用户的请求突然增多,MQ可以配置消费端限流,让消息按照限制的数量进行消费,达到限流的效果。下面我们来代码案例演示。案例我们接着在前面篇章的工程来进行代码演示即可。1.配置消费端限流首先需要确保ack机制为手动确认acknowledge="manual"配置消费端每次消费的数量prefetch="1"<!--定义监听器与队列的绑定--> <rabbit:listener-containerconnection-factory="connectionFactory"acknowledge="manual"prefetch="1"> <rabbit:listenerref="ackListener"queue-names="test_queue_confirm"/> </rabbit:listener-container> 复制2.编写监听器类packa

  • mysql数据库自增id重新从1排序的两种方法

    mysql默认自增ID是从1开始了,但当我们如果有插入表或使用delete删除id之后ID就会不会从1开始了哦。   使用mysql时,通常表中会有一个自增的id字段,但当我们想将表中的数据清空重新添加数据时,希望id重新从1开始计数,用以下两种方法均可: 通常的设置自增字段的方法:创建表格时添加: createtabletable1(idintauto_incrementprimarykey,...) 创建表格后添加: altertabletable1addidintauto_incrementprimarykey自增字段,一定要设置为primarykey. 例子 altertabletablenamedropcolumnid;altertabletablenameaddidmediumint(8)notnullprimarykeyauto_incrementfirst; 方法二: altertabletablenameauto_increment=0

  • Codeforces 1179 D - Fedor Runs for President

    D-FedorRunsforPresident 思路: 推出斜率优化公式后,会发现最优点只可能来自凸斜率中的第一个元素和最后一个元素, 这两个元素不用维护凸斜率也能知道,就是第一个和上一个元素 代码: #pragmaGCCoptimize(2) #pragmaGCCoptimize(3) #pragmaGCCoptimize(4) #include<bits/stdc++.h> usingnamespacestd; #definey1y11 #definefifirst #definesesecond #definepiacos(-1.0) #defineLLlonglong //#definempmake_pair #definepbemplace_back #definelsrt<<1,l,m #definersrt<<1|1,m+1,r #defineULLunsignedLL #definepllpair<LL,LL> #defineplipair<LL,int> #definepiipair<int,int&

  • vue + router 配置子页面并传参

    如图所示,现在router的index.js文件里面配置好路由路径 然后在vue的模板文件中全局化路由的参数对象 最后,直接在页面中使用this.type就可以了

  • 选择模型 | 如何构建一个疾病的动物模型? | animal model of disease

    主流的研究模型(按高大上和难易程度排名):  斑马鱼 Zebrafish 细胞系cellline 小鼠模型mouseinvivomodel 干细胞(病人来源)模型(patient)iPSC-basedmodel 类器官organoidmodel 病人组织primarycells   选择模型 模型是研究的材料来源,也是测序的本体。这里有个最基础的三位一体【疾病+组织细胞+stage/time】,还有其他维度,比如gender,可以根据研究的问题细分。 病人组织:最有用的模型是人本身,因为最终目的是要研究针对人的预防和治疗手段。但因为道德约束,我们只能在死去的自愿捐赠的病人身上取样,人的差异很大,样本小很难做研究。取样可行性、难度、细胞数量、保存限制,都在限制着研究。 类器官:如果能完全模拟人,那就没有道德约束,什么研究都可以做了,这就是iPSC-derivedorganoid的初衷,但目前这个基本是玄学,类器官很难评估,烧了钱还不可靠。 2DiPSC-derivecells:定向分化而来的二维目标组织,优点是可以完全模拟人的遗传多样性,容易取样,目标

  • ajax报错问题的解决

    背景:用ajax与服务器页面进行交互 问题:XMLHttpRequest.status==0并且XMLHttpRequest.readyState==0并且textStatus==error   关于XMLHttpRequest.readyState: 状态码  0-(未初始化)还没有调用send()方法  1-(载入)已调用send()方法,正在发送请求  2-(载入完成)send()方法执行完成,已经接收到全部响应内容  3-(交互)正在解析响应内容  4-(完成)响应内容解析完成,可以在客户端调用了   我在页面调试的时候ajax发送成功并没有任何错误信息返回,但是在直接运行的时候就发生报错 借鉴http://www.cnblogs.com/Mainz/p/3506956.html这篇文章: jQueryajax请求错误返回status0和错误error的问题,分析可能的原因: url不存在(排除) url不可达(排除) 发送了跨域请求(排除) 数据格式错(排除) ajax在完成之前请求已经被取消(ajax请

  • [监控报警]elastalert安装使用

    安装 yum-yinstallpython3gitpython3-devel cd/usr/local/ gitclonehttps://github.com/Yelp/elastalert.gitpip3installvirtualenvmkdir-p /usr/local/elastalert/venv_py3.6_elastalert-0.2.1python3-mvenv/usr/local/elastalert/venv_py3.6_elastalert-0.2.1/cdvenv_py3.6_elastalert-0.2.1/bin/.activatepipinstall-rrequirements.txtpip3installsetuptools-rustpythonsetup.pyinstall复制  复制 配置 #mkidrrules #vimconfig.yaml rules_folder:rules run_every: minutes:1 buffer_time: minutes:15 es_host:192.168.0.37 es_p

相关推荐

推荐阅读