示例代码:

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();
}
}
}

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. 系统事件管理(Events) ---- HTML5+

    模块:events Events模块管理客户端事件,包括系统事件,如扩展API加载完毕.程序前后台切换等. 比如说:网络的链接的和断开这种事件,系统从前台走到后台这种事件: 不包括:点击和滑动页面事件 ...

  2. 最小费用流判负环消圈算法(poj2175)

    Evacuation Plan Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 3384   Accepted: 888   ...

  3. pyhton 函数参数,递归函数,高阶函数(一点点笔记)

    '''def test(x,y): print(x) print(y)test(2,y=3)def test(*args):#参数可以是不确定的多个数,接受N个位置参数,转换成元组形式 print(a ...

  4. VS2012编译FFmpeg 2.1.4

    这次的目标是完整编译FFmpeg包括汇编代码,并且尽量少修改源代码. 环境是WIN7 64位,VS2012, Intel.Parallel.Studio.XE.2013.SP1 一. 安装MinGW, ...

  5. Jenkins的参数化构建

    一.参数化构建日志 1.查看效果 有时候开发需要查看服务器日志,传统的是需要运维登录服务器拉取开发所需要的服务日志,这么做的弊端是:1.如果日志比较大,拉取耗费时间.占用服务器资源.2.占用运维不必要 ...

  6. MySQL集群简介与配置详解

    1. 先了解一下你是否应该用MySQL集群. 减少数据中心结点压力和大数据量处理,采用把MySQL分布,一个或多个application对应一个MySQL数据库.把几个MySQL数据库公用的数据做出共 ...

  7. SQLPlus的两种登录方式的不同效果

    Windows 8,Oralce11g,命令行 1.输入“sqlplus”,回车,提示:请输入用户名,输入用户名,回车,提示,请输入口令,输入口令后,回车,报ORA-12560:TNS:协议适配器错误 ...

  8. HDU_1457_后缀自动机四·重复旋律7

    #1457 : 后缀自动机四·重复旋律7 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成 ...

  9. rac下asm管理的表空间-数据文件的重命名

    asm下表空间的重命名与普通文件系统下的表空间重命名原理是一样的,只不过asm管理的数据文件有一些需要注意的地方,另外在asm下操作数据文件需要格外小心,稍有不慎将会造成数据文件丢失,如可以做备份最好 ...

  10. Python开发【项目】:生产环境下实时统计网站访问日志信息

    日志实时分析系统 生产环境下有需求:要每搁五分钟统计下这段时间内的网站访问量.UV.独立IP等信息,用直观的数据表格表现出来 环境描述: 网站为Nginx服务,系统每日凌晨会对日志进行分割,拷贝到其他 ...