Android Studio 3.1.4
Build #AI-173.4907809, built on July 24, 2018
JRE: 1.8.0_152-release-1024-b02 amd64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Windows 10 10.0
1.前言
1 從這個帖子往后,開頭聲明版本就不用截圖了,用復制出來的文字,反正效果一樣的 2 3 這次要說的是SocketTcp協議的客戶端 4 5 有些小伙伴可能要問了,昨天才到本地注冊和登錄,今天怎么就開始Socket了,是不是進度太快了 6 7 這個問題,我想說一下哈,我並不是按照教程或者目錄啥的做的 8 9 我是對啥感興趣就做啥,但我也是小白,能學會的東西,對於大家來說沒問題的 10 11 好了,話不多說,開始筆記!Lucky~
2.我們還是先創建一個項目,項目名是TCPclient,布局界面文件名為activity_tcpclient.xml
界面設計最終樣式如下
只后的UI會一次比一次復雜,以后就不口頭描述了,直接貼xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 tools:context=".TCPclient"> 8 9 <Button 10 android:id="@+id/Button" 11 style="@android:style/Widget.Material.Light.Button.Small" 12 android:layout_width="175dp" 13 android:layout_height="49dp" 14 android:layout_marginBottom="8dp" 15 android:layout_marginEnd="8dp" 16 android:layout_marginTop="8dp" 17 android:onClick="link" 18 android:text="連 接 服 務 器" 19 app:layout_constraintBottom_toBottomOf="parent" 20 app:layout_constraintEnd_toEndOf="parent" 21 app:layout_constraintHorizontal_bias="0.96" 22 app:layout_constraintStart_toStartOf="parent" 23 app:layout_constraintTop_toTopOf="parent" 24 app:layout_constraintVertical_bias="0.593" /> 25 26 <EditText 27 android:id="@+id/portEdit" 28 android:layout_width="245dp" 29 android:layout_height="43dp" 30 android:layout_marginBottom="8dp" 31 android:layout_marginEnd="16dp" 32 android:layout_marginStart="8dp" 33 android:layout_marginTop="156dp" 34 android:ems="10" 35 android:inputType="textPersonName" 36 android:text="80" 37 app:layout_constraintBottom_toBottomOf="parent" 38 app:layout_constraintEnd_toEndOf="parent" 39 app:layout_constraintHorizontal_bias="1.0" 40 app:layout_constraintStart_toStartOf="parent" 41 app:layout_constraintTop_toTopOf="parent" 42 app:layout_constraintVertical_bias="0.016" /> 43 44 <TextView 45 android:layout_width="145dp" 46 android:layout_height="51dp" 47 android:layout_marginStart="16dp" 48 android:fontFamily="casual" 49 android:text="TcpClient" 50 android:textSize="30sp" 51 app:layout_constraintBottom_toBottomOf="parent" 52 app:layout_constraintHorizontal_bias="0.051" 53 app:layout_constraintLeft_toLeftOf="parent" 54 app:layout_constraintRight_toRightOf="parent" 55 app:layout_constraintStart_toStartOf="parent" 56 app:layout_constraintTop_toTopOf="parent" 57 app:layout_constraintVertical_bias="0.029" /> 58 59 <TextView 60 android:id="@+id/textView" 61 android:layout_width="96dp" 62 android:layout_height="37dp" 63 android:layout_marginBottom="8dp" 64 android:layout_marginEnd="8dp" 65 android:layout_marginStart="16dp" 66 android:layout_marginTop="8dp" 67 android:text="IP、域名" 68 android:textAlignment="center" 69 android:textSize="22sp" 70 app:layout_constraintBottom_toBottomOf="parent" 71 app:layout_constraintEnd_toEndOf="parent" 72 app:layout_constraintHorizontal_bias="0.0" 73 app:layout_constraintStart_toStartOf="parent" 74 app:layout_constraintTop_toTopOf="parent" 75 app:layout_constraintVertical_bias="0.159" /> 76 77 <TextView 78 android:id="@+id/textView2" 79 android:layout_width="96dp" 80 android:layout_height="37dp" 81 android:layout_marginBottom="8dp" 82 android:layout_marginEnd="8dp" 83 android:layout_marginStart="16dp" 84 android:layout_marginTop="8dp" 85 android:text="端口" 86 android:textAlignment="center" 87 android:textSize="22sp" 88 app:layout_constraintBottom_toBottomOf="parent" 89 app:layout_constraintEnd_toEndOf="parent" 90 app:layout_constraintHorizontal_bias="0.0" 91 app:layout_constraintStart_toStartOf="parent" 92 app:layout_constraintTop_toTopOf="parent" 93 app:layout_constraintVertical_bias="0.3" /> 94 95 <TextView 96 android:id="@+id/textView3" 97 android:layout_width="96dp" 98 android:layout_height="37dp" 99 android:layout_marginBottom="8dp" 100 android:layout_marginEnd="8dp" 101 android:layout_marginStart="16dp" 102 android:layout_marginTop="8dp" 103 android:text="發送數據" 104 android:textAlignment="center" 105 android:textSize="22sp" 106 app:layout_constraintBottom_toBottomOf="parent" 107 app:layout_constraintEnd_toEndOf="parent" 108 app:layout_constraintHorizontal_bias="0.0" 109 app:layout_constraintStart_toStartOf="parent" 110 app:layout_constraintTop_toTopOf="parent" 111 app:layout_constraintVertical_bias="0.45" /> 112 113 <EditText 114 android:id="@+id/IPEdit" 115 android:layout_width="245dp" 116 android:layout_height="43dp" 117 android:layout_marginBottom="8dp" 118 android:layout_marginEnd="16dp" 119 android:layout_marginStart="8dp" 120 android:layout_marginTop="88dp" 121 android:ems="10" 122 android:inputType="textPersonName" 123 android:text="127.0.0.1" 124 app:layout_constraintBottom_toBottomOf="parent" 125 app:layout_constraintEnd_toEndOf="parent" 126 app:layout_constraintHorizontal_bias="1.0" 127 app:layout_constraintStart_toStartOf="parent" 128 app:layout_constraintTop_toTopOf="parent" 129 app:layout_constraintVertical_bias="0.004" /> 130 131 <EditText 132 android:id="@+id/dataEdit" 133 android:layout_width="245dp" 134 android:layout_height="43dp" 135 android:layout_marginBottom="8dp" 136 android:layout_marginEnd="16dp" 137 android:layout_marginStart="8dp" 138 android:layout_marginTop="156dp" 139 android:ems="10" 140 android:inputType="textPersonName" 141 android:text="GET /index.html HTTP/1.1" 142 app:layout_constraintBottom_toBottomOf="parent" 143 app:layout_constraintEnd_toEndOf="parent" 144 app:layout_constraintHorizontal_bias="1.0" 145 app:layout_constraintStart_toStartOf="parent" 146 app:layout_constraintTop_toTopOf="parent" 147 app:layout_constraintVertical_bias="0.23" /> 148 149 <Button 150 android:id="@+id/button" 151 style="@android:style/Widget.Material.Light.Button.Small" 152 android:layout_width="175dp" 153 android:layout_height="49dp" 154 android:layout_marginBottom="8dp" 155 android:layout_marginEnd="8dp" 156 android:layout_marginTop="8dp" 157 android:onClick="play" 158 android:text="發 送 數 據" 159 app:layout_constraintBottom_toBottomOf="parent" 160 app:layout_constraintEnd_toEndOf="parent" 161 app:layout_constraintHorizontal_bias="0.084" 162 app:layout_constraintStart_toStartOf="parent" 163 app:layout_constraintTop_toTopOf="parent" 164 app:layout_constraintVertical_bias="0.593" /> 165 166 <EditText 167 android:id="@+id/editText" 168 android:layout_width="354dp" 169 android:layout_height="177dp" 170 android:layout_marginBottom="8dp" 171 android:layout_marginTop="24dp" 172 android:ems="10" 173 android:inputType="textMultiLine" 174 app:layout_constraintBottom_toBottomOf="parent" 175 app:layout_constraintEnd_toEndOf="parent" 176 app:layout_constraintHorizontal_bias="0.513" 177 app:layout_constraintStart_toStartOf="parent" 178 app:layout_constraintTop_toTopOf="parent" 179 app:layout_constraintVertical_bias="0.977" /> 180 181 182 </android.support.constraint.ConstraintLayout>
3.首先我們得給程序增加兩條權限,打開AndroidManifest.xml文件
加入兩條:
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
一定不要加錯了位置~
3.打開TCPclient項目java文件開始碼代碼
1 package com.shawna.tcpclient; 2 3 import android.content.DialogInterface; 4 import android.support.v7.app.AlertDialog; 5 import android.support.v7.app.AppCompatActivity; 6 import android.os.Bundle; 7 import android.view.View; 8 import android.widget.Button; 9 import android.widget.EditText; 10 11 import java.io.InputStream; 12 import java.io.OutputStream; 13 import java.net.InetAddress; 14 import java.net.Socket; 15 16 public class TCPclient extends AppCompatActivity { 17 18 EditText iptoedit;//ip編輯框對象 19 EditText porttoedit;//端口編輯框對象 20 EditText datatoedit;//數據編輯框對象 21 Button Button;//連接服務器按鈕對象 22 EditText edittotext;//接收的數據顯示編輯框對象 23 Socket Socket = null;//Socket 24 boolean buttontitle = true;//定義一個邏輯變量,用於判斷連接服務器按鈕狀態 25 boolean RD = false;//用於控制讀數據線程是否執行 26 27 OutputStream OutputStream = null;//定義數據輸出流,用於發送數據 28 InputStream InputStream = null;//定義數據輸入流,用於接收數據 29 30 @Override 31 protected void onCreate(Bundle savedInstanceState) { 32 super.onCreate(savedInstanceState); 33 setContentView(R.layout.activity_tcpclient); 34 35 iptoedit = (EditText)findViewById(R.id.IPEdit);//獲取ip地址編輯框對象 36 porttoedit = (EditText)findViewById(R.id.portEdit);//獲取端口編輯框對象 37 datatoedit = (EditText)findViewById(R.id.dataEdit);//獲取欲發送的數據編輯框對象 38 Button = (Button)findViewById(R.id.Button);//獲取連接服務器按鈕對象 39 edittotext = (EditText)findViewById(R.id.editText);//獲取接收數據顯示編輯框對象 40 41 } 42 43 //發送數據按鈕按下 44 public void play(View view){ 45 //驗證編輯框用戶輸入內容是否合法 46 if (thisif()) { 47 //啟動一個新的線程,用於發送數據 48 ThreadSendData t1 = new ThreadSendData(); 49 t1.start(); 50 } else{ 51 //這個地方默認是沒有反應,可以自行修改成信息框提示 52 return; 53 } 54 } 55 56 //連接服務器按鈕按下 57 public void link(View view){ 58 //判斷按鈕狀態 59 if (buttontitle == true){ 60 //如果按鈕沒有被按下,則按鈕狀態改為按下 61 buttontitle = false; 62 //讀數據線程可以執行 63 RD = true; 64 //並創建一個新的線程,用於初始化socket 65 Connect_Thread Connect_thread = new Connect_Thread(); 66 Connect_thread.start(); 67 //改變按鈕標題 68 Button.setText("斷 開 連 接"); 69 }else{ 70 //如果按鈕已經被按下,則改變按鈕標題 71 Button.setText("連 接 服 務 器"); 72 //儲存狀態的變量反轉 73 buttontitle = true; 74 try{ 75 //取消socket 76 Socket.close(); 77 //socket設置為空 78 Socket = null; 79 //讀數據線程不執行 80 RD = false; 81 }catch (Exception e){ 82 //如果想寫的嚴謹一點,可以自行改動 83 e.printStackTrace(); 84 } 85 } 86 } 87 88 //用線程創建Socket連接 89 class Connect_Thread extends Thread{ 90 public void run(){ 91 //定義一個變量用於儲存ip 92 InetAddress ipAddress; 93 try { 94 //判斷socket的狀態,防止重復執行 95 if (Socket == null) { 96 //如果socket為空則執行 97 //獲取輸入的IP地址 98 ipAddress = InetAddress.getByName(iptoedit.getText().toString()); 99 //獲取輸入的端口 100 int port = Integer.valueOf(porttoedit.getText().toString()); 101 //新建一個socket 102 Socket = new Socket(ipAddress, port); 103 //獲取socket的輸入流和輸出流 104 InputStream = Socket.getInputStream(); 105 OutputStream = Socket.getOutputStream(); 106 107 //新建一個線程讀數據 108 ThreadReadData t1 = new ThreadReadData(); 109 t1.start(); 110 } 111 } catch (Exception e) { 112 //如果有錯誤則在這里返回 113 e.printStackTrace(); 114 } 115 } 116 } 117 118 //用線程執行讀取服務器發來的數據 119 class ThreadReadData extends Thread{ 120 public void run() { 121 //定義一個變量用於儲存服務器發來的數據 122 String textdata; 123 //根據RD變量的值判斷是否執行讀數據 124 while (RD) { 125 try { 126 //定義一個字節集,存放輸入的數據,緩存區大小為2048字節 127 final byte[] ReadBuffer = new byte[2048]; 128 //用於存放數據量 129 final int ReadBufferLengh; 130 131 //從輸入流獲取服務器發來的數據和數據寬度 132 //ReadBuffer為參考變量,在這里會改變為數據 133 //輸入流的返回值是服務器發來的數據寬度 134 ReadBufferLengh = InputStream.read(ReadBuffer); 135 136 //驗證數據寬度,如果為-1則已經斷開了連接 137 if (ReadBufferLengh == -1) { 138 //重新歸位到初始狀態 139 RD = false; 140 Socket.close(); 141 Socket = null; 142 buttontitle = true; 143 Button.setText("連 接 服 務 器"); 144 } else { 145 //如果有數據正常返回則進行處理顯示 146 147 /* 148 這個地方有個很大的坑,讓我搞了不少的時間 149 我用其他語言寫的Web服務器程序,默認編碼是gb2312 150 AS的默認編碼是utf-8 151 在獲取服務器發來的數據的時候,程序已經對這段gb2312的數據進行編碼... 152 至於編碼是什么就不知道了 153 我研究了很長時間,怎么轉碼也不對,越轉越亂 154 最后測試出來是gb2312編碼已經被轉碼了,我就先恢復gb2312編碼 155 然后轉成程序不會亂碼的utf-8 156 如果目標服務器編碼是utf8的話就不用轉了 157 */ 158 159 //先恢復成GB2312編碼 160 textdata = new String(ReadBuffer,0,ReadBufferLengh,"GB2312");//原始編碼數據 161 //轉為UTF-8編碼后顯示在編輯框中 162 edittotext.setText(new String(textdata.getBytes(),"UTF-8")); 163 } 164 } catch (Exception e) { 165 e.printStackTrace(); 166 } 167 } 168 } 169 } 170 171 //用線程發送數據 172 class ThreadSendData extends Thread{ 173 public void run(){ 174 try { 175 //用輸出流發送數據 176 OutputStream.write(datatoedit.getText().toString().getBytes()); 177 //發送數據之后會自動斷開連接,所以,恢復為最初的狀態 178 //有個坑要說一下,因為發送完數據還得等待服務器返回,所以,不能把Socket也注銷掉 179 buttontitle = true; 180 //改變按鈕標題 181 Button.setText("連 接 服 務 器"); 182 }catch (Exception e){ 183 e.printStackTrace(); 184 } 185 } 186 } 187 188 //驗證編輯框內容是否合法 189 public boolean thisif (){ 190 //定義一個信息框留作備用 191 AlertDialog.Builder message = new AlertDialog.Builder(this); 192 message.setPositiveButton("確定",click1); 193 194 //分別獲取ip、端口、數據這三項的內容 195 String ip = iptoedit.getText().toString(); 196 String port = porttoedit.getText().toString(); 197 String data = datatoedit.getText().toString(); 198 199 //判斷是否有編輯框為空 200 if (ip == null || ip.length() == 0 || port == null || port.length() == 0 || data == null || data.length() == 0){ 201 //如果有空則彈出提示 202 message.setMessage("各數據不能為空!"); 203 AlertDialog m1 = message.create(); 204 m1.show(); 205 return false; 206 }else{ 207 return true; 208 } 209 } 210 211 //信息框按鈕按下事件 212 public DialogInterface.OnClickListener click1 = new DialogInterface.OnClickListener() { 213 @Override 214 public void onClick(DialogInterface dialog, int which) { 215 dialog.cancel(); 216 } 217 }; 218 219 }
4.終於還是在長篇大論下,完成了記錄~
5.補充知識點
1 /* 2 3 Socket在Android4.0版本開始,不能在主線程中使用! 4 5 一些關鍵操作,不要忘記增加權限 6 7 某些命令自帶轉碼效果,一定要注意轉碼 8 9 */
1 //線程的創建與使用 2 //繼承Thread的線程 3 class 線程名 extends Thread{ 4 public void run(){ 5 //執行的代碼 6 } 7 } 8 //使用線程 9 public void 某個函數名(參數類型 參數名){ 10 線程名 自定義名稱 = new 線程名(); 11 自定義名稱.start(); 12 }
6.Lucky~