Android中基於ServerSocket的實際使用與簡單介紹(內附一個PC端群控多臺手機的訊息傳送和接收Demo)
一、要想將ServerSocket整明白首先至少先要知道或是瞭解幾點基礎內容部分(大神或是有一定能力的小神跳過):
1.ServerSocket是需要在服務端定義書寫的而在客戶端不需要ServerSocket,客戶端只需要建立socket就可以了。
2.socket需要在子執行緒中處理,所以必須開闢子執行緒,而老版本的socket不需要在子執行緒中書寫,所以網上的一些比較老版本的帖子Demo什麼的沒有建立子執行緒,這點需要注意。
3.在Android Studio中建立Java程式碼,控制檯會出現中文亂碼的情況(與UTF-8轉碼無關),我在另一個部落格文章中介紹瞭解決方法(轉載的文章),出現亂碼的同學可以去找一下。
4.資料的傳送與接收都需要通過socket來讀取或傳送輸入輸出流。
5.子執行緒不能改變主執行緒的UI,所以需要使用Handler,因為比較簡單所以以上這些就不過多的講解了。
二、為了更好的說明和理解Socket通訊,我將查到的一些可能常出現的異常和使用方法等進行了總結:
異常型別 在瞭解Socket的內容之前,先要了解一下涉及到的一些異常型別。以下四種類型都是繼承於IOException,所以很多之後直接彈出IOException即可。 UnkownHostException: 主機名字或IP錯誤 ConnectException: 伺服器拒絕連線、伺服器沒有啟動、(超出佇列數,拒絕連線)boolean isConnection=socket.isConnected() && !socket.isClosed(); //判斷當前是否處於連線
/** * 1.獲得遠端伺服器的IP 地址. * InetAddress inetAddress = socket.getInetAddress(); * 2.獲得遠端伺服器的埠. * int port = socket.getPort(); * 3. 獲得客戶本地的IP 地址. * InetAddress localAddress = socket.getLocalAddress(); * 4.獲得客戶本地的埠. * int localPort = socket.getLocalPort(); * 5.獲取本地的地址和埠號 * SocketAddress localSocketAddress = socket.getLocalSocketAddress(); * 6.獲得遠端的地址和埠號 * SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress(); */
以上的程式碼是自己找到或是自己整理的關於能更好的加深對socket瞭解的一些知識點段落,在開始看下面程式碼之前最好先了解一下。
三、為了更好的進行了解和總結,並且儘量避免和網上的一些Demo部落格衝突,所以自己寫了兩個Demo(一個在PC端中是Java專案暫且叫D1,另一個在手機端是Android專案暫且叫D2)大致功能為:1)一臺PC能夠連線多臺手機進行資料的傳送和接收;2)PC端可以顯示已連線手機的數量以及ID埠號;3)手機端和PC端的資料傳送會得到返回值來表示是否傳送接收成功;4)實現群發和單獨傳送;都是一些比較小的功能,但卻是一個大專案的雛形,所以我進行了一個簡單的封裝來儘量節省程式碼量和方便後期的修改。話不多說首先介紹D1也就是PC端的Java程式碼如下所示:
1.建立Java工程(還是儘量講的詳細些吧~):
2.建立一個ClientManager的客戶端管理類,實現如下程式碼:
// 管理連線到伺服器中的手機類 public class ClientManager { private static ServerThread serverThread = null; private static int sum = 0; private static Map<String, Socket> clientMap = new HashMap<>(); private static List<String> clientList = new ArrayList<>(); private static class ServerThread implements Runnable { private ServerSocket server; private int port = 10086; private boolean isExit = false;// 一個boolean型別的判斷 預設是退出狀態false // 構造方法初始化 public ServerThread() { try { server = new ServerSocket(port); System.out.println("啟動server,埠號:" + port); } catch (IOException e) { e.printStackTrace(); } } /** * 1.獲得遠端伺服器的IP 地址. * InetAddress inetAddress = socket.getInetAddress(); * 2.獲得遠端伺服器的埠. * int port = socket.getPort(); * 3. 獲得客戶本地的IP 地址. * InetAddress localAddress = socket.getLocalAddress(); * 4.獲得客戶本地的埠. * int localPort = socket.getLocalPort(); * 5.獲取本地的地址和埠號 * SocketAddress localSocketAddress = socket.getLocalSocketAddress(); * 6.獲得遠端的地址和埠號 * SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress(); */ @Override public void run() { try { while (!isExit) { // 等待連線 System.out.println("等待手機的連線中... ..."); final Socket socket = server.accept(); System.out.println("獲取的手機IP地址及埠號:" + socket.getRemoteSocketAddress().toString()); /** * 因為考慮到多手機連線的情況 所以加入執行緒鎖 只允許單執行緒工作 */ new Thread(new Runnable() { private String text; @Override public void run() { try { synchronized (this) { // 在這裡考慮到執行緒總數的計算 也代表著連線手機的數量 ++sum; // 存入到集合和Map中為群發和單獨傳送做準備 String string = socket.getRemoteSocketAddress().toString(); clientList.add(string); clientMap.put(string, socket); } // 定義輸入輸出流 InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); // 接下來考慮輸入流的讀取顯示到PC端和返回是否收到 byte[] buffer = new byte[1024]; int len; while ((len = is.read(buffer)) != -1) { text = new String(buffer, 0, len); System.out.println("收到的資料為:" + text); os.write("已收到訊息".getBytes("utf-8")); } } catch (IOException e) { e.printStackTrace(); } finally { System.out.println("關閉連線:" + socket.getRemoteSocketAddress().toString()); synchronized (this) { --sum; String string = socket.getRemoteSocketAddress().toString(); clientMap.remove(string); clientList.remove(string); } } } }).start(); } } catch (IOException e) { e.printStackTrace(); } } // 關閉server public void stop() { isExit = true; if (server != null) { try { server.close(); System.out.println("已關閉server"); } catch (IOException e) { e.printStackTrace(); } } } } // 啟動server public static ServerThread startServer() { System.out.println("開啟server"); if (serverThread != null) { System.out.println("server不為null正在重啟server"); // 以下為關閉server和socket shutDown(); } // 初始化 serverThread = new ServerThread(); new Thread(serverThread).start(); System.out.println("開啟server成功"); return serverThread; } // 傳送訊息的方法 public static boolean sendMessage(String name, String mag) { try { Socket socket = clientMap.get(name); OutputStream outputStream = socket.getOutputStream(); outputStream.write(mag.getBytes("utf-8")); return true; } catch (IOException e) { e.printStackTrace(); } return false; } // 群發的方法 public static boolean sendMsgAll(String msg){ try { for (Socket socket : clientMap.values()) { OutputStream outputStream = socket.getOutputStream(); outputStream.write(msg.getBytes("utf-8")); } return true; }catch (Exception e){ e.printStackTrace(); } return false; } // 獲取執行緒總數的方法,也等同於<獲取已連線了多少臺手機>的方法+ public static int sumTotal() { return sum; } // 一個獲取list集合的方法,取到所有連線server的手機的ip和埠號的集合 public static List<String> getTotalClients() { return clientList; } public static void shutDown() { for (Socket socket : clientMap.values()) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } serverThread.stop(); clientMap.clear(); clientList.clear(); } }因為這個部落格的程式碼編輯不是特別的會用,所以直接從我的TestDemo中直接複製了~但是我已經儘可能多的每段都敲好了註釋,所以還有什麼不懂的地方可以google一下~或者是執行一下程式碼用debug模式看下程式碼的執行流程~到這裡為止我覺的只需要注意如下幾個方面:
(1)socket = serverSocket.accept();像牛郎織女一樣~一直在苦苦等待自己要等的人出現...如果不出現就一直等下去,直到等到符合條件的請求出現為止,才會分配出一個socket來讓程式向下執行,不然一直持續在阻塞等待狀態下(不信你打log試試看)
(2)使用完之後一定記得要關閉server和socket (比關流要重要的多)在程式碼中會有大量的判斷要考慮,我寫的還不是很成熟很多地方還沒有考慮完美,但是能實現想要的結果。
(3)synchronized的使用在於優化對執行緒的控制,因為是單排車道通過,所以可以從中獲取通過了多少個子執行緒(也就意味著獲取了連線手機的數量),因為每臺手機的IP及埠號都不一致,所以通過這個來儲存到Map中來實現單獨指定手機發送資料的功能,存到Map中的key為IP埠號、value為分配的socket,從而可以通過遍歷Map來實現群發的功能,所以synchronized是不可或缺的。
3.到MyClass中,這個名字是建立工程的時候預設哦~所以不要說我命名不規範~
public class MyClass { public static void main(String[]args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line; boolean isExit = false; ClientManager.startServer(); while (!isExit){ line = br.readLine(); if (line.startsWith("exit")){ System.out.println("退出命令"); break; } if (line.startsWith("send")){ sendMessage(line); }else if (line.startsWith("list")){ printTotal(); }else if (line.startsWith("all")){ allSendMsg(line); }else { System.out.println("輸入錯誤 請重新輸入"); } } // 關閉 清空 ClientManager.shutDown(); } private static void allSendMsg(String line) { String[] field = line.split("//"); if (field.length == 2){ ClientManager.sendMsgAll(field[1]); System.out.println("傳送結果為:" + ClientManager.sendMsgAll(field[1]) ); }else { System.out.println("格式不正確 例:all//message"); } } private static void printTotal() { List<String> totalClients = ClientManager.getTotalClients(); System.out.println("連線數量為:" + totalClients.size()); for (String totalClient : totalClients) { System.out.println(totalClient); } } private static void sendMessage(String line) { String[] field = line.split("//"); if (field.length == 3){ // 格式正確 ClientManager.sendMessage(field[1],field[2]); System.out.println("send結果為:" + ClientManager.sendMessage(field[1],field[2])); }else { System.out.println("命令不正確。例子:send//name//msg"); } } }已上的這些程式碼我覺的應該沒啥大問題,因為自己寫的東西實在是有點淺薄... 敲程式碼其實有時就像是在寫書法一樣,有的人程式碼開啟會讓你看的陶醉和欽佩,而有的人的程式碼就讓人感覺不舒服跟看小學生的字帖兒一樣..我可能就是後者吧 = =' 有不懂的地方可以自己google一下~我就不多說明了。
4.接下來是建立D2,也就是Android專案,首先我建議先把許可權加上不要忘了:
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
5.Android專案我就沒有封裝,直接想寫啥就寫啥了,但是程式碼不多很方便理解,如下所示:
public class MainActivity extends AppCompatActivity { private Button btn; private EditText et; private TextView tv; // private WifiManager w = null; private Socket socket; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = (Button) findViewById(R.id.btn); et = (EditText) findViewById(R.id.et_send); tv = (TextView) findViewById(R.id.tv_js); final Handler handler = new MyHandler(); new Thread(new Runnable() { @Override public void run() { try { socket = new Socket("192.168.1.111", 10086); // 接收 InputStream inputStream = socket.getInputStream(); byte[] buffer = new byte[102400]; int len; while ((len = inputStream.read(buffer)) != -1) { String s = new String(buffer, 0, len); // Message message = Message.obtain(); message.what = 0; message.obj = s; handler.sendMessage(message); } } catch (IOException e) { e.printStackTrace(); } } }).start(); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final String string = et.getText().toString(); new Thread(new Runnable() { @Override public void run() { try { // 傳送 OutputStream outputStream = null; outputStream = socket.getOutputStream(); outputStream.write(("IP:" + getHostIp() + " " + string).getBytes("utf-8")); outputStream.flush();// 清空快取 } catch (IOException e) { e.printStackTrace(); } } }).start(); } }); } // 獲取IP並轉換格式 private String getHostIp() { WifiManager mg = (WifiManager) getSystemService(Context.WIFI_SERVICE); if (mg == null){ return ""; } WifiInfo wifiInfo = mg.getConnectionInfo(); int ip = wifiInfo.getIpAddress(); return ((ip & 0xff) + "." + (ip >> 8 & 0xff) + "." + (ip >> 16 & 0xff) + "." + (ip >> 24 & 0xff)); } private class MyHandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == 0) { String s = (String) msg.obj; tv.setText(s); } } } }以上程式碼值得注意的是如下幾點:
1)不能再子執行緒中重新整理UI,通過Handler來發送msg;
2)通過WifiManager來獲取IP結果需要轉換格式,就好像系統的日期是一串數字要通過轉換格式變成我們能看懂的樣式一樣(那串轉換格式的程式碼我也是從網上Ctrl + C、Ctrl + V來的)
3)socket = new Socket("192.168.1.111", 10086);中第一個引數填的是PC端的IP地址,第二個引數要與PC中Server設定的埠號一致;
4)如果wifi環境不好,可以像下圖程式碼一樣設定超時時間來進行其它操作:
socket = new Socket(); SocketAddress socAddress = new InetSocketAddress(dsName, dstPort); // 設定超時時間 socket.connect(socAddress, 5000); Log.i(TAG, "Connect OK!");
6.Android端的XML檔案程式碼如下圖所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="sq.can_26_socket.MainActivity"> <TextView android:id="@+id/tv_js" android:layout_margin="10dp" android:textSize="20sp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <EditText android:id="@+id/et_send" android:hint="@string/xx" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/btn" android:layout_marginTop="5dp" android:text="@string/send" android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>上方的TextView用來顯示從PC端接收到的資料,EditText用來獲取傳送到PC端的訊息。綜上所有就可以執行你的程式碼啦~先執行PC端的Java程式碼,在執行Android專案到手機中,可以把APK打包到多臺手機進行測試。最後測試的效果如下圖所示:
1)首先執行PC端操作檯會有如下的顯示結果:
2)開啟手機中的專案後,操作檯會顯示(連線數量的上線我不知道是多少):
3)在操作檯上輸入如下綠色的指令後進行群發操作,向兩臺手機發送“這是群發的訊息”,顯示如下:
4)手機接收到的訊息後,顯示的效果如下圖所示:
5)手機向電腦傳送訊息後,操作檯顯示的效果如下圖所示(顯示傳送訊息的手機的IP地址和Message內容):
6)向指定手機發送訊息,操作檯上編輯如下圖綠色部分的內容,執行傳送命令:
7)手機上接收後顯示效果如下所示:
總結:綜上所述就是Server Socket的簡單介紹和部分應用場景的Demo,我將這兩個Demo上傳到Guthub上,有需要的同學可以點選<這裡這裡這裡>進行下載~ 如果哪裡有錯誤歡迎批評指正~如果對我有什麼建議可以留言交流~我會一一回復的~下篇部落格要寫啥膩... ... 有點不知所以然啊,看著那些大神大牛的部落格,感覺都是好高深呀~好像不太適合接觸Android不久的我和一些新手朋友們,所以就像我的簽名一樣,希望通過一篇篇的部落格來逐漸的走向大牛或是架構師的人生巔峰之中~~
相關推薦
Android中基於ServerSocket的實際使用與簡單介紹(內附一個PC端群控多臺手機的訊息傳送和接收Demo)
一、要想將ServerSocket整明白首先至少先要知道或是瞭解幾點基礎內容部分(大神或是有一定能力的小神跳過): 1.ServerSocket是需要在服務端定義書寫的而在客戶端不需要ServerSocket,客戶端只需要建立socket就可以了。 2.socket需要在子
網絡測量中基於Sketch方法的簡單介紹
cnblogs 地址空間 ket ++ 分類 hash 分配 num ati Sketch介紹 為什麽要用Sketch 網絡流主要根據五元組、主機地址、包的大小來分類。在網絡中存在各種各樣的包,如果按照上述分類方法,對每一種包都分配一個計數器來儲存,雖然測量準確,那麽存放
Android中4種基本佈局簡單介紹和總結
Android中4種基本佈局 線性佈局LinearLayout LinearLayout線性佈局,是一種非常常用的佈局方式。此佈局會將控制元件線上性方向上依次排列,方向有水平和垂直兩種,首先看一下垂直方向上的佈局格式,程式碼如下: <Linear
android中canvas.drawText參數的介紹以及繪制一個文本居中的案例
.cn 介紹 ondraw image 圖片 oat sin es2017 tco float baseline = height/2 + mPaint.getTextSize()/2 - mPaint.getFontMetrics().descent;文字尺寸就是 pr
Android圖表控制元件MPAndroidChart的簡單介紹(MPAndroidChart3.0)
每個類對應的圖是什麼github上有詳細的介紹圖表類具有相同的地方X軸:XAxisY軸:YAxis圖例:Legend描述:Description限制線:LimitLine選中圖表中的值,可顯示的檢視:MarkerView 具體在圖表中的表現如下圖以曲線圖為例依賴:projec
如何將量子鏈引入業務系統,使用JSON-RPC與錢包通訊(內附區塊鏈學習論,個人理解,歡迎交流)
提示 Tip1.本文適合想要深入瞭解區塊鏈技術的人員閱讀 Tip2.本文適合需要在業務系統中引入比特幣/量子鏈的技術人員閱讀 引言 入門區塊鏈技術之前,如果對於去中心化有一定了解,這無疑將幫助你更好的理解區塊鏈的底層技術;區塊鏈的底層技術由密碼學,共識演算法,
並查集——簡單易懂(內附並查集刪除操作)
並查集 並查集的定義 並查集是一種樹型的資料結構,用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。常常在使用中以森林來表示。 集就是讓每個元素構成一個單元素的集合,也就是按一定順序將屬於同一組的元素所在的集合合併。 並查集的用途 維護一個無
Varnish的部署與使用例項(內附一鍵安裝部署指令碼github連結)
Varnish的部署與使用 指令碼及原始碼安裝包連結 概述 Varnish是一款高效能且開源的反向代理伺服器和http加速器 與傳統的Squid相比,Varnish具有效能更高,速度更快,管理更方便等諸多優點。 編譯安裝 這裡展
Java中基於靜態變數與單例模式對快取的簡單實現
●What & Why 快取是什麼?他有什麼好處?相信不用說大家都知道。 目前筆者在做一個Java開發的Web專案,專案啟動的時候需要將大量不變的平臺數據放入快取中,方便快速讀取。一開始筆者很疑惑,Java是不能直接操作記憶體的,但是我們快取卻是要把資料放入記憶體
iOS中runtime(執行時)的簡單介紹與應用
最近學習到runtime這一塊知識了所以就總結一下,以備以後要用。但是由於runtime這一塊的知識比較多所以今天在這裡只是做一個簡單的總結。 一、什麼是Runtime? Objective-C runtime是一個實現Objective-C語言的
ES5與ES6中的陣列操作方法簡單介紹
許多同學在寫javascript程式碼的時候都習慣於使用順手和常用的方法,比如說在運算元組時習慣使用for,whlie迴圈,在列舉物件時,習慣使用for in,其實在ES5和ES6出來以後,有很多新的運算元組和物件的方法,這裡我們來重點看一下一些功能強大,但又往往被我們忽視的操作方法。 先來定義一個簡單的陣
Java中單例模式與簡單工廠模式的簡單例項介紹
1:單例設計模式 單例模式也稱為單件模式、單子模式,可能是使用最廣泛的設計模式。其目的是保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點,該例項被所有程式模組共享。單例模式又包含 餓漢式和懶漢式
Android實際開發中的bug總結與解決方法(三)
使用BitmapFactory.Options設定inSampleSize就可以縮小圖片。屬性值inSampleSize表示縮圖大小為原始圖片大小的幾分之一。如果知道圖片的畫素過大,就可以對其進行縮小。 那麼如何才知道圖片過大呢? 方法是:使用BitmapFactory.Options設定inJust
Android中基於Socket的網絡通信
ram pre 請求 android 鏈接 ted block param gen Socket鏈接的建立過程: 1.服務器監聽 2.客戶端發出請求 3.建立鏈接 4.通信 Socketl特點: 1.Socket基於TCP鏈接,數據傳輸有保障 2.Socket適用於建立長時
android application類簡單介紹(一)
cati theme text color raw sdn water bsp public 每次應用程序執行時。應用程序的application類保持實例化的狀態。通過擴展applicaiton類,能夠完畢下面3項工作: 1.對android執行時廣播的應用程序
Android中兩個Activity之間簡單通信
idg tin test ide button ima 接收 9.png set 在Android中,一個界面被稱為一個activity,在兩個界面之間通信,采用的是使用一個中間傳話者(即Intent類)的模式,而不是直接通信。 下面演示如何實現兩個activity之間的通
在Android中使App高速、簡單地支持新浪微博、微信、QQ、facebook等十幾個主流社交平臺的分享功能
分析 ont renren androidm mod 執行 xen 12px 操作 前言 在如今的APP或者遊戲中,分享功能差點兒已經成為標配。分享功能不但能夠滿足用戶的需求。也能夠為產品帶來很多其它的用戶,甚至能夠對用戶的行為、活躍度、年齡段等情況進行數據統計,使得軟
Android平臺Camera實時濾鏡實現方法探討(十)--代碼地址以及簡單介紹(20160118更新)
div iss 將在 spa 方法 target 用途 net dsm 簡單做了個相機和圖片編輯模塊,時間原因非常多功能還沒有做。尚有BUG,見諒,將在以後抽時間改動 代碼地址 PS:請點個Star^-^ -----------------------
Android中dp、dpi與px的關系
聯系 vga 公式 矩形 鏈接 相同 針對 來源 商業 轉自知乎用戶的回答: 1. dpi是dot per inch,每英寸多少點,ppi是 Pixel per inch,每英寸像素數,針對顯示器的設計時,dpi=ppi。ppi計算方法是長寬各自平方之和開方,除以對角線長
Android中的執行緒與執行緒池
執行緒與執行緒池 概括 執行緒分為主執行緒和子執行緒. 主執行緒主要是用來處理和介面相關的事情, 子執行緒主要是用來做耗時的操作,比如 載入遠端資料,資料庫操作等。 在android 中,處理直接使用 Thread以外。 android 還提供了很多類似執行緒的操作便於我們