Java中Nio编程实现网络编程的多客户端与服务器连接完整步骤

Posted Cabbage coder

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中Nio编程实现网络编程的多客户端与服务器连接完整步骤相关的知识,希望对你有一定的参考价值。

前言:

物联网答辩终于结束了 记一下自己网络编程的代码 重连代码写了好久 try catch原来可以这么用!

学习地址:https://www.bilibili.com/video/BV1gz4y1C7RK

项目结构:

1、服务器代码:

package Server;

import Entiles.User;
import Utils.JdbcUtil;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

public class ChatServer 

    public void StratServer(int port) throws IOException 
        //soket通道 客户通道
        //创建服务端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //非堵塞模式
        serverSocketChannel.configureBlocking(false);

        //创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(port));

        //创建selector 选择器
        Selector selector = Selector.open();

        //注册通道
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //轮播查询
        System.out.println("智能水表服务端(端口:"+port+")已经启动");

//        如果有就绪状态的通道 则select方法返回1
        while(true)

            while (selector.select()>0)
//            因为有多个通道 ,所以采用集合 获取所有就绪的通道
                //        得到所有就绪状态的通道集合到key中
                Set<SelectionKey> selectionKeys = selector.selectedKeys(); //selectedKeys所有已经就绪的key集合

//        转成集合迭代器
                Iterator<SelectionKey> iterator = selectionKeys.iterator();

                while(iterator.hasNext())

                    SelectionKey selectionKey = iterator.next();

                    //有人来连
                    if(selectionKey.isAcceptable())
                        acceptOperator(serverSocketChannel,selector);
                    
                    //发过来了已经
                    else if(selectionKey.isReadable())
                        readOperator(selector,selectionKey);
                    
                    //返回水表数据
                    else if(selectionKey.isWritable())
                        writeOperator(selector,selectionKey);
                    
                    iterator.remove();
                
            

        
    

    //处理服务器写事件
    private void writeOperator(Selector selector,SelectionKey selectionKey) 
        try 
            //有channel可写,取出可写的channel
            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            //                    设计非阻塞
            socketChannel.configureBlocking(false);

            socketChannel.write(Charset.forName("UTF-8").encode("数据库存入成功!" ));
            //重新将channel注册到选择器上,设计为监听
            socketChannel.register(selector,SelectionKey.OP_READ);
        catch (IOException e)
            e.printStackTrace();
        



    

    //    处理读事件
    private void readOperator(Selector selector, SelectionKey selectionKey) throws IOException 

        try 
            //                    获取就绪通道
            SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
//                    设计buffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);

//                    循环读客户端数据
            int length=0;
            String msg="";
            if((length=socketChannel.read(buffer))>0)  //读到buffer里面
//                        切换模式
                buffer.flip();
                msg+=Charset.forName("UTF-8").decode(buffer);  //从buffer里面取数据 解码
            
            System.out.println(msg);

            String str[]=msg.split(":");

            String temp=str[1];

            String str2[]=temp.split("、");
            SimpleDateFormat tempDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String datetime = tempDate.format(new java.util.Date());

            JdbcUtil.find(new User(str2[0],str2[1],str2[2],datetime));

            //重新将channel注册到选择器上,设计为监听
            socketChannel.register(selector,SelectionKey.OP_WRITE);
        catch (IOException e)
            selectionKey.cancel();
            selectionKey.channel().close();
            System.out.println("有客户端断连,我已主动关闭");
        


                        //光广播到其他用户上去
//                        if(msg.length()>0)
//                            System.out.println(msg);
//                            castOtherClient(msg,selector,socketChannel);
//                        
    

        //广播到其他客户端
//    private void castOtherClient(String msg, Selector selector, SocketChannel socketChannel) throws IOException 
//
//        //获取所有就绪的channel
//        Set<SelectionKey> selectionKeySet = selector.keys();
//
        循环处理搜索就绪的channel
//        for (SelectionKey selectionKey : selectionKeySet)
            获取每一个channel
//            Channel tarChannel = selectionKey.channel();
//
            不给自己发信息
//            if(tarChannel instanceof SocketChannel && tarChannel!=socketChannel)
//                ((SocketChannel)tarChannel).write(Charset.forName("UTF-8").encode(msg)); //传输数据是编码,发送数据是解码
//            
//        
//    

    //处理接收状态的通道
    private void acceptOperator(ServerSocketChannel serverSocketChannel, Selector selector)  
        try 
            //                    获取连接
            SocketChannel socketChannel = serverSocketChannel.accept();
//                    设计非阻塞
            socketChannel.configureBlocking(false);
