Flex通讯-与Java实现Socket通信实例

Flex通信-与Java实现Socket通信实例
编写Socket Server代码的步骤通常是:
①创建ServerSocket,定义服务端口号
②使用ServerSocket.accept()监听socket请求,如果有请求会创建一个Socket对象
③通过socket.getInputStream()获取客户端的请求数据
④通过socket.getOutputStream()向客户端返回数据
⑤通过socket.close()结束本次会话
按照上面的步骤,如果有多个客户端向服务器发送请求的话,服务器只会处理第一个请求,其它请求会排队等待,只有第一个请求执行socket.close的时候下一个客户端请求才会运行。为了实现多客户端并发请求,在第②步后面需要建立多线程。

废话不多说,直接代码说明。

首先创建一个SocketUtil类,用于创建ServerSocket和获取Socket
public class SocketUtil {    
        
       
    public static ServerSocket getServerSocket(int port){    
        ServerSocket server = null;    
        try {    
            server = new ServerSocket(port);    
            System.out.println("------ServerSocket创建成功,Port:"+port);    
            return server;    
        } catch (IOException e) {    
            if(server!=null && !server.isClosed()){    
                try {    
                    server.close();    
                } catch (IOException e1) {    
                    e1.printStackTrace();    
                }    
            }    
            throw new RuntimeException("创建ServerSocket时发生异常,Port:"+port,e);    
        }    
    }    
        
       
    public static Socket getSocket(ServerSocket server){    
        Socket socket = null;    
        try {    
            socket = server.accept();    
            System.out.println("------Socket连接成功,IP:"+socket.getInetAddress());    
            return socket;    
        } catch (IOException e) {    
            if(socket!=null && !socket.isClosed()){    
                try {    
                    socket.close();    
                } catch (IOException e1) {    
                    e1.printStackTrace();    
                }    
            }    
            throw new RuntimeException("创建Socket时发送异常",e);    
        }    
    }    
        
}   
 
然后创建一个带多线程的类,用于服务器与客户端的IO通信
public class SocketThread implements Runnable {    
    private Socket socket;    
    private String encoding;    
        
    public SocketThread(Socket socket,String encoding) {    
        this.socket = socket;    
        this.encoding = encoding;    
    }    
        
       
    @Override   
    public void run() {    
        try {    
            BufferedReader br = new BufferedReader(new InputStreamReader(socket    
                    .getInputStream(), encoding));    
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(    
                    socket.getOutputStream(), encoding));    
                
            String getMsg;    
            while ((getMsg = br.readLine()) != null && !"exit".equalsIgnoreCase(getMsg)) {    
                // 客户端未提出"exit"命令,则循环交流    
                System.out.println("From client message:" + getMsg);    
                bw.append("你好[" + socket.getInetAddress() + "],服务器收到你的信息:"   
                        + getMsg + "\r\n");    
                bw.flush();    
            }    
                
            //客户端提出"exit"请求,关闭当前socket...    
            br.close();    
            bw.close();    
            socket.close();    
            System.out.println("当前Socket连接结束......");    
        } catch (Exception e) {    
            if(!socket.isClosed()){    
                try {    
                    socket.close();    
                } catch (IOException e1) {    
                    e1.printStackTrace();    
                }    
            }    
            throw new RuntimeException("Socket线程类发送异常...",e);    
        }    
    }    
}   
 
SocketServer类代码
 
public class SocketServer implements Runnable {    
    private int port = 10086;    
    private boolean status = true;    
    private ServerSocket server = null;    
        
    @Override   
    public void run() {    
        //创建Socket服务器    
        server = SocketUtil.getServerSocket(port);    
        while(status){    
            Socket socket = SocketUtil.getSocket(server);    
            new Thread(new SocketThread(socket,"UTF-8")).start();    
        }    
    }    
        
       
    public void startSocket(){    
        new Thread(this).start();    
    }    
        
       
    public void stopSocket(){    
        status = false;    
        if(server!=null && !server.isClosed()){    
            try {    
                server.close();    
            } catch (IOException e) {    
                e.printStackTrace();    
            }    
        }    
    }    
}   
 
最后在Main函数中启动Socket服务即可:
public static void main(String[] args) {    
        new SocketServer().startSocket();    
    }  
 
  • Flex客户端代码
Flex端创建Socket有两种方式:
第一种通过connect方法连接Socket服务器:
var socket:Socket = new Socket();    
socket.connect("localhost",10086);  
 第二种通过创建Socket实例时在构造函数中传入服务器ip和端口号连接:
socket = new Socket("localhost",10086);
 Flex通过ByteArray传送IO数据,这里有一点稍微注意一下在写入的内容后面会加"\r\n"回车换行符,因为java端是通过BufferedReader.readLine的形式获取一行数据,如果不换行服务器端IO就一直处于阻塞状态:
