项目介绍
该项目旨在实现一个网页端的在线五子棋,将实现登陆、好友、房间、对战、观战、聊天等功能
完成该项目需要了解C++、数据库MySQL、基础前端HTML/CSS/JS/Ajax、网络协议WebSocket
项目源码:azhe1/online_gobang - 码云 - 开源中国 (gitee.com)
websocket协议
介绍
为什么项目需要使用websocket协议而不是其它协议呢?因为websocket提供了一个十分强大的功能:能够让服务器主动向客户端推送消息。传统的http协议在建立连接后,只允许客户端向服务器发送请求,服务器向客户端发送应答,连接就会关闭。比如我们想要实现一个在线聊天场景,如果使用http协议,客户端就需要不断地轮询,也就是不停地问服务器:有没有我的消息啊?如果有,再把消息发送给客户端。让客户端轮询会一直建立连接再销毁,这样效率很低而且浪费资源。
所以,websocket协议就完美解决了这个问题,它会在客户端和服务器之间建立一个持久的连接,让客户端和服务器都可以随时主动发送数据。这样,客户端就不需要在轮询了,如果有消息,服务器直接推送给客户端就好了。
协议建立流程
建立HTTP协议:首先建立http协议,包含tcp三次握手等。
握手阶段:建立http协议后,客户端向服务器发送一个http请求,这个请求是由客户端发起的,称为“握手”请求。这个请求遵循HTTP协议,但包含了一些特定的头部,表明客户端想要建立WebSocket连接。
协议升级:如果服务器支持WebSocket,它会以一个HTTP 101 Switching Protocols响应来应答,表示同意将通信协议从HTTP升级到WebSocket。
数据传输:握手成功后,基于TCP的连接将被升级到WebSocket连接,此时数据可以通过这个连接双向传输。
websocketpp库接口
由于本项目使用了http和websocket两种应用层协议,而websocketpp这个网络库恰好支持了这两种协议,所以我们使用了该库作为本项目的依赖库来实现http/websocket服务器。
下面是该库的一些常用网站
github:https://github.com/zaphoyd/websocketpp
用户手册:https://docs.websocketpp.org/
官网:https://www.zaphoyd.com/projects/websocketpp/
connection_hdl相当于websocket连接的句柄,server是endpoint的子类,server也就是我们实例化服务器对象的一个类,所以想要搭建服务器必须了解endpoint里面声明了哪些接口
connection_ptr是websocket连接的智能指针管理对象,后面的各个通信模块都会大量用到这个智能指针,connection类就是该对象所属类,通常用来进行http响应的回复,http请求内容的获取,以及websocket消息的推送,这个指针对象非常的重要。
message_ptr是一个专门用来获取websocket请求消息的指针对象,可以通过get_payload获取websocket请求的有效载荷数据。
timer_ptr是一个定时器对象指针,配合set_timer这个接口来使用,可以在服务器内部设置定时任务,这个接口在我们后面的session模块中会用到,
下面是connection类的实现,从接口对应的协议来划分可以分为两类,一类是http,一类是websocket,send接口用来在websocket连接上发送消息
下面的这些都是websocketpp定义的一些日志等级,http响应状态码,websocket发送数据的类型等
但是在我们后面写项目的时候,会设置set_access_channels(websocketpp::log::alevel::none)
表示禁止websocketpp打印所有日志。
项目设计
下图是项目完整模块图:
实用工具类模块
日志宏
在实现项目的时候,因为项目非常繁杂,仅借助printf等接口打印调试信息,能获取的信息有限。所以为了方便后面进行日志的输出,我们这里封装一个日志宏,通过宏函数来进行调试信息或错误信息的打印。
这段代码定义了一个宏LOG,它用于在符合特定日志等级条件时输出日志信息。这个宏利用了C语言中的宏定义、条件判断、时间处理和变参模板等特性。下面是对这个宏的详细解析:
宏定义(#define):
LOG(level, format, ...)定义了一个接受至少两个参数的宏,第一个参数level是日志等级,第二个参数format是格式化字符串,后面的...表示这个宏可以接受可变数量的参数,用于格式化字符串。
条件判断:
if (level < DEFAULT_LOG_LEVEL) break;这行代码检查当前的日志等级是否低于默认的日志等级(DEFAULT_LOG_LEVEL)。如果是,执行break跳出当前的do-while循环(实际上,这里的do-while循环只是为了在宏中使用break,并不是为了循环)。
时间处理:
使用time(NULL)获取当前时间(自1970年1月1日以来的秒数)。
localtime(&t)将获取到的时间转换为本地时间表示的tm结构体。
strftime(buf, 31, "%H:%M:%S", lt);将tm结构体格式化为“小时:分钟:秒”的形式,并存储在buf数组中。
日志输出:
使用fprintf(stdout, "[%s][%s][%s:%d]" format "\n", logLevelToString(level), buf, __FILE__, __LINE__, ##__VA_ARGS__);输出日志。
这里,stdout表示标准输出,[%s][%s][%s:%d]和format是格式化的占位符,分别用于输出日志等级、时间、文件名、行号和用户定义的消息。
logLevelToString(level)函数用于将日志等级转换为字符串表示。
__FILE__和__LINE__是C语言的预处理器宏,分别表示当前文件名和行号。
##__VA_ARGS__是GCC的扩展,用于处理变参宏中的可变参数列表。如果__VA_ARGS__为空##操作符会导致前面的逗号被删除,以避免语法错误。
这些宏定义是基于之前解析的LOG
宏的特定用途的简化版,它们为不同级别的日志信息提供了便捷的方式。每个宏都预设了一个特定的日志级别,并接受格式化字符串及可变数量的参数,从而允许针对不同情况快速记录日志。
mysql_util
mysql_create
这个函数用于建立与MySQL数据库的连接。它接收数据库的主机名、用户名、密码、数据库名和端口号(默认为3306)作为参数。函数首先初始化一个MySQL连接句柄,然后使用提供的参数尝试连接到数据库。如果连接成功,它将尝试将客户端的字符集设置为MYSQL_CHARACTER_SET
。如果任何步骤失败,函数会记录错误日志并返回NULL
。成功时,返回一个指向已连接的MySQL句柄的指针。
mysql_exec
个函数用于执行一个SQL命令。它接收一个指向MySQL句柄的指针和要执行的SQL命令字符串作为参数。函数调用mysql_query
来执行SQL命令。如果命令执行失败,函数会记录错误信息并返回false成功执行SQL命令后,函数返回ture
mysql_destory
这个函数用于关闭并销毁与MySQL数据库的连接。它接收一个指向MySQL句柄的指针作为参数。如果这个指针不是NULL
,函数会调用mysql_close
来关闭连接,并将指针置为NULL
json_util
serialize
这个方法用于将一个Json::Value
对象(代表一个JSON数据结构)转换成一个字符串形式的JSON文本。它首先创建一个Json::StreamWriter
的实例来执行实际的序列化操作。使用std::stringstream
作为中间存储,最后将序列化的结果赋值给传入的字符串引用。如果过程中发生任何错误,方法将记录一个错误日志并返回false
,否则返回true
unserialize
与serialize
相反,这个方法负责将一个字符串形式的JSON文本反序列化成一个Json::Value
对象。它使用Json::CharReader
来解析JSON字符串,如果解析成功,结果存储在传入的Json::Value
引用中。遇到错误时会记录日志并返回false
,否则返回true
string_util
split
该方法用于将字符串根据指定的分隔符进行分割,并将分割后的字符串存储到一个字符串向量中。
file_util
read
提供了一个read
静态方法,用于读取整个文件的内容到一个字符串中
数据管理模块
数据管理这里的设计分为两个部分,一个是数据库中user表结构的设计,一个是项目代码中user_table类的设计。
表结构设计
用户信息表这里,共创建8个字段,分别是用户的唯一标识,也就是user_id,还有username,password,用户的天梯分数score,total_count总战斗场次,win_count胜利场次,金币(后续没用到),好友id(以逗号作为分隔符将好友id隔开)
当用户进入到游戏大厅页面时,我们要展示出用户的名称,天梯分数,总战斗场次,胜利场次,好友等详细信息。
除此之外,还有创建好友请求数据库的命令。包含请求id,发送人id,接收人id,请求状态(‘已接受’、‘待处理’等),请求创建时间(没用到)
user_table类设计
当我们调用mysql_query执行sql语句时,mysql_query本身确实是线程安全的,如果执行的是增删改这样的sql语句也不会出现线程安全问题,但如果是查询语句,此时就出现线程安全的问题了。
在查询语句执行后,我们是需要调用其他的API来进行结果集的保存,遍历,释放等操作,在执行mysql_store_result之前,上一条在数据库中执行的语句必须是select才行,但在多线程的情况下,你能保证执行完select语句后,下一条语句执行的一定是mysql_store_result
总之,mysql句柄是共享资源,执行查询语句和获取结果集应该是原子操作,否则获取的结果集可能会有问题。
构造和析构
构造函数主要负责创建句柄
析构函数主要负责销毁句柄
insert()
此函数接受一个 Json::Value
类型的参数 user
,这个参数包含了用户的信息,如用户名和密码。目标是将这些信息插入数据库中,以完成用户注册的过程。
login()
函数接受一个包含用户名和密码的 Json::Value
对象 user
作为参数。它通过执行一个 SQL 查询来验证这些登录凭证。如果验证成功,该函数会更新 user
对象,加入用户的其他详细信息,如用户ID、分数、总比赛次数、胜利次数和金币数量。
select_by_name() select_by_id()
通过用户名和id来获取用户详细信息的逻辑和上面一模一样,唯一不同的就是sql语句的筛选条件改动了。需要注意的是:sql语句拼装正确、互斥锁的使用、释放结果集
win() lose() regold()
本质上都是对传入的id做数据库处理,非常简单。
犹豫设计中对游戏结果的处理都不涉及到多进程,所以不必要加锁
find_friend()
用来在数据库中查找好友,逻辑都差不多。唯独就是后面需要处理从数据库中获取的字符串,并存到数组里
add_friend() del_friend()
这里的添加好友是 添加好友请求验证通过以后,正式加为好友的
注意数组结尾空字符串的处理
add_friend_request()
这个是向数据库增加一条 添加好友的请求
handler_friend_request()
这是处理好友请求的函数,根据传来的状态码来判断接收方是接受了还是拒绝了
注意:如果同意了,要给双方互相都添加上好友
select_request_by_id()
通过接受方id来查找请求
select_friend_by_id()
通过id返回json格式的所有好友信息
逻辑都差不多
select_name_by_id()
遍历friends这个json数组,循环查找username放入friends中
其实就是friends中只有好友id,想把每一个人的username也放进去
当然,这样的操作效率十分低下,个人认为原因有以下几点:
1.一次只查询一条数据,需要多次访问数据库
2.每次获取结果都要获取和释放锁,这会带来额外开销
改进:
可以考虑一次性构造一个查询所有sender_id对应用户名的 SQL 查询,例如使用IN子句。这样可以大大减少数据库查询的次数,从而提高效率。如果实现了批量查询,那么只需要在执行批量查询时获取一次锁。如果某些sender_id对应的用户名查询非常频繁,可以考虑使用缓存机制。
在线用户管理模块
在基于 WebSocket 的实时通信应用,如在线游戏、聊天应用或实时数据推送服务中,管理用户ID与其对应的 WebSocket 连接是非常重要的,管理用户ID与其对应的 WebSocket 连接是实现实时通信应用中关键的用户交互、状态跟踪、通信效率和安全性等功能的基础。这种管理机制支持了高效、安全且可扩展的实时通信服务的构建。
实时通信
用户识别:每个 WebSocket 连接对应一个特定的用户。通过将用户ID与其连接关联,服务器能够识别发出请求的用户。
数据推送:服务器可以主动向特定用户推送数据或通知。通过管理用户ID和连接,服务器能够确定应将数据发送到哪个连接。
状态管理
在线状态跟踪:通过跟踪哪些用户ID当前有活跃的 WebSocket 连接,应用可以知道哪些用户在线,从而实现如在线用户列表、用户状态更新等功能。
房间或群组管理:在聊天室中,需要根据用户加入的房间管理其连接。这允许服务器仅向特定房间的成员广播消息。
通信效率和安全性
定向消息发送:管理连接使得服务器能够有效地向单个用户或特定用户发送消息,而无需广播到所有连接,从而提高通信效率。
权限验证:在用户建立 WebSocket 连接时进行身份验证,并将其ID与连接绑定,有助于后续通信中快速验证用户权限,保障通信安全。
4资源管理
连接维护:用户可能因网络问题或客户端行为导致连接频繁断开和重连。通过将用户ID与新的 WebSocket 连接关联,服务器可以管理这些变化,确保用户体验的连贯性。
优化资源利用:在用户离开应用时(如退出游戏大厅或游戏房间),及时从管理结构中移除其连接,有助于优化服务器资源利用,避免不必要的资源占用和泄露。
online_manager类设计
enter_game_hall() enter_game_room()
当服务器与客户端建立好websocket长连接之后,那就需要将用户添加到在线用户管理模块中
也就是把用户id和websocket连接建立映射关系
注意:因为两个哈希表是共享资源,操作哈希表应该加锁保护
exit_game_hall() exit_game_room()
和上面逻辑相似
is_in_game_hall()is_in_game_room()
判断用户是否在房间/大厅(在线管理)中,对不同的场景调用时机不同
我们直接调用find查找uid对应的迭代器,如果迭代器不为end(),那就说明当前用户确实在在线用户管理中
get_conn_from_hall()get_conn_from_room()
通过用户id在游戏大厅/房间在线用户管理中获取对应的通信连接
会话管理模块
cookie和session
Cookie和Session机制是Web开发中用于跟踪用户状态的两种技术,它们共同协作提供了一种在无状态的HTTP协议上维护状态信息的方式。
Cookie
Cookie是由服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下一次向同一服务器再发起请求时被携带并发送到服务器上。Cookie主要用于:
- 记录用户的浏览器活动(如登录状态、访问页面、点击按钮等)。
- 个性化用户界面(如主题偏好设置)。
- 跟踪用户浏览器的会话。
Cookie的限制包括大小限制(每个Cookie的大小限制约为4KB)、数量限制(每个域名下存储的Cookie的数量有限制)和安全性问题(明文传输可能被拦截)。
Session
Session是另一种服务器端存储用户数据的方式,以便跨多个页面请求或访问中持续跟踪用户的状态。与Cookie存储在客户端不同,Session数据默认存储在服务器上,因此可以存储更多信息,更安全。
Session工作流程:
- 用户访问Web应用,服务器为该用户创建一个Session,并生成一个唯一的Session ID。
- 服务器将Session ID发送到用户的浏览器上,通常是通过设置一个名为SessionID的Cookie。
- 用户再次访问时,浏览器会发送包含Session ID的Cookie到服务器。
- 服务器接收到Session ID后,查找对应的Session数据,根据需要进行处理
Cookie和Session的关系和区别
- 关系:通常,Session机制会使用Cookie来存储Session ID,虽然也可以通过其他方式(如URL重写)来传递Session ID。
- 存储位置:Cookie数据存储在客户端(浏览器),而Session数据存储在服务器端。
- 安全性:Session比Cookie更安全,因为Session数据不会在网络中传输。
- 生命周期:Cookie可以设置较长的过期时间,即使关闭浏览器数据也依然保存。Session通常在用户关闭浏览器后结束,但也可以通过服务器设置过期时间。
- 资源消耗:维护Session状态需要服务器资源,对于大量并发用户,需要合理管理Session资源。
简而言之,Cookie和Session都是为了在无状态的HTTP协议中识别和跟踪用户的状态。通过将二者结合使用,可以实现既方便又安全的用户状态管理机制。
为什么要设计会话管理系统
在基于 WebSocket 的实时应用中,实现一个管理用户会话(session)的系统是非常重要的,主要出于以下几个理由:
1. 用户状态管理
状态跟踪:了解用户是否登录、正在游戏中或处于其他状态对于提供个性化服务和维护系统状态一致性至关重要。
动态交互:实时应用,如在线游戏或聊天应用,需要根据用户状态调整其与服务器之间的交互方式。
2. 安全性
认证和授权:会话管理允许应用程序确认用户身份并根据用户的登录状态和权限限制对特定资源的访问。
防止滥用:通过跟踪会话,系统可以检测并防止潜在的滥用行为,如账号共享、暴力破解尝试等。
3. 资源管理
连接优化:管理会话可以帮助应用程序优化资源分配,如合理分配服务器负载、及时清理无效的连接资源,确保系统的高效运行。
超时管理:会话超时机制确保长时间不活跃的会话被自动清理,释放服务器资源,减少不必要的资源占用。
4. 用户体验
持久化会话:即使在网络不稳定导致连接断开的情况下,用户也能够无缝地重新连接到服务,恢复其会话状态,无需重新登录。
实时反馈:会话管理使得服务器能够即时识别并响应用户的行为,提供快速、连贯的用户体验。
5. 数据一致性
会话同步:在分布式系统中,管理用户的会话状态有助于维护跨多个服务器或服务的数据一致性。
事务管理:对于涉及多步操作的业务逻辑,会话可以作为执行这些操作的上下文,保证其原子性和一致性。
6. 应用场景特定需求
在线游戏:需要管理玩家的登录状态、游戏中状态,以及与之关联的游戏数据。
聊天应用:根据用户的在线状态和活跃度调整消息推送策略,提供离线消息功能。
总结来说,实现一个管理用户会话的系统是为了保证实时应用的安全性、效率、用户体验和数据一致性,同时满足特定业务逻辑和应用场景的需求。这是构建可扩展、可维护和用户友好的实时服务的基础之一。
session类和session_manager类设计
session 用来记录用户信息,session_manager用来管理所有session
session类很简单,方法也都是获取、设置、判断等
session_manager管理器
create_session()
用于创建和管理用户会话的,它使得应用能够跟踪和管理用户在整个应用中的会话状态
创建一个新的 session
对象,为它设置状态和跟踪的用户id
将新创建的会话对象添加到 _session
容器中,这个容器以会话ID为键,会话对象智能指针为值。
将用户ID和会话ID的映射关系添加到 _user
容器中,便于后续通过用户ID查找其会话ID。
自增 _next_ssid
,以确保每个会话都有一个唯一的ID。
append_session()
个方法主要用于在某些场景下重新添加或更新现有的会话对象到会话管理器中,例如在会话的状态或属性被更新后。
get_session_by_ssid() get_session_by_uid()
通过ssid获取session
通过uid获取ssid
刚好对应两个哈希表
remove_session()
用于移除session
set_session_expire_time()
设置会话的过期时间,其实分为四种情况,我们需要判断会话原来有没有定时删除的任务,有和没有就会细分为两种情况,在每种情况下面又都会细分两个子情况,也就是看外部给set_session_expire_time传入的时间参数是forever永久,还是timeout。
所以总体的情况就会分为四种,对每一种情况都要有不同的处理。
例如,当用户在登陆成功后,此时服务器会为用户创建一个定时销毁的会话,也就是说,如果在用户登录成功后,因为各种原因没有跳转到大厅页面,就会定时销毁,当用户成功跳转到游戏大厅,那么此时会话就应该从定时销毁变为永久存在,因为连接此时会切换为websocket连接,后续服务器提供所有的业务处理之前,都要在websocket连接的基础上
房间管理模块
设计一个房间管理系统对于任何需要支持多个用户同时参与、实时交互的在线游戏来说都是至关重要的,尤其是对于五子棋这样的游戏。以下是设计房间管理系统的主要原因和其带来的好处:
分隔游戏实例
隔离玩家:允许多对玩家同时进行游戏,每对玩家在自己的房间内游戏,互不干扰。
管理多局游戏:不同的房间可以承载不同的游戏实例,使得服务器能够同时管理多局游戏。
维护游戏状态
游戏进度控制:房间内可以独立地跟踪和控制游戏进度,例如判断游戏开始、进行中和结束状态。
状态同步:确保所有参与房间的玩家看到的游戏状态是同步的,包括棋盘状态和玩家行动。
提高资源利用效率
资源分配:通过房间来分配服务器资源,根据需要动态创建和销毁房间,有效管理服务器资源。
负载管理:分散玩家到不同房间,有助于平衡服务器负载,避免单一游戏实例占用过多资源。
支持额外功能
观战系统:房间管理系统可以方便地支持观战功能,允许非参赛玩家加入房间观看比赛。
聊天和社交:提供房间内聊天功能,增加游戏的社交互动性。
灵活的游戏匹配机制
动态匹配玩家:玩家可以根据等级、偏好等条件被匹配到合适的房间,提高游戏的平衡性和竞争性。
room类设计
room类的一个实例就是一个房间,成员有房间号、黑白棋、棋盘、状态、id等
判断棋局输赢逻辑
构造和析构
简单获取或修改实例信息方法
这些都是用来修改或获取room实例信息的方法,很简单
add/del_watcher()
这些是增加或删除房间中观战者的方法,逻辑就是在用来保存观战人id的数组中遍历
get_watchers()
这个方法是用来获取观众席的id和username
用于在对战中获取观战席信息的
getboard()
这个方法是用来获取棋盘信息的,当有人加入观战时,应该把棋盘中已经下过棋的信息同步给新加入观战的人
handle_chess()
下棋应该判断用户还是否在线
通过传来的“row”和“col”字段判断用户下棋的位置
每下一次棋,都要判断一次输赢
handle_chat()
房间带有聊天功能,也包含简单的敏感词过滤方法示例
不需要做什么处理,要广播消息
handle_exit()
处理房间中的玩家退出,如果有棋手退出应该做出处理和判断输赢
如果是观战玩家退出,也应该做出相应处理
handle_request()
总的处理函数,有任何请求,应该先接入这个方法做判断和分流
通过“optype”字段来判断请求类型,做不同的处理
做完判断后,应该要进行广播
broadcast()
广播函数,也记得包括观战玩家
通过调用connection类里面的send函数就可以在websocket连接上,发送已经序列化好的body数据
room_manager类设计
当匹配队列有两个人,然后会判断两个人是都都还在大厅中,如果都在,出两个队列,再创建房间
这里有三个映射表,能够让我们房间id找到用户id
create_room()
为两个用户创建房间,并返回房间的智能指针管理对象
add_watcher_room()
把观战用户添加到房间中,管理起来
get_room_by_rid()
通过房间ID获取房间信息
get_room_by_uid()
通过用户ID获取房间信息
remove_room()
通过房间ID销毁房间
remove_room_user()
删除房间中指定用户,如果房间中没有用户了,则销毁房间,用户连接断开时被调用
匹配队列管理模块
其实所谓的匹配队列就是阻塞队列,我们匹配队列总共会实现三个,分别对应不同档次分数的玩家进行匹配
除此之外我们还需要为每个阻塞队列创建出一个匹配的线程,这个线程其实就是消费者,用于判断当前队列中用户的个数是否超过2个,如果超过2个,则出队头的前两个用户,为这两个用户创建游戏房间,
match_queue
类
提供一个线程安全的队列,专门用于管理等待匹配的玩家ID。
match_queue类提供了一些简单方法,主要用于操作队列,获取队列信息,阻塞队列
matcher类
用来管理三个队列,根据玩家的积分将他们分配到不同的匹配队列中,并在队列中的玩家数量足够时自动进行匹配,创建游戏房间。
类会在构造的时候创建三个线程,对应三个队列
handle_match()
一个私有方法,用于处理匹配逻辑,包括从队列中取出玩家ID,校验玩家在线状态,创建游戏房间,并通知玩家匹配成功
正常状态下,线程会阻塞在88行wait位置,当有人加入队列,调用队列的push方法,就会唤醒线程,也就是会执行86行判断,如果有足够的人,就往下走,出队玩家,校验状态,创建游戏房间,send通知玩家
add()
先获取玩家分数,讲玩家分配到不同的队列中
del()
到相对应的队列出队玩家
整合封装服务器模块
在整合封装服务器模块这里,
第一是搭建好一个基本的http/websocket服务器出来,能够完成服务端和客户端的通信,
第二是针对客户端发起的一系列业务请求,进行相应的业务处理,而业务处理的完成,其实就是通过我们前面所实现的一系列模块来进行的。
构造函数
首先我们设置日志等级set_access_channels(websocketpp::log::alevel::none)为不打印所有
其次init_asio()初始化 ASIO 库,准备 WebSocket 服务器进行异步 I/O 操作
设置地址重用set_reuse_addr(true)
为websocket设置四个回调函数
http_callback()
首先我们要根据连接获取请求,包括请求的头部、uri
然后根据请求头和uri来判断做什么样的业务处理
reg()
注册业务处理
反序列化获取正文,得到用户名密码 ,需要判断是否为空
然后操作数据库
对客户端响应
注册前端页面,通过ajax向后台发送用户注册请求和注册的用户名密码
如果服务器发送result为true,说明请求成功,跳转登陆页面
注意ajax中url和type设置,携带序列化数据的设置
login()
登陆业务处理
获取正文、判断空、登陆成功给客户端创建session管理
设置响应
逻辑和注册相同,成功返回 true,跳转游戏大厅页面
info()
获取用户信息,这时候应该已经有session管理了,验证session,从数据库取数据
设置响应、发送数据
进入大厅后,向服务器发送信息获取请求,成功后建立websocket连接
设置连接的回调函数
friends_info()
用户好友的信息获取
从cookie中取出session,从session中找到用户id,用用户id找到好友id,再通过好友id找到好友session,再获取好友状态(在线、离线、游戏中)
获取好友信息,服务器响应成功后,将信息添加到页面中
request_info()
获取好友请求信息
通过筛选所有请求收到者和自己的id匹配,拿出匹配的,再转换成user那么放进json里
最后序列化发送
获取好友信息,为按钮绑定点击事件,点击后向后端发送对应的请求
add_info()
这个是请按名称搜索好友的处理
把select到的username发送
和之前一样,将服务器发送来的信息打印在页面上
destroy_session()
将session设置为定时销毁
file_hander()
静态资源请求的处理
如果文件不存在,简单返回个Not Found页面
客户端回调函数,注意像300行,是websocket提供的发送函数,是websocket请求
wsopen_callback()
长连接建立以后调用的函数
从连接中取出uri,分别对进入大厅和房间分别设置回调函数
wsopen_game_hall()
session校验、在线管理、给客户端响应、设置session永久存在
ws_resp()函数是序列化响应,然后发送
wsopen_game_room()
和之前逻辑一样,session校验、在线管理、给客户端响应、设置session永久存在
多一步判断房间是否准备好,响应房间信息
wsclose_callback()
和之前逻辑一样,从连接获取到uri,
分别对大厅和房间设置连接断开的回调函数
wsclose_game_hall()
session验证,如果已经断开就不用操作了,也操作不了
在线管理中删除、设置session定时销毁
wsclose_game_room()
和之前一样,多一步从房间管理中删除
wsmsg_callback()
获取uri,分别设置大厅和房间长连接通信处理
wsmsg_game_hall()
对于不同的请求,进行不同的处理
wsmsg_game_room()
校验信息后,把请求发送给房间管理模块中的总的请求处理