這個系列終於來到下篇了
這次要講的就是TCP協議中以一個客戶端的身份連線至伺服器端的一個過程
在開始之前一樣,提供一下這個系列的前面兩篇
->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊上篇-UDP伺服器端與客戶端實現
->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊中篇-TCP伺服器端實現
->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊下篇-TCP客戶端實現
同樣,因為在前兩篇之中我都有講過一些跟原理比較相關的東西
所以這篇我就不再費唇舌再講一次(-‿◦)
在上篇(中篇)中,我們演示了如何基於TCP通訊下開啟伺服器
所以這篇要演示的就是如何以客戶的身份連線到伺服器
用遊戲的角度來說,就是以玩家連線到伺服器的一個簡單實現
那麼,來看範例吧
還有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通訊的Client端
也就是下圖紅匡內TCP的資料夾內的TCPActivity.java跟TCPClient.java兩個檔案
如果要知道UDP的話,麻煩電梯上到二樓
->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊上篇-UDP伺服器端與客戶端實現
想知道TCP通訊的Server端,請電梯上一樓
->碼農日常-『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-Client端的主要功能
接著就是主程式的部分了
在TCP中篇中,我們由於需要抓出每個連線的裝置之裝置資訊,因此稍微複雜了一點點
但是在Client中,由於只需要連線到指定裝置,並與之溝通
因此整體來說就簡單許多(・ωー)~☆
來吧,看程式
class TCPClient implements Runnable { private String TAG = TCPServer.TAG; private PrintWriter pw; private InputStream is; private DataInputStream dis; private String serverIP; private int serverPort; private boolean isRun = true; private Socket socket; private Context context; public TCPClient(String ip , int port,Context context){ this.serverIP = ip; this.serverPort = port; this.context = context; } public boolean getStatus(){ return isRun; } public void closeClient(){ isRun = false; } public void send(byte[] msg){ try { OutputStream outputStream = socket.getOutputStream(); outputStream.write(msg); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } public void send(String msg){ pw.print(msg); pw.flush(); } @Override public void run() { byte[] buff = new byte[100]; try { /**將Socket指向指定的IP & Port*/ socket = new Socket(serverIP,serverPort); socket.setSoTimeout(5000); pw = new PrintWriter(socket.getOutputStream(),true); is = socket.getInputStream(); dis = new DataInputStream(is); } catch (IOException e) { e.printStackTrace(); } while (isRun){ try { int rcvLen = dis.read(buff); String rcvMsg = new String(buff, 0, rcvLen, "utf-8"); Log.d(TAG, "收到訊息: "+ rcvMsg); Intent intent =new Intent(); intent.setAction(TCPServer.RECEIVE_ACTION); intent.putExtra(TCPServer.RECEIVE_STRING, rcvMsg); context.sendBroadcast(intent); } catch (IOException e) { e.printStackTrace(); } } try { pw.close(); is.close(); dis.close(); socket.close(); Log.d(TAG, "關閉Client"); } catch (IOException e) { e.printStackTrace(); } } }
整體來說跟UDP那篇的架構很相似
分姐一下程式吧
首先是幾個方法
public boolean getStatus(){ return isRun; } public void closeClient(){ isRun = false; } public void send(byte[] msg){ try { OutputStream outputStream = socket.getOutputStream(); outputStream.write(msg); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } public void send(String msg){ pw.print(msg); pw.flush(); }
由上而下分別為
1. 取得裝置是否正在連線
2. 關閉連線
3. 發送byteArray類型的資訊
4. 發送String類型的資訊
接下來看看Run的部分
首先是開啟Socket通道
byte[] buff = new byte[100]; try { /**將Socket指向指定的IP & Port*/ socket = new Socket(serverIP,serverPort); socket.setSoTimeout(5000); pw = new PrintWriter(socket.getOutputStream(),true); is = socket.getInputStream(); dis = new DataInputStream(is); } catch (IOException e) { e.printStackTrace(); }
再來進入到While回圈,用以接收來自Server端回傳的資訊
並用Intent廣播的方式傳回Activity
while (isRun){ try { int rcvLen = dis.read(buff); String rcvMsg = new String(buff, 0, rcvLen, "utf-8"); Log.d(TAG, "收到訊息: "+ rcvMsg); Intent intent =new Intent(); intent.setAction(TCPServer.RECEIVE_ACTION); intent.putExtra(TCPServer.RECEIVE_STRING, rcvMsg); context.sendBroadcast(intent); } catch (IOException e) { e.printStackTrace(); } }
再來,是關閉TCP連線的方法(跳出While回圈即代表關閉連線)
try { pw.close(); is.close(); dis.close(); socket.close(); Log.d(TAG, "關閉Client"); } catch (IOException e) { e.printStackTrace(); }
最後,回頭來看這些方法用在哪裡吧(・ω・)b
4. 撰寫主要程式
接下來就是TCP的Activity的部分
也就是TCPActivity.java這個檔案
這次我先不PO全部,因為會混雜到上篇有聊到的TCPServer的程式
最後我當然會PO全部
Client端+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(); TCPClient tcpClient; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_t_c_p); //設置基本UI setBaseUI(); //設置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 { swFunction.setText("Client"); } }); } /**設置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(); //切換開關在Client模式時 if (tcpClient == null)return; if (text.length() == 0 || !tcpClient.getStatus()) return; exec.execute(() -> tcpClient.send(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-Client的主要部分
粉是連線指定Server
藍是斷開連線
綠是對伺服器發送訊息
土黃則是接收客戶端傳送資訊的部分
最後,PO一下包含上篇有用到的Server端程式全部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); } }
總算是把這個系列給寫完了( ̄ー ̄;
這個系列UDP/TCP都是在我工作中用到的東西
不過我常常覺得如果不是跟遊戲開發商有關的公司的話,可能不一定會用到這個技術吧(應該...
而我們公司因為要發展物聯網技術,因此今天講的這項技術我們研發團隊就把它套用在項目之中了
Okay, 這系列的文章就停在這裡
希望這些文章對各位有幫助!(・ωー)~☆
頭香 !!!
闔家平安發大財!!
你太利害了,我連likecoin 要怎麼轉換成台幣我都不會
我還沒去研究XD 我現在只湊到950拍手而已~ 我記得是先轉比特幣再轉成台幣,所以可能你要研究把比特幣轉丹麥克朗的方法吧
拍拍手,放煙花,棒!
桑Q桑Q~
拍手~
謝謝尼:D
又有新夥伴了 !
已拜訪:D
我其實只要轉成台幣而已
喔喔...比特轉台幣我也還不會弄xD 懇請幫忙膛雷啦哈哈哈
實現TCP/UDP區域網路 三篇完成 拍拍拍
桑Q桑Q
超厲害的
謝謝~(゚Д゚)ノ
最近發懶,眼睛感覺近視又增加了,不太上網,今天來拍拍手~^^
是啊~有一陣子沒看到妳活躍了 感謝來訪哦! 水餃我買了,超好吃的~
拍5拍+推 我對這些,完全不懂 佩服你
謝謝~人各有所長麻 妳也有拍拍手嗎?之後定期去捧場哦(・∀・)
望著我的學弟妹們開心放寒假 我也好想放嗚嗚嗚嗚嗚(是要說幾次??? 我現在在上班,但沒事做(? 所以可以來講講廢話,殺一下時間(喔齁齁 總而言之還是老樣子啦~ 一直在猶豫年後要不要辭職換工作XD 但我現在因為解決了我之前跟你說的那個aar,順便把FireBase也吃了所以感覺又踏過難關一片海闊天空(? 只是我又怕未來又有什麼可怕的東西在等我嗚嗚嗚ಥ‿ಥ 到時候如果真的找不到大神的作業怎麼辦(●´⌓`●) (辣個aar我有幸找到了某位大大的原始碼,雖然不是Flutter,雖然不能直接用,但至少我還可以抄個90%???) 而且老實講我光是aar跟FireBase就搞超久(大概一個半月吧咳咳咳咳咳)(太久了害我有點不好意思(◔‿◔)XD)XD要不是因為後台也沒什麼要我做的新進度不然我早就被一劍劈死了¯\_༼ᴼل͜ᴼ༽_/¯ 所以現在一想到未來就瑟瑟發抖XDDDD 總而言之,新年快樂,祝你2021年發大財⁽⁽◝( •௰• )◜⁾⁾ (這個轉折???)
看你能有事沒事過來扯扯家常我就放心了(・∀・) 我也豪想放寒假啊┏( .-. ┏ ) ┓ 我們公司旁邊就是國中,真是各種羨慕內 換工作我無法給建議啦~不過聽你講講倒是很有意思 aar我後來也還沒研究xD 不過近期是有打算寫Firebase的notification 推播功能的文章 應該會抓在二月的第一週吧,嗯~ 工作就是這樣麻~關關難過關關過咩 所以新年快樂...好啦下個月還有個新年xD 一起發大柴~哈哈
拍拍+5 謝謝分享~
謝謝~感謝拍手來訪!
又有新夥伴加入了喔
收到~
*****
*****
看起來都很複雜 哈哈 推+拍手
哈哈~不是看起來 是真的蠻複雜的~ 感謝留言拍手:)
( ° ∀ ° )ノ゙新年快樂~
新年快樂ヽ(‘ ∇‘ )ノ
又有新夥伴了 !
收到~
今天天氣好好(人 •͈ᴗ•͈) 好想出去放風遛自己¯\(°_o)/¯ 可是為了錢錢 為了可以買酷東西 我要努力上班......ಡ ͜ ʖ ಡ (無意義發言)
今天天氣好好 學生放假真好 難怪這幾天網誌流量大跌 嗚嗚嗚嗚嗚(٭°̧̧̧ω°̧̧̧٭)(٭°̧̧̧ω°̧̧̧٭)(٭°̧̧̧ω°̧̧̧٭)(٭°̧̧̧ω°̧̧̧٭)
幫你拍拍手~~
多謝%%%%%~
(因為沒事做了來講講廢話放鬆身心靈♪ヽ(・ˇ∀ˇ・ゞ)) 終於快放假了!!!(「`・ω・)「 話說我昨天跟我男友吵架 所以今天整天都很不爽ಠ︵ಠ 結果工作效率變超好der~¯\_ಠ_ಠ_/¯ 這就4傳說中工作到忘情的滋味嗎(◔‿◔) (我好久沒有這樣沈浸在程式的茫茫大海中了喔齁齁) (結果心情變超好是怎麼回事XD)
沒錯~今天放假 結果昨天我整個超忙XD 壓在身上的開發案快要到尾聲了,但是這種時候才是最累的時候(;´д`)ゞ 休但幾勒!男友?你是女生!?Σ(・口・) 你勇闖者在這個男性98趴眼鏡99趴的業界,實在是佩服你的勇氣(意義不明) 說不定不爽會促進某個什麼腺素然後就各種小宇宙爆發了 雖然不鼓勵常常吵架,但是總之恭喜(??????)
專業文分享,很棒。 (拍手拍起來^^
謝謝(٭°̧̧̧ω°̧̧̧٭)
你專業分享 我專程拍手
感謝你遠道而來的掌聲xD
*****
*****