示例代码:

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

  1. public class LineSeparateBlockingEchoServer {
  2. public static void main(String[] args) throws Exception {
  3. for (int i = 0; i < 3; i++) {
  4. new Thread(new Client()).start();
  5. }
  6. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  7. serverSocketChannel.bind(new InetSocketAddress(8888));
  8. while (true) {
  9. SocketChannel socketChannel = serverSocketChannel.accept();
  10. new Thread(new ServerHandler(socketChannel)).start();
  11. }
  12. }
  13. private static class ServerHandler implements Runnable {
  14. private SocketChannel socketChannel;
  15. private int lastScannedPos = 0;
  16. public ServerHandler(SocketChannel socketChannel) {
  17. this.socketChannel = socketChannel;
  18. }
  19. @Override
  20. public void run() {
  21. try {
  22. ByteBuffer buf = ByteBuffer.allocate(256);
  23. ByteBuffer writeBuf = ByteBuffer.allocate(256);
  24. byte[] content = null;
  25. while (true) {
  26. if (socketChannel.read(buf) > 0) {
  27. do {
  28. content = extractLine(buf);
  29. if (content != null) {
  30. echo(writeBuf, content);
  31. }
  32. } while (content != null && buf.position() > 0);
  33. }
  34. }
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. }
  38. }
  39. private byte[] extractLine(ByteBuffer buf) {
  40. byte[] result = null;
  41. int totalLen = buf.position();
  42. buf.position(lastScannedPos);
  43. for (int index = lastScannedPos; index < totalLen; index++) {
  44. if (buf.get() == '\n') {
  45. result = new byte[index - (hasSlashRBeforeSlashN(buf) ? 1 : 0)];
  46. buf.position(0);
  47. buf.get(result);
  48. buf.position(index + 1);
  49. buf.limit(totalLen);
  50. buf.compact();
  51. lastScannedPos = 0;
  52. return result.length == 0 ? null : result;
  53. }
  54. }
  55. lastScannedPos = buf.position();
  56. return result;
  57. }
  58. private boolean hasSlashRBeforeSlashN(ByteBuffer buf) {
  59. int posOfSlashN = buf.position() - 1;
  60. if (posOfSlashN > 0) {
  61. return (buf.get(posOfSlashN - 1) == '\r');
  62. }
  63. return false;
  64. }
  65. private void echo(ByteBuffer writeBuf, byte[] content) throws IOException {
  66. System.out.println("ECHO: " + new String(content));
  67. writeBuf.clear();
  68. writeBuf.put(content);
  69. writeBuf.put("\n".getBytes());
  70. writeBuf.flip();
  71. while (writeBuf.hasRemaining()) {
  72. socketChannel.write(writeBuf);
  73. }
  74. }
  75. }
  76. private static class Client implements Runnable {
  77. @Override
  78. public void run() {
  79. try (Socket socket = new Socket()) {
  80. socket.connect(new InetSocketAddress(8888));
  81. DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
  82. dos.write("hello\n".getBytes());
  83. Thread.sleep(100);
  84. dos.write("\n".getBytes());
  85. Thread.sleep(100);
  86. dos.write("你瞅啥?\r\n".getBytes());
  87. Thread.sleep(100);
  88. dos.write("\r\nchi".getBytes());
  89. Thread.sleep(100);
  90. dos.write(" le ".getBytes());
  91. Thread.sleep(100);
  92. dos.write("ma?\nni hao\n\nhi d".getBytes());
  93. Thread.sleep(100);
  94. dos.write("ude\r\n".getBytes());
  95. Thread.sleep(100);
  96. dos.close();
  97. } catch (Exception e) {
  98. e.printStackTrace();
  99. }
  100. }
  101. }
  102. }

