转载请注明出处:https://blog.csdn.net/qq_33427047/article/details/80454815
在上篇中已经实现了Java客户端远程调用Linux服务器深度模型并与之交互,本篇将对Java线程进行改进。
整个项目功能是:用户提问一个问题,client发送至server,返回answer。因此设计思路是:生成3个并发线程ABC:A用来启动模型;B用来和模型socket交互。与此同时,BC两个线程进行同步,依靠判断是否输入question来阻塞和唤醒。
已修改的代码如下:
/**
* 这个是Java client和python server通信的实践,server运行的是Parlai问答模型
* 这一版本是在服务器开启程序的情况下直接进行socket通信
*/
class Example {
private String return_answer, answer = ""; //用来返回模型的答案
private String guide_info = ""; //用来保存提示信息
private String question = ""; //用户提出的问题
private Socket socket; //client和服务器进行socket通信
private InputStream is;
private BufferedReader in;
private OutputStream os;
private PrintWriter pw;
private int judge_answer = 0; //判断是否有答案,没有就继续等待
public String getAnswer() {
//线程启动后,一直监测有没有答案生成。有答案生成,标志judge_answer会被置为1
while (true) {
if(judge_answer != 0) {
break;
}
try {
Thread.sleep(500);
} catch (Exception e) {}
}
return_answer = this.answer;
this.answer = ""; //重置
judge_answer = 0;
return return_answer; //返回某问题对应的答案
}
//当有新问题时,赋值给this.question
public void setQuestion(String question) {
this.question = question;
}
@SuppressWarnings("finally")
//与服务器模型进行socket连接
public String conn2server(String ip, int port) {
this.guide_info = "";
System.out.println("Socket connecting...");
try {
this.socket = new Socket(ip, port);
this.is = this.socket.getInputStream();
this.in = new BufferedReader(new InputStreamReader(this.is));
this.os = this.socket.getOutputStream();//字节输出流
this.pw=new PrintWriter(this.os);//将输出流包装为打印流
//连接模型如果有输出,会保存到answer中(表示模型的提示语)
String info;
int index = 0;
//设置timeout,超时会抛出异常
socket.setSoTimeout(2000);
//如果模型没有输出,socket会一直陷入 in.read 阻塞。所以设置timeout
while((info = in.readLine())!=null){
if((index = info.indexOf("eof")) != -1) {
this.guide_info += info.substring(0, index);
break;
}
this.guide_info += info;
}
System.out.println("收到的Parlai: " + this.guide_info);
} catch (Exception e) {
//timeout
System.out.println("好像没有引导语 ");
return "";
}
finally {
System.out.println("Socket conncted.\n");
return this.guide_info;
}
}
/**
* 方法说明:这个方法和socket2py()方法进行线程协作 (要注意应在同一个对象当中使用方法)
* 具体过程:该方法一直循环,直到用户提出一个问题,即 this.question != "" 时,
* 将阻塞的socket2py方法唤醒,然后令自身阻塞,等待socket2py方法执行完毕
*/
public synchronized void waitQuestion() {
while (true) {
try {
Thread.sleep(3000);
if(this.question != "") {
notifyAll();
this.wait();
}
} catch (Exception e) {}
}
}
/**
* 方法说明:这个方法和waitQuestion()方法进行线程协作 (要注意应在同一个对象当中使用方法)
* 具体过程:该方法会一直与模型保持socket通信
* 1、首先模型启动后,this.question == "",该线程会阻塞
* 2、由于waitQuestion方法会一直监测question的输入,当输入后该线程会被唤醒
* 3、唤醒后执行一次socket 发送、接收操作
* 4、操作结束,得到模型的回答结果并保存,将question置空
* 5、唤醒waitQuestion()线程,执行下一次while循环时阻塞自身,等待下一个问题的输入
*/
public synchronized void socket2py() {
// 返回的结果是字符串类型,强制转换res为String类型
//client.execute("run_parlai", params);
int index;
this.answer = "";
String info = "";
// wait
while (true) {
//同步
while(this.question == "") {
try {
this.wait();
} catch (Exception e) {}
}
this.pw.write(this.question);
this.pw.flush();
try {
while((info=in.readLine())!=null){
if((index = info.indexOf("eof")) != -1) {
this.answer += info.substring(0, index);
break;
}
this.answer += info;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.answer);
this.question = "";
judge_answer = 1;
notify();
}
}
public void closeSocket() {
try {
this.socket.shutdownOutput();
this.socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//关闭输出流
}
/**
* 这个是Java client和python server通信的实践,server运行的是Parlai问答模型
* 这一版本是从Java client远程开启server的python深度学习模型的程序,然后进行socket通信
*/
public void run_py_socket() throws MalformedURLException, InterruptedException {
// TODO Auto-generated method stub
XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
//config.setServerURL(new URL("http://192.168.77.89:8888/RPC2"));
//URL是python服务端的SimpleXMLRPCServer(("192.168.77.89", 8888)),注意http和/RPC2
config.setServerURL(new URL("http://server ip:port/RPC2"));
XmlRpcClient client = new XmlRpcClient();
client.setConfig(config);
Object[] params = null;
try {
// 返回的结果是字符串类型,强制转换res为String类型
System.out.println("Starting program...");
client.execute("run_parlai", params);
System.out.println("Program completed.");
} catch (XmlRpcException e) {
System.out.println("Connection Error.");
// e.printStackTrace();
}
}
}
/**
* 这个是线程类,用于实现RPC、socket、线程协作的3个线程
*/
class RunnableSocket extends Thread {
private int lable; // 用于判断3个线程中,每个线程分别执行什么方法
private String question = ""; // 用户提出的问题
private String guide_answer; // 模型引导语
private Example ex;
public String getGuide_answer() {
return guide_answer;
}
public Example getEx() {
return ex;
}
public void setEx(Example ex) {
this.ex = ex;
}
public String getQuestion() {
return question;
}
public void setQuestion(String question) {
this.question = question;
ex.setQuestion(this.question);
}
public String getAnswer() {
return ex.getAnswer();
}
// 类的构造方法,传入new_ex是因为3个线程应该执行同一个对象的不同方法
public RunnableSocket(int l, Example new_ex) {
this.lable = l;
this.question = "";
this.guide_answer = "";
this.ex = new_ex;
}
public void run() {
if(lable == 0) {
try {
// RPC 启动模型
ex.run_py_socket();
} catch (Exception e) {
e.printStackTrace();
}
}
else if (lable == 1){
ex.setQuestion(this.question);
// 建立socket连接,得到引导语
this.guide_answer = ex.conn2server("server ip", port);
// socket通信
ex.socket2py();
this.question = "";
}
else {
ex.setQuestion(this.question);
// 根据question,和socket2py进行线程协作
ex.waitQuestion();
this.question = "";
}
}
}
/**
* 运行线程类
*/
class RunModel {
private RunnableSocket r0; //用来启动模型
private RunnableSocket r1; //用来socket通信
private RunnableSocket r2; //用来和r1线程进行临界资源同步
private Example ex; //3个线程调用的应该是同一个对象的方法
public RunModel() {
this.ex = new Example();
}
public void setQuestion(String question) {
// 因为3个线程是调用Example类的同一个对象,所以执行一次即可
this.r0.setQuestion(question);
// this.r1.setQuestion(question);
// this.r2.setQuestion(question);
}
public String getAnswer() {
return this.r1.getEx().getAnswer();
}
public String getGuideAnswer() {
//得到引导语,如果立即执行则很可能在引导语生成之前(socket建立连接)就返回""。所以需要等待片刻
try {Thread.sleep(1000);} catch (Exception e) {}
return this.r1.getGuide_answer();
}
public void run_model() {
try {
this.r0 = new RunnableSocket(0, ex);
this.r0.start();
// 启动模型后,睡眠一段时间。否则可能会r1线程先执行,socket连接失败
Thread.sleep(1000);
this.r1 = new RunnableSocket(1, ex);
this.r1.start();
this.r2 = new RunnableSocket(2, ex);
this.r2.start();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
调用run_model() 方法启动3个线程;
setQuestion(***) 方法来 set问题;
getAnswer() 方法获得模型回答。