安卓学习笔记40:基于套接字网络编程

文章目录

  • 零、学习目标
  • 一、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)演示服务器端与安卓客户端进行聊天

零、学习目标

  1. 了解基于套接字网络有两种传输模式
  2. 掌握基于TCP/IP协议的套接字网络编程

一、Socket概述

Socket(套接字)是一种通信机制,可以实现单机或跨网络进行通信,其创建需要明确的区分C(客户端)/S(服务器端),支持多个客户端连接到同一个服务器。

(一)两种传输模式

  1. 面向连接的传输:基于TCP协议,可靠性高,但效率低
  2. 面向无连接的传输:基于UDP协议,可靠性低,但效率高

(二)基于Socket网络编程

  • 在安卓中,直接采用Socket通信应该是我们遇到的最低级的网络运用。尽管已经作了很大程度的抽象,但是纯粹的Socket通信,仍然给开发者留下很多细节需要处理,尤其在服务器端,开发者需要处理多线程以及数据缓冲等的设计问题。相对而言,处于更高抽象层的HTTP等,已经对Socket通信中需要处理的技术细节进行了很好的封装,开发者无须关心,因此,HTTP在网络开发中通常具有决定性的优势。

  • ServerSocket(int aport):创建一个绑定到本机指定端口的服务端Socket;aport就是指定的本机端口。与上述客户端Socket对应,通过TCP连接时,ServerSocket创建后需要在aport端口上进行监听,等待客户端的连接。

三、案例演示 - C/S架构聊天室

(一)运行效果

安卓学习笔记40:基于套接字网络编程_第1张图片

(二)涉及知识点

  1. Swing窗口(JFrame)
  2. Swing文本区(JTextArea)
  3. Swing按钮(JButton)
  4. Java事件处理机制
  5. 数据字节输入流(DataInputStream)
  6. 数据字节输出流(DataOutputStream)
  7. 活动窗口(Activity)
  8. 标签(TextView)
  9. 按钮(Button)
  10. 编辑框(EditText)
  11. 服务器套接字(ServerSocket)
  12. 套接字(Socket)
  13. 消息处理器(Handler)
  14. 线程(Thread)

(三)实现步骤

1、创建聊天服务器端

(1)创建Java项目 - ChatServer

安卓学习笔记40:基于套接字网络编程_第2张图片

(2)创建聊天服务窗口类 - ChatServerWindow

安卓学习笔记40:基于套接字网络编程_第3张图片

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)启动应用,查看效果

安卓学习笔记40:基于套接字网络编程_第4张图片
安卓学习笔记40:基于套接字网络编程_第5张图片

2、创建聊天安卓客户端

(1)创建安卓应用【ChatAndroidClient】

安卓学习笔记40:基于套接字网络编程_第6张图片
安卓学习笔记40:基于套接字网络编程_第7张图片

(2)将图片素材拷贝到drawable目录

安卓学习笔记40:基于套接字网络编程_第8张图片

(3)创建接收按钮背景选择器

安卓学习笔记40:基于套接字网络编程_第9张图片


<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)创建发送按钮背景选择器

安卓学习笔记40:基于套接字网络编程_第10张图片


<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

安卓学习笔记40:基于套接字网络编程_第11张图片


<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

安卓学习笔记40:基于套接字网络编程_第12张图片

<resources>
    <string name="app_name">聊天安卓客户端string>
    <string name="host">服务器地址:string>
    <string name="input_message">请输入聊天内容string>
resources>

(7)在项目清单文件里授权访问因特网

安卓学习笔记40:基于套接字网络编程_第13张图片

(8)主界面类 - MainActivity

安卓学习笔记40:基于套接字网络编程_第14张图片

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!");
            }
        }
    }
}

安卓学习笔记40:基于套接字网络编程_第15张图片

安卓学习笔记40:基于套接字网络编程_第16张图片

3、启动聊天服务器端与安卓客户端进行测试

(1)启动聊天服务器端

安卓学习笔记40:基于套接字网络编程_第17张图片

(2)启动聊天安卓客户端

安卓学习笔记40:基于套接字网络编程_第18张图片

(3)演示服务器端与安卓客户端进行聊天

安卓学习笔记40:基于套接字网络编程_第19张图片
安卓学习笔记40:基于套接字网络编程_第20张图片

你可能感兴趣的:(安卓编程基础)