文章目錄
- 零、學習目標
- 一、Socket概述
- (一)兩種傳輸模式
- (二)基于Socket網路編程
- 三、案例演示 - C/S架構聊天室
- (一)運行效果
- (二)涉及知識點
- (三)實作步驟
- 1、創建聊天服務器端
- (1)創建Java專案 - ChatServer
- (2)創建聊天服務視窗類 - ChatServerWindow
- (3)啟動應用,查看效果
- 2、創建聊天安卓客戶端
- (1)創建安卓應用【ChatAndroidClient】
- (2)將圖片素材拷貝到drawable目錄
- (3)創建接收按鈕背景選擇器
- (4)創建發送按鈕背景選擇器
- (5)主布局資源檔案activity_main.xml
- (6)字串資源檔案strings.xml
- (7)在專案清單檔案里授權訪問因特網
- (8)主界面類 - MainActivity
- 3、啟動聊天服務器端與安卓客戶端進行測驗
- (1)啟動聊天服務器端
- (2)啟動聊天安卓客戶端
- (3)演示服務器端與安卓客戶端進行聊天
零、學習目標
- 了解基于套接字網路有兩種傳輸模式
- 掌味訓于TCP/IP協議的套接字網路編程
一、Socket概述
Socket(套接字)是一種通信機制,可以實作單機或跨網路進行通信,其創建需要明確的區分C(客戶端)/S(服務器端),支持多個客戶端連接到同一個服務器,
(一)兩種傳輸模式
- 面向連接的傳輸:基于TCP協議,可靠性高,但效率低
- 面向無連接的傳輸:基于UDP協議,可靠性低,但效率高
(二)基于Socket網路編程
-
在安卓中,直接采用Socket通信應該是我們遇到的最低級的網路運用,盡管已經作了很大程度的抽象,但是純粹的Socket通信,仍然給開發者留下很多細節需要處理,尤其在服務器端,開發者需要處理多執行緒以及資料緩沖等的設計問題,相對而言,處于更高抽象層的HTTP等,已經對Socket通信中需要處理的技術細節進行了很好的封裝,開發者無須關心,因此,HTTP在網路開發中通常具有決定性的優勢,
-
ServerSocket(int aport):創建一個系結到本機指定埠的服務端Socket;aport就是指定的本機埠,與上述客戶端Socket對應,通過TCP連接時,ServerSocket創建后需要在aport埠上進行監聽,等待客戶端的連接,
三、案例演示 - C/S架構聊天室
(一)運行效果

(二)涉及知識點
- Swing視窗(JFrame)
- Swing文本區(JTextArea)
- Swing按鈕(JButton)
- Java事件處理機制
- 資料位元組輸入流(DataInputStream)
- 資料位元組輸出流(DataOutputStream)
- 活動視窗(Activity)
- 標簽(TextView)
- 按鈕(Button)
- 編輯框(EditText)
- 服務器套接字(ServerSocket)
- 套接字(Socket)
- 訊息處理器(Handler)
- 執行緒(Thread)
(三)實作步驟
1、創建聊天服務器端
(1)創建Java專案 - ChatServer

(2)創建聊天服務視窗類 - ChatServerWindow

