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 数据库代码及实体类:

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

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>