使用webSocket协议实现类论坛帖子列表的点赞实时展现功能

作者: admin 分类: 网络协议 发布时间: 2019-12-24 09:44  阅读: 101 views

在开发的过程中,经常会碰到产品的这样一个要求,界面上的数据要实时展示。像一些全局性的数据,或者业务交集较少的数据可以实时去数据库查询,但是像一些列表类型的、用户访问量大的数据,不适合实时去查询。之前碰到的一个情况是这样的,如下图

论坛帖子列表

帖子列也要展示封面图、帖子标题、帖子标签、用户头像、用户昵称、点赞数、用户属性等等字段。其中,点赞功能的操作发生概率很大,而运营团队做活动,对实时性的要求很高,需要让用户快速定位到热帖是哪个,进而参与话题活动等。

初次做的时候,是拒绝这个功能的(不知道怎么去好的实现),因为访问量大,而且要实时查询数据库的数据,会给社区业务带来一些性能上的影响,所以拒绝了产品的功能要求。按照我们的现有想法来做。

mysql查询 + redis缓存 + 代码优化实现列表的展示。查数据库时精确字段,排除冗余业务字段;用redis做了二级的数据缓存,一级是社区首页的全部数据(入口也),二级是板块、帖子、广告等数据的缓存。针对帖子的发表时间、热度、精选、点赞等不同维度都做了缓存。


现在呢,想想这个功能,可能会有几种方式去做。

  1. ES处理。整个项目后期增加了搜索引擎es,方便快速地查询帖子。在针对帖子的创建、修改等操作都会对es索引进行同步修改。由于ES的特性本身就适合查询(特性是全文检索、存储、分析),速度很快,所以将数据库查询改为 nosql查询是可以达到这个目的的(实时查询ES,我这里的测试,帖子索引数量是百万级,更高的没试过)。可能碰到的问题就是处理好,并发修改时的version版本号问题。(这种需要,客户端不断请求服务端数据),ELK相关请看《ELK学习

再后来,了解到WebSocket协议,那感觉,这个更合适去处理实时性高的场景。

  1. WebSocket处理
    WebSocketWebSocket 是 HTML5 开始提供的一种在单个 TCP连接上进行全双工通讯的协议。能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
    webSocket AWT界面实现
    这种方式不需要客户端轮询请求服务器端。只要首次建立链接之后,服务器端可以推送消息。Websocket 通过HTTP/1.1 协议的101状态码进行握手。

websocket的pom引用

<dependencies>
      <!-- https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket -->
    <dependency>
        <groupId>org.java-websocket</groupId>
        <artifactId>Java-WebSocket</artifactId>
        <version>1.4.0</version>
    </dependency>

  </dependencies>

webSocket的服务端、AWT客户端、H5客户端实现

在官方的git地址中,有很多的例子,请自行查阅
https://github.com/TooTallNate/Java-WebSocket/tree/master/src/main/example

1 .webSocket服务端代码

创建连接并监听事件

package com.chl.websocket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;

import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;

/**
 * webSocket.jar  使用来自以下地址
 * https://repo1.maven.org/maven2/org/java-websocket/Java-WebSocket/1.4.0/ 
 * @author chenhailong
 */
public class WebSocketTest extends WebSocketServer {

    /**
     * 构造函数
     * @param port
     * @throws UnknownHostException
     */
    public WebSocketTest( int port ) throws UnknownHostException {
        super( new InetSocketAddress( port ) );
    }

    /**
     * 构造函数
     * @param address
     */
    public WebSocketTest( InetSocketAddress address ) {
        super( address );
    }

    /**
     * 打开连接时的监听事件
     * conn.send(msg);   //发送信息给最新的客户端
     * broadcast();      //广播模式,发送给所有连接此端口的客户端
     */
    @Override
    public void onOpen( WebSocket conn, ClientHandshake handshake ) {
        conn.send("欢迎访问webSocket服务!");  
        broadcast( "new connection: " + handshake.getResourceDescriptor() );  
        System.out.println( conn.getRemoteSocketAddress().getAddress().getHostAddress() + " 来到了这个聊天室!" );
    }

    /**
     * 关闭连接时的监听事件
     * 
     */
    @Override
    public void onClose( WebSocket conn, int code, String reason, boolean remote ) {
        broadcast("连接为:"+ conn + "的端已经离开了这个聊天室 !" );
        System.out.println("连接为:"+ conn + "的端已经离开了这个聊天室 !");
    }

