Java网络编程学习A轮_07_基于Buffer的Socket编程

示例代码:
https://github.com/gordonklg/study,socket module

A. LineSeparate

基于 Buffer 实现逐行读取的 EchoServer 比传统 Socket 编程困难,相当于需要自己通过 Buffer 实现 BufferedReader 的 readLine 功能。

代码如下,假设单行不超过256字节,支持 Win 和 Linux(不支持单 \r 作为换行符)系统,空行忽略。

代码就不分析了,写了好久才跑对测试,分包粘包真是麻烦,要去刷 LeetCode 基本题提高编码能力了,不能整天都 CTRL C CTRL V 啊。

gordon.study.socket.nio.basic.LineSeparateBlockingEchoServer.java

public class LineSeparateBlockingEchoServer {

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 3; i++) {
            new Thread(new Client()).start();
        }
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8888));
        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            new Thread(new ServerHandler(socketChannel)).start();
        }
    }

    private static class ServerHandler implements Runnable {

        private SocketChannel socketChannel;

        private int lastScannedPos = 0;

        public ServerHandler(SocketChannel socketChannel) {
            this.socketChannel = socketChannel;
        }

        @Override
        public void run() {
            try {
                ByteBuffer buf = ByteBuffer.allocate(256);
                ByteBuffer writeBuf = ByteBuffer.allocate(256);
                byte[] content = null;
                while (true) {
                    if (socketChannel.read(buf) > 0) {
                        do {
                            content = extractLine(buf);
                            if (content != null) {
                                echo(writeBuf, content);
                            }
                        } while (content != null && buf.position() > 0);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private byte[] extractLine(ByteBuffer buf) {
            byte[] result = null;
            int totalLen = buf.position();
            buf.position(lastScannedPos);
            for (int index = lastScannedPos; index < totalLen; index++) {
                if (buf.get() == '\n') {
                    result = new byte[index - (hasSlashRBeforeSlashN(buf) ? 1 : 0)];
                    buf.position(0);
                    buf.get(result);
                    buf.position(index + 1);
                    buf.limit(totalLen);
                    buf.compact();
                    lastScannedPos = 0;
                    return result.length == 0 ? null : result;
                }
            }
            lastScannedPos = buf.position();
            return result;
        }

        private boolean hasSlashRBeforeSlashN(ByteBuffer buf) {
            int posOfSlashN = buf.position() - 1;
            if (posOfSlashN > 0) {
                return (buf.get(posOfSlashN - 1) == '\r');
            }
            return false;
        }

        private void echo(ByteBuffer writeBuf, byte[] content) throws IOException {
            System.out.println("ECHO: " + new String(content));
            writeBuf.clear();
            writeBuf.put(content);
            writeBuf.put("\n".getBytes());
            writeBuf.flip();
            while (writeBuf.hasRemaining()) {
                socketChannel.write(writeBuf);
            }
        }
    }

    private static class Client implements Runnable {

        @Override
        public void run() {
            try (Socket socket = new Socket()) {
                socket.connect(new InetSocketAddress(8888));
                DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                dos.write("hello\n".getBytes());
                Thread.sleep(100);
                dos.write("\n".getBytes());
                Thread.sleep(100);
                dos.write("你瞅啥?\r\n".getBytes());
                Thread.sleep(100);
                dos.write("\r\nchi".getBytes());
                Thread.sleep(100);
                dos.write(" le ".getBytes());
                Thread.sleep(100);
                dos.write("ma?\nni hao\n\nhi d".getBytes());
                Thread.sleep(100);
                dos.write("ude\r\n".getBytes());
                Thread.sleep(100);
                dos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

B.CustomProtocol

最经典的自定义协议就是基于 TLV (Type, Length, Value) 格式编码。我们约定 Type 占用1字节,0表示通讯结束,1表示文本消息;Length 占用2字节。

代码依然很难写,而且极不优雅,留给自己以后吐槽用吧。

gordon.study.socket.nio.basic.CustomProtocolBlockingPrintServer.java

public class CustomProtocolBlockingPrintServer {

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 3; i++) {
            new Thread(new Client()).start();
        }
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8888));
        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            new Thread(new ServerHandler(socketChannel)).start();
        }
    }

    private static class ServerHandler implements Runnable {

        private SocketChannel socketChannel;

        private int nextMsgLen = 0;

        public ServerHandler(SocketChannel socketChannel) {
            this.socketChannel = socketChannel;
        }

        @Override
        public void run() {
            try {
                ByteBuffer buf = ByteBuffer.allocate(256);
                while (!Thread.currentThread().isInterrupted()) {
                    if (socketChannel.read(buf) > 0) {
                        extractMessageAndPrint(buf);
                    }
                }
                System.out.println("===============exit==============");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private void extractMessageAndPrint(ByteBuffer buf) {
            if (nextMsgLen == 0) {// means we havn't get full "head" info
                buf.flip();
                int type = buf.get();
                if (type == 0) {
                    Thread.currentThread().interrupt();
                    return;
                }
                if (buf.remaining() < 2) {
                    buf.rewind();
                    buf.compact();
                } else {
                    int length = buf.getChar();
                    if (buf.remaining() < length - 3) {
                        nextMsgLen = length;
                        buf.rewind();
                        buf.compact();
                    } else {
                        byte[] content = new byte[length - 3];
                        buf.get(content);
                        System.out.println(new String(content));
                        buf.compact();
                        if (buf.position() > 0) {
                            extractMessageAndPrint(buf);
                        }
                    }
                }
            } else {
                buf.flip();
                if (buf.remaining() >= nextMsgLen) {
                    byte[] content = new byte[nextMsgLen - 3];
                    buf.position(3);
                    buf.get(content);
                    System.out.println(new String(content));
                    buf.compact();
                    nextMsgLen = 0;
                    if (buf.position() > 0) {
                        extractMessageAndPrint(buf);
                    }
                } else {
                    buf.compact();
                }
            }
        }
    }

    private static class Client implements Runnable {

        @Override
        public void run() {
            try (Socket socket = new Socket()) {
                socket.connect(new InetSocketAddress(8888));
                DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                print(dos, "hello");
                Thread.sleep(100);
                print(dos, "");
                Thread.sleep(100);
                print(dos, "你瞅啥?");
                Thread.sleep(100);

                dos.writeByte(1);
                dos.flush();
                Thread.sleep(100);
                dos.write((byte) (9 >> 8 & 0xFF));
                dos.flush();
                Thread.sleep(100);
                dos.write((byte) (9 & 0xFF));
                dos.flush();
                Thread.sleep(100);
                dos.write("ni".getBytes());
                dos.flush();
                Thread.sleep(100);
                dos.write(" ".getBytes());
                dos.flush();
                Thread.sleep(100);

                ByteBuffer buf = ByteBuffer.allocate(100);
                buf.put("hao".getBytes());
                buf.put((byte) 1);
                buf.put((byte) 0);
                buf.put((byte) 9);
                buf.put("abcdef".getBytes());
                buf.put((byte) 1);
                buf.put((byte) 0);
                buf.put((byte) 8);
                buf.put("12345".getBytes());
                buf.flip();
                byte[] bytes = new byte[buf.remaining()];
                buf.get(bytes);
                dos.write(bytes);
                dos.flush();
                Thread.sleep(100);

                dos.writeByte(0);
                dos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private void print(DataOutputStream dos, String message) throws IOException {
            byte[] bytes = message.getBytes();
            int totalLength = 3 + bytes.length;
            dos.writeByte(1);
            dos.write((byte) (totalLength >> 8 & 0xFF));
            dos.write((byte) (totalLength & 0xFF));
            dos.write(bytes);
            dos.flush();
        }
    }
}

转载于:https://www.cnblogs.com/gordonkong/p/7434655.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java Socket编程中,可以使用select()方法实现多路复用的功能,以便同时处理多个Socket连接。select()方法会阻塞当前线程,直到其中一个Socket连接上有事件发生,或者超时时间到了,或者被中断。 以下是一个简单的使用select()方法的例子: ```java Selector selector = Selector.open(); ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.bind(new InetSocketAddress(8080)); serverSocket.configureBlocking(false); serverSocket.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); // 阻塞当前线程,直到有事件发生 Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { // 处理连接请求 SocketChannel client = serverSocket.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { // 处理读取数据 SocketChannel client = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = client.read(buffer); // 处理读取到的数据 } keyIterator.remove(); } } ``` 在上述代码中,首先创建一个Selector对象,并将ServerSocketChannel注册到Selector上,然后通过调用select()方法进行事件轮询,当有事件发生时,就可以通过SelectionKey来获取对应的Channel,并处理相应的事件。在处理完事件后不要忘记调用keyIterator.remove()方法将SelectionKey从selectedKeys集合中移除,以便下次轮询时不会重复处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值