Andorid达成TCP通讯


android官方文档:https://developer.android.google.cn/reference/kotlin/java/net/Socket?authuser=002
软件一共有两种模式分别是TCP客户端模式和服务端模式

上效果

客户端模式(点对点通讯,一对一的意思):
在这里插入图片描述
服务端模式(一对多,广播模式):
在这里插入图片描述

一、图片资源

将图片复制保存于文件目录drawable文件夹下
在这里插入图片描述

一、介绍

服务端(Server)步骤:
创建ServerSocket:服务端通过创建一个ServerSocket对象并绑定到指定端口来监听客户端的连接请求。
等待客户端连接:调用ServerSocket的accept()方法,该方法会阻塞直到有客户端连接。当连接成功时,accept()方法返回一个Socket对象,代表与客户端的连接。
获取输入输出流:通过返回的Socket对象获取输入流(InputStream)和输出流(OutputStream),用于与客户端进行数据读写。
读写数据:通过输入流读取客户端发送的数据,通过输出流向客户端发送数据。
关闭连接:通信完成后,关闭输入输出流和Socket。
客户端(Client)步骤:
创建Socket:客户端创建一个Socket对象,指定服务端的IP地址和端口号,尝试连接服务端。
获取输入输出流:连接建立后,通过Socket对象获取输入流和输出流。
读写数据:通过输出流向服务端发送数据,通过输入流读取服务端返回的数据。
关闭连接:通信完成后,关闭输入输出流和Socket。

二、大致流程

1.初始界面,定义一个Switch控件实现按键切换,最底下定义ScrollView,实现一个消息的滚动

在这里插入图片描述

代码如下:

<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#D7F7FF"
    android:padding="16dp">
  <Switch
    android:id="@+id/switch1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="50dp"
    android:checked="true"
    android:text="当前连接模式为:TCPClient" />
  <TextView
    android:id="@+id/tvStatus"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="未连接"
    android:textColor="#000"
    android:textSize="18sp" />
  <!-- 服务器模式下的端口输入 -->
    <LinearLayout
      android:id="@+id/serverPortLayout"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="horizontal"
      android:visibility="gone">
    <EditText
      android:id="@+id/etServerPort"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:hint="服务器端口"
      android:inputType="number"
      android:text="8080" />
  </LinearLayout>
  <!-- 客户端模式下的IP和端口输入 -->
    <LinearLayout
      android:id="@+id/clientLayout"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="horizontal">
    <EditText
      android:id="@+id/etServerIP"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:hint="服务器IP"
      android:text="192.168.1.100" />
    <EditText
      android:id="@+id/etClientPort"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:hint="端口"
      android:inputType="number"
      android:text="8080" />
  </LinearLayout>
  <Button
    android:id="@+id/btnConnect"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="连接" />
  <Button
    android:id="@+id/btnDisconnect"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="断开连接"
    android:enabled="false" />
  <View
    android:layout_width="match_parent"
    android:layout_height="1dp"
    android:background="#ccc"
    android:layout_marginVertical="8dp" />
  <EditText
    android:id="@+id/etMessage"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textColor="#000"
    android:hint="输入消息" />
  <Button
    android:id="@+id/btnSend"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="发送"
    android:enabled="false" />
  <TextView
    android:id="@+id/tvreceivet"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="发送的消息:"
    android:textStyle="bold" />
  <ScrollView
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1">
  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
  <ImageView
    android:id="@+id/imageView"
    android:layout_width="0dp"
    android:layout_height="30dp"
    android:layout_weight="0.2"
    app:srcCompat="@drawable/people" />
  <TextView
    android:id="@+id/tvMessages"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:textColor="#000"
    android:textSize="20dp" />
</LinearLayout>
</ScrollView>
</LinearLayout>

在这里面界面采用的是LinearLayout即线性布局,这种好处是有着极大的适配性,线性布局有两种,水平布局和垂直布局,有兴趣的可以尝试一下。
新建文件
我们找到java文件下新建类,将名称命名为SimpleSocketServer
在这里插入图片描述

代码如下:
SimpleSocketServer.java

