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

Workerman TCP服务器查询问题:客户端无响应及500错误排查

问题描述

我用Workerman搭建了一个加载数据库的HTTP服务器(通过$api->get()路由判断),监听地址为tcp://127.0.0.1:7000,服务器端代码如下:

$DB = new DB();
$DB->load();

$api->get('/keyword/{name}', function ($request, $name) use ($DB) {
    $res =  $DB->db($name);
    return $res;
});

客户端代码存在多种问题:

  1. 最初用fsockopen连接并发送关键词,服务器显示请求命中(总请求数5),但客户端收不到响应:
try {
    $search = $request->input('search'); // 多个关键词
    $searches = explode("\r\n", $search);
    foreach ($searches as $keyword) {
        $url = 'tcp://127.0.0.1:7000/keyword/'.$keyword;
        $fp = fsockopen($url);
        
        fwrite($fp, $keyword);
        $results = fread($fp, 8192);

        return $results;
    }
} catch (\Exception $e) {
    return response($e->getMessage(), 500);
}
  1. 改用socket_create后提示路径错误;用file_get_contents发送GET请求时出现HTTP 500错误:
try {
    $fp = fsockopen('tcp://127.0.0.1', 7000, $errno, $errstr, 30);
    if (!$fp) {
        throw new \Exception("Error opening socket: $errstr ($errno)");
    }
    
    // 以GET请求发送关键词
    $url = 'http://127.0.0.1:7000/keyword/'.$search;

    $response = '';
    while (!feof($fp)) {
        $response = file_get_contents($url);
    }
    fclose($fp); 
    return $response;
} catch (\Exception $e) {
    return response($e->getMessage(), 500);
}

错误信息:

file_get_contents(http://127.0.0.1:7000/keyword/Jake): Failed to open stream: HTTP request failed! HTTP/1.1 500 Internal Server Error

file_get_contents(http://127.0.0.1:7000/keyword/bo): Failed to open stream: HTTP request failed! HTTP/1.1 500 Internal Server Error
:Request 4: 60.661055ms

file_get_contents(http://127.0.0.1:7000/keyword/jen): Failed to open stream: HTTP request failed! HTTP/1.1 500 Internal Server Error
:Request 5: 66.206243ms

请问正确的TCP Socket连接、发送请求及接收响应的方式是什么?如何解决当前的路径错误和500内部服务器错误?


解决方案

一、核心问题梳理

你的服务器是Workerman HTTP服务(而非裸TCP服务),必须发送标准HTTP格式的请求才能被正确解析。之前的错误均源于请求格式不规范、代码逻辑混乱。

二、正确的客户端实现方式

方式1:用file_get_contents发送标准HTTP请求

直接发送符合HTTP规范的请求,无需额外手动建立socket:

try {
    $search = $request->input('search');
    $searches = explode("\r\n", trim($search));
    $results = [];
    
    foreach ($searches as $keyword) {
        $keyword = trim($keyword);
        $encodedKeyword = urlencode($keyword); // 对关键词URL编码,避免特殊字符解析失败
        $url = "http://127.0.0.1:7000/keyword/{$encodedKeyword}";
        
        // 设置HTTP上下文,模拟标准请求头
        $context = stream_context_create([
            'http' => [
                'method' => 'GET',
                'header' => "Host: 127.0.0.1:7000\r\n",
                'timeout' => 10
            ]
        ]);
        
        $response = file_get_contents($url, false, $context);
        if ($response === false) {
            throw new \Exception("请求关键词「{$keyword}」失败");
        }
        $results[$keyword] = $response;
    }
    
    return response($results);
} catch (\Exception $e) {
    return response($e->getMessage(), 500);
}

方式2:用fsockopen手动构建HTTP请求

如果必须使用socket方式,需要构造完整的HTTP请求报文:

try {
    $search = $request->input('search');
    $searches = explode("\r\n", trim($search));
    $results = [];
    
    foreach ($searches as $keyword) {
        $keyword = trim($keyword);
        $encodedKeyword = urlencode($keyword);
        $host = '127.0.0.1';
        $port = 7000;
        
        // 建立socket连接(仅传主机和端口,路径放在请求报文中)
        $fp = fsockopen($host, $port, $errno, $errstr, 10);
        if (!$fp) {
            throw new \Exception("连接失败: $errstr ($errno)");
        }
        
        // 构建标准HTTP GET请求报文
        $request = "GET /keyword/{$encodedKeyword} HTTP/1.1\r\n";
        $request .= "Host: {$host}:{$port}\r\n";
        $request .= "Connection: close\r\n\r\n"; // 关闭连接,确保能接收完整响应
        
        // 发送请求
        fwrite($fp, $request);
        
        // 接收并拼接响应
        $response = '';
        while (!feof($fp)) {
            $response .= fgets($fp, 8192);
        }
        fclose($fp);
        
        // 分离HTTP响应头和响应体
        list($headers, $body) = explode("\r\n\r\n", $response, 2);
        $results[$keyword] = $body;
    }
    
    return response($results);
} catch (\Exception $e) {
    return response($e->getMessage(), 500);
}

三、500内部错误排查方案

  1. 检查服务器端DB->db($name)方法:500错误大概率是该方法抛出异常(比如数据库查询失败、关键词不存在),可在服务器代码中添加异常捕获和日志记录:
$api->get('/keyword/{name}', function ($request, $name) use ($DB) {
    try {
        $res = $DB->db($name);
        return $res;
    } catch (\Exception $e) {
        // 记录错误日志到文件
        file_put_contents('workerman_error.log', date('Y-m-d H:i:s') . " Error: {$e->getMessage()}\n", FILE_APPEND);
        return response('服务器内部错误', 500);
    }
});
  1. 查看Workerman原生日志:默认日志目录为./logs,里面会记录服务器端的详细错误信息,是排查500错误最直接的方式。
  2. 验证关键词格式:确保客户端发送的关键词已做URL编码,避免特殊字符导致路由解析失败。

四、避免重复踩坑

  • 不要混淆裸TCP服务和HTTP服务:Workerman的$api->get()是基于HTTP协议的路由,必须发送标准HTTP请求。
  • fsockopen参数错误:第一个参数仅需主机名/IP,路径应放在HTTP请求报文中,而非连接地址里。
  • file_get_contents无需提前建立socket:它会自动处理连接,之前的重复连接属于逻辑冗余。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 05:55:43