您好,登錄后才能下訂單哦!
TCP/IP通信協議是一種可靠的網絡協議,它在通信的兩端各建立一個Socket,從而在通信的兩端之間形成網絡虛擬鏈路。一旦建立了虛擬的網絡鏈路,兩端的程序就可以通過虛擬鏈路進行通信了。Java對基于TCP協議的網絡通信提供了良好的封裝,Java使用Socket對象來代表兩端通信接口,并通過Socket產生IO流來進行網絡通信。
下面的程序Demo是實現一個簡單的C/S聊天室的應用,每個客戶端該包含兩條線程:一條負責生成主界面,響應用戶動作,并將用戶輸入的數據寫入Socket對應的輸出流中;另一條負責讀取Socket對應的輸入流中的數據(從服務器發送過來的數據),并負責將這些數據在程序界面上顯示出來。
客戶端程序是一個Android應用,因此需要創建一個Android項目,這個Android應用的界面中包含兩個文本框:一個用于接收用戶的輸入;另一個用于顯示聊天信息。界面中還有一個按鈕,當用戶單擊該按鈕時,程序向服務器發送聊天信息。
layout/activity_main.xml界面布局代碼如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <!-- 定義一個文本框,它用于接收用戶的輸入 --> <EditText android:id="@+id/input" android:layout_width="280dp" android:layout_height="wrap_content" /> <Button android:id="@+id/send" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="8dp" android:text="發送" /> </LinearLayout> <!-- 定義一個文本框,它用于顯示來自服務器的信息 --> <TextView android:id="@+id/show" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffff" android:gravity="top" android:textColor="#f000" android:textSize="18sp" /> </LinearLayout>
客戶端的Activity負責生成程序界面,并為程序的按鈕單擊事件綁定事件監聽器,當用戶單擊按鈕時向服務器發送信息。
MainActivity.java邏輯代碼如下:
package com.fukaimei.multithreadclient; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends AppCompatActivity { // 定義界面上的兩個文本框 EditText input; TextView show; // 定義界面上的一個按鈕 Button send; Handler handler; // 定義與服務器通信的子線程 ClientThread clientThread; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); input = (EditText) findViewById(R.id.input); send = (Button) findViewById(R.id.send); show = (TextView) findViewById(R.id.show); handler = new Handler() // ② { @Override public void handleMessage(Message msg) { // 如果消息來自于子線程 if (msg.what == 0x123) { // 將讀取的內容追加顯示在文本框中 show.append("\n" + msg.obj.toString()); } } }; clientThread = new ClientThread(handler); // 客戶端啟動ClientThread線程創建網絡連接、讀取來自服務器的數據 new Thread(clientThread).start(); // ① send.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { try { // 當用戶按下發送按鈕后,將用戶輸入的數據封裝成Message // 然后發送給子線程的Handler Message msg = new Message(); msg.what = 0x234; msg.obj = input.getText().toString(); clientThread.revHandler.sendMessage(msg); // 清空input文本框 input.setText(""); } catch (Exception e) { e.printStackTrace(); } } }); } }
當用戶單擊該程序界面中的“發送”按鈕后,程序將會把input輸入框中的內容發送給clientThread的revHandler對象,clientThread負責將用戶輸入的內容發送給服務器。
ClientThread子線程負責建立與遠程服務器的連接,并負責與遠程服務器通信,讀到數據之后便通過Handler對象發送一條消息;當ClientThread子線程收到UI線程發送過來的消息后,還負責將用戶輸入的內容發送給遠程服務器。
ClientThread.java邏輯代碼如下:
package com.fukaimei.multithreadclient; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; import java.net.SocketTimeoutException; public class ClientThread implements Runnable { private static final String TAG = "ClientThread"; private Socket s; // 定義向UI線程發送消息的Handler對象 private Handler handler; // 定義接收UI線程的消息的Handler對象 public Handler revHandler; // 該線程所處理的Socket所對應的輸入流 BufferedReader br = null; OutputStream os = null; public ClientThread(Handler handler) { this.handler = handler; } public void run() { try { s = new Socket("172.xx.xx.xxx", 30000); br = new BufferedReader(new InputStreamReader( s.getInputStream())); os = s.getOutputStream(); // 啟動一條子線程來讀取服務器響應的數據 new Thread() { @Override public void run() { String content = null; // 不斷讀取Socket輸入流中的內容 try { while ((content = br.readLine()) != null) { // 每當讀到來自服務器的數據之后,發送消息通知程序 // 界面顯示該數據 Message msg = new Message(); msg.what = 0x123; msg.obj = content; handler.sendMessage(msg); } } catch (IOException e) { e.printStackTrace(); } } }.start(); // 為當前線程初始化Looper Looper.prepare(); // 創建revHandler對象 revHandler = new Handler() { @Override public void handleMessage(Message msg) { // 接收到UI線程中用戶輸入的數據 if (msg.what == 0x234) { // 將用戶在文本框內輸入的內容寫入網絡 try { os.write((msg.obj.toString() + "\r\n") .getBytes("utf-8")); } catch (Exception e) { e.printStackTrace(); } } } }; // 啟動Looper Looper.loop(); } catch (SocketTimeoutException e1) { Log.d(TAG, "網絡連接超時!"); } catch (Exception e) { e.printStackTrace(); } } }
上面線程的功能也非常簡單,它只是不斷地獲取Socket輸入流中的內容,當讀到Socket輸入流中的內容后,便通過Handler對象發送一條消息,消息負責攜帶讀到的數據。除此之外,該子線程還負責讀取UI線程發送的消息,接收到消息之后,該子線程負責中攜帶的數據發送給遠程服務器。
服務器端應該包含多條線程,每個Socket對應一條線程,該線程負責讀取Socket對應輸入流,并將讀到的數據向每個Socket輸出流發送一遍,因此需要在服務器端使用List來保存所有的Socket。
下面是服務器端的代碼。程序為服務器提供了兩個類:一個是創建ServerSocket監聽的主類;另一個是負責處理每個Socket通信的線程類。
/MultiThreadServer/src/MyServer.java邏輯代碼如下:
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; public class MyServer { // 定義保存所有Socket的ArrayList public static ArrayList<Socket> socketList = new ArrayList<Socket>(); public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(30000); while (true) { // 此行代碼會阻塞,將一直等待別人的連接 Socket s = ss.accept(); socketList.add(s); // 每當客戶端連接后啟動一條ServerThread線程為該客戶端服務 new Thread(new ServerThread(s)).start(); } } }
上面的程序是服務器端只負責接收客戶端Socket的連接請求,每當客戶端Socket連接到該ServerSocket之后,程序將對應Socket加入socketList集合中保存,并為該Socket啟動一條線程,該程序負責處理該Socket所有的通信任務。服務器端線程類的代碼如下。
/MultiThreadServer/src/ServerThread.java邏輯代碼如下:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; import java.util.Iterator; // 負責處理每條線程通信的線程類 public class ServerThread implements Runnable { // 定義當前線程所處理的Socket Socket s = null; // 該線程所處理的Socket所對應的輸入流 BufferedReader br = null; public ServerThread(Socket s) throws IOException { this.s = s; // 初始化該Socket對應的輸入流 br = new BufferedReader(new InputStreamReader(s.getInputStream(), "utf-8")); } @Override public void run() { String content = null; // 采用循環不斷從Socket中讀取客戶端發送過來的數據 while ((content = readFromClient()) != null) { // 遍歷socketList中的每個Socket // 將讀取的內容向每個Socket發送一次 for (Iterator<Socket> it = MyServer.socketList.iterator(); it.hasNext();) { Socket s = it.next(); try { OutputStream os = s.getOutputStream(); os.write((content + "\n").getBytes("utf-8")); } catch (Exception e) { e.printStackTrace(); // 刪除該Socket it.remove(); System.out.println(MyServer.socketList); } } } } // 定義讀取客戶端數據的方法 private String readFromClient() { try { return br.readLine(); } catch (IOException e) { // 如果捕獲到異常,表明該Socket對應的客戶端已經關閉 e.printStackTrace(); // 刪除該Socket MyServer.socketList.remove(s); } return null; } }
上面的服務器端線程類不斷讀取客戶端數據,程序使用readFromClient()方法來讀取客戶端數據,如果在讀數據過程中捕獲到IOException異常,則表明該Socket對應的客戶端Socket出現問題,程序就將該Socket從socketList中刪除。
當服務器線程讀到客戶端數據之后,程序遍歷socketList集合,并將該數據向socketList集合中的每個Socket發送一次——該服務器線程將把從Socket中讀到的數據向socketList中的每個Socket轉發一次。
先運行上面程序的MyServer類,該類運行后只是作為服務器,看不到任何輸出。接著可以運行Android客戶端——相當于啟動聊天界面登錄該服務器,接下來在任何一個Android客戶端輸入一些內容后單擊“發送”按鈕,將可以看到所有客戶端(包含自己)都會收到剛剛輸入的內容,這樣就簡單實現了一個C/S結構的聊天室的功能。
注意:由于該程序需要訪問互聯網,因此還需要在清單文件AndroidManifest.xml文件中授權訪問互聯網的權限:
<!-- 授權訪問互聯網--> <uses-permission android:name="android.permission.INTERNET" />
Demo程序運行效果界面截圖如下:
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。