//                     注册通道
            socketChannel.register(selector,SelectionKey.OP_READ);

            //回复客户端消息
            socketChannel.write(Charset.forName("UTF-8").encode("您已成功连接到服务器!"));
        catch (IOException e)
            e.printStackTrace();
        

    

    public static void main(String[] args) throws IOException 
        new ChatServer().StratServer(7890);
    


解释:
其中我的readOperator函数是负责读取客户端传来的信息。Nio编程核心是通道和选择器,选择器通过不断轮询,执行对应的函数。
此项目我加入了数据库,如果收到客户端信息,存入数据库。

2、客户端代码

2.1、负责发送的主客户端

package Client;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;

public class ChatClient 

    public void startClient(String name ) throws IOException, InterruptedException 
        //        创建通道 绑定主机和端口
        SocketChannel socketChannel = null;
            socketChannel = SocketChannel.open(new InetSocketAddress(
                    "127.0.0.1", 7890));


        //接受服务端响应的数据
        Selector selector = Selector.open();
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
        //创建线程
        new Thread(new ClientThread(selector)).start();//负责拿到服务器端数据



        //向服务端发送数据
        System.out.println("请输入抄水表编号、抄水量、抄表员(抄水时间自动生成)(请在1min中内完成)");
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine())
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            String str = scanner.nextLine(); //键盘获取输入的内容
            if(str.length()>0)
               socketChannel.write(Charset.forName("UTF-8").encode("客户端:"+name+":"+str+"(已加载数据库)"));
                //System.out.println(Charset.forName("UTF-8").encode(name+":"+str));
            

        //设计非堵塞模式
        socketChannel.configureBlocking(false);

        //设计buffer


    




    

    public static void main(String[] args) throws IOException, InterruptedException 
        new ChatClient().startClient("gx");
    


2.2、负责接收服务器信息的客户端线程

package Client;

import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

public class ClientThread implements Runnable
    private Selector selector;
    ClientThread(Selector selector)
        this.selector=selector;
    

    @Override
    public void run() 
//        while(true)

            for(;;)
                try 
                    int length=selector.select();
                    if(length ==0)
                        continue;
                    

                    //        得到所有就绪状态的通道集合到key中
                    Set<SelectionKey> selectionKeys = selector.selectedKeys(); //selectedKeys所有已经就绪的key集合

                    //        转成集合迭代器
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();

                    while(iterator.hasNext())

                        SelectionKey selectionKey = iterator.next();

                        if(selectionKey.isReadable())
                            readOperator(selector,selectionKey);
                        
                        iterator.remove();
                    

                 catch (IOException | InterruptedException e) 
                    e.printStackTrace();
                

            

        
    //    处理接收服务器信息事件
    private void readOperator(Selector selector, SelectionKey selectionKey) throws InterruptedException 
        try 
            //                    获取就绪通道
            SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
//                    设计buffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);

//                    循环读客户端数据
            int length=0;
            String msg="";
            if((length=socketChannel.read(buffer))>0)  //读到buffer里面
//                        切换模式
                buffer.flip();
                msg+= Charset.forName("UTF-8").decode(buffer);  //从buffer里面取数据 解码
            

            System.out.println(msg);

            //重新将channel注册到选择器上,设计为监听
            socketChannel.register(selector,SelectionKey.OP_READ);

        catch (IOException e)
            selectionKey.cancel();
            System.out.println("服务器中断 开始准备重连");

            while (true)
                try 
                    new ChatClient().startClient("gx");

                 catch (IOException ioException) 
                    System.out.println("正在重连(5s) ");
                    //ioException.printStackTrace();
                    Thread.sleep(5000);
                    continue;
                

            
        




    




3、关键重连代码解释

1 重连代码解释

如果服务器突然关闭,如果在这里不try catch异常,而是向上抛出的话,随着服务器异常关闭,客户端也挂了,而且不会重连。
所以我们需要捕获这个异常,并且开始不断重连。
我写了一个死循环,不断连接,如果连不上他还是会报错,所以连不上的错也要捕获异常,不然程序就报错结束了,所以捕获连接没上的话,continue重新执行while循环,直到连接上服务器。

2 如果客户端关闭 那么服务器也要主动关闭他

4 数据库代码及实体类:

如果还想实现数据库方面代码,私我

以上是关于Java中Nio编程实现网络编程的多客户端与服务器连接完整步骤的主要内容,如果未能解决你的问题,请参考以下文章

java 基础之--nio 网络编程

Java NIO Socket编程实例

Java网络编程基础— 基于TCP的NIO简单聊天系统

Java网络编程与NIO详解12:常见NIO和BIO的Java面试题,一网打尽!

Java网络编程中怎样用管道流

网络编程--03NIO