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; });
客户端代码存在多种问题:
- 最初用
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); }
- 改用
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内部错误排查方案
- 检查服务器端
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); } });
- 查看Workerman原生日志:默认日志目录为
./logs,里面会记录服务器端的详细错误信息,是排查500错误最直接的方式。 - 验证关键词格式:确保客户端发送的关键词已做URL编码,避免特殊字符导致路由解析失败。
四、避免重复踩坑
- 不要混淆裸TCP服务和HTTP服务:Workerman的
$api->get()是基于HTTP协议的路由,必须发送标准HTTP请求。 fsockopen参数错误:第一个参数仅需主机名/IP,路径应放在HTTP请求报文中,而非连接地址里。file_get_contents无需提前建立socket:它会自动处理连接,之前的重复连接属于逻辑冗余。
内容的提问来源于stack exchange,提问作者Jake

