上文以及以后也将会用到的代码块插件叫Highlight.js,支持的语言以及风格也比较多,还可以自己写css调整样式,算是一款不错的国产插件
github:WP Code Highlight.js
编者注:
该文章由旧博客系统迁移而来,上述插件为wordpress相关插件且已停止维护。
上篇已经详细拆解了服务端的代码,总体来说比较简单,服务器要做的是连接、接收、处理、发送。在上文的例子中为了方便测试处理方式比较简单,就是收到任意客户端发来的信息时直接广播给其他所有的客户端。本文就来解析客户端的实现以及应用。
纵观服务端的实现,客户端的实现已经非常明了:1、开启线程被动接收数据->处理数据 2、实现一个开启线程发送数据的方法 因为要开启线程处理数据,所以客户端依然要继承Thread,且基本数据有一下几个:
private String host;private int port;private ObjectInputStream in;private ObjectOutputStream out;private Socket socket;然后实现构造方法:
public ConnectClient(String host, int port) { this.host = host; this.port = port; try { socket = new Socket(host, port); out = new ObjectOutputStream(socket.getOutputStream()); in = new ObjectInputStream(socket.getInputStream()); } catch (IOException e) { }
}这里需要注意上文提到IO流的初始化顺序的问题,因为服务端是先I后O,所以这里先O后I。
后面的实现非常简单,接收信息的方法写在run中,然后写一个发送方法:
public void run() {
try { while (true) { Object msg = in.readObject(); //1 } } catch (IOException | ClassNotFoundException e) { //2 }
}
public void sendData(TalkPkg tp) { new Thread(() -> { try { out.writeObject(tp); } catch (IOException e) { e.printStackTrace(); } }).start();}那么问题来了,接收到的消息如何处理呢?考虑到客户端的代码要放在Android上跑,所以比较稳妥的做法是给各个情况分别写一个监听事件,这里写两个事件作为示范,分别在run方法中的接收信息的注释1和注释2处。显然,注释1的地方应该是接收到了信息,等待处理;第二个是则是输入流发生异常(一般是连接中断)。所以我们定义两个内部接口,分别为onReceivedListener和onConnectLostListener来处理上述两种情况,代码如下:
public interface onReceivedListener { void onReceived(Object msg);}
public interface onConnectLostListener { void onConnectLost();}并且在类中声明该接口:
private onReceivedListener receivedListener;private onConnectLostListener connectLostListener;set方法:
public void setReceivedListener(onReceivedListener on) { this.receivedListener = on;}
public void setConnectLostListener(onConnectLostListener connectLostListener) { this.connectLostListener = connectLostListener;}在相关位置调用:
public void run() {
try { while (true) { Object msg = in.readObject(); receivedListener.onReceived(msg); } } catch (IOException | ClassNotFoundException e) { connectLostListener.onConnectLost(); }
}至此,客户端的主要代码就完成了,先贴出完整代码:
public class ConnectClient extends Thread { private String host; private int port; private ObjectInputStream in; private ObjectOutputStream out; private Socket socket; private onReceivedListener receivedListener; private onConnectLostListener connectLostListener;
public ConnectClient(String host, int port) { this.host = host; this.port = port; try { socket = new Socket(host, port); out = new ObjectOutputStream(socket.getOutputStream()); in = new ObjectInputStream(socket.getInputStream()); } catch (IOException e) { } }
public interface onReceivedListener { void onReceived(Object msg); }
public interface onConnectLostListener { void onConnectLost(); }
public void setReceivedListener(onReceivedListener on) { this.receivedListener = on; }
public void setConnectLostListener(onConnectLostListener connectLostListener) { this.connectLostListener = connectLostListener; }
@Override public void run() {
try { while (true) { Object msg = in.readObject(); receivedListener.onReceived(msg); } } catch (IOException | ClassNotFoundException e) { connectLostListener.onConnectLost(); } }
public void sendData(TalkPkg tp) { new Thread(() -> { try { out.writeObject(tp); } catch (IOException e) { e.printStackTrace(); } }).start(); }}我们使用实际环境来测试客户端及服务端,客户端当然万年Android,服务端ubuntu+jre10server版,不过在测试之前还有最后一个小问题,就是用来进行数据传输的类必须实现Serializable接口,并定义一个serialVersionUID,如果没有定义serialVersionUID,java会自动生成一个serialVersionUID来使用,这个序列化版本ID的作用是确定类的版本一致,需要注意的是,如果不自己定义serialVersionUID而让java自动生成,那么需要保证类的结构一模一样,并且类所处的包的相对路径也一模一样,java自动生成的版本ID才能成功的进行序列化和反序列化,才能在IO流中传输。这里我们传输的类比较简单,所以不定义serialVersionUID而是让java去自动生成。以下为该类的代码:
package server.myconnect;
import java.io.Serializable;
public class TalkPkg implements Serializable { private int avatar_id; private String message;
public TalkPkg(int avatar_id, String message) { this.avatar_id = avatar_id; this.message = message; }
public int getAvtar_id() { return avatar_id; }
public String getMessage() { return message; }
}传输的数据也简单,就一个头像id和信息,下面我们在客户端中使用ConnectClient来完成数据传输。基本界面用recyclerview+adapter实现,这里不谈。下面是主要代码:
new Thread(() -> { connectClient=new ConnectClient("SERVER_ADDRESS",8000); connectClient.start(); connectClient.setReceivedListener(new ConnectClient.onReceivedListener() { @Override public void onReceived (Object msg){ TalkPkg res = (TalkPkg) msg; chat_list.add(new ChatItem(false, res.getAvtar_id(), res.getMessage())); runOnUiThread(new Runnable() { @Override public void run() { chatAdapter.notifyItemInserted(chat_list.size()); } }); } });}).start();
send.setOnClickListener(new View.OnClickListener() { @Override public void onClick (View v){ connectClient.sendData(new TalkPkg(1, input.getText().toString())); chat_list.add(new ChatItem(true, 1, input.getText().toString())); chatAdapter.notifyItemInserted(chat_list.size()); Toast.makeText(MainActivity.this, "sended", Toast.LENGTH_SHORT).show(); }});ConnectClient的构造方法中有阻塞方法,所以初始化过程需要放在线程中执行,而后为此设置一个监听事件,当接收到信息时,将信息提取并且显示在列表中。下面的点击事件也比较简单,为发送按钮设置监听事件,发送信息、添加进列表等,下面来测试实际效果,用一台ubuntu服务器来部署服务端,用刚才说到的Android8.0作为客户端,用Intellij的控制台充当另一个客户端。服务端main方法中的代码:
ConnectServer cs=new ConnectServer(8000);cs.start();另一个客户端的代码也是几句(这里略掉了服务器IP):
ConnectClient cc=new ConnectClient("SERVER_ADDRESS",8000);Scanner in=new Scanner(System.in);String t;while (true){ System.out.println("pls input:"); t=in.next(); cc.sendData(new TalkPkg(1, t));}