close

這個系列終於來到下篇了

這次要講的就是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

怎麼樣?很有誠意對吧(-‿◦)

來看一下專案結構吧

截圖 2021-01-02 下午4.39.38

 

身為一個二合一的APP,所以主畫面(MainActivity.java)便是選擇UDP或是TCP的頁面而已

Screenshot_1609576851

 

當然,這次的文章我們是選擇UDP得這個畫面

 

而在檔案中還有一個CommendFun.java

截圖 2021-01-02 下午4.43.08

 

裡面只放了一個用來取得手機所連線到之WIfi之IP而已

我直接PO一下吧

 

CommendFun.java

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.javaTCPClient.java兩個檔案

如果要知道UDP的話,麻煩電梯上到二樓

->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊上篇-UDP伺服器端與客戶端實現

想知道TCP通訊的Server端,請電梯上一樓

->碼農日常-『Android studio』在Android上實現TCP/UDP區域網路通訊中篇-TCP伺服器端實現

截圖 2021-01-09 下午3.40.25

 

1.2 加入權限

 

本次的專案因為涉及到網路使用以及Wifi使用

因此必須加入相關權限

 

請至AndroidManifest.xml的部分

加入以下粉底白字兩行

 

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. 畫介面

 

這是這次介面的內容

Screenshot_1610777856

請仔細看一下本介面右上角有個切換元件

由於一個IP不會同時存在Server端與Client端

因此我們必須在介面上加以互鎖以防止閃退

效果如下

 

來吧,上介面

 

activity_t_c_p.xml

截圖 2021-01-09 下午4.02.01

<?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中,由於只需要連線到指定裝置,並與之溝通

因此整體來說就簡單許多(・ωー)~☆

來吧,看程式

 

TCPClient.java

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出來

 

TCPActvity.java

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, 這系列的文章就停在這裡

希望這些文章對各位有幫助!(・ωー)~☆

TK

arrow
arrow

    碼農日常 發表在 痞客邦 留言(23) 人氣()