package com.example.soket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* 实现TCP通信服务端功能,支持多客户端连接和消息广播
*/
public class SimpleSocketServer {
// 服务器套接字,用于监听客户端连接
private ServerSocket serverSocket;
// 客户端连接处理器列表,用于管理所有连接的客户端
private List<ClientHandler> clients = new ArrayList<>();
  // 服务器运行状态标志
  private boolean isRunning = false;
  /**
  * 启动服务器并在指定端口监听
  * port为端口号
  */
  public void start(int port) {
  try {
  // 创建服务器套接字,绑定到指定端口
  serverSocket = new ServerSocket(port);
  isRunning = true;
  System.out.println("服务器启动,监听端口: " + port);
  // 主循环,持续接受客户端连接
  while (isRunning) {
  // 等待客户端连接
  Socket clientSocket = serverSocket.accept();
  System.out.println("客户端连接: " + clientSocket.getInetAddress());
  // 为每个客户端创建独立的处理器,多线程防止卡死
  ClientHandler clientHandler = new ClientHandler(clientSocket);
  clients.add(clientHandler);
  // 在新线程中处理客户端通信
  new Thread(clientHandler).start();
  }
  } catch (IOException e) {
  System.out.println("服务器异常: " + e.getMessage());
  }
  }
  /**
  * 停止服务器并释放资源
  */
  public void stop() {
  isRunning = false;
  try {
  // 停止所有客户端连接
  for (ClientHandler client : clients) {
  client.stop();
  }
  // 关闭服务器套接字
  if (serverSocket != null) {
  serverSocket.close();
  }
  System.out.println("服务器已停止");
  } catch (IOException e) {
  //否抛出异常
  System.out.println("停止服务器时出错: " + e.getMessage());
  }
  }
  /**
  * 单个客户端的通信
  */
  private class ClientHandler implements Runnable {
  // 客户端套接字
  private Socket clientSocket;
  // 输出流,用于向客户端发送数据
  private PrintWriter out;
  // 输入流,用于接收客户端数据
  private BufferedReader in;
  // 客户端连接状态标志
  private boolean isConnected = false;
  public ClientHandler(Socket socket) {
  this.clientSocket = socket;
  }
  /**
  * 客户端通信处理主方法
  */
  @Override
  public void run() {
  try {
  // 初始化输入输出流
  out = new PrintWriter(clientSocket.getOutputStream(), true);
  in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
  isConnected = true;
  String inputLine;
  // 持续读取客户端发送的消息
  while (isConnected && (inputLine = in.readLine()) != null) {
  System.out.println("收到消息: " + inputLine);
  // 将收到的消息广播给所有客户端
  broadcastMessage("客户端说: " + inputLine);
  }
  } catch (IOException e) {
  System.out.println("客户端处理异常: " + e.getMessage());
  } finally {
  // 确保连接被正确关闭
  stop();
  }
  }
  /**
  * 向当前客户端发送消息
  * message为要发送消息的内容
  */
  public void sendMessage(String message) {
  if (out != null) {
  out.println(message);
  }
  }
  /**
  * 停止当前客户端连接并释放资源
  */
  public void stop() {
  isConnected = false;
  try {
  // 关闭所有流和套接字
  if (in != null) in.close();
  if (out != null) out.close();
  if (clientSocket != null) clientSocket.close();
  // 从客户端列表中移除
  clients.remove(this);
  System.out.println("客户端断开连接");
  } catch (IOException e) {
  System.out.println("关闭客户端连接时出错: " + e.getMessage());
  }
  }
  }
  /**
  * 广播消息给所有连接的客户端,服务端为一对多
  */
  private void broadcastMessage(String message) {
  for (ClientHandler client : clients) {
  client.sendMessage(message);
  }
  }
  /**
  * 服务器主入口方法
  */
  public static void main(String[] args) {
  SimpleSocketServer server = new SimpleSocketServer();
  // 启动服务器,监听2333端口
  server.start(2333);
  Runtime.getRuntime().addShutdownHook(new Thread(() -> {
  System.out.println("正在关闭服务器...");
  server.stop();
  }));
  // 控制台输入处理:支持服务器向所有客户端发送消息
  try (BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) {
  String command;
  // 读取控制台输入
  while ((command = consoleReader.readLine()) != null) {
  if ("stop".equalsIgnoreCase(command)) {
  // 输入"stop"命令停止服务器
  server.stop();
  break;
  } else {
  // 其他输入作为消息广播给所有客户端
  server.broadcastMessage("服务器说: " + command);
  }
  }
  } catch (IOException e) {
  e.printStackTrace();
  }
  }
  }

MainActivity.java