package net.hw.chat;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 功能:聊天服務視窗類
* 作者:華衛
* 日期:2021年01月01日
*/
public class ChatServerWindow extends JFrame {
/**
* 定義埠號常量
*/
static final int PORT = 8888;
private JButton btnClose;
private JButton btnSend;
/**
* 資料輸入流
*/
private DataInputStream netIn;
/**
* 資料輸出流
*/
private DataOutputStream netOut;
private JScrollPane panContent;
private JScrollPane panInput;
private JPanel panel1;
private JPanel panel2;
/**
* 服務器端套接字
*/
private static ServerSocket ss;
/**
* 客戶端套接字
*/
private static Socket socket;
/**
* 聊天資訊串列
*/
private JTextArea txtChatMessageList;
/**
* 聊天資訊輸入框
*/
private JTextArea txtInputMessage;
/**
* 來自客戶端的訊息
*/
private static String clientMsg;
/**
* 服務器端的訊息
*/
private static String serverMsg;
/**
* 執行緒回圈控制變數
*/
private static boolean isRunning;
public static void main(String[] args) {
new ChatServerWindow();
}
/**
* 構造方法
*/
public ChatServerWindow() {
super("聊天服務器端");
initUI();
try {
// 創建服務器端套接字
ss = new ServerSocket(PORT);
txtChatMessageList.append("服務器已啟動...\n");
txtChatMessageList.append("等待客戶請求...\n");
isRunning = true;
new Thread(new Runnable() {
@Override
public void run() {
while (isRunning) {
try {
// 監聽其它設備的連接請求,處于阻塞狀態
socket = ss.accept();
if (!txtChatMessageList.getText().toString()
.contains("連接了一個客戶端,")) {
txtChatMessageList.append("連接了一個客戶端,\n");
}
netIn = new DataInputStream(socket.getInputStream());
netOut = new DataOutputStream(socket
.getOutputStream());
// 初始化服務器端訊息
if (null == serverMsg || serverMsg.equals("")) {
serverMsg = "歡迎您,新朋友! ";
}
// 獲取輸出流(套接字輸出流-->資料輸出流)
netOut = new DataOutputStream(socket
.getOutputStream());
// 向客戶端輸出資訊
netOut.writeUTF(serverMsg);
// 清空輸出流緩沖資料
netOut.flush();
// 獲取客戶端訊息
displayClientMsg();
} catch (IOException e) {
}
}
}
}).start();
} catch (IOException e1) {
}
/* 給各個控制元件注冊監聽器,撰寫事件代碼 */
btnSend.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
serverMsg = txtInputMessage.getText();
if (!serverMsg.trim().equals("")) {
txtChatMessageList.append("服務器>>>" + serverMsg + "\n");
if (netOut != null) {
netOut.writeUTF(serverMsg);
}
} else {
JOptionPane.showMessageDialog(null, "不能發送空資訊!", "服務器",
JOptionPane.WARNING_MESSAGE);
}
txtInputMessage.setText("");
txtInputMessage.requestFocus();
} catch (IOException ie) {
}
}
});
// 給關閉按鈕注冊監聽器
btnClose.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
releaseResource();
System.exit(0);
}
});
// 給視窗注冊監聽器
addWindowListener(new WindowAdapter() {
public void windowActivated(WindowEvent e) {
txtInputMessage.requestFocus();
}
public void windowClosing(WindowEvent e) {
releaseResource();
System.exit(0);
}
});
}
/**
* 釋放資源
*/
private void releaseResource() {
isRunning = false;
try {
if (netIn != null && netOut != null) {
netIn.close();
netOut.close();
}
if (socket != null && !socket.isClosed()) {
socket.close();
}
if (ss != null && !ss.isClosed()) {
ss.close();
}
} catch (IOException e) {
}
}
/**
* 初始化用戶界面
*/
private void initUI() {
// 創建組件
panel1 = new JPanel();
panel2 = new JPanel();
txtChatMessageList = new JTextArea(15, 60);
txtInputMessage = new JTextArea(3, 60);
panContent = new JScrollPane(txtChatMessageList,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
panInput = new JScrollPane(txtInputMessage,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
btnClose = new JButton("關閉");
btnSend = new JButton("發送");
// 添加組件
getContentPane().add(panContent, "Center");
getContentPane().add(panel1, "South");
panel1.setLayout(new GridLayout(0, 1));
panel1.add(panInput);
panel1.add(panel2);
panel2.add(btnSend);
panel2.add(btnClose);
// 設定組件屬性
txtChatMessageList.setEditable(false);
txtChatMessageList.setFont(new Font("宋體", Font.PLAIN, 13));
txtInputMessage.setFont(new Font("宋體", Font.PLAIN, 15));
txtChatMessageList.setLineWrap(true);
txtInputMessage.setLineWrap(true);
txtInputMessage.requestFocus();
setSize(450, 350);
setLocation(50, 200);
setResizable(false);
setLocationRelativeTo(null);
setVisible(true);
}
// 顯示客戶端資訊
void displayClientMsg() {
try {
clientMsg = netIn.readUTF();
txtChatMessageList.append("客戶端>>>" + clientMsg + "\n");
} catch (IOException e) {
}
}
}
(3)啟動應用,查看效果


2、創建聊天安卓客戶端
(1)創建安卓應用【ChatAndroidClient】


(2)將圖片素材拷貝到drawable目錄

(3)創建接收按鈕背景選擇器

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/receive" android:state_pressed="false"/>
<item android:drawable="@drawable/receive_pressed" android:state_pressed="true"/>
</selector>
(4)創建發送按鈕背景選擇器

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/send" android:state_pressed="false"/>
<item android:drawable="@drawable/send_pressed" android:state_pressed="true"/>
</selector>
(5)主布局資源檔案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:background="@drawable/background"
android:orientation="vertical"
android:padding="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tvHost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/host"
android:textColor="#0000ff"
android:textSize="20sp" />
<EditText
android:id="@+id/edtHost"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="#ffffff"
android:padding="5dp"
android:singleLine="true"
android:textColor="#000000"
android:textSize="20sp" />
</LinearLayout>
<Button
android:id="@+id/btnReceiveMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@drawable/btn_receive_selector" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/edtMessage"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="4"
android:hint="@string/input_message"
android:paddingLeft="5dp"
android:singleLine="true"
android:textColor="#000000"
android:textSize="20sp" />
<Button
android:id="@+id/btnSendMessage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginTop="5dp"
android:layout_weight="1"
android:background="@drawable/btn_send_selector"
android:textSize="18sp" />
</LinearLayout>
<EditText
android:id="@+id/edtMessageList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="false"
android:gravity="left|top"
android:inputType="textMultiLine|none"
android:scrollbars="vertical"
android:textSize="18sp">
<requestFocus />
</EditText>
</LinearLayout>
(6)字串資源檔案strings.xml

<resources>
<string name="app_name">聊天安卓客戶端</string>
<string name="host">服務器地址:</string>
<string name="input_message">請輸入聊天內容</string>
</resources>
(7)在專案清單檔案里授權訪問因特網

(8)主界面類 - MainActivity

package net.hw.chat_android_client;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 功能:安卓聊天客戶端
* 作者:華衛
* 日期:2021年01月01日
*/
public class MainActivity extends AppCompatActivity {
/**
* 發送資訊按鈕
*/
private Button btnSendMessage;
/**
* 接收資訊按鈕
*/
private Button btnReceiveMessage;
/**
* 訊息編輯框
*/
private EditText edtMessage;
/**
* 客戶端套接字
*/
private Socket socket;
/**
* 訊息處理器(發送與處理訊息)
*/
private Handler handler;
/**
* 服務器埠號
*/
private static final int PORT = 8888;
/**
* 服務器端主機地址
*/
private String host;
/**
* 初始化網路連接的執行緒
*/
private Thread initNetworkThread;
/**
* 聊天訊息構建器
*/
private StringBuilder chatMesssageBuilder;
/**
* 聊天資訊串列編輯框
*/
private EditText edtMessageList;
/**
* 服務器地址
*/
private EditText edtHost;
/**
* 資料輸出流
*/
private static DataOutputStream netOut;
/**
* 資料輸入流
*/
private static DataInputStream netIn;
/**
* 來自客戶端的訊息
*/
private static String clientMsg;
/**
* 服務器端的訊息
*/
private static String serverMsg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局資源檔案設定用戶界面
setContentView(R.layout.activity_main);
// 通過資源索引獲得界面控制元件實體
btnSendMessage = findViewById(R.id.btnSendMessage);
btnReceiveMessage = findViewById(R.id.btnReceiveMessage);
edtMessage = findViewById(R.id.edtMessage);
edtMessageList = findViewById(R.id.edtMessageList);
edtHost = findViewById(R.id.edtHost);
// 設定服務器地址
edtHost.setText("192.168.1.5");
// 實體化聊天訊息構建器
chatMesssageBuilder = new StringBuilder();
// 給發送按鈕注冊監聽器
btnSendMessage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendMessage(); // 發送訊息
}
});
// 給接收按鈕注冊監聽器
btnReceiveMessage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
receiveMessage(); // 接收訊息
}
});
// 給訊息編輯框注冊監聽器
edtMessage.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
sendMessage(); // 收發訊息
}
return false;
}
});
// 創建訊息處理器
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0x001) {
// 設定聊天資訊串列內容
edtMessageList.setText(chatMesssageBuilder.toString());
// 清空輸入框
edtMessage.setText("");
}
}
};
}
/**
* 接收訊息:接收來自服務器端的訊息
*/
private void receiveMessage() {
new Thread() {
@Override
public void run() {
// 獲取主機
host = edtHost.getText().toString();
// 非空校驗
if (host.length() == 0) {
Toast.makeText(MainActivity.this, "請輸入服務器地址!", Toast.LENGTH_LONG);
return;
}
// 采用短連接,接收一次訊息,立馬斷開連接
try {
// 創建客戶端套接字
socket = new Socket(host, PORT);
// 接收服務器端發送的訊息
netIn = new DataInputStream(socket.getInputStream());
// 從資料輸入流讀取內容
serverMsg = netIn.readUTF();
// 在聊天資訊串列里添加服務器端的資訊
chatMesssageBuilder.append("服務器>>>" + serverMsg + "\n");
// 發送訊息
handler.sendEmptyMessage(0x001);
// 關閉輸入流
netIn.close();
// 關閉客戶端套接字
socket.close();
} catch (UnknownHostException e) {
Toast.makeText(MainActivity.this, "未知的主機例外!",
Toast.LENGTH_LONG).show();
} catch (IOException e) {
Toast.makeText(MainActivity.this, "輸入輸出例外!",
Toast.LENGTH_LONG).show();
}
}
}.start();
}
/**
* 發送訊息:向服務器端發送訊息
*/
private void sendMessage() {
// 啟動子執行緒,執行發送聊天內容
new Thread() {
@Override
public void run() {
// 獲取主機
host = edtHost.getText().toString();
// 非空校驗
if (host.length() == 0) {
Toast.makeText(MainActivity.this, "請輸入服務器地址!",
Toast.LENGTH_LONG);
return;
}
// 采用短連接,發送一次訊息,立馬斷開連接
try {
// 創建客戶端套接字
socket = new Socket(host, PORT);
// 向服務器端發送資訊
netOut = new DataOutputStream(socket.getOutputStream());
// 獲取客戶端訊息
clientMsg = edtMessage.getText().toString();
// 不允許發送空訊息給服務器端
if (clientMsg.length() == 0) {
return;
}
// 向服務器端發送訊息
netOut.writeUTF(clientMsg);
// 清空輸出流緩沖資料
netOut.flush();
// 添加客戶端資訊
chatMesssageBuilder.append("客戶端>>>" + clientMsg + "\n");
// 發送訊息
handler.sendEmptyMessage(0x001);
// 關閉輸出流
netOut.close();
// 關閉客戶端套接字
socket.close();
} catch (UnknownHostException e) {
Toast.makeText(MainActivity.this, "未知的主機例外!",
Toast.LENGTH_LONG).show();
} catch (IOException e) {
Toast.makeText(MainActivity.this, "輸入輸出例外!",
Toast.LENGTH_LONG).show();
}
}
}.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (socket != null) {
try {
// 關閉套接字
socket.close();
} catch (Exception e) {
System.out.println("例外:無法關閉Socket!");
}
}
}
}


3、啟動聊天服務器端與安卓客戶端進行測驗
(1)啟動聊天服務器端

(2)啟動聊天安卓客戶端

(3)演示服務器端與安卓客戶端進行聊天


轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/243867.html
標籤:其他
