十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PHP社区交友系统实战:WebSocket实时消息与WebRTC音视频通话

PHP社区交友系统实战:WebSocket实时消息与WebRTC音视频通话 简介这份开源PHP社区交友系统面向想快速搭建私域社交平台的个人开发者与初创团队涵盖网站端和APP端支持实时消息、视频通话、语音通话等功能是一套低门槛的完整交友解决方案。包体共2000个文件以JS、CSS、HTML等前端资源为主配合JSON、XML配置文件与MD说明文档另有少量Python、Shell脚本及SQL数据库文件压缩包约152.71MB目录结构清晰便于二次开发与定位修改。目前已有134人学习下载。通过配套视频教程读者可完成从导入数据库、上传源码、修改config配置到Android Studio编译APP的全流程并获得默认管理员账号与后台管理入口既能作为社交类项目的起步模板也可当作学习PHP交友系统开发的教学参考。1. 社交项目从零到上线PHP方案为什么还能打同样想做一个社区交友产品多数人卡在同一个选择题上功能全的框架太重能跑快的 Demo 又跑不通消息和通话。这套 PHP 社区交友系统把路径收窄了——PHP 做服务端WebSocket 管实时消息WebRTC 管音视频通话Android 工程给到完整源码按流程导入数据库、改一遍config.php网站和 APP 能前后脚跑起来。它适合三类人准备做同城或兴趣社交验证市场的小团队需要一版能演示完整交友闭环的学生以及接了这类外包、不想从零写 IM 的开发者。整条链路以 PHP 的 LNMP 环境为底座前端资源里已经带好 Bootstrap、Materialize 和 Twemoji 表情方案骨架和界面都不需要重新拼装。下文按架构选型、部署、二次开发和 APP 打包四段拆开讲。2. 实时消息、音视频通话的数据流动与表结构设计2.1 一个交友系统最耗时的不是 CRUD是实时通道社区交友系统拆开看无非是用户、关系链、内容、消息四件事。前两件用 PHP 处理数据库读写没有任何问题真正的分水岭在“实时”两个字。实时消息、在线状态、通话邀请这些能力传统的 PHP-FPM 请求响应模型做不到需要一条客户端与服务器之间的长连接通道。常见做法是引入 Swoole 或 Workerman 做常驻进程单独监听一个 WebSocket 端口原有 PHP 框架继续负责登录、资料、内容等常规 REST 接口。这套方案的合理之处在于改动量最小PHP 代码仍然以$_GET、$_POST方式工作只有实时模块是另外一套进程。数据流向通道服务端职责文本消息WebSocketWS 端口写库、推送在线接收方、离线则落库补偿在线状态WebSocket 心跳维护在线用户表、推送上下线事件通话信令WebSocket转发 SDP、ICE candidate不做媒体数据转发通话媒体流WebRTC P2P服务端不参与打洞失败时走 relay 中继弱网场景兜底HTTP 轮询客户端间歇拉取离线消息WebSocket 连接数有限单端口两三千连接就会开始抖动所以部署时要以独立端口、独立进程运行不跟 Nginx 抢 80 端口。2.2 消息收发先写库、再推送、失败靠拉取补偿消息模块最容易踩的坑是把“推送成功”当作“送达成功”。正确做法是客户端把消息 POST 给 PHP 接口PHP 先把消息写入message表落库再往 WebSocket 服务端发一条“有新消息”的通知由 WS 服务端负责把消息内容推给目标用户。如果目标用户不在线这条消息就留在库里等对方下次登录或主动拉取时再补。这种做法牺牲了一点实时性但换来了消息不丢。项目配置文件里的 WS 参数长这样// config.php 消息服务相关配置 define(WS_HOST, 0.0.0.0); // WebSocket 监听地址0.0.0.0 表示允许外部连接 define(WS_PORT, 9502); // 长连接端口客户端连接 ws://域名:9502 define(WS_HEARTBEAT, 40); // 心跳间隔秒数超过则判定掉线 define(POLL_TIMEOUT, 30); // 离线消息拉取超时时间其中WS_PORT是部署时最容易出问题的一项服务器安全组和防火墙都要放行这个端口否则客户端连不上 WebSocket消息会一直走 HTTP 拉取兜底实时性退化成轮询。WS_HEARTBEAT建议设在 30 到 60 秒之间太短会导致频繁重连太长则服务端无法及时感知掉线后续加“对方正在输入”这类状态会滞后明显。2.3 音视频通话服务端只做信令不碰媒体流视频通话和语音通话在这套系统里走的是 WebRTC 方案。业务上看起来是一个房间、两个人、一路流实际上通话双方各自采集音视频通过 WebSocket 交换 SDP会话描述和 ICE candidate网络候选地址最终建立点对点连接。PHP 侧要做的只是把这些信令消息原样转发给通话对方不解析、不存储、不转发音视频数据本身。这个边界非常重要——一旦把媒体流引入 PHP 进程做中转带宽和 CPU 都会瞬间打满一个小型交友站根本扛不住几路通话。Android 端和 Web 端在 WebRTC 的 API 差异很大但信令流程是统一的。通话开始时呼叫方创建一个RTCPeerConnection通过 WebSocket 发送call信令被叫方收到后同样创建RTCPeerConnection回传answer信令之后两端持续交换candidate。这套逻辑在 APP 和浏览器上是一致的只是具体 API 名不同。系统源码里已经封装好了这一层二次开发时不要轻易改信令字段名否则客户端和浏览器端会各等各的表现为“能拨通但看不到对方画面”。2.4 数据库核心表用户、关系、消息三类表的业务语义导入数据库文件之前先理解这三张表各自的角色后面改需求、加功能才不慌。用户表存账号、密码哈希、昵称、头像和最后在线时间关系表存的是“谁关注了谁”“谁和谁是好友”这类二元关系设计成双向记录可以免去查两次的麻烦消息表则按会话维度存储聊天记录每行包含发送者、接收者和消息内容。建表 SQL 大致如下CREATE TABLE user ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, username VARCHAR(64) NOT NULL COMMENT 登录名唯一, password VARCHAR(255) NOT NULL COMMENT 密码字段建议存 password_hash 结果, nickname VARCHAR(32) NOT NULL DEFAULT COMMENT 昵称页面展示用, avatar VARCHAR(255) NOT NULL DEFAULT COMMENT 头像地址, last_active_at INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 最后在线时间戳, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE friend_relation ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, user_id INT UNSIGNED NOT NULL COMMENT 主动方用户ID, friend_id INT UNSIGNED NOT NULL COMMENT 被动方用户ID, relation_type TINYINT NOT NULL DEFAULT 0 COMMENT 0关注 1好友, created_at INT UNSIGNED NOT NULL COMMENT 建立时间, PRIMARY KEY (id), KEY idx_user_friend (user_id, friend_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户关系表; CREATE TABLE message ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, from_uid INT UNSIGNED NOT NULL COMMENT 发送者ID, to_uid INT UNSIGNED NOT NULL COMMENT 接收者ID, content TEXT NOT NULL COMMENT 消息内容, msg_type TINYINT NOT NULL DEFAULT 0 COMMENT 0文本 1图片 2语音 3视频, is_read TINYINT NOT NULL DEFAULT 0 COMMENT 0未读 1已读, created_at INT UNSIGNED NOT NULL, PRIMARY KEY (id), KEY idx_to_uid_created (to_uid, created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT聊天消息表;注意user表用utf8mb4而不是utf8否则发一条带 emoji 的消息会直接写入失败。message表的索引建在to_uid created_at上因为最常执行的查询是“拉取某个人最近 N 条消息”。如果将来消息量大可以按from_uid与to_uid做水平分表但那是后话初期单表足够。3. LNMP 环境部署与 config.php 配置实战3.1 PHP 扩展少了哪一个模块会导致白屏这套系统跑在典型的 LNMP 环境上Nginx 负责接收请求PHP-FPM 负责解析 PHP 文件MySQL 存数据。最容易翻车的是 PHP 扩展缺失缺少pdo_mysql则所有数据库操作直接报错缺少mbstring则包含中文的页面输出会出现乱码缺少curl则头像上传、远程拉取等功能失效。安装和检查命令如下# Ubuntu/Debian 系安装 LNMP 基础组件 apt update apt install -y nginx mysql-server php7.4 php7.4-fpm \ php7.4-mysql php7.4-mbstring php7.4-curl php7.4-zip # 检查关键扩展是否已加载 php -m | grep -E pdo_mysql|mbstring|curl|zipphp -m输出的模块列表里只要两行以上缺失大概率就是安装阶段漏了包。zip扩展容易被忽略但 APP 端的接口如果依赖 ZIP 压缩上传头像缺了它接口会返回 500 或直接空白。每次改完php.ini或装完新扩展记得systemctl restart php7.4-fpm让配置生效只重启 Nginx 不重启 PHP-FPM 是排错时最常见的失误之一。3.2 三步走数据库导入、源码上传、配置文件修改部署流程在摘要里写得很精简实际执行时每步有两个细节要注意。第一步导入数据库用命令行导入比 phpMyAdmin 更稳避免大文件超时mysql -u root -p -e CREATE DATABASE IF NOT EXISTS chat_system DEFAULT CHARSET utf8mb4; mysql -u root -p chat_system chat_system.sql导入成功后检查一下有多少张表mysql -u root -p -e USE chat_system; SHOW TABLES;正常应该在 20 张以上。如果表数量明显偏少说明 SQL 文件执行到一半中断需要删库重导不要继续往下走。第二步上传源码到网站根目录注意是整个源码目录的upload或public内容上传而不是把源码目录直接传上去否则 Nginx 的 root 路径会多套一层目录。第三步修改config.php这是整套系统能跑起来的核心// config.php 关键配置项 define(DB_HOST, 127.0.0.1); // 数据库地址本机用 127.0.0.1 define(DB_PORT, 3306); // 数据库端口默认 3306 define(DB_NAME, chat_system); // 数据库名与导入时创建的库一致 define(DB_USER, root); // 数据库账号 define(DB_PASS, your_password); // 数据库密码 define(SITE_URL, https://your-domain.com); // 前台域名影响所有资源加载路径 define(API_URL, https://your-domain.com/api.php); // 接口入口 define(DEFAULT_ADMIN, admin); // 默认管理员账号 define(DEFAULT_ADMIN_PWD, admin); // 默认管理员密码DB_PASS不要留空MySQL 的 root 账号空密码在 PHP 7.4 下会拒绝连接。SITE_URL的末尾不要带斜杠否则前后台拼接 URL 时会出现双斜杠导致 CSS、JS 资源 404。API_URL要与 Nginx 的站点配置对应如果改了伪静态规则这里也要同步改。第三步完成后访问域名使用默认管理员账号admin、密码admin登录后台先改密码再开始配置菜单这是任何管理系统上线的第一原则。3.3 Nginx 站点配置与 PHP-FPM 转发config.php改完以后网站还不一定能访问Nginx 站点配置没写好PHP 文件会被当成静态文件下载或者直接返回 403。典型配置如下server { listen 80; server_name your-domain.com; root /var/www/chat_system; # 源码实际路径 index index.php index.html; # 伪静态把非真实文件的请求交给入口文件 location / { try_files $uri $uri/ /index.php?$query_string; } # PHP 请求转发给 PHP-FPM location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php7.4-fpm.sock; } # 防护禁止访问隐藏文件 location ~ /\. { deny all; } }try_files那一行是关键——社区交友系统的前台页面大量使用路由重写没有这条规则点击“动态”“私信”会全部 404。fastcgi_pass用的是 socket 方式如果 PHP-FPM 没启动这里会报 502 Bad Gateway排查时先systemctl status php7.4-fpm确认进程存活。修改完配置执行nginx -t检查语法合法后systemctl reload nginx生效。网站根目录的写权限也要确认头像上传、聊天图片这类功能需要 PHP 进程有写入权限一般设置为chown -R www-data:www-data /var/www/chat_system。很多用户把整个目录设成 777虽然能跑但隐患很大不推荐。4. 消息模块二次开发与常见故障排查4.1 接口改造示例发消息先校验再落库部署完成只是开始实际使用中往往会根据业务需要调整消息接口。比如要给消息内容加上长度限制和敏感词过滤改造点就在send_message接口。常见的最简实现如下// api/send_message.php ?php require_once ../config.php; require_once ../lib/db.php; $from_uid intval($_POST[from_uid] ?? 0); $to_uid intval($_POST[to_uid] ?? 0); $content trim($_POST[content] ?? ); // 参数校验为空直接返回错误码 if (!$from_uid || !$to_uid || !$content) { echo json_encode([code 400, msg 参数不完整]); exit; } // 长度限制防止单条消息撑爆数据库 if (mb_strlen($content, utf8) 2000) { echo json_encode([code 400, msg 消息内容过长]); exit; } // 先落库再通知 WebSocket 服务端推送 $stmt $pdo-prepare( INSERT INTO message (from_uid, to_uid, content, is_read, created_at) VALUES (?, ?, ?, 0, UNIX_TIMESTAMP()) ); $stmt-execute([$from_uid, $to_uid, $content]); $msg_id $pdo-lastInsertId(); // 调用 WS 服务端推送接口通知接收方拉取新消息 file_get_contents(http://127.0.0.1:9502/push?to_uid{$to_uid}msg_id{$msg_id}); echo json_encode([code 200, msg_id $msg_id]);这段代码里每个判断都有明确目的intval防止 SQL 注入和非法 IDtrim去掉首尾空行避免发空白消息mb_strlen用 UTF-8 字符数限制长度而不是字节数避免一个汉字算三个长度导致中文消息被误杀。落库之后再通知 WS 服务端保证推送时消息 ID 已经存在接收方拿到消息 ID 可以直接回查。测试接口时用 curl 模拟前端行为curl -X POST https://your-domain.com/api/send_message.php \ -d from_uid1to_uid2contenthello正常返回{code:200,msg_id:123}msg_id自增且落库成功。如果返回code:400优先检查 POST 字段名和config.php里的API_URL是否一致。接口层排查完毕再去排查消息能不能实时到达对端。4.2 高频故障对照白屏、403、消息延迟、通话黑屏运行期间的故障大多是环境或配置问题而不是代码本身的 bug。把高频问题按现象、原因、处理方式整理成对照表排错时不至于没头绪现象原因处理方式首页白屏PHP 扩展缺失或代码目录权限不对php -m检查 pdo_mysql、mbstring检查目录属主页面 403Nginx 找不到 index.php检查 root 路径是否多套了一层目录登录后立即跳回首页Session 未生效检查session.save_path是否可写消息发出去对方收不到WebSocket 端口被防火墙拦截放行WS_PORT用telnet 域名 9502测连通性消息延迟几秒才到WS 未连上走了轮询兜底浏览器控制台看 socket 连接状态检查 WS 服务进程语音视频通话黑屏浏览器或 APP 未授权摄像头麦克风检查站点必须 HTTPSHTTP 下浏览器禁用 WebRTC 采集头像上传失败upload目录不可写chown -R www-data并确认文件夹存在白屏是最容易误判的很多人以为是代码问题实际是 PHP 报错被display_errors关闭了。临时开启方式是在config.php顶部加一行ini_set(display_errors, 1);定位完再删掉。WebSocket 的端口测试用telnet最快连不上就去看云服务器安全组和系统防火墙这是消息模块故障的最大来源。4.3 弱网兜底断线重连与离线消息补偿移动端网络环境复杂WebSocket 断开是常态。客户端不能只依赖长连接必须有重连和补偿机制。重连不能写成死循环要用指数退避否则断线时会瞬间重建几十个连接把服务器打挂。前端断线重连的逻辑一般这样处理// web/assets/js/ws-client.js let ws null; let retryTimes 0; function connectWS() { ws new WebSocket(wss://your-domain.com:9502); ws.onopen function() { retryTimes 0; console.log(WS 已连接); // 连接成功后拉一次离线消息补偿断线期间漏掉的内容 fetch(/api/pull_offline.php?uid currentUid) .then(res res.json()) .then(data { /* 渲染离线消息 */ }); }; ws.onclose function() { // 指数退避重连1s, 2s, 4s, 8s... 上限 30s let delay Math.min(1000 * Math.pow(2, retryTimes), 30000); retryTimes; setTimeout(connectWS, delay); }; ws.onerror function() { ws.close(); // 触发 onclose 走重连 }; } connectWS();重连成功后的第一件事是拉离线消息不是把本地缓存直接渲染出来。因为断线期间 WebSocket 推送的消息全部丢失只有服务端的message表才是全量数据。pull_offline.php接口按to_uid查is_read0的记录返回后客户端再标记已读。这个“重连即对账”的机制比任何断线续传都要可靠也是整个消息系统在弱网下不丢消息的底线保障。5. Android Studio 封装 APP 与签名验证5.1 打开工程后先改这三处APP 源码拿到手后用 Android Studio 直接Open工程目录即可首次打开会自动下载 Gradle 和依赖库这个过程受网络影响可能较长建议用国内镜像源加速。工程能编译通过之后先不要着急打包按顺序改三处一是接口地址APP 里所有的请求都指向一个统一配置类二是网络安全配置三是应用包名。接口地址一般在app/src/main/java/com/example/chat/api/ApiConfig.java这样的文件里public class ApiConfig { // 服务器地址换成你的域名或服务器IP public static final String BASE_URL https://your-domain.com; // WebSocket 地址注意是 wss 而不是 https public static final String WS_URL wss://your-domain.com:9502; // 超时时间单位毫秒 public static final int TIMEOUT 15000; }BASE_URL和WS_URL的域名必须与config.php里的SITE_URL一致否则 APP 可以登录但消息推送会失败。如果服务器还没有配 HTTPS测试阶段可以先用 HTTP但需要在AndroidManifest.xml里给application标签加android:usesCleartextTraffictrueapplication android:usesCleartextTraffictrue android:allowBackupfalse android:labelstring/app_name android:supportsRtltrueusesCleartextTraffic只建议在调试期打开正式发布必须关掉并切换 HTTPS。Android 9 及以上默认禁止明文流量不配置这个属性APP 请求 HTTP 接口会直接报CLEARTEXT communication not permitted表现就是登录按钮点了没反应。5.2 签名打包、包名排查与产物验证改完配置就可以生成签名的 APK 了。签名文件用 keytool 生成打包配置写到build.gradle里一套完整的流程如下# 生成签名文件别名、口令自己记好 keytool -genkey -v -keystore chat_sign.jks -alias chat \ -keyalg RSA -keysize 2048 -validity 36500// android/app/build.gradle 片段 android { signingConfigs { release { storeFile file(../keystore/chat_sign.jks) storePassword your_password keyAlias chat keyPassword your_password } } buildTypes { release { minifyEnabled true shrinkResources true signingConfig signingConfigs.release } } }minifyEnabled true会启用代码混淆体积变小但也会把部分反射调用的类移除如果打包后发现登录页白屏先把混淆关掉确认是混淆问题再补 keep 规则。签名文件、密码、别名三者必须和build.gradle完全对应否则打包直接失败。项目改包名时最容易漏改的是AndroidManifest.xml里的package属性、build.gradle里的applicationId和 Java 代码里的import路径三处不一致会编译报错。稳妥的做法是全局搜索替换旧包名再逐个确认替换结果。打包完成后用 apksigner 验证签名/opt/android-sdk/build-tools/30.0.2/apksigner verify --print-certs app-release.apk输出Signer #1 certificate DN: CNchat代表签名有效可以正常安装分发。验证签名之后再拿一台真机装上 APK用抓包工具确认请求指向你自己的域名同时观察 WebSocket 的9502端口是否连通。这两个验证都通过网站和 APP 就算是真正串起来了。本文还有配套的精品资源点击获取
返回列表