- 環(huán)境準(zhǔn)備
【服務(wù)器端】
JDK1.6,“java.net”包對網(wǎng)絡(luò)編程提供了非常全面的支持,包括Socket
開發(fā)環(huán)境:Eclipse
【客戶端】
Flex4,”flash.net”包也提供了Socket的支持
開發(fā)環(huán)境:FlashBuilder4
- 實(shí)例效果
我是用Java啟動(dòng)一個(gè)ServerSocket作為服務(wù)器,F(xiàn)lex創(chuàng)建一個(gè)頁面,當(dāng)點(diǎn)擊頁面上的按鈕時(shí)向Java服務(wù)器發(fā)送消息。
Flex客戶端輸入“阿里巴巴”再點(diǎn)擊按鈕:
- 注意事項(xiàng)
Flex項(xiàng)目分為兩種:一種是普通的本地項(xiàng)目,也就是不依賴其它服務(wù)器;另一種是遠(yuǎn)程服務(wù)器項(xiàng)目,這需要依賴其它語言的服務(wù)器容器。如果是普通的本地項(xiàng)目,Socket通信是非常容易的,但是如果是遠(yuǎn)程項(xiàng)目,Socket通信需要考慮Flex安全沙箱問題,后面會(huì)詳細(xì)介紹。
- Java Socket服務(wù)器
編寫Socket Server代碼的步驟通常是:
①創(chuàng)建ServerSocket,定義服務(wù)端口號
②使用ServerSocket.accept()監(jiān)聽socket請求,如果有請求會(huì)創(chuàng)建一個(gè)Socket對象
③通過socket.getInputStream()獲取客戶端的請求數(shù)據(jù)
④通過socket.getOutputStream()向客戶端返回?cái)?shù)據(jù)
⑤通過socket.close()結(jié)束本次會(huì)話
按照上面的步驟,如果有多個(gè)客戶端向服務(wù)器發(fā)送請求的話,服務(wù)器只會(huì)處理第一個(gè)請求,其它請求會(huì)排隊(duì)等待,只有第一個(gè)請求執(zhí)行socket.close的時(shí)候下一個(gè)客戶端請求才會(huì)運(yùn)行。為了實(shí)現(xiàn)多客戶端并發(fā)請求,在第②步后面需要建立多線程。
廢話不多說,直接代碼說明。
首先創(chuàng)建一個(gè)
SocketUtil類,用于創(chuàng)建ServerSocket和獲取Socket
public class SocketUtil {
/**
* 創(chuàng)建ServerSocket
* @param port
* @return
*/
public static ServerSocket getServerSocket(int port){
ServerSocket server = null;
try {
server = new ServerSocket(port);
System.out.println("------ServerSocket創(chuàng)建成功,Port:"+port);
return server;
} catch (IOException e) {
if(server!=null && !server.isClosed()){
try {
server.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
throw new RuntimeException("創(chuàng)建ServerSocket時(shí)發(fā)生異常,Port:"+port,e);
}
}
/**
* 獲取Socket
* @param server
* @return
*/
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("創(chuàng)建Socket時(shí)發(fā)送異常",e);
}
}
}
?
然后創(chuàng)建一個(gè)帶多線程的類,用于服務(wù)器與客戶端的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"命令,則循環(huán)交流
System.out.println("From client message:" + getMsg);
bw.append("你好[" + socket.getInetAddress() + "],服務(wù)器收到你的信息:"
+ getMsg + "\r\n");
bw.flush();
}
//客戶端提出"exit"請求,關(guān)閉當(dāng)前socket...
br.close();
bw.close();
socket.close();
System.out.println("當(dāng)前Socket連接結(jié)束......");
} catch (Exception e) {
if(!socket.isClosed()){
try {
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
throw new RuntimeException("Socket線程類發(fā)送異常...",e);
}
}
}
??
SocketServer類代碼
public class SocketServer implements Runnable {
private int port = 10086;
private boolean status = true;
private ServerSocket server = null;
@Override
public void run() {
//創(chuàng)建Socket服務(wù)器
server = SocketUtil.getServerSocket(port);
while(status){
Socket socket = SocketUtil.getSocket(server);
new Thread(new SocketThread(socket,"UTF-8")).start();
}
}
/**
* 啟動(dòng)服務(wù)器
*/
public void startSocket(){
new Thread(this).start();
}
/**
* 關(guān)閉服務(wù)器
*/
public void stopSocket(){
status = false;
if(server!=null && !server.isClosed()){
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
?
最后在Main函數(shù)中啟動(dòng)Socket服務(wù)即可:
public static void main(String[] args) {
new SocketServer().startSocket();
}
?
- Flex客戶端代碼
Flex端創(chuàng)建Socket有兩種方式:
第一種通過connect方法連接Socket服務(wù)器:
var socket:Socket = new Socket();
socket.connect("localhost",10086);
?第二種通過創(chuàng)建Socket實(shí)例時(shí)在構(gòu)造函數(shù)中傳入服務(wù)器ip和端口號連接:
socket = new Socket("localhost",10086);
?Flex通過ByteArray傳送IO數(shù)據(jù),這里有一點(diǎn)稍微注意一下在寫入的內(nèi)容后面會(huì)加"
\r\n
"回車換行符,因?yàn)閖ava端是通過BufferedReader.readLine的形式獲取一行數(shù)據(jù),如果不換行服務(wù)器端IO就一直處于阻塞狀態(tài):
//ByteArray存放數(shù)據(jù)
var message:ByteArray = new ByteArray();
//使用UTF形式防止中文亂碼
message.writeUTFBytes(txt_socket.text+"\r\n");
//數(shù)據(jù)寫入緩沖區(qū)
socket.writeBytes(message);
?Flex有多種事件用于監(jiān)聽Socket的狀態(tài):
Event.CONNECT:Socket與服務(wù)器成功連接時(shí)觸發(fā)的事件
Event.CLOSE:Socket與服務(wù)器斷開連接時(shí)觸發(fā)的事件
IOErrorEvent.IO_ERROR:Socket通信時(shí)發(fā)生IO錯(cuò)誤時(shí)觸發(fā)的事件
ProgressEvent.SOCKET_DATA:服務(wù)器返回?cái)?shù)據(jù)時(shí)觸發(fā)的事件
新建一個(gè)Flex普通項(xiàng)目,入口文件定義為index.mxml,在mxml文件中新建一個(gè)textinput文本框用于獲取用戶輸入的內(nèi)容,button按鈕用戶發(fā)送內(nèi)容到j(luò)ava socket服務(wù)器,一個(gè)label用戶顯示向前socket狀態(tài),另一個(gè)label用于顯示從服務(wù)器返回的信息。 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){
//連接服務(wù)器(ip,port)
socket = new Socket("localhost",10086);
//成功連接狀態(tài)事件
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();
});
//服務(wù)器信息事件
socket.addEventListener(ProgressEvent.SOCKET_DATA,function dataFun(e:ProgressEvent):void{
var getMsg:ByteArray = new ByteArray;
socket.readBytes(getMsg);
l_result.text = getMsg.toString();
});
}
//ByteArray存放數(shù)據(jù)
var message:ByteArray = new ByteArray();
//使用UTF形式防止中文亂碼
message.writeUTFBytes(txt_socket.text+"\r\n");
//數(shù)據(jù)寫入緩沖區(qū)
socket.writeBytes(message);
//將緩沖區(qū)數(shù)據(jù)發(fā)送出去
socket.flush();
//清空文本框內(nèi)容
txt_socket.text = "";
}
]]>
</fx:Script>
<fx:Declarations>
<!-- 將非可視元素(例如服務(wù)、值對象)放在此處 -->
</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>
?
代碼編寫完成后運(yùn)行index.mxml文件,最后執(zhí)行效果就如前面【實(shí)例效果】所示。
- 安全沙箱
下面這段是從網(wǎng)上抄的:
----------------------------------------------------------------------------
在 Adobe Flash Player 升級到 9.0.124 后,由于安全策略更改,原來 Socket 或 XmlSocket 的應(yīng)用里的 http 方式加載安全策略的手段不能繼續(xù)使用。更改如下:
1, 首先檢測目標(biāo)服務(wù)器的 843 端口是否提供安全策略 ?
2, 如果 1 沒有檢測到策略,則檢測 actionscript 是否使用了 Security.loadPolicyFile(xmlsocket://)手段提供安全策略,如果還沒檢測到,則使用第 3 步檢測
3, 檢測目標(biāo)服務(wù)器目標(biāo)端口是否提供安全策略。
1, 首先檢測目標(biāo)服務(wù)器的 843 端口是否提供安全策略 ?
2, 如果 1 沒有檢測到策略,則檢測 actionscript 是否使用了 Security.loadPolicyFile(xmlsocket://)手段提供安全策略,如果還沒檢測到,則使用第 3 步檢測
3, 檢測目標(biāo)服務(wù)器目標(biāo)端口是否提供安全策略。
在說具體處理方式前,我先描述一下 Flash Player 的驗(yàn)證過程。在 Flex 程序發(fā)出 Socket 或 XmlSocket( 以下統(tǒng)稱為 Socket) 請求前, FlashPlayer 會(huì)先判斷是否為本地調(diào)用,如果不是。即用一個(gè) Socket 去鏈接到你的服務(wù)端,三次握手成功后一方面發(fā)出字符串“ <policy-file-request/>\0 “另一方面監(jiān)聽返回的安全策略。安全策略接收成功后, FlashPlayer 就斷開驗(yàn)證的 Socket ,然后再運(yùn)行程序本身的 Socket 。在整個(gè) SWF 運(yùn)行期間,無論你請求多少次,只要域相同, FlashPlayer 就只驗(yàn)證一次。這里有兩個(gè)重點(diǎn): ?
第一個(gè)是驗(yàn)證的 Socket 和程序的 Socket 是兩個(gè) Socket 。所以你在本地測試時(shí),服務(wù)端監(jiān)聽到 N 個(gè) Socket 請求,但布置到服務(wù)端后,服務(wù)端會(huì)監(jiān)聽到 N+1 個(gè)請求。
第二是驗(yàn)證的 Socket 發(fā)送“ <policy-file-request/>\0 “請求和接收你的策略文件是沒有先后關(guān)系的,所以你沒必要接收完“ <policy-file-request/>\0 “后才發(fā)策略文件。我的做法是只要監(jiān)聽到請求,就把策略字符串發(fā)過去。
-----------------------------------------------------------------------------------
那么簡單的說,如果Flex項(xiàng)目依賴其它語言的服務(wù)器的話(比如依賴J2EE服務(wù)器),在flex的socket客戶端向JavaSocket服務(wù)器發(fā)送請求之前,F(xiàn)lex會(huì)優(yōu)先發(fā)送一個(gè)安全驗(yàn)證消息,如果java服務(wù)器不返回驗(yàn)證消息則當(dāng)前socket通信失敗。
解決辦法有很多種,我在網(wǎng)上也看了很多,但是很多寫得有問題。
根據(jù)我多方調(diào)查,個(gè)人覺得這種方案比較靠譜:
在Java服務(wù)器端創(chuàng)建一個(gè)端口號為843的ServerSocket監(jiān)聽Flex安全沙箱驗(yàn)證消息,如果接收到
<policy-file-request/>文件信息,則向客戶端返回XMl驗(yàn)證內(nèi)容:“<?xml version=\"1.0\"?><cross-domain-policy><site-control permitted-cross-domain-policies=\"all\"/><allow-access-from domain=\"*\" to-ports=\"*\"/></cross-domain-policy>\0”
具體代碼如下:
/**
* 處理與Flex認(rèn)證的線程類
* @author Administrator
*/
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 {
//接收并發(fā)送Flex安全驗(yàn)證請求
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認(rèn)證");
pw.print(cross_xml);
pw.flush();
br.close();
pw.close();
socket.close();
System.out.println("完成policy-file-request認(rèn)證");
}
} catch (IOException e) {
if(!socket.isClosed()){
try {
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
throw new RuntimeException("執(zhí)行policy認(rèn)證時(shí)發(fā)生異常",e);
}
}
}
public class PolicyServer implements Runnable{
private final int policy_port = 843;
private boolean status = true;
private ServerSocket server = null;
@Override
public void run() {
//創(chuàng)建安全驗(yàn)證服務(wù)器
server = SocketUtil.getServerSocket(policy_port);
while(status){
Socket socket = SocketUtil.getSocket(server);
new Thread(new PolicyThread(socket)).start();
}
}
/**
* 啟動(dòng)服務(wù)器
*/
public void startPolicy(){
new Thread(this).start();
}
/**
* 關(guān)閉服務(wù)器
*/
public void stopPolicy(){
status = false;
if(server!=null && !server.isClosed()){
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
?
Flex客戶端向Java發(fā)送第一次Socket請求(例子里的端口號是10086)時(shí),ServerSocket843端口會(huì)收到安全沙箱驗(yàn)證,隨后server將正確的驗(yàn)證消息返回給Flex客戶端,F(xiàn)lex認(rèn)證成功后真正的10086端口Socket連結(jié)就已經(jīng)搭建了,隨后雙方就可以暢通無阻通信了(一次會(huì)話只進(jìn)行一次沙箱驗(yàn)證)。
- Servlet啟動(dòng)ServerSocket
我通常比較喜歡創(chuàng)建一個(gè)servlet,在web.xml中配置容器啟動(dòng)時(shí)運(yùn)行servlet的init方法,這樣端口號為10086和843的serverSocket就會(huì)啟動(dòng):
public class InitServers extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public InitServers() {
super();
}
/**
* @see Servlet#init(ServletConfig)
*/
public void init(ServletConfig config) throws ServletException {
new PolicyServer().startPolicy();
new SocketServer().startSocket();
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
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>
?
以上一個(gè)完整的Flex+Java的Socket通信就完成了。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