//ByteArray存放数据    
var message:ByteArray = new ByteArray();    
//使用UTF形式防止中文乱码    
message.writeUTFBytes(txt_socket.text+"\r\n");    
//数据写入缓冲区    
socket.writeBytes(message);   
//ByteArray存放数据 var message:ByteArray = new ByteArray(); //使用UTF形式防止中文乱码 message.writeUTFBytes(txt_socket.text+"\r\n"); //数据写入缓冲区 socket.writeBytes(message);
 Flex有多种事件用于监听Socket的状态:
Event.CONNECT:Socket与服务器成功连接时触发的事件    
Event.CLOSE:Socket与服务器断开连接时触发的事件    
IOErrorEvent.IO_ERROR:Socket通信时发生IO错误时触发的事件    
ProgressEvent.SOCKET_DATA:服务器返回数据时触发的事件   
 
新建一个Flex普通项目,入口文件定义为index.mxml,在mxml文件中新建一个textinput文本框用于获取用户输入的内容,button按钮用户发送内容到java socket服务器,一个label用户显示向前socket状态,另一个label用于显示从服务器返回的信息。 index.mxml代码如下:
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"     
               xmlns:s="library://ns.adobe.com/flex/spark"     
               xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">   
   
    <fx:Script>   
        <![CDATA[   
               
               
            private var socket:Socket = null;   
            protected function button1_clickHandler(event:MouseEvent):void   
            {   
                if(socket==null || !socket.connected){   
                    //连接服务器(ip,port)   
                    socket = new Socket("localhost",10086);   
                       
                    //成功连接状态事件   
                    socket.addEventListener(Event.CONNECT,function connFun(e:Event):void{   
                        l_status.text = "Connect to server success...";   
                    });   
                    //连接中断事件   
                    socket.addEventListener(Event.CLOSE,function closeFun(e:Event):void{   
                        l_status.text = "Connect to server closed...";   
                    });   
                    //连接异常事件   
                    socket.addEventListener(IOErrorEvent.IO_ERROR,function closeFun(e:IOErrorEvent):void{   
                        l_status.text = "Connect exception ..."+e.toString();   
                    });   
                    //服务器信息事件   
                    socket.addEventListener(ProgressEvent.SOCKET_DATA,function dataFun(e:ProgressEvent):void{   
                        var getMsg:ByteArray = new ByteArray;   
                        socket.readBytes(getMsg);   
                        l_result.text = getMsg.toString();   
                    });   
                }   
                   
                //ByteArray存放数据   
                var message:ByteArray = new ByteArray();   
                //使用UTF形式防止中文乱码   
                message.writeUTFBytes(txt_socket.text+"\r\n");   
                //数据写入缓冲区   
                socket.writeBytes(message);   
                //将缓冲区数据发送出去   
                socket.flush();   
                //清空文本框内容   
                txt_socket.text = "";   
            }   
        ]]>   
    </fx:Script>   
   
    <fx:Declarations>   
        <!-- 将非可视元素(例如服务、值对象)放在此处 -->   
    </fx:Declarations>   
    <s:Button x="156" y="56" label="按钮" click="button1_clickHandler(event)"/>   
    <s:TextInput x="20" y="56" id="txt_socket"/>   
    <s:Label x="20" y="104" id="l_status"/>   
    <s:Label x="234" y="65" id="l_result"/>   
</s:Application>   
 
代码编写完成后运行index.mxml文件,最后执行效果就如前面【实例效果】所示。
  • 安全沙箱
下面这段是从网上抄的:
----------------------------------------
在 Adobe Flash Player 升级到 9.0.124 后,由于安全策略更改,原来 Socket 或 XmlSocket 的应用里的 http 方式加载安全策略的手段不能继续使用。更改如下:
1, 首先检测目标服务器的 843 端口是否提供安全策略
 