B.CustomProtocol

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

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

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

  1. public class CustomProtocolBlockingPrintServer {
  2. public static void main(String[] args) throws Exception {
  3. for (int i = 0; i < 3; i++) {
  4. new Thread(new Client()).start();
  5. }
  6. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  7. serverSocketChannel.bind(new InetSocketAddress(8888));
  8. while (true) {
  9. SocketChannel socketChannel = serverSocketChannel.accept();
  10. new Thread(new ServerHandler(socketChannel)).start();
  11. }
  12. }
  13. private static class ServerHandler implements Runnable {
  14. private SocketChannel socketChannel;
  15. private int nextMsgLen = 0;
  16. public ServerHandler(SocketChannel socketChannel) {
  17. this.socketChannel = socketChannel;
  18. }
  19. @Override
  20. public void run() {
  21. try {
  22. ByteBuffer buf = ByteBuffer.allocate(256);
  23. while (!Thread.currentThread().isInterrupted()) {
  24. if (socketChannel.read(buf) > 0) {
  25. extractMessageAndPrint(buf);
  26. }
  27. }
  28. System.out.println("===============exit==============");
  29. } catch (Exception e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. private void extractMessageAndPrint(ByteBuffer buf) {
  34. if (nextMsgLen == 0) {// means we havn't get full "head" info
  35. buf.flip();
  36. int type = buf.get();
  37. if (type == 0) {
  38. Thread.currentThread().interrupt();
  39. return;
  40. }
  41. if (buf.remaining() < 2) {
  42. buf.rewind();
  43. buf.compact();
  44. } else {
  45. int length = buf.getChar();
  46. if (buf.remaining() < length - 3) {
  47. nextMsgLen = length;
  48. buf.rewind();
  49. buf.compact();
  50. } else {
  51. byte[] content = new byte[length - 3];
  52. buf.get(content);
  53. System.out.println(new String(content));
  54. buf.compact();
  55. if (buf.position() > 0) {
  56. extractMessageAndPrint(buf);
  57. }
  58. }
  59. }
  60. } else {
  61. buf.flip();
  62. if (buf.remaining() >= nextMsgLen) {
  63. byte[] content = new byte[nextMsgLen - 3];
  64. buf.position(3);
  65. buf.get(content);
  66. System.out.println(new String(content));
  67. buf.compact();
  68. nextMsgLen = 0;
  69. if (buf.position() > 0) {
  70. extractMessageAndPrint(buf);
  71. }
  72. } else {
  73. buf.compact();
  74. }
  75. }
  76. }
  77. }
  78. private static class Client implements Runnable {
  79. @Override
  80. public void run() {
  81. try (Socket socket = new Socket()) {
  82. socket.connect(new InetSocketAddress(8888));
  83. DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
  84. print(dos, "hello");
  85. Thread.sleep(100);
  86. print(dos, "");
  87. Thread.sleep(100);
  88. print(dos, "你瞅啥?");
  89. Thread.sleep(100);
  90. dos.writeByte(1);
  91. dos.flush();
  92. Thread.sleep(100);
  93. dos.write((byte) (9 >> 8 & 0xFF));
  94. dos.flush();
  95. Thread.sleep(100);
  96. dos.write((byte) (9 & 0xFF));
  97. dos.flush();
  98. Thread.sleep(100);
  99. dos.write("ni".getBytes());
  100. dos.flush();
  101. Thread.sleep(100);
  102. dos.write(" ".getBytes());
  103. dos.flush();
  104. Thread.sleep(100);
  105. ByteBuffer buf = ByteBuffer.allocate(100);
  106. buf.put("hao".getBytes());
  107. buf.put((byte) 1);
  108. buf.put((byte) 0);
  109. buf.put((byte) 9);
  110. buf.put("abcdef".getBytes());
  111. buf.put((byte) 1);
  112. buf.put((byte) 0);
  113. buf.put((byte) 8);
  114. buf.put("12345".getBytes());
  115. buf.flip();
  116. byte[] bytes = new byte[buf.remaining()];
  117. buf.get(bytes);
  118. dos.write(bytes);
  119. dos.flush();
  120. Thread.sleep(100);
  121. dos.writeByte(0);
  122. dos.close();
  123. } catch (Exception e) {
  124. e.printStackTrace();
  125. }
  126. }
  127. private void print(DataOutputStream dos, String message) throws IOException {
  128. byte[] bytes = message.getBytes();
  129. int totalLength = 3 + bytes.length;
  130. dos.writeByte(1);
  131. dos.write((byte) (totalLength >> 8 & 0xFF));
  132. dos.write((byte) (totalLength & 0xFF));
  133. dos.write(bytes);
  134. dos.flush();
  135. }
  136. }
  137. }

Java网络编程学习A轮_07_基于Buffer的Socket编程的更多相关文章

  1. Python网络编程03 /缓存区、基于TCP的socket循环通信、执行远程命令、socketserver通信

    Python网络编程03 /缓存区.基于TCP的socket循环通信.执行远程命令.socketserver通信 目录 Python网络编程03 /缓存区.基于TCP的socket循环通信.执行远程命 ...

  2. POCO库中文编程参考指南(8)丰富的Socket编程

    POCO库中文编程参考指南(8)丰富的Socket编程 作者:柳大·Poechant 博客:Blog.CSDN.net/Poechant 邮箱:zhongchao.ustc#gmail.com (# ...

  3. Java网络编程学习A轮_01_目标与基础复习

    A. A轮目标 复习网络编程基础知识,重点学习下TCP三次握手四次挥手,以及可能引发的异常情况. 回顾 Socket 编程,好多年没写(chao)过相关代码了. 重学 NIO,以前学的基本忘光了,毕竟 ...

  4. Java网络编程学习A轮_06_NIO入门

    参考资料: 老外写的教程,很适合入门:http://tutorials.jenkov.com/java-nio/index.html 上面教程的译文:http://ifeve.com/overview ...

  5. Java网络编程学习A轮_08_NIO的Reactor模型

    参考资料: 了解 Java NIO 的 Reactor 模型,大神 Doug Lea 的 PPT Scalable IO in Java 必看:http://gee.cs.oswego.edu/dl/ ...

  6. Java网络编程学习A轮_05_Socket编程

    示例代码: https://github.com/gordonklg/study,socket module A. Socket 编程简单例子 最简单的 Socket 编程是通过回车/换行符,整行读取 ...

  7. Java基于TCP的Socket编程练习

    环境:Notpad ++ 6.0 + JDK 6.0.31 问题:使用套接字编写客户-服务器程序,实现客户-服务器交互计算.客户将三角形3个边的长度发给服务器,服务器把计算出的三角形的面积返回给客户. ...

  8. 基于MFC的socket编程(异步非阻塞通信)

       对于许多初学者来说,网络通信程序的开发,普遍的一个现象就是觉得难以入手.许多概念,诸如:同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)等,初学者往往迷惑不清, ...

  9. 基于win32的socket编程及程序实现

    初步研究了win32平台的Windows Sockets,它是Microsoft Windows的网络程序设计接口,它是从Berkeley Sockets扩展而来的,以动态链接库的形式提供给我们使用. ...

随机推荐

  1. 编程中,static的用法详解

    C++的static有两种用法:面向过程程序设计中的static和面向对象程序设计中的static.前者应用于普通变量和函数,不涉及类:后者主要说明static在类中的作用.一.面向过程设计中的sta ...

  2. SaltStack配置管理-jinja模板

    上一篇:SaltStack配置管理-状态间关系 需求:需要apache配置文件里面监听的端口是每个minion本地的地址 saltstack默认的模板是jinjia模板 参考文档:http://doc ...

  3. HanLP https://pypi.python.org/pypi/sumy/

    HanLP - 汉语言处理包 http://hanlp.linrunsoft.com/doc.html https://pypi.python.org/pypi/sumy/

  4. Python爬虫实例(五) requests+flask构建自己的电影库

    目标任务:使用requests抓取电影网站信息和下载链接保存到数据库中,然后使用flask做数据展示. 爬取的网站在这里 最终效果如下: 主页: 可以进行搜索:输入水形物语 点击标题进入详情页: 爬虫 ...

  5. Django - Cookie、Session、自定义分页和Django分页器

    2. 今日内容 https://www.cnblogs.com/liwenzhou/p/8343243.html 1. Cookie和Session 1. Cookie 服务端: 1. 生成字符串 2 ...

  6. Silverlight中ListBox的数据绑定

    在Silverlight中ListBox是一个非常强大的控件.总结下ListBox的绑定数据的方式. 首先,新建一个Book类, public class Book { public string B ...

  7. Git入门基本操作

    由David发表在天码营 Git简介 Git不仅仅是实际项目开发中进行代码管理的神器,也是你在天码营学习需要掌握的工具. Git是一种代码版本控制工具.我们在实际项目中和天码营的学习过程中都会产生大量 ...

  8. ubuntu 用法

    1:改变某一个目录的拥有者 sudo chown -hR user:user ./目录名    //     user:user  用户名:组名 sudo chmod  777 文件     //给文 ...

  9. mysqldump 导出统一限制每张数据表导出的记录数

    mysqldump 导出统一限制每张数据表导出的记录数 在工作过程中,需要将生产的数据导出到本地开发环境,我希望可以导出部分数据.而服务器数据量比较大(上千万),如果选择直接从服务器导出数据, 正在运 ...

  10. 多线程Java面试题总结

    57.Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别?答:sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指 ...