服务部署方式参照:https://github.com/mywiki/mpush-doc/blob/master/SUMMARY.md 完成
mpush官方详细开发文档:http://mpush.mydoc.io/?t=134820
redis默认只能本机访问,需要修改配置文件,请参考https://blog.csdn.net/weiyangdong/article/details/79916445进行修改
完整web项目demol连接:https://download.csdn.net/download/qq_16758997/10943141
安装Mpush-Alloc服务时记得修改mpush.config配置文件中的ws-server-port的端口号(端口号为模拟客户端中的端口号)
一、新建一个普通的maven web工程或新建一个web工程再转换为maven工程(文章采用后一种方式,jdk1.8,tomcat 9.0)
二、修改pom.xml文件如下:
4.0.0
webmpush
webmpush
0.0.1-SNAPSHOT
war
com.github.mpusher
mpush-client
0.8.0
src
src
**/*.java
maven-compiler-plugin
3.7.0
1.8
maven-war-plugin
3.2.1
WebContent
新加标签部分,导入mpush相关的jar包
在src目录下新建application.conf文件,文件内容如下
##################################################################################################################
#
# NOTICE:
#
# 系统配置文件,所有列出的项是系统所支持全部配置项
# 如果要覆盖某项的值可以添加到mpush.conf中。
#
# 配置文件格式采用HOCON格式。解析库由https://github.com/typesafehub/config提供。
# 具体可参照说明文档,比如含有特殊字符的字符串必须用双引号包起来。
#
##################################################################################################################
mp {
#基础配置
home=${user.dir} //程序工作目录
#日志配置
log-level=warn
log-dir=${mp.home}/logs
log-conf-path=${mp.home}/conf/logback.xml
#核心配置
core {
max-packet-size=10k //系统允许传输的最大包的大小
compress-threshold=10k //数据包启用压缩的临界值,超过该值后对数据进行压缩
min-heartbeat=3m //最小心跳间隔
max-heartbeat=3m //最大心跳间隔
max-hb-timeout-times=2 //允许的心跳连续超时的最大次数
session-expired-time=1d //用于快速重连的session 过期时间默认1天
epoll-provider=netty //nio:jdk自带,netty:由netty实现
}
#安全配置
security {
#rsa 私钥、公钥key长度为1024;可以使用脚本bin/rsa.sh生成, @see com.mpush.tools.crypto.RSAUtils#main
private-key="MIIBNgIBADANBgkqhkiG9w0BAQEFAASCASAwggEcAgEAAoGBAKCE8JYKhsbydMPbiO7BJVq1pbuJWJHFxOR7L8Hv3ZVkSG4eNC8DdwAmDHYu/wadfw0ihKFm2gKDcLHp5yz5UQ8PZ8FyDYvgkrvGV0ak4nc40QDJWws621dm01e/INlGKOIStAAsxOityCLv0zm5Vf3+My/YaBvZcB5mGUsPbx8fAgEAAoGAAy0+WanRqwRHXUzt89OsupPXuNNqBlCEqgTqGAt4Nimq6Ur9u2R1KXKXUotxjp71Ubw6JbuUWvJg+5Rmd9RjT0HOUEQF3rvzEepKtaraPhV5ejEIrB+nJWNfGye4yzLdfEXJBGUQzrG+wNe13izfRNXI4dN/6Q5npzqaqv0E1CkCAQACAQACAQACAQACAQA="
public-key="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCghPCWCobG8nTD24juwSVataW7iViRxcTkey/B792VZEhuHjQvA3cAJgx2Lv8GnX8NIoShZtoCg3Cx6ecs+VEPD2fBcg2L4JK7xldGpOJ3ONEAyVsLOttXZtNXvyDZRijiErQALMTorcgi79M5uVX9/jMv2Ggb2XAeZhlLD28fHwIDAQAB"
aes-key-length=16 //AES key 长度
}
#网络配置
net {
local-ip="127.0.0.1" //本地ip, 默认取第一个网卡的本地IP
public-ip="127.0.0.1" //外网ip, 默认取第一个网卡的外网IP
connect-server-bind-ip="" //connSrv 绑定的本地ip (默认anyLocalAddress 0.0.0.0 or ::0)
connect-server-register-ip=${mp.net.public-ip} //公网ip, 注册到zk中的ip, 默认是public-ip
connect-server-port=3000 //长链接服务对外端口, 公网端口
connect-server-register-attr { //注册到zk里的额外属性,比如配置权重,可在alloc里排序
weight:1
}
gateway-server-bind-ip="" //gatewaySrv 绑定的本地ip (默认anyLocalAddress 0.0.0.0 or ::0)
gateway-server-register-ip=${mp.net.local-ip} //本地ip, 注册到zk中的ip, 默认是local-ip
gateway-server-port=3001 //网关服务端口, 内部端口
gateway-server-net=tcp //网关服务使用的网络类型tcp/udp/sctp/udt
gateway-client-port=4000 //UDP 客户端端口
gateway-server-multicast="239.239.239.88" //239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效
gateway-client-multicast="239.239.239.99" //239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效
gateway-client-num=1 //网关客户端连接数
admin-server-port=3002 //控制台服务端口, 内部端口
ws-server-port=0 //websocket对外端口, 公网端口, 0表示禁用websocket
ws-path="/" //websocket path
public-host-mapping { //本机局域网IP和公网IP的映射关系, 该配置后续会被废弃
//"10.0.10.156":"111.1.32.137"
//"10.0.10.166":"111.1.33.138"
}
snd_buf { //tcp/udp 发送缓冲区大小
connect-server=32k
gateway-server=0
gateway-client=0 //0表示使用操作系统默认值
}
rcv_buf { //tcp/udp 接收缓冲区大小
connect-server=32k
gateway-server=0
gateway-client=0 //0表示使用操作系统默认值
}
write-buffer-water-mark { //netty 写保护
connect-server-low=32k
connect-server-high=64k
gateway-server-low=10m
gateway-server-high=20m
}
traffic-shaping { //流量整形配置
gateway-client {
enabled:false
check-interval:100ms
write-global-limit:30k
read-global-limit:0
write-channel-limit:3k
read-channel-limit:0
}
gateway-server {
enabled:false
check-interval:100ms
write-global-limit:0
read-global-limit:30k
write-channel-limit:0
read-channel-limit:3k
}
connect-server {
enabled:false
check-interval:100ms
write-global-limit:0
read-global-limit:100k
write-channel-limit:3k
read-channel-limit:3k
}
}
}
#Zookeeper配置
zk {
server-address="127.0.0.1:2181" //多台机器使用","分隔如:"10.0.10.44:2181,10.0.10.49:2181" @see org.apache.zookeeper.ZooKeeper#ZooKeeper()
namespace=mpush
digest=mpush //zkCli.sh acl 命令 addauth digest mpush
watch-path=/
retry {
#initial amount of time to wait between retries
baseSleepTimeMs=3s
#max number of times to retry
maxRetries=3
#max time in ms to sleep on each retry
maxSleepMs=5s
}
connectionTimeoutMs=5s
sessionTimeoutMs=5s
}
#Redis集群配置
redis {
cluster-model=single //single,cluster,sentinel
sentinel-master:"",
nodes:["127.0.0.1:6379"] //["127.0.0.1:6379"]格式ip:port
password="" //your password
config {
maxTotal:8,
maxIdle:4,
minIdle:1,
lifo:true,
fairness:false,
maxWaitMillis:5000,
minEvictableIdleTimeMillis:300000,
softMinEvictableIdleTimeMillis:1800000,
numTestsPerEvictionRun:3,
testOnCreate:false,
testOnBorrow:false,
testOnReturn:false,
testWhileIdle:false,
timeBetweenEvictionRunsMillis:60000,
blockWhenExhausted:true,
jmxEnabled:false,
jmxNamePrefix:pool,
jmxNameBase:pool
}
}
#HTTP代理配置
http {
proxy-enabled=true//启用Http代理
max-conn-per-host=5 //每个域名的最大链接数, 建议web服务nginx超时时间设长一点, 以便保持长链接
default-read-timeout=10s //请求超时时间
max-content-length=5m //response body 最大大小
dns-mapping { //域名映射外网地址转内部IP, 域名部分不包含端口号
//"mpush.com":["127.0.0.1:8080", "127.0.0.1:8081"]
}
}
#线程池配置
thread {
pool {
conn-work:0 //接入服务线程池大小,0表示线程数根据cpu核数动态调整(2*cpu)
gateway-server-work:0 //网关服务线程池大小,0表示线程数根据cpu核数动态调整(2*cpu)
http-work:0 //http proxy netty client work pool size,0表示线程数根据cpu核数动态调整(2*cpu)
ack-timer:1 //处理ACK消息超时
push-task:0 //消息推送中心,推送任务线程池大小, 如果为0表示使用Gateway Server的work线程池,tcp下推荐0
gateway-client-work:0 //网关客户端线程池大小,0表示线程数根据cpu核数动态调整(2*cpu),该线程池在客户端运行
push-client:2 //消息推送回调处理,该线程池在客户端运行
event-bus { //用户处理内部事件分发
min:1
max:16
queue-size:10000 //大量的online,offline
}
mq { //用户上下线消息, 踢人等
min:1
max:4
queue-size:10000
}
}
}
#推送消息流控
push {
flow-control { //qps = limit/(duration)
global:{ //针对非广播推送的流控,全局有效
limit:5000 //qps = 5000
max:0 //UN limit
duration:1s //1s
}
broadcast:{ //针对广播消息的流控,单次任务有效
limit:3000 //qps = 3000
max:100000 //10w
duration:1s //1s
}
}
}
#系统监控配置
monitor {
dump-dir=${mp.home}/tmp
dump-stack=false //是否定时dump堆栈
dump-period=1m //多久监控一次
print-log=true //是否打印监控日志
profile-enabled=false //开启性能监控
profile-slowly-duration=10ms //耗时超过10ms打印日志
}
#SPI扩展配置
spi {
thread-pool-factory:"com.mpush.tools.thread.pool.DefaultThreadPoolFactory"
dns-mapping-manager:"com.mpush.common.net.HttpProxyDnsMappingManager"
}
}
三、新建一个普通的类,需要实现两个接口 PushSender(mpush启动是需要使用), ServletContextListener(web项目的监听器需要继承的类)
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.FutureTask;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import com.mpush.api.push.PushContext;
import com.mpush.api.push.PushResult;
import com.mpush.api.push.PushSender;
import com.mpush.api.service.Listener;
public class ServiceManager implements PushSender, ServletContextListener {
public static PushSender pushSender = null;
// 在tomcat启动是启动消息发送服务
// 启动一个定时器
@Override
public void contextInitialized(ServletContextEvent arg0) {
// PushClient PushClient=new PushClient();
if (pushSender == null)
pushSender = PushSender.create();
pushSender.start().join();
}
public static PushSender getPushSender() {
if (pushSender == null)
pushSender = PushSender.create();
return pushSender;
}
@Override
public void contextDestroyed(ServletContextEvent arg0) {
pushSender.stop();
}
@Override
public void start(Listener listener) {
// TODO Auto-generated method stub
}
@Override
public void stop(Listener listener) {
// TODO Auto-generated method stub
}
@Override
public CompletableFuture start() {
// TODO Auto-generated method stub
return null;
}
@Override
public CompletableFuture stop() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean syncStart() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean syncStop() {
// TODO Auto-generated method stub
return false;
}
@Override
public void init() {
// TODO Auto-generated method stub
}
@Override
public boolean isRunning() {
// TODO Auto-generated method stub
return false;
}
@Override
public FutureTask send(PushContext context) {
// TODO Auto-generated method stub
return null;
}
}
在tomcat启动是就启动mpush消息发送服务,采用单例模式,增加静态公用get方法获取详细推送服务实例,共其它类调用
// 在tomcat启动是启动消息发送服务
// 启动一个定时器
@Override
public void contextInitialized(ServletContextEvent arg0) {
// PushClient PushClient=new PushClient();
if (pushSender == null)
pushSender = PushSender.create();
pushSender.start().join();
}
public static PushSender getPushSender() {
if (pushSender == null)
pushSender = PushSender.create();
return pushSender;
}
四、增加web项目监听器配置,修改web.xml文件
webpush
index.html
index.htm
index.jsp
default.html
default.htm
default.jsp
com.webpush.service.ServiceManager
新增自定义web项目的监听器(这里的类路径是第三步新建的监听器类路径)
到这里就已经完成mpush继承环境的开发搭建了,以下的内容是增加测试的servlet
五、新建两个servlet类并添加web.xml文件配置:
第一个servlet类:
package com.webpush.pushmessage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.mpush.api.push.AckModel;
import com.mpush.api.push.MsgType;
import com.mpush.api.push.PushCallback;
import com.mpush.api.push.PushContext;
import com.mpush.api.push.PushMsg;
import com.mpush.api.push.PushResult;
import com.mpush.api.push.PushSender;
import com.webpush.service.ServiceManager;//刚才第三步新建servlet监听器的类(修改为自己的类路径)
/**
* Servlet implementation class Htmlpushmesg
*/
@WebServlet("/htmlpushmesg")
public class Htmlpushmesg extends HttpServlet {
private static final long serialVersionUID = 1L;
PushResult pushResult=null;//消息推送结果
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
int msgType=Integer.valueOf(request.getParameter("msgtype"));
String pushMsg=request.getParameter("pushMsg");
String broadcast=request.getParameter("broadcast");
String[] userIds=request.getParameterValues("userId");
List users=new ArrayList();
if(userIds!=null)
for(int i=0,len=userIds.length;i future = */sender.send(context);
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10));
request.setAttribute("pushResult", pushResult);
request.getRequestDispatcher("/push.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
第二个servlet类:
在新建第二个servlet类之前先建一个redis的配置文件,文件名:redis.properties,放在src目录下
jedis.pool.maxActive=1024
jedis.pool.maxIdle=200
jedis.pool.maxWait=10000
jedis.pool.testOnBorrow=true
jedis.pool.testOnReturn=true
jedis.pool.timeout=10000
# ip地址必须和文件application.conf中的redis的IP地址相同
redisReadURL=127.0.0.1
redisReadPort=6379
# ip地址必须和文件application.conf中的redis的IP地址相同
redisWriteURL=127.0.0.1
redisWritePort=6379
# 你的redis密码,同文件application.conf中的redis中的密码
password=
servlet类
package com.webpush.pushmessage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.mpush.tools.config.CC;
import com.mpush.tools.config.data.RedisNode;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* 获取所有在线用户的信息
*/
@WebServlet("/userStutas")
public class UserStutas extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setAttribute("users", getRedisdata());
request.getRequestDispatcher("/userlist.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
/**
* redis操作部分
*/
// 连接实例的最大连接数
private static int MAX_ACTIVE = 1024;
// 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
private static int MAX_IDLE = 200;
// 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException
private static int MAX_WAIT = 10000;
// 连接超时的时间
private static int TIMEOUT = 10000;
// 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
private static boolean TEST_ON_BORROW = true;
private static JedisPool jedisPool = null;
//加载配置文件
static void load() {
Config config = ConfigFactory.load();//扫描加载所有可用的配置文件
String custom_conf = "mp.conf";//加载自定义配置, 值来自jvm启动参数指定-Dmp.conf
if (config.hasPath(custom_conf)) {
File file = new File(config.getString(custom_conf));
if (file.exists()) {
Config custom = ConfigFactory.parseFile(file);
config = custom.withFallback(config);
}
}
Config cfg = CC.cfg.getObject("mp").toConfig().getObject("redis").toConfig();
try {
JedisPoolConfig redisconf = new JedisPoolConfig();
redisconf.setMaxTotal(MAX_ACTIVE);
redisconf.setMaxIdle(MAX_IDLE);
redisconf.setMaxWaitMillis(MAX_WAIT);
redisconf.setTestOnBorrow(TEST_ON_BORROW);
RedisNode redisnode=cfg.getList("nodes")
.stream()//第一纬度数组
.map(v -> RedisNode.from(v.unwrapped().toString()))
.collect(Collectors.toCollection(ArrayList::new)).get(0);
jedisPool = new JedisPool(redisconf, redisnode.getHost(), redisnode.getPort(), TIMEOUT, cfg.getString("password"));
} catch (Exception e) {
e.printStackTrace();
}
}
public Map>> getRedisdata() {
Jedis jedis = getJedis();
Map>> map = new HashMap>>();
for(String key : jedis.keys("mp:ur:*")) {
//System.err.println(key+"\t");
Map> maplist=new HashMap>();
for(String ke : jedis.hkeys(key)) {
maplist.put(ke, jedis.hmget(key,ke));
//System.out.print(ke+"\t");
//System.out.println(jedis.hmget(key,ke));
}
map.put(key, maplist);
}
returnResource(jedis);
return map;
}
/**
* 初始化Redis连接池
*/
static {
load();
}
/**
* 获取Jedis实例
*/
public synchronized static Jedis getJedis() {
try {
if (jedisPool != null) {
Jedis resource = jedisPool.getResource();
return resource;
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/***
*
* 释放资源
*/
public static void returnResource(final Jedis jedis) {
if (jedis != null) {
jedisPool.returnResource(jedis);
}
}
}
修改web.xml文件
webpush
index.html
index.htm
index.jsp
default.html
default.htm
default.jsp
com.webpush.service.ServiceManager
htmlpushmesg
com.webpush.pushmessage.Htmlpushmesg
htmlpushmesg
/htmlpushmesg.do
userStutas
com.webpush.pushmessage.UserStutas
userStutas
/userStutas.do
web Socket客户端,接受消息测试(app请在官网下载,如果Android7.0及以上版本点击绑定退出,请换成Android6.0及以下版本安装测试)
MPush WebSocket Client
消息推送页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="com.webpush.redis.RedisUtils,java.util.*"%>
Insert title here
消息推送
推送结果:<%=request.getAttribute("pushResult")%>
客户端所有用户列表
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*,com.alibaba.fastjson.JSON"%>
Insert title here
所有用户列表数据
用户名
版本号
主机
在线情况
客户端类型
<%
Map>> map = (Map>>) request.getAttribute("users");
Set set = map.keySet();
for (String user : set) {
Set se = map.get(user).keySet();
Iterator it = se.iterator();
%>
<%=user.split("mp:ur:")[1]%>
<%
String list = map.get(user).get(it.next()).get(0);
Map m = JSON.parseObject(list, Map.class);
%>
<%=m.get("clientVersion")%>
<%=m.get("hostAndPort")%>
<%="true".equals(m.get("online").toString()) ? "在线 " : "离线 "%>
<%=m.get("osName")%>
<%
while (it.hasNext()) {
%>
<%
list = map.get(user).get(it.next()).get(0);
m = JSON.parseObject(list, Map.class);
%>
<%=m.get("clientVersion")%>
<%=m.get("hostAndPort")%>
<%="true".equals(m.get("online").toString()) ? "在线 " : "离线 "%>
<%=m.get("osName")%>
<%
}
}
%>
然后运行web项目,查看效果
如果对您有帮助记得点个赞哦
欢迎加群:517413713 讨论