    /**
     * 监听字符串消息,并广播
     */
    @Override
    public void onMessage( WebSocket conn, String message ) {
        broadcast( message );
        System.out.println("连接为:"+ conn + "的端发送消息: " + message );
    }

    /**
     * 监听字节消息,并广播
     */
    @Override
    public void onMessage( WebSocket conn, ByteBuffer message ) {
        broadcast( message.array() );
        System.out.println("连接为:"+ conn + "的端发送消息: " + message );
    }

    /**
     * main方法,创建并开启webSocket服务端, 轮询消息并输出
     * @param args
     * @throws InterruptedException
     * @throws IOException
     */
    public static void main( String[] args ) throws InterruptedException , IOException {
        int port = 9999; 
        try {
            port = Integer.parseInt( args[ 0 ] );
        } catch ( Exception ex ) {
        }
        WebSocketTest s = new WebSocketTest( port );
        s.start();
        System.out.println( "WebSocket 服务端已经运行,端口为: " + s.getPort() );

        //这里是控制台输入,可以更改为数据库读取或者nosql查询等
        BufferedReader sysin = new BufferedReader( new InputStreamReader( System.in ) );
        while ( true ) { //可以设置查询时间
            String in = sysin.readLine();
            s.broadcast( in );
            if( in.equals( "exit" ) ) {
                s.stop(1000);
                break;
            }
        }
    }

    /**
     * 监听错误事件
     */
    @Override
    public void onError( WebSocket conn, Exception ex ) {
        ex.printStackTrace();
        if( conn != null ) {
            // some errors like port binding failed may not be assignable to a specific websocket
        }
    }

    /**
     * 监听开启事件
     */
    @Override
    public void onStart() {
        System.out.println("WebSocket 服务端已经正常开启!");
        setConnectionLostTimeout(0);
        setConnectionLostTimeout(100);
    }

}

2 .webSocket客户端AWT实现代码

简单类似聊天室功能,做连接、关闭、输入等范例

package com.chl.websocket;

import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.net.URI;
import java.net.URISyntaxException;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;

/**
 * 这里是官网的客户端例子,
 * 利用awt组件做了一个聊天界面,可以开启、关闭webSocket连接。也可以接收其他端或服务端的消息并展示
 * 为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking
 * @author chenhailong
 *
 */
public class WebSocketClientAWT extends JFrame implements ActionListener {
    private static final long serialVersionUID = -6056260699202978657L;

    private final JTextField uriField;
    private final JButton connect;
    private final JButton close;
    private final JTextArea ta;
    private final JTextField chatField;
    @SuppressWarnings("rawtypes")
    private final JComboBox draft;
    private WebSocketClient cc;

