zoukankan      html  css  js  c++  java
  • Java面试题:用Java的套接字编程实现一个多线程的回显(echo)服务器。

    [
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class EchoServer {
    
        private static final int ECHO_SERVER_PORT = 6789;
    
        public static void main(String[] args) {        
            try(ServerSocket server = new ServerSocket(ECHO_SERVER_PORT)) {
                System.out.println("服务器已经启动...");
                while(true) {
                    Socket client = server.accept();
                    new Thread(new ClientHandler(client)).start();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private static class ClientHandler implements Runnable {
            private Socket client;
    
            public ClientHandler(Socket client) {
                this.client = client;
            }
    
            @Override
            public void run() {
                try(BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
                        PrintWriter pw = new PrintWriter(client.getOutputStream())) {
                    String msg = br.readLine();
                    System.out.println("收到" + client.getInetAddress() + "发送的: " + msg);
                    pw.println(msg);
                    pw.flush();
                } catch(Exception ex) {
                    ex.printStackTrace();
                } finally {
                    try {
                        client.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    }
    

    注意:上面的代码使用了Java 7的TWR语法,由于很多外部资源类都间接的实现了AutoCloseable接口(单方法回调接口),因此可以利用TWR语法在try结束的时候通过回调的方式自动调用外部资源类的close()方法,避免书写冗长的finally代码块。此外,上面的代码用一个静态内部类实现线程的功能,使用多线程可以避免一个用户I/O操作所产生的中断影响其他用户对服务器的访问,简单的说就是一个用户的输入操作不会造成其他用户的阻塞。当然,上面的代码使用线程池可以获得更好的性能,因为频繁的创建和销毁线程所造成的开销也是不可忽视的。
    下面是一段回显客户端测试代码:

    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.Socket;
    import java.util.Scanner;
    
    public class EchoClient {
    
        public static void main(String[] args) throws Exception {
            Socket client = new Socket("localhost", 6789);
            Scanner sc = new Scanner(System.in);
            System.out.print("请输入内容: ");
            String msg = sc.nextLine();
            sc.close();
            PrintWriter pw = new PrintWriter(client.getOutputStream());
            pw.println(msg);
            pw.flush();
            BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
            System.out.println(br.readLine());
            client.close();
        }
    }
    

    如果希望用NIO的多路复用套接字实现服务器,代码如下所示。NIO的操作虽然带来了更好的性能,但是有些操作是比较底层的,对于初学者来说还是有些难于理解。

    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.CharBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    
    public class EchoServerNIO {
    
        private static final int ECHO_SERVER_PORT = 6789;
        private static final int ECHO_SERVER_TIMEOUT = 5000;
        private static final int BUFFER_SIZE = 1024;
    
        private static ServerSocketChannel serverChannel = null;
        private static Selector selector = null;    // 多路复用选择器
        private static ByteBuffer buffer = null;    // 缓冲区
    
        public static void main(String[] args) {
            init();
            listen();
        }
    
        private static void init() {
            try {
                serverChannel = ServerSocketChannel.open();
                buffer = ByteBuffer.allocate(BUFFER_SIZE);
                serverChannel.socket().bind(new InetSocketAddress(ECHO_SERVER_PORT));
                serverChannel.configureBlocking(false);
                selector = Selector.open();
                serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        private static void listen() {
            while (true) {
                try {
                    if (selector.select(ECHO_SERVER_TIMEOUT) != 0) {
                        Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                        while (it.hasNext()) {
                            SelectionKey key = it.next();
                            it.remove();
                            handleKey(key);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        private static void handleKey(SelectionKey key) throws IOException {
            SocketChannel channel = null;
    
            try {
                if (key.isAcceptable()) {
                    ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                    channel = serverChannel.accept();
                    channel.configureBlocking(false);
                    channel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    channel = (SocketChannel) key.channel();
                    buffer.clear();
                    if (channel.read(buffer) > 0) {
                        buffer.flip();
                        CharBuffer charBuffer = CharsetHelper.decode(buffer);
                        String msg = charBuffer.toString();
                        System.out.println("收到" + channel.getRemoteAddress() + "的消息:" + msg);
                        channel.write(CharsetHelper.encode(CharBuffer.wrap(msg)));
                    } else {
                        channel.close();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                if (channel != null) {
                    channel.close();
                }
            }
        }
    
    }
    
    import java.nio.ByteBuffer;
    import java.nio.CharBuffer;
    import java.nio.charset.CharacterCodingException;
    import java.nio.charset.Charset;
    import java.nio.charset.CharsetDecoder;
    import java.nio.charset.CharsetEncoder;
    
    public final class CharsetHelper {
        private static final String UTF_8 = "UTF-8";
        private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder();
        private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
    
        private CharsetHelper() {
        }
    
        public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{
            return encoder.encode(in);
        }
    
        public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{
            return decoder.decode(in);
        }
    }
    
    ]
    转载请保留页面地址:https://www.breakyizhan.com/javamianshiti/2473.html
  • 相关阅读:
    SQL Server 基础知识/数据类型/数值类型
    javascript中slice(),splice(),split(),substring(),substr()使用方法
    Sublime text设置快捷键让编写的HTML文件在打指定浏览器预览
    常用开发环境配置和使用技巧
    JavaScript 模块化简析
    MySQL重置root用户密码的方法(转)
    SpringMVC 文件上传配置,多文件上传,使用的MultipartFile(转)
    Postman 安装及使用入门教程(转)
    HTTP状态码:400500 错误代码
    (转)Eclipse快捷键大全,导包快捷键:ctrl+Shift+/
  • 原文地址:https://www.cnblogs.com/breakyizhan/p/13263539.html
Copyright © 2011-2022 走看看