zoukankan      html  css  js  c++  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操作所产生的中断影响其他用户对服务器的访问,简单的说就是一个用户的输入操作不会造成其他用户的阻塞。当然,上面的代码使用线程池可以获得更好的性能,因为频繁的创建和销毁线程所造成的开销也是不可忽视的。

    下面是一段回显客户端测试代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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的操作虽然带来了更好的性能,但是有些操作是比较底层的,对于初学者来说还是有些难于理解。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    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();
                }
            }
        }
     
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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);
        }
    }

     

  • 相关阅读:
    开源权限框架shiro 入门
    Struts1.2入门笔记
    memcache概述
    教你如何将中文转换成全拼
    WPF第一章(XAML前台标记语言(Chapter02代码讲解))
    WPF第一章(XAML前台标记语言)
    WPF简介
    Activity以singleTask模式启动,intent传值的解决办法
    linux下查看文件编码以及编码转换
    Fedora 17字体美化
  • 原文地址:https://www.cnblogs.com/gjack/p/8901323.html
Copyright © 2011-2022 走看看