
第一步:先搞懂HTTP服务器的底层逻辑——其实就3步
在写代码之前,你得先明白:HTTP服务器的核心,其实就是“和浏览器对话”的逻辑。我当时被“服务器”这三个字吓住,后来朋友用快递柜打比方,一下就懂了——你可以把HTTP服务器想成小区门口的快递柜:
而PHP做这个的关键,就是用Socket扩展来当“快递柜的通信线路”。我当时一开始没搞懂Socket是啥,直接复制代码,结果运行报错“Call to undefined function socket_create()”——后来才知道,得先去php.ini里把extension=socket
前面的分号去掉,开启Socket扩展(像给PHP装个“电话卡”,才能和浏览器通话)。
再补个小知识点:为什么选Socket?因为Socket是PHP最基础的网络通信工具,不用依赖任何框架,能帮你直接理解“服务器和浏览器怎么说话”。就像学骑自行车,先骑没有辅助轮的,学会了再骑带辅助轮的——等你搞懂Socket版的服务器,再学Nginx、Apache这些高级服务器,就会觉得“哦,原来它们的底层也是这套逻辑”。
第二步:写代码——从0到1搭建服务器的具体操作
接下来直接上干货,我把当时的代码拆成“4个模块”,你跟着复制就行,每一步我都标了“坑点”,帮你避坑。
模块1:先搭Socket的“基础通信线路”
你得创建一个server.php
文件,先写“装电话、占线、接电话”的代码——这是服务器能运行的基础:
// 创建Socket(装电话):用IPv4、TCP协议
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) {
die('Socket创建失败:' . socket_strerror(socket_last_error()));
}
//
绑定IP和端口(占线):127.0.0.1是本地地址,8000是端口(别用80,容易被占用)
$result = socket_bind($socket, '127.0.0.1', 8000);
if (!$result) {
die('绑定失败:' . socket_strerror(socket_last_error()));
}
//
监听端口(等电话):最多允许10个等待连接(不用改)
$result = socket_listen($socket, 10);
if (!$result) {
die('监听失败:' . socket_strerror(socket_last_error()));
}
echo "服务器已启动,地址:http://127.0.0.1:8000n";
坑点提醒:我当时第一次运行这个代码,报错“Permission denied”(权限不够),后来才知道——Linux/Mac下用1024以下的端口需要root权限,所以直接用8000、8080这种“高位端口”就行,省得麻烦。
模块2:加“循环接受请求”——让服务器一直“在线”
刚才的代码只能“接一次电话”,浏览器刷新页面就会断。所以得加个while(true)
循环,让服务器一直等新的请求:
// 循环接受连接(一直等电话)
while (true) {
// 接受客户端连接(接电话):这个函数会“卡住”,直到有浏览器连进来
$client = socket_accept($socket);
if (!$client) {
echo '连接失败:' . socket_strerror(socket_last_error()) . "n";
continue;
}
// 读取浏览器发的请求(听用户说什么):1024是最多读1024字节
$request = socket_read($client, 1024);
if (!$request) {
socket_close($client);
continue;
}
// 这里后面加“处理请求”的代码
}
为什么要循环? 因为HTTP是“无状态”的——每个请求都是独立的,服务器处理完一个请求,得赶紧回到“等电话”的状态,不然下一个请求就找不到它了。我当时没加循环,第一次访问成功,刷新就报错“无法连接”,后来加了循环才解决。
模块3:解析请求——听懂浏览器“要什么”
浏览器发过来的请求是一堆字符串,比如:
GET /index.html HTTP/1.1rn
Host: 127.0.0.1:8000rn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...rn
你得从中拆出关键信息:请求方法(比如GET)、请求路径(比如/index.html)。我当时用最笨但有效的方法——用explode
拆分字符串:
// 解析请求行:请求的第一行是“方法 路径 协议”,比如“GET /index.html HTTP/1.1”
$requestLines = explode("rn", $request);
$requestLine = $requestLines[0]; // 取第一行
list($method, $path, $protocol) = explode(' ', $requestLine);
// 打印请求信息(方便调试)
echo "收到请求:方法=$method,路径=$pathn";
坑点提醒:如果浏览器发的是POST请求,请求体在rnrn
后面,比如表单提交的数据,这时候得用socket_read
多读一次——我当时试POST请求,结果只拿到请求头,没拿到请求体,后来查资料才知道,POST的请求体是在请求头之后的,得判断$request
里有没有rnrn
,再拆分。
模块4:构造响应——给浏览器“想要的东西”
解析完请求,接下来要给浏览器返回内容。HTTP响应得严格按照“状态行→响应头→空行→响应体”的顺序来,不然浏览器会“听不懂”。我当时写的第一个响应是这样的:
//
状态行:HTTP/1.1是协议版本,200是“成功”状态码,OK是描述 $statusLine = "HTTP/1.1 200 OKrn";
//
响应头:告诉浏览器“返回的是HTML”,编码是UTF-8 $headers = "Content-Type: text/html; charset=utf-8rn";
$headers .= "Connection: closern"; // 处理完请求就断开连接
//
空行:必须有,用来分隔响应头和响应体 $blankLine = "rn";
//
响应体:你要显示的内容,比如HTML $body = "
我的PHP服务器 ";$body .= "
成功啦!这是PHP服务器返回的内容~
";$body .= "
你请求的路径是:$path
";$body .= "";
// 把响应拼起来
$response = $statusLine . $headers . $blankLine . $body;
// 给浏览器发响应(挂电话前把快递给用户)
socket_write($client, $response);
// 关闭客户端连接(挂电话)
socket_close($client);
关键提醒:响应头里的 Content-Type
一定要对——如果返回JSON,就写application/json
;返回图片,就写image/jpeg
,不然浏览器会乱显示(比如我之前返回JSON没改Content-Type
,结果浏览器显示成纯文本,全是大括号);空行不能少—— rnrn
是HTTP协议的“分隔符”,少了这个,浏览器会认为响应头还没结束,一直等,最后超时。第三步:运行和调试——解决你大概率会遇到的3个坑
代码写好了,接下来运行试试。打开命令行,进入
server.php
所在的文件夹,输入php server.php
,然后打开浏览器,输入http://127.0.0.1:8000
——如果看到你写的HTML内容,就说明成了!要是没成功,大概率是以下3个坑:坑1:端口被占用
症状:运行代码报错“socket_bind() failed: Address already in use”。
解决办法:
Windows:打开命令行,输入 netstat -ano | findstr 8000
(8000是你用的端口),找到占用端口的进程ID(最后一列),然后用taskkill /pid 进程ID /f
杀掉;Linux/Mac:输入 lsof -i 8000
,找到进程ID,用kill -9 进程ID
杀掉。坑2:Socket扩展没开
症状:报错“Call to undefined function socket_create()”。
解决办法:找到php.ini文件(可以用
php ini
命令查位置),打开后找到extension=socket
,把前面的分号去掉,保存后重启命令行。坑3:响应格式错误
症状:浏览器显示“无法解析服务器的响应”或“ ERR_INVALID_HTTP_RESPONSE”。
解决办法:检查响应的结构——是不是有状态行?是不是有响应头?是不是有
rnrn
空行?我当时第一次写响应,漏了空行,结果浏览器一直转圈,后来加了rnrn
就好了。最后再给你个“进阶小技巧”:如果想让服务器返回不同的内容(比如访问
/about
显示关于页面,访问/api
返回JSON),可以加个switch
判断路径:switch ($path) {
case '/about':
$body = "
关于我们
这是用PHP服务器做的关于页面~
";break;
case '/api':
$body = json_encode(['code' => 200, 'msg' => '成功', 'data' => ['name' => 'PHP Server']]);
$headers = "Content-Type: application/json; charset=utf-8rn"; // 改响应头
break;
default:
$body = "
404 没找到
你访问的路径$path不存在~
";$statusLine = "HTTP/1.1 404 Not Foundrn"; // 改状态码
break;
}
我当时加了这个判断后,访问
http://127.0.0.1:8000/api
,浏览器直接显示JSON数据,感觉特别有成就感——原来自己写的服务器,真的能处理不同的请求!对了,你要是怕记不住状态码,可以存一下这个我整理的常用HTTP状态码表,关键时刻能救急:
状态码 含义 什么时候用 200 成功 返回正常内容(比如HTML、JSON) 404 未找到 请求的路径不存在(比如访问/a/b/c.html) 500 服务器内部错误 PHP代码写错了(比如语法错误、变量未定义) 400 请求错误 浏览器发的请求格式不对(比如没有请求行) 怎么样?是不是没想象中难?你跟着步骤试的时候,要是遇到报错,记得先看命令行里的输出——PHP会把错误信息打出来,比如“Undefined variable: path”就是变量没定义,“socket_write() expects parameter 1 to be resource”就是
$client
没拿到连接。我当时第一次成功运行的时候,盯着浏览器里的“成功啦!”字样,兴奋得截图发了朋友圈——原来所谓的“服务器”,也不过是一堆按规则写的代码而已。你要是试成功了,欢迎在评论区告诉我,我帮你看看能不能再加个“静态文件处理”(比如返回CSS、JS文件)的功能~
你肯定遇到过这种情况——服务器返回的HTML页面光秃秃的,想加个CSS让标题变红色、加个JS让按钮跳一下,结果链接了.css或.js文件后,要么浏览器显示“无法加载资源”,要么页面压根没变化。其实要让PHP服务器返回静态文件,核心就两件事:把文件内容“读出来”,再“明确告诉浏览器这是什么类型的文件”。我之前第一次试的时候,直接在HTML里写
,结果浏览器请求style.css时,服务器还是返回之前的“成功啦”HTML——后来才反应过来,服务器根本没处理静态文件的逻辑,得自己加代码去读文件。比如要返回style.css,得用
file_get_contents()
函数把style.css里的内容“读”进PHP变量里,就像你打开电脑里的TXT文件看内容一样,PHP得先拿到文件里的代码,才能传给浏览器。然后最容易忘但最关键的一步:得让浏览器“认得出”这个文件是CSS还是JS。怎么认?靠响应头里的
Content-Type
啊!比如CSS文件,你得在响应头里写Content-Type: text/css
;JS文件就写application/javascript
。我之前犯过超蠢的错:把CSS的Content-Type
写成了text/html
,结果浏览器把CSS代码当成HTML解析,页面上直接显示一堆body { background: #f0f0f0; }
的文字,差点以为服务器崩了,后来改对了才恢复正常。还有啊,文件路径一定要核对清楚!比如你的CSS文件放在server.php同目录的“static”文件夹里,那读文件的路径就得是“static/style.css”,要是写成“statics/style.css”(多了个s
)、或者“style.css”(没加static文件夹),服务器找不到文件,就会返回404。我当时调试了半小时,才发现是文件夹名拼错了,拍着脑门骂自己“怎么这么粗心”。
运行代码时提示“Call to undefined function socket_create()”怎么办?
这是因为PHP的Socket扩展没开启。解决方法:找到php.ini文件(可通过php ini命令查位置),打开后找到extension=socket这一行,把前面的分号去掉,保存后重启命令行或服务器即可。
启动服务器时提示“Address already in use”怎么解决?
这是端口被其他程序占用了。Windows系统:用netstat -ano | findstr 端口号找到占用进程ID,再用taskkill /pid 进程ID /f杀掉;Linux/Mac系统:用lsof -i 端口号找到进程ID,再用kill -9 进程ID结束进程,之后重新启动服务器。
浏览器显示“无法解析服务器响应”或“ERR_INVALID_HTTP_RESPONSE”怎么办?
大概率是响应格式错了。检查这四点:①有没有状态行(如HTTP/1.1 200 OK);②响应头有没有写对Content-Type(比如HTML用text/html,JSON用application/json);③响应头和响应体之间有没有加rnrn空行;④响应内容有没有语法错误(比如HTML标签没闭合)。
这个PHP服务器能处理POST请求吗?需要怎么改?
可以处理。POST请求的请求体在rnrn后面,需要先拆分请求内容:用explode("rnrn", $request)把请求分成“请求头”和“请求体”两部分,第二部分就是POST的数据(比如表单提交的username=test&password=123)。之后用parse_str()函数解析成数组,就能拿到POST参数了。
怎么让服务器返回CSS、JS这类静态文件?
需要先读取文件内容,再设置正确的Content-Type。比如返回CSS文件:①用file_get_contents()读取.css文件的内容;②响应头里的Content-Type改成text/css;③把读取的内容作为响应体返回。JS文件同理,Content-Type用application/javascript即可。注意要确保文件路径正确,避免404错误。
原文链接:https://www.mayiym.com/46343.html,转载请注明出处。