当前位置:   article > 正文

Android与Java NIO实现简单Echo服务器与客户端_用 nio技术替代一客户一线程技术改写 echo 项目的客户机/服务器设计。

用 nio技术替代一客户一线程技术改写 echo 项目的客户机/服务器设计。

上一篇用Java IO来做了个Demo,于是乎进一步,用Java NIO来做一个。

NIO的优势在于非阻塞。使用了Selector在一个线程里进行轮询,就能够完成接入、收\发消息的操作,不需要每建立一个连接都新启动一个线程的方式。


Server端代码:

  1. public class EchoServer {
  2. private static final int MAX_SIZE = 256; // max size 256
  3. private static Charset mCharSet = Charset.forName("UTF-8"); //encode and decode charset
  4. public static void main(String args[]) {
  5. try {
  6. Selector selector = Selector.open(); // init an selector
  7. initServerChannel(selector);
  8. startSelector(selector);
  9. } catch (IOException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. private static void initServerChannel(Selector selector) {
  14. ServerSocketChannel serverChannel = null;
  15. try {
  16. serverChannel = ServerSocketChannel.open();
  17. serverChannel.configureBlocking(false); // not blocking
  18. serverChannel.socket().bind(new InetSocketAddress(8999));
  19. serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 将ServerChannl注册为accept感兴趣类型
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. private static void startSelector(Selector selector) {
  25. int loopCount = 0;
  26. while (true) {
  27. int n = 0; // Selector轮询注册来的Channel, 阻塞到至少有一个通道在你注册的事件上就绪了。
  28. try {
  29. n = selector.select();
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. }
  33. if (n == 0) {
  34. continue;
  35. }
  36. System.out.println("loopCount:" + loopCount);
  37. Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
  38. while (iterator.hasNext()) {
  39. SelectionKey selectionKey = iterator.next(); // 获取SelectionKey
  40. SocketChannel socketChannel = null;
  41. SelectableChannel selectableChannel = selectionKey.channel();
  42. // 每一步都要判断selectionKey.isValid(),避免断开连接产生的java.nio.channels.CancelledKeyException
  43. if (selectionKey.isValid() && selectionKey.isAcceptable()) {
  44. System.out.println("selectionKey isAcceptable");
  45. acceptClient(selectionKey, (ServerSocketChannel) selectableChannel);
  46. }
  47. if (selectionKey.isValid() && selectionKey.isReadable()) {
  48. // a channel is ready for reading
  49. System.out.println("selectionKey isReadable");
  50. socketChannel = (SocketChannel) selectableChannel;// 返回为之创建此键的通道。
  51. readMsg(selectionKey, socketChannel);
  52. }
  53. if (selectionKey.isValid() && selectionKey.isWritable()) {
  54. // a channel is ready for writing
  55. System.out.println("selectionKey isWritable");
  56. socketChannel = (SocketChannel) selectableChannel;
  57. writeMsg(selectionKey, socketChannel);
  58. }
  59. iterator.remove();
  60. }
  61. loopCount++;
  62. }
  63. }
  64. private static void acceptClient(SelectionKey selectionKey, ServerSocketChannel serverChannel) {
  65. // 此方法返回的套接字通道(如果有)将处于阻塞模式。
  66. try {
  67. SocketChannel socketChannel = serverChannel.accept();
  68. socketChannel.configureBlocking(false);
  69. // 向Selector注册Channel,设置读取为感兴趣操作,此类操作将会在下一次选择器select操作时被交付。同时附加byteBuffer对象作为数据传递的容器
  70. socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
  71. System.out.println("connected from:" + socketChannel.getRemoteAddress());
  72. } catch (IOException e) {
  73. e.printStackTrace();
  74. selectionKey.cancel();
  75. }
  76. }
  77. private static void readMsg(SelectionKey selectionKey, SocketChannel socketChannel) {
  78. if (selectionKey == null || socketChannel == null) {
  79. return;
  80. }
  81. ByteBuffer dataBuffer = ByteBuffer.allocate(MAX_SIZE);
  82. int count = 0;
  83. try {
  84. count = socketChannel.read(dataBuffer);
  85. } catch (IOException e) {
  86. e.printStackTrace();
  87. selectionKey.cancel();
  88. try {
  89. socketChannel.close();
  90. } catch (IOException e1) {
  91. e1.printStackTrace();
  92. }
  93. }
  94. if (count > 0) {
  95. dataBuffer.flip();
  96. byte[] bytes = new byte[dataBuffer.remaining()];
  97. dataBuffer.get(bytes);
  98. String data = new String(bytes, mCharSet);
  99. System.out.println("received: " + data);
  100. selectionKey.attach(dataBuffer); // 给SelectionKey附加上的DataBuffer对象
  101. selectionKey.interestOps(SelectionKey.OP_WRITE); // 读取完,设置写入为感兴趣操作,这样在接下来一个循环会触发到服务端写操作,给用户返回数据
  102. }
  103. if (count == -1) {
  104. selectionKey.cancel();
  105. try {
  106. socketChannel.close();
  107. } catch (IOException e) {
  108. e.printStackTrace();
  109. }
  110. }
  111. }
  112. private static void writeMsg(SelectionKey selectionKey, SocketChannel socketChannel) {
  113. if (selectionKey == null || socketChannel == null) {
  114. return;
  115. }
  116. ByteBuffer dataBuffer = (ByteBuffer) selectionKey.attachment();
  117. String data = new String(dataBuffer.array(), mCharSet);
  118. String result = "response: " + data.trim();
  119. dataBuffer.flip();
  120. System.out.println("send back: " + result);
  121. dataBuffer = ByteBuffer.wrap(result.getBytes(mCharSet));
  122. //输出到通道
  123. try {
  124. while (dataBuffer.hasRemaining()) {
  125. socketChannel.write(dataBuffer);
  126. }
  127. //将缓冲区的当前位置和界限之间的字节(如果有)复制到缓冲区的开始处
  128. dataBuffer.compact();
  129. selectionKey.interestOps(SelectionKey.OP_READ); // 写入完毕,设置读取为感兴趣操作
  130. } catch (IOException e) {
  131. e.printStackTrace();
  132. selectionKey.cancel();
  133. try {
  134. socketChannel.close();
  135. } catch (IOException e1) {
  136. e1.printStackTrace();
  137. }
  138. }
  139. }
  140. }

Android客户端:

  1. public class MainActivity extends AppCompatActivity {
  2. private static final String TAG = MainActivity.class.getSimpleName();
  3. private EditText mIpEt;
  4. private EditText mPortEt;
  5. private Button mConnBtn;
  6. private TextView mScreenTv;
  7. private EditText mInputEt;
  8. private Button mSendBtn;
  9. private SocketThread mSocketThread;
  10. private static Handler mMainHander;
  11. private static final int MSG_CONNECT = 0x001;
  12. private static final int MSG_RECEIVE = 0x002;
  13. private static final int MSG_SEND_ERROR = 0x003;
  14. private static final String DATA_RECEIVE = "data_receive";
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.activity_main);
  19. mIpEt = findViewById(R.id.main_ip_et);
  20. mPortEt = findViewById(R.id.main_port_et);
  21. mConnBtn = findViewById(R.id.main_connect_btn);
  22. mScreenTv = findViewById(R.id.main_screen_tv);
  23. mInputEt = findViewById(R.id.main_input_et);
  24. mSendBtn = findViewById(R.id.main_send_btn);
  25. // defalut value. Change it to your own server ip
  26. mIpEt.setText("172.16.62.65");
  27. mPortEt.setText("8999");
  28. mConnBtn.setOnClickListener(new View.OnClickListener() {
  29. @Override
  30. public void onClick(View v) {
  31. String ip = mIpEt.getText().toString();
  32. String port = mPortEt.getText().toString();
  33. if (TextUtils.isEmpty(ip) || TextUtils.isEmpty(port)) {
  34. Toast.makeText(MainActivity.this, "ip or port is null", Toast.LENGTH_SHORT).show();
  35. } else {
  36. connectToServer(ip, Integer.valueOf(port));
  37. }
  38. }
  39. });
  40. mSendBtn.setOnClickListener(new View.OnClickListener() {
  41. @Override
  42. public void onClick(View v) {
  43. String data = mInputEt.getText().toString();
  44. if (!TextUtils.isEmpty(data)) {
  45. mSocketThread.sendMsgToServer(data);
  46. }
  47. }
  48. });
  49. // TODO handler may cause memory leaks
  50. mMainHander = new Handler() {
  51. @Override
  52. public void handleMessage(Message msg) {
  53. switch (msg.what) {
  54. case MSG_CONNECT:
  55. Toast.makeText(MainActivity.this, "Connect to Server Success", Toast.LENGTH_SHORT).show();
  56. mConnBtn.setText("Connected");
  57. mConnBtn.setEnabled(false);
  58. break;
  59. case MSG_RECEIVE:
  60. Bundle data = msg.getData();
  61. String dataStr = data.getString(DATA_RECEIVE);
  62. Log.i(TAG, "received data:" + dataStr);
  63. CharSequence originData = mScreenTv.getText();
  64. String result = originData + "\n" + dataStr;
  65. mScreenTv.setText(result);
  66. break;
  67. case MSG_SEND_ERROR:
  68. Toast.makeText(MainActivity.this, "Send Error, Connection may be Closed", Toast.LENGTH_SHORT).show();
  69. break;
  70. }
  71. }
  72. };
  73. }
  74. private void connectToServer(String ip, int port) {
  75. mSocketThread = new SocketThread(ip, port);
  76. mSocketThread.start();
  77. }
  78. private static class SocketThread extends Thread {
  79. private static final int MAX_SIZE = 256; // max size 256
  80. private static Charset mCharSet = Charset.forName("UTF-8"); //encode and decode charset
  81. private String mIp;
  82. private int mPort;
  83. private SocketChannel mClientChannel;
  84. private Selector mSelector;
  85. public SocketThread(String ip, int port) {
  86. this.mIp = ip;
  87. this.mPort = port;
  88. try {
  89. mSelector = Selector.open();
  90. } catch (IOException e) {
  91. e.printStackTrace();
  92. }
  93. }
  94. @Override
  95. public void run() {
  96. initClientChannel(mSelector);
  97. startSelector(mSelector);
  98. }
  99. private void initClientChannel(Selector selector) {
  100. try {
  101. mClientChannel = SocketChannel.open();
  102. mClientChannel.configureBlocking(false);
  103. mClientChannel.connect(new InetSocketAddress(mIp, mPort));
  104. mClientChannel.register(selector, SelectionKey.OP_CONNECT);
  105. } catch (IOException e) {
  106. e.printStackTrace();
  107. }
  108. }
  109. private void startSelector(Selector selector) {
  110. while (true) {
  111. int n = 0; // Selector轮询注册来的Channel, 阻塞到至少有一个通道在你注册的事件上就绪了。
  112. try {
  113. n = selector.select();
  114. } catch (IOException e) {
  115. e.printStackTrace();
  116. }
  117. if (n == 0) {
  118. continue;
  119. }
  120. Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
  121. while (iterator.hasNext()) {
  122. SelectionKey selectionKey = iterator.next(); // 获取SelectionKey
  123. // 每一步都要判断selectionKey.isValid(),避免断开连接产生的java.nio.channels.CancelledKeyException
  124. if (selectionKey.isValid() && selectionKey.isConnectable()) {
  125. connectServer(selectionKey);
  126. }
  127. if (selectionKey.isValid() && selectionKey.isReadable()) {
  128. // a channel is ready for reading
  129. readMsg(selectionKey);
  130. }
  131. if (selectionKey.isValid() && selectionKey.isWritable()) {
  132. // a channel is ready for writing
  133. }
  134. iterator.remove();
  135. }
  136. }
  137. }
  138. private void connectServer(SelectionKey selectionKey) {
  139. try {
  140. mClientChannel.finishConnect();
  141. mMainHander.sendEmptyMessage(MSG_CONNECT);
  142. selectionKey.interestOps(SelectionKey.OP_READ);
  143. Log.i(TAG, "connected to:" + mClientChannel.socket().getInetAddress());
  144. } catch (IOException e) {
  145. e.printStackTrace();
  146. selectionKey.cancel();
  147. try {
  148. mClientChannel.close();
  149. } catch (IOException e1) {
  150. e1.printStackTrace();
  151. }
  152. }
  153. }
  154. private void readMsg(SelectionKey selectionKey) {
  155. if (selectionKey == null || mClientChannel == null) {
  156. return;
  157. }
  158. ByteBuffer dataBuffer = ByteBuffer.allocate(MAX_SIZE); // 从SelectionKey中取出注册时附加上的DataBuffer对象
  159. int count = 0;
  160. try {
  161. count = mClientChannel.read(dataBuffer);
  162. } catch (IOException e) {
  163. e.printStackTrace();
  164. selectionKey.cancel();
  165. try {
  166. mClientChannel.close();
  167. } catch (IOException e1) {
  168. e1.printStackTrace();
  169. }
  170. }
  171. if (count > 0) {
  172. dataBuffer.flip();
  173. byte[] bytes = new byte[dataBuffer.remaining()];
  174. dataBuffer.get(bytes);
  175. String data = new String(bytes, mCharSet);
  176. Message message = mMainHander.obtainMessage();
  177. message.what = MSG_RECEIVE;
  178. Bundle bundle = new Bundle();
  179. bundle.putString(DATA_RECEIVE, data);
  180. message.setData(bundle);
  181. mMainHander.sendMessage(message);
  182. }
  183. }
  184. public void sendMsgToServer(final String data) {
  185. new Thread(new Runnable() {
  186. @Override
  187. public void run() {
  188. ByteBuffer dataBuffer = ByteBuffer.allocate(data.length());
  189. dataBuffer.put(data.getBytes(mCharSet));
  190. dataBuffer.flip();
  191. //输出到通道
  192. try {
  193. while (dataBuffer.hasRemaining()) {
  194. mClientChannel.write(dataBuffer);
  195. }
  196. Log.i(TAG, "send data" + data);
  197. } catch (IOException e) {
  198. e.printStackTrace();
  199. mMainHander.sendEmptyMessage(MSG_SEND_ERROR);
  200. try {
  201. if (mClientChannel != null) {
  202. mClientChannel.close();
  203. }
  204. } catch (IOException e1) {
  205. e1.printStackTrace();
  206. }
  207. }
  208. }
  209. }).start();
  210. }
  211. }
  212. }

MainActivity的布局文件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical"
  6. android:layout_margin="15dp">
  7. <EditText
  8. android:id="@+id/main_ip_et"
  9. android:layout_width="match_parent"
  10. android:layout_height="wrap_content"
  11. android:hint="ip address"/>
  12. <EditText
  13. android:id="@+id/main_port_et"
  14. android:layout_width="match_parent"
  15. android:layout_height="wrap_content"
  16. android:hint="port"
  17. android:inputType="number"/>
  18. <Button
  19. android:id="@+id/main_connect_btn"
  20. android:layout_width="wrap_content"
  21. android:layout_height="wrap_content"
  22. android:layout_gravity="center"
  23. android:text="Connect"/>
  24. <EditText
  25. android:id="@+id/main_input_et"
  26. android:layout_width="match_parent"
  27. android:layout_height="wrap_content"
  28. android:hint="message to server"/>
  29. <Button
  30. android:id="@+id/main_send_btn"
  31. android:layout_width="wrap_content"
  32. android:layout_height="wrap_content"
  33. android:layout_gravity="center"
  34. android:text="Send"/>
  35. <ScrollView
  36. android:layout_width="match_parent"
  37. android:layout_height="match_parent"
  38. android:layout_marginTop="10dp">
  39. <TextView
  40. android:id="@+id/main_screen_tv"
  41. android:layout_width="match_parent"
  42. android:layout_height="wrap_content"
  43. android:text="received message will be shown here"/>
  44. </ScrollView>
  45. </LinearLayout>

最后,同样不要忘记清单文件中声明网络权限。

NIO的使用比IO要复杂一些,但是它非阻塞的特性,极大减少线程使用这些优势,还是很值得研究的。毕竟真正到了项目级别的代码,不可能用Java IO去实现,一定是基于NIO的网络框架。


Java NIO相关例子网上不太多,自己做的时候也踩了一些坑,尤其要注意ByteBuffer的使用方式,Java IO数据是面向流的,而NIO是面向Buffer的,Buffer的读写是基本功,如果有疑惑可以去查阅相关资料。也欢迎大家一起来探讨、学习~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/160733
推荐阅读
相关标签
  

闽ICP备14008679号