2, 如果 1 没有检测到策略,则检测 actionscript 是否使用了 Security.loadPolicyFile(xmlsocket://)手段提供安全策略,如果还没检测到,则使用第 3 步检测
3, 检测目标服务器目标端口是否提供安全策略。

在说具体处理方式前,我先描述一下 Flash Player 的验证过程。在 Flex 程序发出 Socket 或 XmlSocket( 以下统称为 Socket) 请求前, FlashPlayer 会先判断是否为本地调用,如果不是。即用一个 Socket 去链接到你的服务端,三次握手成功后一方面发出字符串“ <policy-file-request/>\0 “另一方面监听返回的安全策略。安全策略接收成功后, FlashPlayer 就断开验证的 Socket ,然后再运行程序本身的 Socket 。在整个 SWF 运行期间,无论你请求多少次,只要域相同, FlashPlayer 就只验证一次。这里有两个重点:
 
第一个是验证的 Socket 和程序的 Socket 是两个 Socket 。所以你在本地测试时,服务端监听到 N 个 Socket 请求,但布置到服务端后,服务端会监听到 N+1 个请求。
第二是验证的 Socket 发送“ <policy-file-request/>\0 “请求和接收你的策略文件是没有先后关系的,所以你没必要接收完“ <policy-file-request/>\0 “后才发策略文件。我的做法是只要监听到请求,就把策略字符串发过去。
-----------------------------------------------

那么简单的说,如果Flex项目依赖其它语言的服务器的话(比如依赖J2EE服务器),在flex的socket客户端向JavaSocket服务器发送请求之前,Flex会优先发送一个安全验证消息,如果java服务器不返回验证消息则当前socket通信失败。
解决办法有很多种,我在网上也看了很多,但是很多写得有问题。
根据我多方调查,个人觉得这种方案比较靠谱:
在Java服务器端创建一个端口号为843的ServerSocket监听Flex安全沙箱验证消息,如果接收到<policy-file-request/>文件信息,则向客户端返回XMl验证内容:“<?xml version=\"1.0\"?><cross-domain-policy><site-control permitted-cross-domain-policies=\"all\"/><allow-access-from domain=\"*\" to-ports=\"*\"/></cross-domain-policy>\0”
public class PolicyThread implements Runnable {    
    private final String policy_xml = "<policy-file-request/>";    
    private final String cross_xml = "<?xml version=\"1.0\"?><cross-domain-policy><site-control permitted-cross-domain-policies=\"all\"/><allow-access-from domain=\"*\" to-ports=\"*\"/></cross-domain-policy>\0";    
    private Socket socket;    
        
    public PolicyThread(Socket socket){    
        this.socket = socket;    
    }    
        
    @Override   
    public void run() {    
        try {    
            //接收并发送Flex安全验证请求    
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));    
            PrintWriter pw = new PrintWriter(socket.getOutputStream());    
            char[] by = new char[22];    
            br.read(by, 0, 22);    
            String s = new String(by);    
            if(s.equals(policy_xml)){    
                System.out.println("接收policy-file-request认证");    
                pw.print(cross_xml);    
                pw.flush();    
                br.close();    
                pw.close();    
                socket.close();    
                System.out.println("完成policy-file-request认证");    
            }    
        } catch (IOException e) {    
            if(!socket.isClosed()){    
                try {    
                    socket.close();    
                } catch (IOException e1) {    
                    e1.printStackTrace();    
                }    
            }    
            throw new RuntimeException("执行policy认证时发生异常",e);    
        }    
    }    
   
} 
 
public class PolicyServer implements Runnable{    
        
    private final int policy_port = 843;    
    private boolean status = true;    
        
    private ServerSocket server = null;    
    @Override   
    public void run() {    
        //创建安全验证服务器    
        server = SocketUtil.getServerSocket(policy_port);    
            
        while(status){    
            Socket socket = SocketUtil.getSocket(server);    
            new Thread(new PolicyThread(socket)).start();    
        }    
    }    
        
        
       
    public void startPolicy(){    
        new Thread(this).start();    
    }    
        
       
    public void stopPolicy(){    
        status = false;    
        if(server!=null && !server.isClosed()){    
            try {    
                server.close();    
            } catch (IOException e) {    
                e.printStackTrace();    
            }    
        }    
    }    
}   
 
Flex客户端向Java发送第一次Socket请求(例子里的端口号是10086)时,ServerSocket843端口会收到安全沙箱验证,随后server将正确的验证消息返回给Flex客户端,Flex认证成功后真正的10086端口Socket连结就已经搭建了,随后双方就可以畅通无阻通信了(一次会话只进行一次沙箱验证)。
  • Servlet启动ServerSocket
我通常比较喜欢创建一个servlet,在web.xml中配置容器启动时运行servlet的init方法,这样端口号为10086和843的serverSocket就会启动:
public class InitServers extends HttpServlet {    
    private static final long serialVersionUID = 1L;    
           
       
    public InitServers() {    
        super();    
    }    
   
       
    public void init(ServletConfig config) throws ServletException {    
        new PolicyServer().startPolicy();    
        new SocketServer().startSocket();    
    }    
   
       
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {    
        // TODO Auto-generated method stub    
    }    
   
       
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {    
        // TODO Auto-generated method stub    
    }    
   
}   
 
 web.xml
<servlet>   
        <display-name>InitServers</display-name>   
        <servlet-name>InitServers</servlet-name>   
        <servlet-class>socket.InitServers</servlet-class>   
        <load-on-startup>1</load-on-startup>   
    </servlet>