WebSockets 的延迟断开机制
Delayed disconnect mechanism for WebSockets
我正在尝试将 "delayed disconnect" 机制添加到我正在开发的 WebSocket 聊天中。这意味着如果用户断开连接,但在特定时间限制内重新连接 - 我将使用 30 秒作为示例 - 断开连接将被忽略。这样做的原因是如果用户暂时失去连接的概念验证 - 例如移动用户进入电梯。
我决定为此使用 cookie。我想出的逻辑是,当打开 WebSocket 时,它也会打开 HttpSession。由此,我可以检查是否存在具有特定 id 的 cookie。如果是,则他们不会被视为新用户。但是,为此我需要能够将 cookie 的到期时间设置为 30 秒 after 套接字已关闭。
我已经知道 Cookie.setMaxAge() 会那样做,但是当我在服务器上的 OnClose() 方法中尝试这个时,服务器抛出一个 NullPointerException。这并不奇怪,因为我显然是在用户会话关闭后尝试访问它。
那么,有没有办法做到这一点?
2 月 16 日更新 我决定尝试在发送消息时完全重置 cookie。这 部分 有效,因为 cookie 已生成并添加到 HttpSession,但在重新连接时服务器认为用户是全新的。所以,我认为我的问题是 cookie 没有发送给用户。
更新 2 阅读 this question 后,我已将 cookie 生成移动到配置 class 中,在成功握手时调用该配置。如果请求没有 cookie,它将被视为一个全新的连接,并将其记录到系统控制台作为概念证明。我必须做的一件事是在开始时延长 cookie 的生命周期:目前,大概是 10 分钟。如果我找不到完全按照我上面所说的去做的方法,我会用这个。
2 月 19 日更新 我完全放弃了饼干。请参阅我的解决方案。
我通过完全放弃 cookie 解决了这个问题。我刚刚展示了相关 classes 中的方法;如果这还不够,我将编辑我的答案以包含完整代码。
在配置 class 中,我得到了请求的 x-forwarded-for header。这与客户端的 IP 地址相匹配,特别是因为我的后端服务器位于代理后面。如果用户的 IP 地址在用户列表中,则他们的连接是 "refreshed";否则,它们将被添加到列表中。断开连接后,无论出于何种原因,用户都会被标记为断开连接。
一个单独的 ConnectionMonitor class 实现了 Runnable 接口并且每 10 秒运行一次,并检查是否有任何客户端已断开连接超过 30 秒。如果是,则将其从用户列表中删除。
MyConfigClass.modifyHandshake()
@Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response)
{
HttpSession theSession = (HttpSession) request.getHttpSession();
config.getUserProperties().put(HttpSession.class.getName(), theSession);
String ID = request.getHeaders().get("x-forwarded-for").get(0);
if (ChatroomServerEndpoint.users.containsKey(ID))
{
// if this user isn't new, add them back onto the list
User oldUser = ChatroomServerEndpoint.users.get(ID);
System.out.println("An old user with " + ID + " has returned.");
ChatroomServerEndpoint.users.remove(oldUser);
ChatroomServerEndpoint.users.put(ID, oldUser);
oldUser.toggleConnection(true);
System.out.println(oldUser + ", " + ChatroomServerEndpoint.users.size() );
}
else
{
// add a new user to the list
System.out.println("A new user with ID " + ID + " has arrived!");
User newUser = new User(ID);
ChatroomServerEndpoint.users.put(ID, newUser);
System.out.println(newUser + ", " + ChatroomServerEndpoint.users.size() );
}
// put this ID into the configuration for proof of concept
config.getUserProperties().put("newUser", ID);
}
ConnectionMonitor.updateUsers() 在单独的线程中运行。
void updateUsers()
{
for(String id : ChatroomServerEndpoint.users.keySet())
{
User theUser = ChatroomServerEndpoint.users.get(id);
if (theUser.getStatus() == User.Connection.DISCONNECTED)
{
// get the time at which the user disconnected
Calendar disconnectDate = theUser.getdisconnectionDate();
// Calendar.getTime.getTime returns milliseconds,
// so, multiply maxDisconnectTime by 1000 to see if the user has expired
if (theDate.getTime().getTime() - disconnectDate.getTime().getTime()
>= maxDisconnectTime * 1000 )
{
System.out.println(id + " has timed out");
ChatroomServerEndpoint.users.remove(id);
}
}
}
}
用户
public class User {
// the ID is the user's IP address
private String id;
// connection status
public enum Connection
{
CONNECTED,
DISCONNECTED
}
private Connection status;
// the time of disconnection
private Calendar disconnectionDate;
// each user needs a WebSocket Session to be able to send and receive messages
private Session userSession;
/**
* @return the id of this user
*/
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
/**
* @return connection status
*/
public Connection getStatus() {
return status;
}
public void setStatus(Connection status) {
this.status = status;
}
public Calendar getdisconnectionDate() {
return disconnectionDate;
}
public void setdisconnectionDate(Calendar disconnectionDate) {
this.disconnectionDate = disconnectionDate;
}
/**
* @return the userSession
*/
public Session getUserSession() {
return userSession;
}
/**
* @param userSession the userSession to set
*/
public void setUserSession(Session userSession) {
this.userSession = userSession;
}
/**
* @param newID the new ID of the user
*/
public User (String newID)
{
this.id = newID;
this.status = Connection.CONNECTED;
}
/**
* Toggles the connection
* @param toggle - if true, the user is connected
*/
public void toggleConnection(boolean toggle)
{
if (toggle == false)
{
status = Connection.DISCONNECTED;
disconnectionDate = Calendar.getInstance();
}
else
{
status = Connection.CONNECTED;
disconnectionDate = Calendar.getInstance();
disconnectionDate.add(Calendar.HOUR, 1); // give an extra hour to prevent them being disconnected too soon
}
}
}
我正在尝试将 "delayed disconnect" 机制添加到我正在开发的 WebSocket 聊天中。这意味着如果用户断开连接,但在特定时间限制内重新连接 - 我将使用 30 秒作为示例 - 断开连接将被忽略。这样做的原因是如果用户暂时失去连接的概念验证 - 例如移动用户进入电梯。
我决定为此使用 cookie。我想出的逻辑是,当打开 WebSocket 时,它也会打开 HttpSession。由此,我可以检查是否存在具有特定 id 的 cookie。如果是,则他们不会被视为新用户。但是,为此我需要能够将 cookie 的到期时间设置为 30 秒 after 套接字已关闭。
我已经知道 Cookie.setMaxAge() 会那样做,但是当我在服务器上的 OnClose() 方法中尝试这个时,服务器抛出一个 NullPointerException。这并不奇怪,因为我显然是在用户会话关闭后尝试访问它。
那么,有没有办法做到这一点?
2 月 16 日更新 我决定尝试在发送消息时完全重置 cookie。这 部分 有效,因为 cookie 已生成并添加到 HttpSession,但在重新连接时服务器认为用户是全新的。所以,我认为我的问题是 cookie 没有发送给用户。
更新 2 阅读 this question 后,我已将 cookie 生成移动到配置 class 中,在成功握手时调用该配置。如果请求没有 cookie,它将被视为一个全新的连接,并将其记录到系统控制台作为概念证明。我必须做的一件事是在开始时延长 cookie 的生命周期:目前,大概是 10 分钟。如果我找不到完全按照我上面所说的去做的方法,我会用这个。
2 月 19 日更新 我完全放弃了饼干。请参阅我的解决方案。
我通过完全放弃 cookie 解决了这个问题。我刚刚展示了相关 classes 中的方法;如果这还不够,我将编辑我的答案以包含完整代码。
在配置 class 中,我得到了请求的 x-forwarded-for header。这与客户端的 IP 地址相匹配,特别是因为我的后端服务器位于代理后面。如果用户的 IP 地址在用户列表中,则他们的连接是 "refreshed";否则,它们将被添加到列表中。断开连接后,无论出于何种原因,用户都会被标记为断开连接。
一个单独的 ConnectionMonitor class 实现了 Runnable 接口并且每 10 秒运行一次,并检查是否有任何客户端已断开连接超过 30 秒。如果是,则将其从用户列表中删除。
MyConfigClass.modifyHandshake()
@Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response)
{
HttpSession theSession = (HttpSession) request.getHttpSession();
config.getUserProperties().put(HttpSession.class.getName(), theSession);
String ID = request.getHeaders().get("x-forwarded-for").get(0);
if (ChatroomServerEndpoint.users.containsKey(ID))
{
// if this user isn't new, add them back onto the list
User oldUser = ChatroomServerEndpoint.users.get(ID);
System.out.println("An old user with " + ID + " has returned.");
ChatroomServerEndpoint.users.remove(oldUser);
ChatroomServerEndpoint.users.put(ID, oldUser);
oldUser.toggleConnection(true);
System.out.println(oldUser + ", " + ChatroomServerEndpoint.users.size() );
}
else
{
// add a new user to the list
System.out.println("A new user with ID " + ID + " has arrived!");
User newUser = new User(ID);
ChatroomServerEndpoint.users.put(ID, newUser);
System.out.println(newUser + ", " + ChatroomServerEndpoint.users.size() );
}
// put this ID into the configuration for proof of concept
config.getUserProperties().put("newUser", ID);
}
ConnectionMonitor.updateUsers() 在单独的线程中运行。
void updateUsers()
{
for(String id : ChatroomServerEndpoint.users.keySet())
{
User theUser = ChatroomServerEndpoint.users.get(id);
if (theUser.getStatus() == User.Connection.DISCONNECTED)
{
// get the time at which the user disconnected
Calendar disconnectDate = theUser.getdisconnectionDate();
// Calendar.getTime.getTime returns milliseconds,
// so, multiply maxDisconnectTime by 1000 to see if the user has expired
if (theDate.getTime().getTime() - disconnectDate.getTime().getTime()
>= maxDisconnectTime * 1000 )
{
System.out.println(id + " has timed out");
ChatroomServerEndpoint.users.remove(id);
}
}
}
}
用户
public class User {
// the ID is the user's IP address
private String id;
// connection status
public enum Connection
{
CONNECTED,
DISCONNECTED
}
private Connection status;
// the time of disconnection
private Calendar disconnectionDate;
// each user needs a WebSocket Session to be able to send and receive messages
private Session userSession;
/**
* @return the id of this user
*/
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
/**
* @return connection status
*/
public Connection getStatus() {
return status;
}
public void setStatus(Connection status) {
this.status = status;
}
public Calendar getdisconnectionDate() {
return disconnectionDate;
}
public void setdisconnectionDate(Calendar disconnectionDate) {
this.disconnectionDate = disconnectionDate;
}
/**
* @return the userSession
*/
public Session getUserSession() {
return userSession;
}
/**
* @param userSession the userSession to set
*/
public void setUserSession(Session userSession) {
this.userSession = userSession;
}
/**
* @param newID the new ID of the user
*/
public User (String newID)
{
this.id = newID;
this.status = Connection.CONNECTED;
}
/**
* Toggles the connection
* @param toggle - if true, the user is connected
*/
public void toggleConnection(boolean toggle)
{
if (toggle == false)
{
status = Connection.DISCONNECTED;
disconnectionDate = Calendar.getInstance();
}
else
{
status = Connection.CONNECTED;
disconnectionDate = Calendar.getInstance();
disconnectionDate.add(Calendar.HOUR, 1); // give an extra hour to prevent them being disconnected too soon
}
}
}