import com.fengyulei.fylsipserver.config.ConfigInfo;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
//创建方式和sip的服务器创建差不多
@Component
public class MediaUdpServer implements Runnable{
private static final Logger logger = LoggerFactory.getLogger(MediaUdpServer.class);
@Autowired
private ConfigInfo configInfo;
//udp模式,直接注入
@Autowired
private MediaUdpHandler mediaUdpHandler;
private Bootstrap bootstrap = null;
private EventLoopGroup workerGroup = null;
private Integer port;
//跟随springboot异步启动
@PostConstruct
private void init(){
this.port=configInfo.getMediaUdpTcpServerPort();
Thread thread=new Thread(this);
thread.setDaemon(true);
thread.setName("media udp server");
thread.start();
}
@Override
public void run(){
workerGroup= new NioEventLoopGroup();
try {
bootstrap = new Bootstrap();
bootstrap.group(workerGroup)//
.channel(NioDatagramChannel.class) //
.option(ChannelOption.SO_RCVBUF,1024*1024)
.handler(new ChannelInitializer<NioDatagramChannel>() { //
@Override
public void initChannel(NioDatagramChannel ch) throws Exception {
//udp可以使用单例Handler netty udp只有一个通道
ch.pipeline().addLast(mediaUdpHandler);
}
});
logger.info("UDP服务启动成功port:{}", port);
bootstrap.bind(port).sync().channel().closeFuture().sync();
} catch (InterruptedException e) {
logger.error(e.getMessage(),e);
} finally {
workerGroup.shutdownGracefully();
}
}
}
import java.util.HashMap;
import java.util.Map;
import com.fengyulei.fylsipserver.media.codec.CommonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fengyulei.fylsipserver.media.common.utils.BitUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MediaUdpHandler extends SimpleChannelInboundHandler<DatagramPacket> {
private static final Logger logger = LoggerFactory.getLogger(MediaUdpHandler.class);
//解析类,该类是在github上嫖过来的,然后根据解析博客做下简单修改,有兴趣的具体查看:文章开头说明
//这个类后面会解释
@Autowired
private CommonParser mParser;
//因为使用的是复用端口,所以是以ssrc区分每个实例
private static volatile Map<String,SsrcUdpHandler> map=new HashMap<>();
public static SsrcUdpHandler remove(String ssrc){
return map.remove(ssrc);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
//获取数据
ByteBuf byteBuf = msg.content();
int readableBytes = byteBuf.readableBytes();
if(readableBytes <=0){
return;
}
byte[] copyData = new byte[readableBytes];
//转换字节
byteBuf.readBytes(copyData);
int length = copyData.length;
if(length<=12){
return;
}
//这里是截取ssrc的字节,rtp头部固定12个字节,最后四个字节就是 ssrc,就是唯一标识,一般由sip服务器指定,头部其他字段请参考文章开头rtp解析博客
int uploadSsrc = BitUtils.byteToInt(copyData[8],copyData[9],copyData[10],copyData[11]);
//SsrcUdpHandler 是一个推流实例,后面再解释
SsrcUdpHandler ssrcUdpHandler=map.get(String.valueOf(uploadSsrc));
//根据ssrc判断是否有存在推流实例,不存在则创建,并启动rtmp推流器线程
if(ssrcUdpHandler==null||!ssrcUdpHandler.getRtmpPusher().mRunning){
logger.info("UDP接收流:[{}]",uploadSsrc);
ssrcUdpHandler=new SsrcUdpHandler(mParser,String.valueOf(uploadSsrc));
map.put(String.valueOf(uploadSsrc),ssrcUdpHandler);
}
ssrcUdpHandler.read(copyData);
}
}
import com.fengyulei.fylsipserver.config.ConfigInfo;
import com.fengyulei.fylsipserver.media.codec.CommonParser;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
//tcp和udp有些区别,区别主要是是tcp的handler无法复用
@Component
public class MediaTcpServer implements Runnable{
private static final Logger logger = LoggerFactory.getLogger(MediaTcpServer.class);
@Autowired
private ConfigInfo configInfo;
//解析器依然可以公共单例
@Autowired
private CommonParser mParser;
//tcp的话不能注入,每次都要实例化一个
/* @Autowired
private MediaTcpHandler mediaTcpHandler;*/
private ServerBootstrap serverBootstrap = null;
private EventLoopGroup group = null;
private EventLoopGroup work = null;
private Integer port;
@PostConstruct
private void init(){
//赋值端口
this.port=configInfo.getMediaUdpTcpServerPort();
Thread thread=new Thread(this);
thread.setDaemon(true);
thread.setName("media tcp server");
thread.start();
}
@Override
public void run(){
try {
group = new NioEventLoopGroup();
serverBootstrap = new ServerBootstrap();
serverBootstrap.group(group)//
.channel(NioServerSocketChannel.class) //
//.option(ChannelOption.SO_RCVBUF,1024*1024)
//设置队列大小
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() { //
@Override
public void initChannel(SocketChannel ch) throws Exception {
//解决TCP粘包问题 rtp ps tcp发送的前2个字节代表分包的长度,所以可以通过前2个字节进行拆包
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024*1024,0,2));
//每次使用构造函数传入解析器
ch.pipeline().addLast(new MediaTcpHandler(mParser));
}
});
logger.info("TCP服务启动成功port:{}", port);
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
logger.error(e.getMessage(),e);
} finally {
group.shutdownGracefully();
}
}
}
//tcp handler不能归spring管理,不支持单例模式。netty没创建一次通道都会新建一次handler,就是每个通道都有自己的handler
// 使用单例注入的话会导致netty只能接收一次tcp连接
//@Component
public class MediaTcpHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(MediaTcpHandler.class);
//@Autowired 通过构造方法传入单例解析器
private CommonParser mParser;
public MediaTcpHandler(CommonParser mParser) {
this.mParser = mParser;
}
private static volatile Map<String,SsrcTcpHandler> map=new HashMap<>();
public static SsrcTcpHandler remove(String ssrc){
return map.remove(ssrc);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
ByteBuf byteBuf = (ByteBuf) msg;
int readableBytes = byteBuf.readableBytes();
if(readableBytes <=0){
return;
}
byte[] copyData = new byte[readableBytes];
byteBuf.readBytes(copyData);
int length = copyData.length;
//rtp tcp的头部比udp多出2个字节
if(length<=14){
return;
}
//logger.info(HexStringUtils.toHexString(copyData));
//rtp tcp的头部比udp多出2个字节
int uploadSsrc = BitUtils.byteToInt(copyData[10],copyData[11],copyData[12],copyData[13]);
SsrcTcpHandler ssrcTcpHandler=map.get(String.valueOf(uploadSsrc));
if(ssrcTcpHandler==null||!ssrcTcpHandler.getRtmpPusher().mRunning){
logger.info("TCP接收流:[{}]",uploadSsrc);
ssrcTcpHandler=new SsrcTcpHandler(mParser,String.valueOf(uploadSsrc));
ssrcTcpHandler.setChannel(ctx.channel());
map.put(String.valueOf(uploadSsrc),ssrcTcpHandler);
}
ssrcTcpHandler.read(copyData);
}catch (Exception e){
logger.error(e.getMessage(),e);
}finally {
release(msg);
}
}
private void release(Object msg){
try{
ReferenceCountUtil.release(msg);
}catch(Exception e){
logger.error(e.getMessage(),e);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error(cause.getMessage(),cause);
ctx.channel().close();
}
/**
* TCP建立连接
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//super.channelActive(ctx);
logger.info("新建连接--->");
}
/**
* TCP连接断开
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//super.channelInactive(ctx);
logger.info("断开连接--->");
}
}
下节简单说下解析部分,该部分主要参考文章开头部分进行理解,所以说明会比较少,只说一些注意的地方