You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Angular请求停止Spring Boot中运行的ServerSocket?

解决方案:通过API请求终止Spring Boot中的ServerSocket服务

你的核心问题有两个:一是当前startServer()控制器方法是同步执行的,启动服务器后会一直卡在while(true)循环里,占用Spring Boot的请求处理线程,导致后续的停止请求根本无法被处理;二是你的ServerSocket服务没有设计可外部触发的终止机制。下面是具体的修复方案:

1. 重构Server类,添加可终止的控制逻辑

首先要把ServerSocket实例和循环控制变量提取为类成员(避免用static方法引发的线程安全问题),用volatile关键字保证多线程下的变量可见性,确保停止信号能被监听线程及时感知:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public class Server {
    private static final int PORT = 2014;
    private ServerSocket serverSocket;
    private ArrayList<Room> listRooms;
    // 用volatile保证多线程下的可见性,控制循环是否继续
    private volatile boolean isRunning = false;

    public void start() throws IOException {
        serverSocket = new ServerSocket(PORT);
        Log.log("Server initialized on port " + PORT);
        listRooms = new ArrayList<>();
        Room room = new Room("Principal");
        addRoom(room);
        isRunning = true;

        // 启动监听循环
        while (isRunning) {
            try {
                Socket socket = serverSocket.accept();
                Log.log("Established connection with " + socket.getInetAddress().getHostAddress());
                new Thread(new User(socket, room)).start();
            } catch (IOException e) {
                // 如果是因为serverSocket被关闭导致的异常,说明是正常终止,无需报错
                if (!isRunning) {
                    Log.log("Server stopped gracefully");
                } else {
                    Log.log("Server error: " + e.getMessage());
                }
            }
        }
    }

    public void stop() throws IOException {
        isRunning = false;
        // 关闭ServerSocket会中断accept()方法,抛出IOException,从而退出循环
        if (serverSocket != null && !serverSocket.isClosed()) {
            serverSocket.close();
        }
        Log.log("Server shutdown requested");
    }

    public boolean isRunning() {
        return isRunning;
    }

    private void addRoom(Room room) {
        listRooms.add(room);
    }

    // 保留你的Room、User等其他类的实现
}

2. 修改Spring Boot控制器,异步启动服务器并保存实例

现在需要把Server实例保存为控制器的成员,并且异步启动服务器,避免阻塞请求处理线程,这样后续的停止请求才能被正常处理:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;

@RestController
@RequestMapping("/api/server")
public class ServerController {
    // 保存Server实例,用于后续停止操作
    private Server server;

    @PostMapping("/start")
    public String startServer() {
        if (server != null && server.isRunning()) {
            return "Server is already running";
        }
        try {
            server = new Server();
            // 用异步线程启动服务器,避免阻塞控制器线程
            new Thread(server::start).start();
            return "Server started successfully";
        } catch (IOException e) {
            return "Failed to start server: " + e.getMessage();
        }
    }

    @PostMapping("/stop")
    public String stopServer() {
        if (server == null || !server.isRunning()) {
            return "Server is not running";
        }
        try {
            server.stop();
            server = null; // 重置实例,允许重新启动
            return "Server stopped successfully";
        } catch (IOException e) {
            return "Failed to stop server: " + e.getMessage();
        }
    }
}

注意:这里用@PostMapping而非@GetMapping,因为启动/停止服务器是修改系统状态的操作,符合RESTful规范;同时给Server类添加了isRunning()方法来暴露运行状态,避免重复启动。

3. Angular端发送请求的示例

在Angular中,你可以通过HttpClient来调用这两个控制接口,实现前端的启动/停止操作:

服务层代码

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ServerService {
  private baseUrl = 'http://localhost:8080/api/server'; // 替换为你的Spring Boot端口

  constructor(private http: HttpClient) { }

  startServer() {
    return this.http.post(`${this.baseUrl}/start`, {});
  }

  stopServer() {
    return this.http.post(`${this.baseUrl}/stop`, {});
  }
}

组件代码

import { Component } from '@angular/core';
import { ServerService } from './server.service';

@Component({
  selector: 'app-server-control',
  template: `
    <button (click)="start()" [disabled]="isRunning">Start Server</button>
    <button (click)="stop()" [disabled]="!isRunning">Stop Server</button>
    <p class="status">{{statusMessage}}</p>
  `,
  styles: [`
    .status { margin-top: 10px; color: #666; }
    button { margin-right: 10px; padding: 8px 16px; }
  `]
})
export class ServerControlComponent {
  isRunning = false;
  statusMessage = '';

  constructor(private serverService: ServerService) { }

  start() {
    this.serverService.startServer().subscribe({
      next: (res: any) => {
        this.statusMessage = res;
        this.isRunning = true;
      },
      error: (err) => {
        this.statusMessage = 'Error starting server: ' + err.message;
      }
    });
  }

  stop() {
    this.serverService.stopServer().subscribe({
      next: (res: any) => {
        this.statusMessage = res;
        this.isRunning = false;
      },
      error: (err) => {
        this.statusMessage = 'Error stopping server: ' + err.message;
      }
    });
  }
}

额外优化建议

  • 优雅关闭用户连接:当前的stop方法仅关闭了ServerSocket,不再接受新连接,但已建立的用户连接会继续运行。如果需要完全终止服务,可以在Server类中维护所有User线程的列表,在stop方法中遍历关闭每个用户的Socket。
  • Spring异步支持:可以使用Spring的@Async注解替代手动创建线程,配合自定义线程池,更好地管理异步任务。
  • 状态持久化:如果是多实例部署场景,建议用分布式锁或Redis存储服务器运行状态,避免多实例冲突。

内容的提问来源于stack exchange,提问作者jeeves

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 23:28:12