您好,登錄后才能下訂單哦!
相信大家在上一篇中已經(jīng)了解了Android中WIFI熱點(diǎn)通信的相關(guān)操作知識(shí)(http://smallwoniu.blog.51cto.com/3911954/1536126),今天我們將在上一篇代碼基礎(chǔ)之上進(jìn)行Socket編程,實(shí)現(xiàn)一個(gè)簡單的多人聊天室功能,以達(dá)到熱點(diǎn)網(wǎng)絡(luò)上的通信目的。
首先,我們先來看一張最終效果圖:
<=======>
(說明:由于目前作服務(wù)器端的手機(jī),只是實(shí)現(xiàn)了數(shù)據(jù)的接收和轉(zhuǎn)發(fā),自己發(fā)送的數(shù)據(jù)并未顯示到自己的界面上,還需大家完善。。。)
一.框架搭建
在上一章的代碼基礎(chǔ)上,新增加了四個(gè)類:
GameServer:服務(wù)器端實(shí)現(xiàn)。
SocketClient:客戶端實(shí)現(xiàn)類。
ChatAdapter:聊天列表適配器。
ChatMessage:聊天信息實(shí)體。
GroupChatActivity:聊天室Acitivity。
1.1.相關(guān)類圖
在熱點(diǎn)連接成功后,開始聊天通信過程,服務(wù)器端與客戶端的類實(shí)現(xiàn)如下圖所示:
1.2.說明:
服務(wù)端:套接字GameServer,端口和套接字監(jiān)聽函數(shù)beginListen(),接收數(shù)據(jù)的函數(shù)serverAcceptClientMsg(),發(fā)送數(shù)據(jù)的函數(shù)sendMsgToAllCLients,以及網(wǎng)絡(luò)通訊流BufferedReader。
客戶端:套接字SocketClient,套接字連接函數(shù)startConnServer(),接收數(shù)據(jù)的函數(shù)acceptGameServerMsg(),發(fā)送數(shù)據(jù)的函數(shù)sendMsg()。
前面提到過創(chuàng)建熱點(diǎn)成功后,會(huì)自動(dòng)在當(dāng)前手機(jī)后臺(tái)創(chuàng)建GameServer,同時(shí)開啟線程監(jiān)聽端口并等待連接,當(dāng)其余玩家成功連接上熱點(diǎn)后,每個(gè)手機(jī)客戶端后臺(tái)對應(yīng)會(huì)創(chuàng)建一個(gè)獨(dú)立的Socket,用于發(fā)送和接收消息。在客戶端中通過client.getInputStream()接收數(shù)數(shù)據(jù),ClientMsgListener.handlerHotMsg(getSMsg)將數(shù)據(jù)反映到UI界面上,最終實(shí)現(xiàn)了客戶端接收服務(wù)器端數(shù)據(jù)刷新UI界面的功能。
二.通信模塊
2.1.服務(wù)器端
由于軟件的通信載體是在手機(jī)上,所以在創(chuàng)建完成熱點(diǎn)之后,在后臺(tái)也同時(shí)創(chuàng)建了游戲的服務(wù)器,開啟了監(jiān)聽PORT線程,等待其他客戶端連接。這樣設(shè)計(jì)的目的是為了在當(dāng)有其他手機(jī)端連接上指定WIFI熱點(diǎn)時(shí)就與后臺(tái)服務(wù)器端進(jìn)行了連接,即實(shí)現(xiàn)了TCP/IP通訊前期準(zhǔn)備。主要業(yè)務(wù)設(shè)計(jì)如圖所示:
核心代碼:
beginListenandAcceptMsg()
/** init server to listen **/ public void beginListenandAcceptMsg() { new Thread(new Runnable() { @Override public void run() { try { // init server mServerSocket = new ServerSocket(); mServerSocket.setReuseAddress(true); InetSocketAddress address = new InetSocketAddress(mPort); mServerSocket.bind(address); mServerMsgListener.handlerHotMsg(Global.INT_SERVER_SUCCESS); Log.i(TAG, "server =" + mServerSocket); } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } //server accept from socket msg if(mServerSocket != null) { while(onGoinglistner) { try { Socket socket = mServerSocket.accept(); if(socket != null) { if(!socketQueue.contains(socket)) { socketQueue.add(socket); count++; //記錄連接人數(shù) } Log.i(TAG, "接收客戶端消息" + socket); serverAcceptClientMsg(socket); } } catch (IOException e) { e.printStackTrace(); } } } } }).start(); }
serverAcceptClientMsg()
/** * accept from socket msg * @param socket */ private void serverAcceptClientMsg(final Socket socket) { new Thread(new Runnable(){ @Override public void run() { while(!socket.isClosed()) { try { //此處可以根據(jù)連接的客戶端數(shù)量count做一些數(shù)據(jù)分發(fā)等操作。 //接收客戶端消息 in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); String str = in.readLine(); if(str == null || str.equals("")) { break; } Log.i(TAG, "client" + socket + "str =" + str); mServerMsgListener.handlerHotMsg(str); } catch (Exception e) { e.printStackTrace(); } } } }).start(); }
sendMsg()
/**send msg to the socket**/ public void sendMsg(Socket client, String chatMsg) { Log.i(TAG, "into sendMsg(final Socket client,final ChatMessage msg) msg = " + chatMsg); PrintWriter out = null; if (client.isConnected()) { if (!client.isOutputShutdown()) { try { out = new PrintWriter(client.getOutputStream()); out.println(chatMsg); out.flush(); Log.i(TAG, "into sendMsg(final Socket client,final ChatMessage msg) msg = " + chatMsg + " success!"); } catch (IOException e) { e.printStackTrace(); Log.d(TAG, "into sendMsg(final Socket client,final ChatMessage msg) fail!"); } } } Log.i(TAG, "out sendMsg(final Socket client,final ChatMessage msg) msg = " + chatMsg); }
2.2.客戶端
這里的客戶端建立指的是當(dāng)其他手機(jī)在該軟件的WIFI管理界面上,點(diǎn)擊可用WIFI列表中指定的WIFI進(jìn)行連接操作,連接成功后,會(huì)在后臺(tái)創(chuàng)建客戶端,與服務(wù)器相連。主要業(yè)務(wù)設(shè)計(jì)如圖所示:
核心代碼:
connServerandAcceptMsg()
/**after hot pot created and connected successful , start connect GameServer**/ public void connServerandAcceptMsg() { Log.i(TAG, "into connectServer()"); new Thread(new Runnable() { @Override public void run() { try { client = new Socket(site, port); Log.i(TAG, "Client is created! site:" + site + " port:" + port); //callback mClientMsgListener.handlerHotMsg(Global.INT_CLIENT_SUCCESS); //accept msg from GameServer acceptGameServerMsg(); } catch (UnknownHostException e) { e.printStackTrace(); mClientMsgListener.handlerErorMsg(Global.INT_CLIENT_FAIL); } catch (IOException e) { e.printStackTrace(); mClientMsgListener.handlerErorMsg(Global.INT_CLIENT_FAIL); } } }).start(); Log.i(TAG, "out connectServer()"); }
acceptGameServerMsg()
/**accept msg from GameServer**/ private void acceptGameServerMsg() { new Thread(new Runnable() { @Override public void run() { while(onGoinglistner){ if(client != null && client.isConnected()) { if(!client.isInputShutdown()) { try { in = new BufferedReader(new InputStreamReader(client.getInputStream())); String getSMsg = in.readLine(); Log.i(TAG, "into acceptMsg() SMsg =" + getSMsg); if(getSMsg != null || !getSMsg.equals("")) { //callback mClientMsgListener.handlerHotMsg(getSMsg); } } catch (IOException e) { e.printStackTrace(); } } } } } }).start(); }
sendMsg()
/**send msg to GameServer**/ public String sendMsg(final String chatMsg) { Log.i(TAG, "into sendMsgsendMsg(final ChatMessage msg) msg =" + chatMsg); new Thread(new Runnable() { @Override public void run() { try { if (client != null && client.isConnected()) { if (!client.isOutputShutdown()) { PrintWriter out = new PrintWriter(client.getOutputStream()); out.println(chatMsg); // out.println(JsonUtil.obj2Str(msg)); Log.i(TAG, "成功發(fā)送msg =" + chatMsg); out.flush(); } } } catch (IOException e) { e.printStackTrace(); Log.d(TAG, "client snedMsg error!"); } } }).start(); return ""; }
以上兩大部分為Socket編程部分,為了能夠?qū)?shù)據(jù)反映到UI 前臺(tái),這里我們將每次線程接收到的數(shù)據(jù)先以接口回調(diào)方法( mClientMsgListener.handlerHotMsg(getSMsg);)的形式傳遞,在其對應(yīng)的方法中再利用Handler消息機(jī)制將數(shù)據(jù)發(fā)送到各自對應(yīng)的Handler中,最后根據(jù)邏輯將其反映到UI上,以上就是代碼的大體流程。
2.3.通信過程
下載過完整代碼的朋友就會(huì)發(fā)現(xiàn)代碼中許多重要的方法中我加入了Log,目的就是為了方便自己能夠更加清晰的了解整個(gè)代碼的流程,當(dāng)然大家也可以在此基礎(chǔ)上進(jìn)行不斷的修改和完善
點(diǎn)擊創(chuàng)建熱點(diǎn)按鈕:
點(diǎn)擊搜索熱點(diǎn)按鈕:
點(diǎn)擊列表“WIFI-TEST”進(jìn)行連接
三.總結(jié)
1.此案例由于是從本人畢業(yè)設(shè)計(jì)中扒下來的,可能現(xiàn)在有些地方代碼框架設(shè)計(jì)的不是很合理,如:GroupChatActivity就是為了方便實(shí)現(xiàn)聊天功能后添加的,大家在學(xué)習(xí)完之后可以在Activity跳轉(zhuǎn)時(shí)的基礎(chǔ)上,進(jìn)一步按照自己的邏輯來實(shí)現(xiàn)一些東西。
2.UI如何更新?
服務(wù)器端只是實(shí)現(xiàn)數(shù)據(jù)轉(zhuǎn)發(fā),未對自己發(fā)送數(shù)據(jù)進(jìn)行顯示,了解了整個(gè)代碼的同學(xué)可能已經(jīng)發(fā)現(xiàn)不論是Server還是Client端,在接收到數(shù)據(jù)之后,我們通過各自的監(jiān)聽器(mServerMsgListener,mClientMsgListener)來回調(diào)對應(yīng)的方法(handlerHotMsg,handlerErrorMsg),在方法中我們將數(shù)據(jù)添加msg.obj中,最終以消息傳遞的方式發(fā)送到各自對應(yīng)的handler中(clientHandler,serverHandler),在那里我們就可以根據(jù)數(shù)據(jù)來更新界面。
3.題外話:
要是有人對熱點(diǎn)通信特別感興趣,想在此的基礎(chǔ)之上開發(fā)小游戲,前臺(tái)游戲繪制界面就不用多說了,我主要想說的是后臺(tái)數(shù)據(jù)部分,最好能給所有操作制定了一系列對應(yīng)的數(shù)據(jù)規(guī)則,如:出牌操作:在傳輸?shù)臄?shù)據(jù)串前面加上規(guī)則字符---->“《#CARD》+數(shù)據(jù)段”,之后作為整體發(fā)送出去,這樣的話,接收方在接收到數(shù)據(jù)后可以方便的更新UI,實(shí)現(xiàn)對應(yīng)的游戲動(dòng)畫。(個(gè)人經(jīng)驗(yàn),僅供參考)
×××:http://down.51cto.com/data/1856373
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。