    /**
     * 利用AWT组件构建一个聊天界面
     * @param defaultlocation
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public WebSocketClientAWT( String defaultlocation ) {
        super( "WebSocket 聊天客户端" );
        Container c = getContentPane();
        GridLayout layout = new GridLayout();
        layout.setColumns( 1 );
        layout.setRows( 6 );
        c.setLayout( layout );

        Draft[] drafts = { new Draft_6455() };
        draft = new JComboBox( drafts );
        c.add( draft );

        uriField = new JTextField();
        uriField.setText( defaultlocation );
        c.add( uriField );

        connect = new JButton( "建立连接!" );
        connect.addActionListener( this );
        c.add( connect );

        close = new JButton( "关闭连接!" );
        close.addActionListener( this );
        close.setEnabled( false );
        c.add( close );

        JScrollPane scroll = new JScrollPane();
        ta = new JTextArea();
        scroll.setViewportView( ta );
        c.add( scroll );

        chatField = new JTextField();
        chatField.setText( "" );
        chatField.addActionListener( this );
        c.add( chatField );

        java.awt.Dimension d = new java.awt.Dimension( 300, 400 );
        setPreferredSize( d );
        setSize( d );

        addWindowListener( new java.awt.event.WindowAdapter() {
            @Override
            public void windowClosing( WindowEvent e ) {
                if( cc != null ) {
                    cc.close();
                }
                dispose();
            }
        } );

        setLocationRelativeTo( null );
        setVisible( true );
    }

    /**
     * 事件监听
     */
    public void actionPerformed( ActionEvent e ) {

        if( e.getSource() == chatField ) {
            if( cc != null ) {
                cc.send( chatField.getText() );
                chatField.setText( "" );
                chatField.requestFocus();
            }

        } else if( e.getSource() == connect ) {
            try {
                // cc = new ChatClient(new URI(uriField.getText()), area, ( Draft ) draft.getSelectedItem() );
                cc = new WebSocketClient( new URI( uriField.getText() ), (Draft) draft.getSelectedItem() ) {

                    @Override
                    public void onMessage( String message ) {
                        ta.append( "got: " + message + "\n" );
                        ta.setCaretPosition( ta.getDocument().getLength() );
                    }

                    @Override
                    public void onOpen( ServerHandshake handshake ) {
                        ta.append( "You are connected to ChatServer: " + getURI() + "\n" );
                        ta.setCaretPosition( ta.getDocument().getLength() );
                    }

                    @Override
                    public void onClose( int code, String reason, boolean remote ) {
                        ta.append( "You have been disconnected from: " + getURI() + "; Code: " + code + " " + reason + "\n" );
                        ta.setCaretPosition( ta.getDocument().getLength() );
                        connect.setEnabled( true );
                        uriField.setEditable( true );
                        draft.setEditable( true );
                        close.setEnabled( false );
                    }

                    @Override
                    public void onError( Exception ex ) {
                        ta.append( "Exception occured ...\n" + ex + "\n" );
                        ta.setCaretPosition( ta.getDocument().getLength() );
                        ex.printStackTrace();
                        connect.setEnabled( true );
                        uriField.setEditable( true );
                        draft.setEditable( true );
                        close.setEnabled( false );
                    }
                };

                close.setEnabled( true );
                connect.setEnabled( false );
                uriField.setEditable( false );
                draft.setEditable( false );
                cc.connect();
            } catch ( URISyntaxException ex ) {
                ta.append( uriField.getText() + " is not a valid WebSocket URI\n" );
            }
        } else if( e.getSource() == close ) {
            cc.close();
        }
    }

    public static void main( String[] args ) {
        String location;
        if( args.length != 0 ) { //有参是取这里
            location = args[ 0 ];
            System.out.println( "默认的服务端url为: \'" + location + "\'" );
        } else {
            location = "ws://localhost:9999";
            System.out.println( "默认的服务端url为:  \'" + location + "\'" );
        }
        new WebSocketClientAWT( location );
    }

}

3.html5的js实现websocket

服务器推送数据,前端客户端可以正常监听处理

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>WebSocket的html样例</title>

<script type="text/javascript">
         //客户端的WebSocket连接
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               alert("您的浏览器支持 WebSocket!");
               // 打开一个 web socket
               var ws = new WebSocket("ws://localhost:9999/");
               //这是连接事件
               ws.onopen = function()
               {
                  // Web Socket 已连接上,使用 send() 方法发送数据
                  ws.send("发送数据");
                  alert("数据发送中...");
               };
               //这是消息监听事件,在一个html标签中显示
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  var context = document.getElementById("show").innerText;
                  document.getElementById("show").innerText = context +"\n"+ received_msg;
               };

               //这是关闭事件(服务器关闭)
               ws.onclose = function()
               { 
                  // 关闭 websocket
                  alert("连接已关闭..."); 
               };
            }
            else
            {
               // 浏览器不支持 WebSocket
               alert("您的浏览器不支持 WebSocket!");
            }
         }
      </script>
</head>
<body onload="WebSocketTest()">
    <br><hr> <div id="show"></div>
</body>
</html>

webSocket AWT界面实现

服务器端输出如下:

## 客户端连接时输出
127.0.0.1 来到了这个聊天室!

## 客户端发送消息时输出
连接为:org.java_websocket.WebSocketImpl@46775c78的端发送消息: 你好
连接为:org.java_websocket.WebSocketImpl@46775c78的端发送消息: 这是一个AWT界面

## 服务端输出内容,客户端也会显示
收到了
你是第一个来的客户

这只是websocket的简单实现,如果要实现帖子点赞数的实时展示,对于数据的更新还要做对应的处理。比如持久化及即时展现,还是要做些设计的。用户点赞的数据是经过内存,再持久化到数据库。还是直接到数据库,根据自身的情况做处理。


   原创文章,转载请标明本文链接: 使用webSocket协议实现类论坛帖子列表的点赞实时展现功能

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

一条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注

更多阅读