package com.example.soket;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
    private TextView tvStatus, tvMessages, tvrect;
    private EditText etServerIP, etClientPort, etServerPort, etMessage;
    private Button btnConnect, btnDisconnect, btnSend;
    private LinearLayout serverPortLayout, clientLayout;
    private Switch aSwitch;
    // 客户端相关
    private Socket clientSocket;
    private PrintWriter clientOut;
    private BufferedReader clientIn;
    private boolean isClientConnected = false;
    // 服务器相关
    private ServerSocket serverSocket;
    private Socket serverClientSocket;
    private PrintWriter serverOut;
    private BufferedReader serverIn;
    private boolean isServerRunning = false;
    private boolean isServerClientConnected = false;
    private Handler handler = new Handler(Looper.getMainLooper());
    private ExecutorService executorService = Executors.newFixedThreadPool(2);
    private boolean isServerMode = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();
        setupListeners();
    }
    private void initViews() {
        tvStatus = findViewById(R.id.tvStatus);
        tvMessages = findViewById(R.id.tvMessages);
        etServerIP = findViewById(R.id.etServerIP);
        etClientPort = findViewById(R.id.etClientPort);
        etServerPort = findViewById(R.id.etServerPort);
        etMessage = findViewById(R.id.etMessage);
        btnConnect = findViewById(R.id.btnConnect);
        btnDisconnect = findViewById(R.id.btnDisconnect);
        btnSend = findViewById(R.id.btnSend);
        aSwitch = findViewById(R.id.switch1);
        tvrect = findViewById(R.id.tvreceivet);
        serverPortLayout = findViewById(R.id.serverPortLayout);
        clientLayout = findViewById(R.id.clientLayout);
        switchserver();
    }
    private void setupListeners() {
        btnConnect.setOnClickListener(v -> {
            if (isServerMode) {
                startServer();
            } else {
                connectToServer();
            }
        });
        btnDisconnect.setOnClickListener(v -> {
            if (isServerMode) {
                stopServer();
            } else {
                disconnectFromServer();
            }
        });
        btnSend.setOnClickListener(v -> sendMessage());
    }
    private void connectToServer() {
        String ip = etServerIP.getText().toString();
        String portStr = etClientPort.getText().toString();
        if (ip.isEmpty() || portStr.isEmpty()) {
            updateStatus("请输入有效的IP和端口");
            return;
        }
        int port = Integer.parseInt(portStr);
        updateStatus("正在连接...");
        executorService.execute(() -> {
            try {
                clientSocket = new Socket(ip, port);
                clientOut = new PrintWriter(clientSocket.getOutputStream(), true);
                clientIn = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                isClientConnected = true;
                handler.post(() -> {
                    updateStatus("已连接到服务器");
                    btnConnect.setEnabled(false);
                    btnDisconnect.setEnabled(true);
                    btnSend.setEnabled(true);
                });
                startReceivingClientMessages();
            } catch (IOException e) {
                handler.post(() -> updateStatus("连接失败: " + e.getMessage()));
            }
        });
    }
    private void disconnectFromServer() {
        executorService.execute(() -> {
            try {
                isClientConnected = false;
                if (clientOut != null) clientOut.close();
                if (clientIn != null) clientIn.close();
                if (clientSocket != null) clientSocket.close();
                handler.post(() -> {
                    updateStatus("已断开连接");
                    btnConnect.setEnabled(true);
                    btnDisconnect.setEnabled(false);
                    btnSend.setEnabled(false);
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
    private void startServer() {
        String portStr = etServerPort.getText().toString();
        if (portStr.isEmpty()) {
            updateStatus("请输入服务器端口");
            return;
        }
        int port = Integer.parseInt(portStr);
        updateStatus("正在启动服务器...");
        executorService.execute(() -> {
            try {
                serverSocket = new ServerSocket(port);
                isServerRunning = true;
                handler.post(() -> {
                    updateStatus("服务器已启动,等待客户端连接...");
                    btnConnect.setEnabled(false);
                    btnDisconnect.setEnabled(true);
                    btnSend.setEnabled(false);
                });
                // 等待客户端连接
                serverClientSocket = serverSocket.accept();
                serverOut = new PrintWriter(serverClientSocket.getOutputStream(), true);
                serverIn = new BufferedReader(new InputStreamReader(serverClientSocket.getInputStream()));
                isServerClientConnected = true;
                handler.post(() -> {
                    updateStatus("客户端已连接");
                    btnSend.setEnabled(true);
                });
                startReceivingServerMessages();
            } catch (IOException e) {
                if (isServerRunning) {
                    handler.post(() -> updateStatus("服务器错误: " + e.getMessage()));
                }
            }
        });
    }
    private void stopServer() {
        executorService.execute(() -> {
            try {
                isServerRunning = false;
                isServerClientConnected = false;
                if (serverOut != null) serverOut.close();
                if (serverIn != null) serverIn.close();
                if (serverClientSocket != null) serverClientSocket.close();
                if (serverSocket != null) serverSocket.close();
                handler.post(() -> {
                    updateStatus("服务器已停止");
                    btnConnect.setEnabled(true);
                    btnDisconnect.setEnabled(false);
                    btnSend.setEnabled(false);
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
    private void sendMessage() {
        String message = etMessage.getText().toString();
        if (message.isEmpty()) return;
        executorService.execute(() -> {
            if (isServerMode && isServerClientConnected && serverOut != null) {
                serverOut.println(message);
                handler.post(() -> {
                    etMessage.setText("");
                    appendMessage("服务器发送: " + message);
                });
            } else if (!isServerMode && isClientConnected && clientOut != null) {
                clientOut.println(message);
                handler.post(() -> {
                    etMessage.setText("");
                    appendMessage("客户端发送: " + message);
                });
            }
        });
    }
    private void startReceivingClientMessages() {
        executorService.execute(() -> {
            try {
                String message;
                while (isClientConnected && (message = clientIn.readLine()) != null) {
                    String finalMessage = message;
                    handler.post(() -> appendMessage("客户端接收: " + finalMessage));
                }
            } catch (IOException e) {
                if (isClientConnected) {
                    handler.post(() -> updateStatus("连接异常: " + e.getMessage()));
                }
            }
        });
    }
    private void startReceivingServerMessages() {
        executorService.execute(() -> {
            try {
                String message;
                while (isServerClientConnected && (message = serverIn.readLine()) != null) {
                    String finalMessage = message;
                    handler.post(() -> appendMessage("服务器接收: " + finalMessage));
                }
            } catch (IOException e) {
                if (isServerClientConnected) {
                    handler.post(() -> updateStatus("客户端断开连接"));
                    // 重新等待客户端连接
                    if (isServerRunning) {
                        handler.post(() -> {
                            updateStatus("等待客户端连接...");
                            btnSend.setEnabled(false);
                        });
                        stopServer();
                        startServer();
                    }
                }
            }
        });
    }
    private void switchserver() {
        aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
                isServerMode = !isChecked;
                if (isChecked) {
                    // TCP Client 模式
                    aSwitch.setText("当前连接模式为:TCPClient");
                    tvrect.setText("Client发送:");
                    serverPortLayout.setVisibility(android.view.View.GONE);
                    clientLayout.setVisibility(android.view.View.VISIBLE);
                    btnConnect.setText("连接");
                    btnDisconnect.setText("断开连接");
                } else {
                    // TCP Server 模式
                    aSwitch.setText("当前连接模式为:TCPServer");
                    tvrect.setText("Server发送:");
                    serverPortLayout.setVisibility(android.view.View.VISIBLE);
                    clientLayout.setVisibility(android.view.View.GONE);
                    btnConnect.setText("启动服务器");
                    btnDisconnect.setText("停止服务器");
                }
                // 重置状态
                resetConnection();
            }
        });
    }
    private void resetConnection() {
        // 断开现有连接
        if (isServerMode && isServerRunning) {
            stopServer();
        } else if (!isServerMode && isClientConnected) {
            disconnectFromServer();
        }
        handler.post(() -> {
            updateStatus("未连接");
            btnConnect.setEnabled(true);
            btnDisconnect.setEnabled(false);
            btnSend.setEnabled(false);
            tvMessages.setText("");
        });
    }
    private void updateStatus(String status) {
        tvStatus.setText("状态: " + status);
    }
    private void appendMessage(String message) {
        tvMessages.append(message + "\n");
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        executorService.shutdown();
        if (isServerMode) {
            stopServer();
        } else {
            disconnectFromServer();
        }
    }
}

2.设置网络权限

打开AndroidMainfest.xml
添加网络权限
在这里插入图片描述

<uses-permission android:name="android.permission.INTERNET" /><!--网络权限-->

3.实现效果:

在这里插入图片描述
在这里插入图片描述

总结:

服务端模式:启动本地服务监听端口,等待客户端连接

客户端模式:连接指定IP和端口的远程服务器

实时通信:支持双向消息收发,完整显示通信记录

技术特点
使用ExecutorService线程池处理网络操作,避免主线程阻塞

Handler机制实现线程间UI更新

完整的连接状态管理和异常处理

自动资源释放,防止内存泄漏

工作流程
模式选择 → 2. 参数配置 → 3. 建立连接 → 4. 消息通信 → 5. 断开清理

代码文件:
通过网盘分享的文件:soket.rar
链接: https://pan.baidu.com/s/1_Dg1kdrS1LNUVc3mX6NUMA?pwd=1111 提取码: 1111
网络调试助手:
通过网盘分享的文件:网络调试助手.exe
链接: https://pan.baidu.com/s/1lSJ28OpqA-v6qjJ_W6d_5g?pwd=1111 提取码: 1111

声明:

萌新一枚,如有错误地方,恳请各位大佬批评指正!
图片素材和软件仅为个人学习、研究及欣赏之用。本人尊重并感谢所有设计师的创作成果。相关图片版权归原作者所有,任何商业用途均须获得原作者授权。如涉及侵权,请及时联系我进行删除。

posted on 2025-11-08 13:39  blfbuaa  阅读(4)  评论(0)    收藏  举报