我刚接触网络I/O编程,遇到了一个问题--基本上我想做的是让一个桌面应用程序与google maps javascript API对话。为了促进这一点,我构建了一个java applet,它将作为桌面应用程序和浏览器javascript应用程序之间的桥梁。当我在Eclipse中一起运行桌面应用程序和applet时,它们可以完美地通信,并且我能够通过将字符串写入绑定到applet建立ServerSocket连接的同一端口的套接字来调用applet函数。为了在Eclipse中进行测试,我将字符串"sendJSAlertTest“发送到套接字的输出流,然后使用java.lang.reflect API从ServerSocket输入流派生一个方法实例,最后在applet中调用生成的方法。当小程序在浏览器中运行时,我将"sendJSAlert“写到套接字中,因为它会导致实际的javascript调用。在使用小程序查看器的Eclipse中,结果是桌面应用程序上下文打印输出"awesome“,小程序上下文打印sendJSAlertTest()方法的输出"Hello Client,I'm a Server!”。将"sendJSAlert“传递给在浏览器中运行的小程序的结果是,桌面应用程序打印null,这表明由于某种原因,ServerSocket的输入流是空的,而浏览器本身在应该生成一个带有文本"Hello Client,I'm a Server!”的javascript警告框时什么也不做。我使用的浏览器是Google Chrome,目前我只是在本地机器上运行所有东西(例如,还没有远程服务器参与)
下面是相关的Java代码和HTML:
SocketClient.java
import java.awt.event.*;
import java.io.*;
import java.net.*;
public class SocketClient {
Socket socket = null;
PrintWriter out = null;
BufferedReader in = null;
private InetAddress myAddress;
private String remoteFunction;
public SocketClient(){
}
public void listenSocket(int portNum){
//Create socket connection
try{
System.out.println("@Client Trying to create socket bound to port " + portNum);
socket = new Socket(<my hostname here as a string>, portNum);
System.out.println("the attached socket port is " + socket.getLocalPort());
System.out.flush();
out = new PrintWriter(socket.getOutputStream(), true);
out.println("sendJSAlertTest");
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = in.readLine();
System.out.println("@CLient side Text received from server: " + line);
} catch (UnknownHostException e) {
System.out.println("Unknown host: <my hostname here as a string>.eng");
System.exit(1);
} catch (IOException e) {
System.out.println("No I/O");
e.printStackTrace();
System.exit(1);
}
}
public void setRemoteFunction(String funcName){
remoteFunction = funcName;
}
public String getRemoteFunction(){
return remoteFunction;
}
}
SocketServer.java
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.*;
class SocketServer {
ServerSocket server = null;
Socket client = null;
BufferedReader in = null;
PrintWriter out = null;
String line;
private NetComm hNet;
private Method serverMethod;
SocketServer(NetComm netmain){
hNet = netmain;
}
public void listenSocket(int portNum){
try{
System.out.println("@server Trying to create socket bound to port " + portNum);
server = new ServerSocket(portNum);
System.out.println("the attached socket port is " + server.getLocalPort());
} catch (IOException e) {
System.out.println("Could not listen on port " + portNum);
System.exit(-1);
}
try{
client = server.accept();
System.out.println("Connection accepted!");
} catch (IOException e) {
System.out.println("Accept failed: " + portNum);
System.exit(-1);
}
try{
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
out = new PrintWriter(client.getOutputStream(), true);
} catch (IOException e) {
System.out.println("Accept failed: " + portNum);
System.exit(-1);
}
while(true){
try{
System.out.println("trying to read from inputstream...");
line = in.readLine();
System.out.println(line);
//Now that we have a method name, invoke it
try {
serverMethod = hNet.getClass().getDeclaredMethod(line,
String.class);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
serverMethod.invoke(hNet, "Hello Client, I'm a Server!");
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Send data back to client
out.println("awesome " + line);
} catch (IOException e) {
System.out.println("Read failed");
System.out.flush();
System.exit(-1);
}
}
}
protected void finalize(){
//Clean up
try{
in.close();
out.close();
server.close();
} catch (IOException e) {
System.out.println("Could not close.");
System.exit(-1);
}
}
public int getBoundLocalPort(){
return server.getLocalPort();
}
}
NetComm.java
import cresco.ai.att.ccm.core.CCMMain;
import cresco.ai.att.ccm.gui.DataPanel;
import java.io.*;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import javax.servlet.*;
import javax.servlet.http.*;
import java.applet.*;
public class NetComm extends JApplet{//HttpServlet{
private CCMMain hMain;
private DataPanel dpLocal;
private SocketServer sockserver;
private Method serverMethod;
String testStr;
Integer testInt; /*integer */
Character testChar; /*character*/
//Testing this...
ServerSocket server = null;
Socket client = null;
BufferedReader in = null;
PrintWriter out = null;
String line;
@Override
public void init(){
sockserver = new SocketServer(this);
//For offline debug (should be disabled in a release to the webapp):
//initSocketServer is commented out in the release version and
//invoked in the Eclipse testbed version. In the webapp,
//initSocketServer is invoked from javascript (see below js sockPuppet())
//////initSocketServer(0);
String msg = "Hello from Java (using javascript alert)";
try {
getAppletContext().showDocument(new URL("javascript:doAlert(\"" +
msg +"\")"));
}
catch (MalformedURLException me) { }
}
public void sendJSAlertTest(String message){
System.out.println("sendJSAlert remotely invoked, with message: " +
message);
}
public void sendJSAlert(String message){
try {
getAppletContext().showDocument(new URL("javascript:doAlert(\"" +
message +"\")"));
}
catch (MalformedURLException me) { }
}
public void initSocketServer(int portNum){
sockserver.listenSocket(portNum);
}
public void finalizeSocketServer(){
sockserver.finalize();
}
public int socket2Me(int portNum){
try {
socks.add(new ServerSocket(portNum));
return 0; //socket opened successfully
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return -1; //socket failed to open
}
}
public int getSocketServerPort(){
return sockserver.getBoundLocalPort();
}
public void showRectTest(){
try {
getAppletContext().showDocument(new
URL("javascript:overlayRect()"));
}
catch (MalformedURLException me) { }
}
public void setGUI(DataPanel d){
dpLocal = d;
}
}
MapViz.html
<html>
<head>
<title>Welcome to Geographic Midpoint Map Vizualization!</title>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta charset="UTF-8">
<link href="https://google-developers.appspot.com/maps/documentation/javascript
/examples/default.css"
rel="stylesheet" type="text/css">
...google maps stuff omitted...
<script type="text/javascript">
<script type="text/javascript">
function overlayRect(){
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.fillStyle="#FF0000";
ctx.fillRect(0,0,150,75);
}
function doAlert(s){
alert(s);
}
function testJava(){
document.ccmApplet.showRectTest();
}
function sockPuppet(){
var i = parseInt(document.getElementById("args").value,10);
alert("parsing the input args... got " + i);
if(i == NaN || i == null){
i = 0;
}
alert("passed NaN OR null block, i is " + i);
//i = 6672; //because $%*& you, that's why!
document.ccmApplet.initSocketServer(i);
//document.ccmApplet.listenSocket(i);
alert("inittializing socket server...");
//queryPort();
alert("querying port...");
document.ccmApplet.finalizeSocketServer();
//document.ccmApplet.finalize();
alert("finalizing socket server...");
}
function queryPort(){
var d = document.getElementById("debug");
var s1 = "Last port opened was: ";
//var s2 = document.ccmApplet.getLastBoundPort();
var s2 = document.ccmApplet.getSocketServerPort();
var sFinal = s1.concat(s2);
d.value = sFinal;
}
</script>
</head>
<body>
<applet width="500" height="50" name="ccmApplet" archive="CCM.jar"
code="cresco.ai.att.ccm.io.NetComm" MAYSCRIPT></applet>
<p></p>
<canvas id="myCanvas" width="200" height="100"></canvas>
<div id="map_canvas"></div>
<input id="args" type="textentry" value="" />
<button height="50" width="50" onClick="sockPuppet()">Test Socket
Creation</button>
<input id="debug" type="debugthingy" value="debug area..." />
<button height="50" width="50" onClick="testJava()">Test Java Callback</button>
</body>
</html>
在webapp中,我用本地计算机上的有效端口号填充了args输入,然后按下Test Socket Connection按钮,这将调用sockPuppet() javascript。这将创建一个ServerSocket并将其绑定到指定的端口,然后我通过SocketClient.listenSocket将我的桌面客户端应用程序单独连接到该端口。在桌面应用程序上下文中,Eclipse的结果是"awesome“,而在appletviewer上下文中,结果是输出"sendJSAlert remotely,with message: Hello Client,I'm a Server!”。调用sendJSAlert()的webapp应用程序应该对同一消息调用javascript alert函数,并创建一个弹出框,其中包含消息"sendJSAlert remotely,with message : Hello Client,I'm a Server!“但是在浏览器( Chrome java或javascript调试控制台)中什么也没有发生,桌面应用程序的输出是空的,而不是像预期的那样“棒极了的sendJSAlert”
那么问题是:可能是什么原因导致了不同的结果?我知道浏览器的安全沙箱可能是一个问题,但我已经包含了一个权限文件,它应该允许在任何本地主机端口上通过套接字进行通信:
grant {
permission java.net.SocketPermission
"localhost:1024-",
"accept, connect, listen, resolve";
};
当然有可能我没有正确地应用权限(我使用的是sun policytool gui);在applet代码中到底需要做什么(如果有的话)才能应用这些权限?安全问题会不会导致我看到的响应不足?我预计Chrome的java调试控制台会报告一个异常,但没有任何异常...
任何帮助都将不胜感激,谢谢!
-CCJ
更新:好的,一些新的信息:我在打开了javascript控制台的情况下,在Chrome中再次运行了这个小程序(我可以发誓我以前尝试过没有效果,但显然没有),并收到了以下控制台输出--
"Uncaught Error: java.security.AccessControlException: access denied
("java.net.SocketPermission" "<myipaddress>:4218" "accept,resolve") MapVizApp.html:154
sockPuppet MapVizApp.html:154 onclick MapVizApp.html:179 Uncaught Error: Error
calling method on NPObject. sockPuppet onclick "
所以现在的问题是,为什么我要触发这个安全异常?具有上述权限的策略文件与html页面和包含applet的jar文件位于相同的工作目录中,我将以下内容添加到系统的JRE安全策略文件中
//Grants my NetComm applet the ability to accept, connect, and listen on unpriv. ports
grant codeBase "file:${user.home}\Desktop\dev\invention\ATT\MapViz\CCM.jar" {
permission java.net.SocketPermission
"localhost:1024-",
"accept, connect, listen, resolve";
};
我还没有给applet签名,但我的理解是,如果策略文件是有序的,applet就不需要签名……如果我说错了,请告诉我。无论如何,有没有人有任何建议,为什么在策略文件具有上述权限的情况下仍会抛出此安全异常?JRE查找的工作目录中的策略文件是否有命名约定?我的工作目录策略文件现在只被命名为ccmPolFile,但是我不清楚JRE应该如何定位它;我是否需要在applet代码中添加一些东西,以便将JRE指向预期的工作目录策略文件?此外,我添加的系统策略文件授权本身不应该足以满足CCM.jar中我的小程序的套接字权限吗?
更新2:我对小程序进行了签名,并在我的java.security文件中的${java.home}/lib/security中添加了policy.url.3=file:${user.home}\Desktop\dev\invention\ATT\MapViz\ccmPolFile.policy行(通过http://docs.oracle.com/javase/tutorial/security/tour2/step4.html#Approach2,这显然是JRE定位要加载的策略文件的方式)……可悲的是,结果是完全相同的安全异常。我所知道的唯一一件事是
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
// perform the security-sensitive operation here
return null;
}
});
这应该可以让我做几乎任何事情,因为applet现在已经签名了。我想继续退出等式,但由于某些原因,策略文件不起作用。我很快就会回来告诉你它是如何工作的
发布于 2012-07-20 05:08:07
好的,所以在上面的更新2之后,我将SocketServer.java代码中的listenSocket()方法改为
public void listenSocket(int portNum){
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
int portNum = 4444;
try{
System.out.println("@server Trying to create socket bound to port " + portNum);
server = new ServerSocket(portNum);
System.out.println("the attached socket port is " + server.getLocalPort());
} catch (IOException e) {
System.out.println("Could not listen on port " + portNum);
System.exit(-1);
}
try{
client = server.accept();
System.out.println("Connection accepted!");
} catch (IOException e) {
System.out.println("Accept failed: " + portNum);
System.exit(-1);
}
try{
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
out = new PrintWriter(client.getOutputStream(), true);
} catch (IOException e) {
System.out.println("Accept failed: " + portNum);
System.exit(-1);
}
while(portNum==4444){
try{
System.out.println("trying to read from inputstream...");
line = in.readLine();
System.out.println(line);
//Now that we have a method name, invoke it
try {
serverMethod = hNet.getClass().getDeclaredMethod(line,
String.class);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
serverMethod.invoke(hNet, "Hello from Javascript invoked by the
desktop app!");
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Send data back to client
out.println("awesome " + line);
//System.out.println(line);
//System.out.flush();
} catch (IOException e) {
System.out.println("Read failed");
System.out.flush();
System.exit(-1);
}
}
return null;
}
});//end doPrivileged
}
显然,这是一个不安全的东西,但它确实起到了作用--我没有收到任何安全异常,而且桌面应用程序打印出“棒极了的sendJSAlert”,所以我知道IO是通过套接字在客户机和服务器上下文之间工作的。实际的js alert函数没有触发,但我认为这与上面listenSocket()中可怕的无限while循环有关……
带回家的消息:出于某些原因,为了从google chrome中的小程序建立套接字连接,我需要对小程序进行签名,并使用AccessController.doPrivileged()来调用我的安全敏感代码,尽管我已经设置了本地策略和安全文件来授予我的小程序这些权限。
googlers用户可查看参考文献:
http://www.coderanch.com/how-to/java/HowCanAnAppletReadFilesOnTheLocalFileSystem
http://docs.oracle.com/javase/7/docs/api/java/security/AccessController.html
http://www-personal.umich.edu/~lsiden/tutorials/signed-applet/signed-applet.html
http://docs.oracle.com/javase/tutorial/security/tour2/step4.html
更新:终于100%工作了:D我把上面SocketServer.java中的listenSocket()方法改成了这样:
public void listenSocket(int portNum){
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
int portNum = 4444;
try{
System.out.println("@server Trying to create socket bound to port " + portNum);
server = new ServerSocket(portNum);
System.out.println("the attached socket port is " + server.getLocalPort());
} catch (IOException e) {
System.out.println("Could not listen on port " + portNum);
System.exit(-1);
}
try{
client = server.accept();
System.out.println("Connection accepted!");
} catch (IOException e) {
System.out.println("Accept failed: " + portNum);
System.exit(-1);
}
try{
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
out = new PrintWriter(client.getOutputStream(), true);
} catch (IOException e) {
System.out.println("Accept failed: " + portNum);
System.exit(-1);
}
try {
line = in.readLine();
System.out.println("line is " + line + " from the inputstream to the
serversocket");
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(line != null){
System.out.println("trying to read from non-null inputstream...");
//line = in.readLine();
System.out.println(line);
//Now that we have a method name, invoke that bitch!
try {
serverMethod = hNet.getClass().getDeclaredMethod(line, String.class);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
serverMethod.invoke(hNet, "Hello From Javascript invoked by a desktop
app!");
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Send data back to client
out.println("awesome " + line);
//System.out.println(line);
//System.out.flush();
}
return null;
}
});//end doPrivileged
}
server.accept()方法会一直阻塞,直到建立连接为止,所以对于我一次只想向serversocket输入流传递一个命令的场景来说,while循环是没有意义的。对if的更改允许程序实际上继续执行java.reflect内容,该内容调用applet中的方法,该方法直接调用javascript函数。由于端口仍然是硬编码的,并且小程序使用doPrivileged(...)这仍然不是一个很好的解决方案,但它确实满足了通过java applet桥从桌面java应用程序调用web浏览器中的javascript的用例,因此它为更健壮的实现提供了一个良好的跳板!
https://stackoverflow.com/questions/11552980
复制相似问题