JavaTCP网络编程——单服务端与多客户端通讯

最近在学习Java的网络编程,特此记录一下学习成果。

先上图先,本代码是能正常运行的!!!!!!!!!!!

以上是代码的运行截图,使用两台虚拟机作为客户端,本机作为服务端进行通讯。服务端的干的活很简单,就是接收客户端的信息,然后把客户端的信息打上一个IP地址前缀返回给客户端!接下来的看看代码的实现吧!!!

public static void main(String[] args) throws  Exception{

        // 设置服务端就监听本机的某端口
        ServerSocket server = new ServerSocket(10000);
        System.out.println("服务端已启动");

        // 服务端需要不停的监听客户端的连接,所以这里要写一个死循环
        while (true){
            // server.accept() 会堵塞主线程,直到收到客户端的连接才会继续下去
            final Socket socket = server.accept();
            String hostAddress = socket.getInetAddress().getHostAddress();  // 获取客户端的IP地址
            System.out.println("["+hostAddress+"]已连接");

            // 开启一个线程池,由于要实现与多客户端通讯,所以必须使用线程池
            // 来一个客户就开一个线程专门为其服务
            // 此时将会一个主线程(负责不断监听请求),和0~N个任务线程
            ExecutorService service = Executors.newCachedThreadPool();

            service.execute(()->{
                receive(socket);    // 提交线程任务
            });

            // 这个是为了实现客户端和服务端对话用
            // 一个线程接收客户端信息,一个线程接收用户的输入,从而实现对话的
            // 但是考虑到命令行下是单线程的,无法实现与多客户端对话,自己有懒得去写GUI,故嘿嘿..
            // 取消注释能再命令行实现与单个客户端对话
//          service.execute(()->{
//              sender(socket);
//          });
        }
    }

以上为服务端主线程的流程,下面是服务端的任务线程,我这边偷懒写成一个简单static方法,使用lamada调用,各位也可以写成一个Runable Class

    private static void receive(Socket socket){
        try {
            InputStream inputStream = socket.getInputStream();
            // 将客户端发来的信息装在BufferedReader中
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
            String hostAddress = socket.getInetAddress().getHostAddress();
            while (true){
                // 判断客户端是否断开连接,比如在Linux上按 ctr+c 退出进程
                if(socket.isClosed()){
                    System.out.println("["+socket.getInetAddress().getHostAddress()+"]已断开连接");
                    //break;
                    return;     // 退出当前线程
                }

                // 读取客户端的来的信息,这儿会堵塞线程
                String msg = reader.readLine();
                String receiveMsg = ("["+hostAddress+"]:"+msg).toString().trim();
                System.out.println(receiveMsg);

                // 用PrintWriter装饰socket的OutputStream
                PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
                // 给客户端发送信息
                out.println(receiveMsg);
                System.out.println("服务端:"+receiveMsg);
                // 检测关键字
                if(msg.equals("bye")){
                    System.out.println("["+socket.getInetAddress().getHostAddress()+"]再见");
                    return;     // 退出当前线程
                }
            }
        } catch (Exception e) {
            if(socket.isClosed()){
                System.out.println("["+socket.getInetAddress().getHostAddress()+"]socket关闭");
            }
            if(socket.isConnected()){
                System.out.println("["+socket.getInetAddress().getHostAddress()+"]已断开连接");
            }
            return;
        }
    }

//    private static void sender(Socket socket){
//        try {
//            System.out.println("服务端已启动,请输入");
//            Scanner scanner = new Scanner(System.in);
//            String msg = scanner.nextLine();
//
//            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
//            while(true){
//                out.println(msg);
//                System.out.println("服务端:["+msg+"]");
//                msg = scanner.nextLine();
//                if(msg.equals("bye"))
//                    break;
//            }
//        }catch (Exception e){
//            e.printStackTrace();
//        }
//    }

至此 服务端就写完了,接下来是客户端编程,客户端至少会有两个线程,一个是负责接收服务端的信息,一个负责给服务端发送信息,我这边是把这两个任务单独写成线程,主线程基本啥都没干、空跑,各位也可把其中上述的任一个任务写在主线程中:

// 客户端的主线程,就干了三件事
    public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
        // 1、创建TCP连接
        final Socket socket = new Socket("192.168.0.142", 10000);

        // 2、开启任务线程
        ExecutorService service = Executors.newCachedThreadPool();
        Future<Boolean> sender = service.submit(new Sender(socket));
        Future<Boolean> receive = service.submit(new Receiver(socket));

        // 3、关闭线程池,结束程式
        if(!sender.get() && !receive.get()){
            System.out.println("Sender Over");
            System.out.println("Receive Over");
            service.shutdown();
        }
    }

下面就是客户端的任务线程了,一个发送一个接收,这边是写成一个Callable类,为了能够判断这两个任务何时接收(return false),在主线中去获取到这个两个任务的返回值,然后关闭线程池,结束客户端进程

class Sender implements Callable<Boolean> {
    private Socket socket = null;

    public Sender(Socket socket){
        this.socket = socket;
    }

    @Override
    public Boolean call() throws Exception {
        try{
            System.out.println("客户端已启动,请输入");
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            boolean flag = true;
            Scanner scanner = null;
            while(flag){
                scanner = new Scanner(System.in);
                String msg = scanner.nextLine();

                if(msg.equals("bye")){
                    out.println(msg);
                    flag = false;
                }
                out.println(msg);
                System.out.println("客户端:["+msg+"]");
            }
            scanner.close();
            return false;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }
}

// 这两个类的代码比较简单,大部分与服务端类似
class Receiver implements Callable<Boolean>{

    private Socket socket = null;

    public Receiver(Socket socket){
        this.socket = socket;
    }

    @Override
    public Boolean call() throws Exception {
        try {
            InputStream inputStream = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
            boolean flag = true;
            while (flag){
                String msg = reader.readLine();
                System.out.println("服务端:"+ msg);
                if(msg.contains("bye")){
                    System.out.println("客户端收到byebye了");
                    flag = false;
                }
            }
            inputStream.close();
            reader.close();
            return false;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }
}

以上就是我这一天的学习成果,若有大佬看到,能不能给小弟指导指导。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值