這篇要來聊在Android中實現Wifi通訊之中的TCP通訊
我的Blog有寫了關於TCP/UDP通訊的相關單元
在這裡
->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊上篇-UDP伺服器端與客戶端實現
->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊中篇-TCP伺服器端實現
->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊下篇-TCP客戶端實現
開發上若有TCP/UDP的需求歡迎抄走參考我的程式哦(・ωー)~☆
在上篇我有提到關於TCP/UDP的一些參考資源,這次我就不再花時間講廢話了
上篇內容提到的是關於如何以UDP模式在同一區域網路內達成不同手機之間的文字通訊
在那篇,我們演示了如何在UDP上面寫監聽端以及發送端
這篇我們改用TCP來實現相似的功能
不過因為TCP要同時介紹客戶端(Client)跟伺服器(Sever)端文章篇幅會太大
所以我TCP的部分我便把他切成中篇跟下篇方便介紹( ゚Д゚)b
總之,先來看範例
以及Github
->https://github.com/thumbb13555/UDP_TCP_ConnectDemo
開始吧!(-‿◦)
1. 介紹&加入權限
1.1 介紹專案結構
本次專案為了節省我Github的空間,所以我直接把UDP跟TCP的功能都放在一個APP
怎麼樣?很有誠意對吧(-‿◦)
來看一下專案結構吧
身為一個二合一的APP,所以主畫面(MainActivity.java)便是選擇UDP或是TCP的頁面而已
當然,這次的文章我們是選擇UDP得這個畫面
而在檔案中還有一個CommendFun.java
裡面只放了一個用來取得手機所連線到之WIfi之IP而已
我直接PO一下吧
public class CommendFun { @SuppressLint("DefaultLocale") public static String getLocalIP(Context context) { WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(WIFI_SERVICE); assert wifiManager != null; WifiInfo info = wifiManager.getConnectionInfo(); int ipAddress = info.getIpAddress(); return String.format("%d.%d.%d.%d" , ipAddress & 0xff , ipAddress >> 8 & 0xff , ipAddress >> 16 & 0xff , ipAddress >> 24 & 0xff); } }
本篇的內容是要講關於TCP通訊的Server端
也就是下圖紅匡內TCP的資料夾內的TCPActivity.java跟TCPServer.java兩個檔案
如果要知道UDP的話,麻煩電梯上樓
->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊上篇-UDP伺服器端與客戶端實現
想知道TCP通訊的客戶端,請電梯下樓
->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊下篇-TCP客戶端實現
1.2 加入權限
本次的專案因為涉及到網路使用以及Wifi使用
因此必須加入相關權限
請至AndroidManifest.xml的部分
加入以下粉底白字兩行
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.jetec.udp_tcp_connectdemo"> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".TCP.TCPActivity"></activity> <activity android:name=".UDP.UDPActivity" /> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
網路的話不用特別問使用者,因此準備工作到這篇囉
接下來畫介面
2. 畫介面
這是這次介面的內容
請仔細看一下本介面右上角有個切換元件
由於一個IP不會同時存在Server端與Client端
因此我們必須在介面上加以互鎖以防止閃退
效果如下
來吧,上介面
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".UDP.UDPActivity"> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.1" /> <TextView android:id="@+id/textView_IP" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:text="本機IP: " android:textColor="@android:color/background_dark" android:textSize="18sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/guideline2" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ToggleButton android:id="@+id/toggleButton_Server" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textOff="開啟Server" android:textOn="關閉Server" app:layout_constraintBottom_toTopOf="@+id/guideline6" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/editText_Port" app:layout_constraintTop_toTopOf="@+id/guideline2" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.4" /> <EditText android:id="@+id/editText_ReceiveMessage" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" android:focusable="false" android:focusableInTouchMode="false" android:gravity="top" android:minLines="20" android:inputType="textMultiLine" app:layout_constraintBottom_toTopOf="@+id/guideline4" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView3" app:layout_constraintVertical_bias="0.0" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.9" /> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:text="收到的消息" android:textAppearance="@style/TextAppearance.AppCompat.Large" android:textStyle="bold" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline3" /> <EditText android:id="@+id/editText_Input" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:ems="10" android:hint="請輸入內容" android:inputType="textPersonName" android:text="Hello! I'm samsung" app:layout_constraintBottom_toBottomOf="@+id/button_Send" app:layout_constraintEnd_toStartOf="@+id/button_clear" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/button_Send" /> <EditText android:id="@+id/editText_Port" android:layout_width="0dp" android:layout_height="0dp" android:ems="10" android:hint="Port" android:inputType="number" android:text="8888" app:layout_constraintBottom_toTopOf="@+id/guideline6" app:layout_constraintEnd_toStartOf="@+id/guideline5" app:layout_constraintStart_toEndOf="@+id/textView4" app:layout_constraintTop_toTopOf="@+id/guideline2" /> <Button android:id="@+id/button_Send" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="發送" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline4" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.7" /> <EditText android:id="@+id/editText_RemoteIp" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:ems="10" android:inputType="numberDecimal" android:text="192.168.50.5" app:layout_constraintBottom_toTopOf="@+id/toggleButton_ClientConnect" app:layout_constraintEnd_toStartOf="@+id/guideline5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView5" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.17" /> <TextView android:id="@+id/textView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:text="本機Port: " android:textAppearance="@style/TextAppearance.AppCompat.Large" android:textSize="18sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/guideline6" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline2" /> <TextView android:id="@+id/textView5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:text="Remote Ip:" android:textAppearance="@style/TextAppearance.AppCompat.Body1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline6" /> <TextView android:id="@+id/textView6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="Remote Port: " android:textAppearance="@style/TextAppearance.AppCompat.Body1" app:layout_constraintStart_toStartOf="@+id/guideline5" app:layout_constraintTop_toTopOf="@+id/guideline6" /> <EditText android:id="@+id/editText_RemotePort" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:ems="10" android:inputType="textPersonName" android:text="8888" app:layout_constraintBottom_toTopOf="@+id/toggleButton_ClientConnect" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline5" app:layout_constraintTop_toBottomOf="@+id/textView6" /> <ToggleButton android:id="@+id/toggleButton_ClientConnect" android:layout_width="0dp" android:textOff="連線至指定Server" android:textOn="斷線" android:enabled="false" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:layout_marginBottom="8dp" app:layout_constraintBottom_toTopOf="@+id/guideline3" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> <Button android:id="@+id/button_clear" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="清除" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/button_Send" app:layout_constraintTop_toTopOf="@+id/guideline4" /> <Switch android:id="@+id/switch_ModeChange" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Server" app:layout_constraintBottom_toTopOf="@+id/guideline2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline5" app:layout_constraintTop_toTopOf="parent" tools:ignore="UseSwitchCompatOrMaterialXml" /> </androidx.constraintlayout.widget.ConstraintLayout>
3. 撰寫TCP-Server端的主要功能
接下來開始編寫主要程式部分
這一次的範例是撰寫TCP-Server的部分
也就是TCPServer.java的這個檔案
我先PO全部給你,再來一部分一部分解釋(;´д`)ゞ
class TCPServer implements Runnable { public static final String TAG = "MyTCP"; public static final String RECEIVE_ACTION = "GetTCPReceive"; public static final String RECEIVE_STRING = "ReceiveString"; public static final String RECEIVE_BYTES = "ReceiveBytes"; private int port; private boolean isOpen; private Context context; public ArrayList<ServerSocketThread> SST = new ArrayList<>(); /**建立建構子*/ public TCPServer(int port,Context context){ this.port = port; isOpen = true; this.context = context; } //取得開啟狀態 public boolean getStatus(){ return isOpen; } //關閉伺服器 public void closeServer(){ isOpen = false; //找出所有正在連線的裝置執行緒,並一一清除、斷線 for (ServerSocketThread s : SST){ s.isRun = false; } SST.clear(); } /**取得Socket許可(握手)*/ private Socket getSocket(ServerSocket serverSocket){ try { return serverSocket.accept(); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "更新Server狀態"); return null; } } @Override public void run() { try { /**在本機的Port上開啟伺服器*/ ServerSocket serverSocket = new ServerSocket(port); //設置Timeout,以便更新裝置連進來的狀況 serverSocket.setSoTimeout(2000); while (isOpen){ Log.e(TAG, "監測裝置輸入..."); if (!isOpen) break; Socket socket = getSocket(serverSocket); if (socket != null){ //如果Socket不為null,表示有裝置連入了 new ServerSocketThread(socket,context); } } serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } /**監聽裝置連入與收發狀態之執行緒*/ public class ServerSocketThread extends Thread{ private Socket socket; private PrintWriter pw; private InputStream is; private boolean isRun = true; private Context context; ServerSocketThread(Socket socket, Context context){ this.socket = socket; this.context = context; String ip = socket.getInetAddress().toString(); Log.d(TAG, "檢測到新的裝置,Ip: " + ip); try { socket.setSoTimeout(2000); OutputStream os = socket.getOutputStream(); is = socket.getInputStream(); pw = new PrintWriter(os,true); start(); } catch (IOException e) { e.printStackTrace(); } } public void sendData(String msg){ pw.print(msg); pw.flush(); } @Override public void run() { byte[] buff = new byte[100]; SST.add(this); while (isRun && !socket.isClosed() && !socket.isInputShutdown()){ try { //監聽訊息是否有送過來 int rcvLen; if ((rcvLen = is.read(buff)) != -1 ){ String string = new String(buff, 0, rcvLen); Log.d(TAG, "收到訊息: " + string); /**收到訊息後,以廣播的方式回傳到Activity*/ Intent intent = new Intent(); intent.setAction(RECEIVE_ACTION); intent.putExtra(RECEIVE_STRING,string); intent.putExtra(RECEIVE_BYTES, buff); context.sendBroadcast(intent); } } catch (IOException e) { e.printStackTrace(); } } //跳出While迴圈即為斷開連線 try { socket.close(); SST.clear(); Log.e(TAG, "關閉Server"); } catch (IOException e) { e.printStackTrace(); } } } }
以上就是重點程式的部分
可以注意到我有標注到粉底白字的部分
那部分也是個重點,不過請容我一部份一部份解釋
首先是在本類別中提供外部接口的部分
//取得開啟狀態 public boolean getStatus(){ return isOpen; } //關閉伺服器 public void closeServer(){ isOpen = false; //找出所有正在連線的裝置執行緒,並一一清除、斷線 for (ServerSocketThread s : SST){ s.isRun = false; } SST.clear(); }
再來是重點部分
@Override public void run() { try { /**在本機的Port上開啟伺服器*/ ServerSocket serverSocket = new ServerSocket(port); //設置Timeout,以便更新裝置連進來的狀況 serverSocket.setSoTimeout(2000); while (isOpen){ Log.e(TAG, "監測裝置輸入..."); if (!isOpen) break; Socket socket = getSocket(serverSocket); if (socket != null){ //如果Socket不為null,表示有裝置連入了 new ServerSocketThread(socket,context); } } serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } }
這個部分是利用while迴圈持續監視是否有裝置連入
而TCP連線特殊的地方是,他必須設置粉底白字的Timeout的部分
而每次達到TimeOut時,While迴圈會重新跑一次
這時候就會重新監測裝置的連入
我已經在程式中寫好Log了,看著Log就可以了解到其中意義
而藍底白字的部分則是取得在迴圈時Socket的連線狀態
其對應的副程式在這邊
/**取得Socket許可(握手)*/ private Socket getSocket(ServerSocket serverSocket){ try { return serverSocket.accept(); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "更新Server狀態"); return null; } }
如果在Timeout前有裝置連入,getSocket便會回傳serverSocket.accept()
而只要有拿到Socket,便會進入到下面ServerSocketThread取得資訊並建立監聽
再來是ServerSocketThread的部分
以及其對應的ArrayList<ServerSocketThread> SST = new ArrayList<>();
這個陣列是用來儲存連線中的裝置資訊
在這個Thread中可以獲得連入裝置的資訊(主要是ip)
也因此,如果去檢測一下陣列SST的長度的話,就可以知道有多少裝置在線囉
那麼,來PO一下內容
/**監聽裝置連入與收發狀態之執行緒*/ public class ServerSocketThread extends Thread{ private Socket socket; private PrintWriter pw; private InputStream is; private boolean isRun = true; private Context context; ServerSocketThread(Socket socket, Context context){ this.socket = socket; this.context = context; String ip = socket.getInetAddress().toString(); Log.d(TAG, "檢測到新的裝置,Ip: " + ip); try { socket.setSoTimeout(2000); OutputStream os = socket.getOutputStream(); is = socket.getInputStream(); pw = new PrintWriter(os,true); start(); } catch (IOException e) { e.printStackTrace(); } } public void sendData(String msg){ pw.print(msg); pw.flush(); } @Override public void run() { byte[] buff = new byte[100]; SST.add(this); while (isRun && !socket.isClosed() && !socket.isInputShutdown()){ try { //監聽訊息是否有送過來 int rcvLen; if ((rcvLen = is.read(buff)) != -1 ){ String string = new String(buff, 0, rcvLen); Log.d(TAG, "收到訊息: " + string); /**收到訊息後,以廣播的方式回傳到Activity*/ Intent intent = new Intent(); intent.setAction(RECEIVE_ACTION); intent.putExtra(RECEIVE_STRING,string); intent.putExtra(RECEIVE_BYTES, buff); context.sendBroadcast(intent); } } catch (IOException e) { e.printStackTrace(); } } //跳出While迴圈即為斷開連線 try { socket.close(); SST.clear(); Log.e(TAG, "關閉Server"); } catch (IOException e) { e.printStackTrace(); } } }
所以,這時候再回去看一下關閉Server的程式
public void closeServer(){ isOpen = false; //找出所有正在連線的裝置執行緒,並一一清除、斷線 for (ServerSocketThread s : SST){ s.isRun = false; } SST.clear(); }
就能稍微理解一二了(・ω・)b
最後是取得資訊並把資料傳回Activity
使用以下這個部分
@Override public void run() { byte[] buff = new byte[100]; SST.add(this); while (isRun && !socket.isClosed() && !socket.isInputShutdown()){ try { //監聽訊息是否有送過來 int rcvLen; if ((rcvLen = is.read(buff)) != -1 ){ String string = new String(buff, 0, rcvLen); Log.d(TAG, "收到訊息: " + string); /**收到訊息後,以廣播的方式回傳到Activity*/ Intent intent = new Intent(); intent.setAction(RECEIVE_ACTION); intent.putExtra(RECEIVE_STRING,string); intent.putExtra(RECEIVE_BYTES, buff); context.sendBroadcast(intent); } } catch (IOException e) { e.printStackTrace(); } } //跳出While迴圈即為斷開連線 try { socket.close(); SST.clear(); Log.e(TAG, "關閉Server"); } catch (IOException e) { e.printStackTrace(); } }
一樣使用廣播(粉底白字)的方式,就能將收到的訊息傳到Activity囉!
4. 撰寫主要程式
接下來就是TCP的Activity的部分
也就是TCPActivity.java這個檔案
這次我先不PO全部,因為會混雜到下篇要聊到的TCPClient的程式
最後我當然會PO全部
Server端+UI設置的程式如下
public class TCPActivity extends AppCompatActivity { EditText edRemoteIp, edLocalPort, edReceiveMessage, edInputBox, edRemotePort; ToggleButton btClientConnect, btOpenServer, btServer; @SuppressLint("UseSwitchCompatOrMaterialCode") Switch swFunction; ExecutorService exec = Executors.newCachedThreadPool(); MyBroadcast myBroadcast = new MyBroadcast(); StringBuffer stringBuffer = new StringBuffer(); TCPServer tcpServer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_t_c_p); //設置基本UI setBaseUI(); //設置TCP伺服器功能 setServerSwitch(); //設置發送資料功能(伺服器) setSendFunction(); //註冊廣播,接收來自對方設備回傳的資料 IntentFilter intentFilter = new IntentFilter(TCPServer.RECEIVE_ACTION); registerReceiver(myBroadcast, intentFilter); } private void setBaseUI() { TextView tvLocalIp = findViewById(R.id.textView_IP); tvLocalIp.setText("本機IP: " + getLocalIP(this)); Button btClear = findViewById(R.id.button_clear); btClear.setOnClickListener(v -> { stringBuffer.delete(0, stringBuffer.length()); edReceiveMessage.setText(stringBuffer); }); btOpenServer = findViewById(R.id.toggleButton_Server); btClientConnect = findViewById(R.id.toggleButton_ClientConnect); edRemoteIp = findViewById(R.id.editText_RemoteIp); edRemotePort = findViewById(R.id.editText_RemotePort); edLocalPort = findViewById(R.id.editText_Port); edReceiveMessage = findViewById(R.id.editText_ReceiveMessage); edInputBox = findViewById(R.id.editText_Input); swFunction = findViewById(R.id.switch_ModeChange); swFunction.setChecked(false); //設置模式切換之Switch swFunction.setOnCheckedChangeListener((buttonView, isChecked) -> { btOpenServer.setEnabled(!isChecked); btClientConnect.setEnabled(isChecked); if (!isChecked) { swFunction.setText("Server"); } else { if (tcpServer != null && tcpServer.getStatus()) { tcpServer.closeServer(); btServer.setChecked(false); } swFunction.setText("Client"); } }); } /**設置TCP伺服器功能*/ private void setServerSwitch() { btServer = findViewById(R.id.toggleButton_Server); ExecutorService exec = Executors.newCachedThreadPool(); btServer.setOnCheckedChangeListener((buttonView, isChecked) -> { if (isChecked) { int port = Integer.parseInt(edLocalPort.getText().toString()); tcpServer = new TCPServer(port, this); exec.execute(tcpServer); edLocalPort.setEnabled(false); } else { tcpServer.closeServer(); edLocalPort.setEnabled(true); } }); } /**設置發送資料功能(伺服器)*/ private void setSendFunction() { Button btSend = findViewById(R.id.button_Send); btSend.setOnClickListener(v -> { String text = edInputBox.getText().toString(); //切換開關在Server模式時 if (tcpServer == null)return; if (text.length() == 0 || !tcpServer.getStatus()) return; //此處Lambda表達式相等於下方註解部分 if (tcpServer.SST.size() == 0) return; exec.execute(() -> tcpServer.SST.get(0).sendData(text)); // exec.execute(new Runnable() { // @Override // public void run() { // tcpServer.SST.get(0).sendData(text); // } // }); }); } private class MyBroadcast extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String mAction = intent.getAction(); assert mAction != null; /**接收來自TCP回傳之訊息*/ switch (mAction) { case TCPServer.RECEIVE_ACTION: String msg = intent.getStringExtra(TCPServer.RECEIVE_STRING); byte[] bytes = intent.getByteArrayExtra(TCPServer.RECEIVE_BYTES); stringBuffer.append("收到: ").append(msg).append("\n"); edReceiveMessage.setText(stringBuffer); break; } } } @Override protected void onDestroy() { super.onDestroy(); //取消監聽廣播 unregisterReceiver(myBroadcast); } }
在我的標注中有深紫紅、粉、藍、綠、土黃這五個顏色的底
深紫紅是剛才我們上面TCP-Server的主要部分
粉則是開啟Server
藍是斷開連線
綠是對連線過來的裝置發送訊息
土黃則是接收客戶端傳送資訊的部分
最後,PO一下包含下篇會用到的Client端程式全部PO出來
public class TCPActivity extends AppCompatActivity { EditText edRemoteIp, edLocalPort, edReceiveMessage, edInputBox, edRemotePort; ToggleButton btClientConnect, btOpenServer, btServer; @SuppressLint("UseSwitchCompatOrMaterialCode") Switch swFunction; ExecutorService exec = Executors.newCachedThreadPool(); MyBroadcast myBroadcast = new MyBroadcast(); StringBuffer stringBuffer = new StringBuffer(); TCPServer tcpServer; TCPClient tcpClient; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_t_c_p); //設置基本UI setBaseUI(); //設置TCP伺服器功能 setServerSwitch(); //設置TCP客戶端功能 setClientSwitch(); //設置發送資料功能(含伺服器/客戶端) setSendFunction(); //註冊廣播,接收來自對方設備回傳的資料 IntentFilter intentFilter = new IntentFilter(TCPServer.RECEIVE_ACTION); registerReceiver(myBroadcast, intentFilter); } private void setBaseUI() { TextView tvLocalIp = findViewById(R.id.textView_IP); tvLocalIp.setText("本機IP: " + getLocalIP(this)); Button btClear = findViewById(R.id.button_clear); btClear.setOnClickListener(v -> { stringBuffer.delete(0, stringBuffer.length()); edReceiveMessage.setText(stringBuffer); }); btOpenServer = findViewById(R.id.toggleButton_Server); btClientConnect = findViewById(R.id.toggleButton_ClientConnect); edRemoteIp = findViewById(R.id.editText_RemoteIp); edRemotePort = findViewById(R.id.editText_RemotePort); edLocalPort = findViewById(R.id.editText_Port); edReceiveMessage = findViewById(R.id.editText_ReceiveMessage); edInputBox = findViewById(R.id.editText_Input); swFunction = findViewById(R.id.switch_ModeChange); swFunction.setChecked(false); //設置模式切換之Switch swFunction.setOnCheckedChangeListener((buttonView, isChecked) -> { btOpenServer.setEnabled(!isChecked); btClientConnect.setEnabled(isChecked); if (!isChecked) { if (tcpClient != null && tcpClient.getStatus()) { tcpClient.closeClient(); btClientConnect.setChecked(false); } swFunction.setText("Server"); } else { if (tcpServer != null && tcpServer.getStatus()) { tcpServer.closeServer(); btServer.setChecked(false); } swFunction.setText("Client"); } }); } /**設置TCP伺服器功能*/ private void setServerSwitch() { btServer = findViewById(R.id.toggleButton_Server); ExecutorService exec = Executors.newCachedThreadPool(); btServer.setOnCheckedChangeListener((buttonView, isChecked) -> { if (isChecked) { int port = Integer.parseInt(edLocalPort.getText().toString()); tcpServer = new TCPServer(port, this); exec.execute(tcpServer); edLocalPort.setEnabled(false); } else { tcpServer.closeServer(); edLocalPort.setEnabled(true); } }); } /**設置TCP客戶端功能*/ private void setClientSwitch() { String remoteIp = edRemoteIp.getText().toString(); int remotePort = Integer.parseInt(edRemotePort.getText().toString()); btClientConnect.setOnCheckedChangeListener((buttonView, isChecked) -> { if (isChecked) { tcpClient = new TCPClient(remoteIp, remotePort, this); exec.execute(tcpClient); edRemoteIp.setEnabled(false); edRemotePort.setEnabled(false); } else { tcpClient.closeClient(); edRemoteIp.setEnabled(true); edRemotePort.setEnabled(true); } }); } /**設置發送資料功能(含伺服器/客戶端)*/ private void setSendFunction() { Button btSend = findViewById(R.id.button_Send); btSend.setOnClickListener(v -> { String text = edInputBox.getText().toString(); if (swFunction.isChecked()) { //切換開關在Client模式時 if (tcpClient == null)return; if (text.length() == 0 || !tcpClient.getStatus()) return; exec.execute(() -> tcpClient.send(text)); } else { //切換開關在Server模式時 if (tcpServer == null)return; if (text.length() == 0 || !tcpServer.getStatus()) return; //此處Lambda表達式相等於下方註解部分 if (tcpServer.SST.size() == 0) return; exec.execute(() -> tcpServer.SST.get(0).sendData(text)); // exec.execute(new Runnable() { // @Override // public void run() { // tcpServer.SST.get(0).sendData(text); // } // }); } }); } private class MyBroadcast extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String mAction = intent.getAction(); assert mAction != null; /**接收來自UDP回傳之訊息*/ switch (mAction) { case TCPServer.RECEIVE_ACTION: String msg = intent.getStringExtra(TCPServer.RECEIVE_STRING); byte[] bytes = intent.getByteArrayExtra(TCPServer.RECEIVE_BYTES); stringBuffer.append("收到: ").append(msg).append("\n"); edReceiveMessage.setText(stringBuffer); break; } } } @Override protected void onDestroy() { super.onDestroy(); //取消監聽廣播 unregisterReceiver(myBroadcast); } }
到此為止完成了TCP的伺服器端了
其實TCP伺服器的原理是廣泛被應用在所有的線上遊戲中
我們今天做的內容相當於是遊戲運營商在開啟伺服器的行為
也就是說,遊戲運營商為何能夠抓到玩家IP國籍等等資訊,跟這次的內容大有相關
好喔,那今天的文章到此,希望本文能對你有幫助
留言列表