Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

基于Broadway的HTML5视频监控

9
Oct
2017

基于Broadway的HTML5视频监控

By Alex
/ in C++,Java,JavaScript,Network
/ tags Multimedia, 视频监控
0 Comments
简介

Broadway是一个基于JavaScript的H.264解码器,支持Baseline Profile,我们在HTML5视频监控技术预研一文中介绍过这个库。如果你的监控摄像头支持Baseline的H.264码流,利用Broadway可以实现不需要重新编码的视频监控,这样服务器的负载可以大大减轻。

本文不进行理论知识的讨论,仅仅给出一个简单的实现。此实现由三个部分组成:

  1. 基于live555的C++程序,用来从视频源取RTP流,解析出NALU然后通过WebSocket推送给WebSocket服务器
  2. 基于Spring Boot的Java WebSocket服务器,接收C++程序推送来的NALU并广播给客户端
  3. 基于Broadway的HTML5视频监控客户端,为了简化开发,我们使用了Broadway的一个封装http-live-player

代码托管于GitHub:https://github.com/gmemcc/h5vs.git

C++部分

这部分主要是一个RTSP客户端,功能上面已经介绍过,此客户端依赖于我以前一篇文章中的live555 RTSP客户端封装。

WebSocket客户端
WebSocketClient.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//
// Created by alex on 10/9/17.
//
 
#ifndef LIVE5555_WEBSOCKETCLIENT_H
#define LIVE5555_WEBSOCKETCLIENT_H
 
#include <pthread.h>
 
#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/client.hpp>
 
typedef websocketpp::client<websocketpp::config::asio_client> WebSocketppClient;
typedef websocketpp::connection_hdl WebSocketppConnHdl;
 
class WebSocketClient {
private:
    char *url;
    pthread_t wsThread;
    WebSocketppClient *wsppClient;
    WebSocketppConnHdl wsppConnHdl;
public:
    WebSocketClient( char *url );
 
    char *getUrl() const;
 
    virtual void connect();
 
    virtual void sendBytes( unsigned char *buf, unsigned size );
 
    virtual void sendText( char *text );
 
    virtual ~WebSocketClient();
 
    pthread_t getWsThread() const;
 
    WebSocketppClient *getWsppClient();
 
    void setWsppConnHdl( WebSocketppConnHdl wsppConnHdl );
};
 
 
#endif //LIVE5555_WEBSOCKETCLIENT_H

WebSocketClient.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//
// Created by alex on 10/9/17.
//
 
#include "WebSocketClient.h"
 
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
 
#include "spdlog/spdlog.h"
 
static auto LOGGER = spdlog::stdout_color_st( "WebSocketClient" );
 
WebSocketClient::WebSocketClient( char *url ) : url( url ), wsppClient( new WebSocketppClient()) {
}
 
WebSocketClient::~WebSocketClient() {
    delete wsppClient;
}
 
static void *wsRoutine( void *arg ) {
    WebSocketClient *client = (WebSocketClient *) arg;
 
    WebSocketppClient *wsppClient = client->getWsppClient();
    wsppClient->clear_access_channels( websocketpp::log::alevel::frame_header );
    wsppClient->clear_access_channels( websocketpp::log::alevel::frame_payload );
    wsppClient->init_asio();
 
    websocketpp::lib::error_code ec;
    WebSocketppClient::connection_ptr con = wsppClient->get_connection( std::string( client->getUrl()), ec );
    wsppClient->connect( con );
    client->setWsppConnHdl( con->get_handle());
    wsppClient->run();
}
 
void WebSocketClient::connect() {
    pthread_create( &wsThread, NULL, wsRoutine, (void *) this );
}
 
void WebSocketClient::sendBytes( unsigned char *buf, unsigned size ) {
    wsppClient->send( wsppConnHdl, buf, size, websocketpp::frame::opcode::BINARY );
}
 
void WebSocketClient::sendText( char *text ) {
    wsppClient->send( wsppConnHdl, text, strlen( text ), websocketpp::frame::opcode::TEXT );
}
 
char *WebSocketClient::getUrl() const {
    return url;
}
 
pthread_t WebSocketClient::getWsThread() const {
    return wsThread;
}
 
WebSocketppClient *WebSocketClient::getWsppClient() {
    return wsppClient;
};
 
void WebSocketClient::setWsppConnHdl( WebSocketppConnHdl wsppConnHdl ) {
    this->wsppConnHdl = wsppConnHdl;
}
主程序
wspush.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <iostream>
#include "live5555/client.h"
 
#include "spdlog/spdlog.h"
 
#include "WebSocketClient.h"
 
static auto LOGGER = spdlog::stdout_color_st( "wspush" );
 
class VideoSink : public SinkBase {
private:
#ifdef _SAVE_H264_SEQ
    FILE *os = fopen( "./rtsp.h264", "w" );
#endif
    WebSocketClient *wsClient;
    bool firstFrameWritten;
    const char *sPropParameterSetsStr;
    unsigned char const start_code[4] = { 0x00, 0x00, 0x00, 0x01 };
public:
    VideoSink( UsageEnvironment &env, unsigned int recvBufSize, WebSocketClient *wsClient ) : SinkBase( env, recvBufSize ), wsClient( wsClient ) {
        // 缓冲区前面留出起始码4字节
        recvBuf += sizeof( start_code );
    }
 
    virtual ~VideoSink() {
    }
 
    virtual void onMediaSubsessionOpened( MediaSubsession *subSession ) {
        sPropParameterSetsStr = subSession->fmtp_spropparametersets();
    }
 
    void afterGettingFrame( unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime ) override {
        size_t scLen = sizeof( start_code );
        if ( !firstFrameWritten ) {
            // 填写起始码
            memcpy( recvBuf - scLen, start_code, scLen );
            // 防止RTSP源不送SPS/PPS
            unsigned numSPropRecords;
            SPropRecord *sPropRecords = parseSPropParameterSets( sPropParameterSetsStr, numSPropRecords );
            for ( unsigned i = 0; i < numSPropRecords; ++i ) {
                unsigned int propLen = sPropRecords[ i ].sPropLength;
                size_t bufLen = propLen + scLen;
                unsigned char buf[bufLen];
                memcpy( buf, start_code, scLen );
                memcpy( buf + scLen, sPropRecords[ i ].sPropBytes, propLen );
                wsClient->sendBytes( buf, bufLen );
#ifdef _SAVE_H264_SEQ
                fwrite( buf, sizeof( unsigned char ), bufLen, os );
#endif
            }
            firstFrameWritten = true;
        }
#ifdef _SAVE_H264_SEQ
        fwrite( recvBuf - scLen, sizeof( unsigned char ), frameSize + scLen, os );
#endif
        unsigned naluHead = recvBuf[ 0 ];
        unsigned nri = naluHead >> 5;
        unsigned f = nri >> 2;
        unsigned type = naluHead & 0b00011111;
        wsClient->sendBytes( recvBuf - scLen, frameSize + scLen );
        LOGGER->trace( "NALU info: nri {} type {}", nri, type );
        SinkBase::afterGettingFrame( frameSize, numTruncatedBytes, presentationTime );
    }
};
 
class H264RTSPClient : public RTSPClientBase {
private:
    VideoSink *videoSink;
public:
    H264RTSPClient( UsageEnvironment &env, const char *rtspURL, VideoSink *videoSink ) :
        RTSPClientBase( env, rtspURL ), videoSink( videoSink ) {}
 
protected:
    // 测试用的摄像头(RTSP源)仅仅有一个子会话,因此这里简化了实现:
    bool acceptSubSession( const char *mediumName, const char *codec ) override {
        return true;
    }
 
    MediaSink *createSink( const char *mediumName, const char *codec, MediaSubsession *subSession ) override {
        videoSink->onMediaSubsessionOpened( subSession );
        return videoSink;
    }
};
 
int main() {
    spdlog::set_pattern( "%Y-%m-%d %H:%M:%S.%e [%l] [%n] %v" );
    spdlog::set_level( spdlog::level::trace );
 
    WebSocketClient *wsClient;
    wsClient = new WebSocketClient( "ws://192.168.0.89:9090/h264src" );
    wsClient->connect();
    sleep( 3 ); // 等待WebSocket连接建立
    wsClient->sendText( "ch1" );
    TaskScheduler *scheduler = BasicTaskScheduler::createNew();
    BasicUsageEnvironment *env = BasicUsageEnvironment::createNew( *scheduler );
    VideoSink *sink = new VideoSink( *env, 1024 * 1024, wsClient );
    H264RTSPClient *client = new H264RTSPClient( *env, "rtsp://admin:kingsmart123@192.168.0.196:554/ch1/sub/av_stream", sink );
    client->start();
    return 0;
}
Java部分

这部分实现了NALU转发功能,基于Spring Boot实现。

主程序
VideoSurveillanceApp.java
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package cc.gmem.study.kurento;
 
import org.kurento.client.KurentoClient;
import org.kurento.client.KurentoClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.*;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
import sun.security.acl.PrincipalImpl;
 
import java.security.Principal;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
@SpringBootApplication
@EnableWebSocket
@EnableWebSocketMessageBroker
@EnableScheduling
public class VideoSurveillanceApp extends AbstractWebSocketMessageBrokerConfigurer implements WebSocketConfigurer {
 
    private static final Logger LOGGER = LoggerFactory.getLogger( VideoSurveillanceApp.class );
 
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        // WebSocket消息缓冲区大小,如果客户端发来的消息较大,需要按需调整
        container.setMaxTextMessageBufferSize( 1024 * 1024 );
        container.setMaxBinaryMessageBufferSize( 1024 * 1024 );
        return container;
    }
 
    @Override
    public void registerWebSocketHandlers( WebSocketHandlerRegistry registry ) {
        registry.addHandler( h264FrameSinkHandler(), "/h264sink" );
        registry.addHandler( h264FrameSrcHandler(), "/h264src" );
    }
 
    @Bean
    public WebSocketHandler h264FrameSrcHandler() {
        return new H264FrameSrcHandler();
    }
 
    @Bean
    public WebSocketHandler h264FrameSinkHandler() {
        return new H264FrameSinkHandler();
    }
 
    public static void main( String[] args ) {
        new SpringApplication( VideoSurveillanceApp.class ).run( args );
    }
 
}
H264FrameSrcHandler

此Bean接受C++程序的NALU推送:

H264FrameSrcHandler.java
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package cc.gmem.study.kurento;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
 
import javax.inject.Inject;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
 
public class H264FrameSrcHandler extends AbstractWebSocketHandler {
 
    private static final Logger LOGGER = LoggerFactory.getLogger( H264FrameSrcHandler.class );
 
    private Map<String, String> sessionIdToChannel = new ConcurrentHashMap<>();
 
    @Inject
    private H264FrameSinkHandler sinkHandler;
 
    public void afterConnectionEstablished( WebSocketSession session ) throws Exception {
        LOGGER.debug( "{} connected.", session.getRemoteAddress() );
    }
 
    @Override
    protected void handleBinaryMessage( WebSocketSession session, BinaryMessage message ) throws Exception {
        ByteBuffer payload = message.getPayload();
        StringBuilder hex = new StringBuilder();
        byte[] pa = payload.array();
        int len = 16;
        if ( pa.length < 16 ) len = pa.length;
        for ( byte i = 0; i < len; i++ ) {
            hex.append( String.format( "%02x ",Byte.toUnsignedInt( pa[i] )  ) );
        }
        LOGGER.debug( "Received binary message {} bytes: {}...", payload.array().length, hex );
        String chnl = sessionIdToChannel.get( session.getId() );
        if ( chnl != null ) sinkHandler.broadcast( chnl, payload );
    }
 
    @Override
    protected void handleTextMessage( WebSocketSession session, TextMessage message ) throws Exception {
        String payload = message.getPayload();
        sessionIdToChannel.put( session.getId(), payload );
        LOGGER.debug( "Received text message: {}", payload );
    }
}
H264FrameSinkHandler

此Bean向Web客户端广播NALU:

H264FrameSinkHandler.java
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package cc.gmem.study.kurento;
 
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.mutable.MutableInt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
 
import javax.inject.Inject;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
 
public class H264FrameSinkHandler extends TextWebSocketHandler {
 
    private static final Logger LOGGER = LoggerFactory.getLogger( H264FrameSinkHandler.class );
 
    public static final String ACTION_INIT = "init";
 
    private static final String ACTION_INIT_RESP = "initresp";
 
    public static final String ACTION_PLAY = "play";
 
    public static final String ACTION_STOP = "stop";
 
    public static final String KEY_ACTION = "action";
 
 
    @Inject
    private ObjectMapper om;
 
    private Map<String, List<WebSocketSession>> chnlToSessions = new ConcurrentHashMap<>();
 
    @Override
    protected void handleTextMessage( WebSocketSession session, TextMessage message ) throws Exception {
        String client = session.getId() + '@' + session.getRemoteAddress();
        Map req = om.readValue( message.getPayload(), Map.class );
        Map resp = new LinkedHashMap();
        Object action = req.get( KEY_ACTION );
        if ( ACTION_INIT.equals( action ) ) {
            String channel = (String) req.get( "channel" );
            LOGGER.debug( "{} request to subscribe channel {}", client, channel );
            addPushTarget( channel, session );
 
            resp.put( KEY_ACTION, ACTION_INIT_RESP );
            resp.put( "width", 352 );
            resp.put( "height", 288 );
            session.sendMessage( new TextMessage( om.writeValueAsString( resp ) ) );
        } else if ( ACTION_PLAY.equals( action ) ) {
            LOGGER.debug( "{} request to receive nalu push", session.getRemoteAddress(), client );
            session.getAttributes().put( ACTION_PLAY, true );
        }
    }
 
    private synchronized void addPushTarget( String channel, WebSocketSession session ) {
        List<WebSocketSession> sessions = chnlToSessions.get( channel );
        if ( sessions == null ) {
            sessions = new ArrayList<>();
            chnlToSessions.put( channel, sessions );
        }
        sessions.add( session );
    }
 
    public synchronized void broadcast( String chnl, ByteBuffer payload ) {
        List<WebSocketSession> sessions = chnlToSessions.get( chnl );
        if ( sessions == null ) return;
        sessions.forEach( sess -> {
            try {
                if ( sess.isOpen() && Boolean.TRUE.equals( sess.getAttributes().get( ACTION_PLAY ) ) ) {
                    sess.sendMessage( new BinaryMessage( payload ) );
                }
            } catch ( Exception e ) {
                LOGGER.error( e.getMessage(), e );
            }
        } );
    }
 
    @Scheduled( fixedRate = 10000 )
    public synchronized void cleanup() {
        final MutableInt counter = new MutableInt( 0 );
        chnlToSessions.values().forEach( sessions -> {
            Iterator<WebSocketSession> it = sessions.listIterator();
            while ( it.hasNext() ) {
                if ( !it.next().isOpen() ) {
                    it.remove();
                    counter.increment();
                }
            }
        } );
        if ( counter.intValue() > 0 ) LOGGER.debug( "Remove {} invalid websocket session.", counter );
    }
}
Web部分

我们对http-live-player进行了简单的修改,主要是修改其通信方式以配合上述WebSocket服务器。核心代码没有变动,因此这里不张贴其代码。

客户端代码
broadway.html
XHTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Broadway Video Surveillance</title>
    <script src="js/broadway/http-live-player.js"></script>
    <link rel="stylesheet" href="style.css">
</head>
<body>
<div class="nvbar">
    <div class="title">基于Broadway+WebSocket的视频监控示例</div>
    <div class="subtitle">http://192.168.0.89:9090/broadway.html</div>
</div>
<div class="videos-wrapper">
    <div id="videos"></div>
</div>
<script type="text/javascript">
    var videos = document.getElementById( 'videos' );
    for ( var i = 0; i < 9; i++ ) {
        var canvas = document.createElement( "canvas" );
        videos.appendChild( canvas );
        var player = new WSAvcPlayer( canvas, "webgl", 'ch1', true );
        player.connect( "ws://" + document.location.host + "/h264sink" );
    }
</script>
</body>
</html>
效果截图

下面的截图是开了九画面的视频监控,使用的是子码流,在测试机器上CPU压力不大。

html5-h264

注意:如果Broadway来不及解码,http-live-player会把缓冲区中的所有NALU全部丢弃,这可能导致暂时的花屏。选择适当的帧率、码率、画幅可以尽量避免这种情况的发生。

← 2017年10月五大道
Log4J2学习笔记 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • 基于Kurento搭建WebRTC服务器
  • HTML5视频监控技术预研
  • 实时通信协议族
  • 基于MinGW的海康视频监控开发
  • Java NIO浅析

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • Bazel学习笔记 38 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • NaCl学习笔记 32 people like this
  • PhoneGap学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
  • Three.js学习笔记 24 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2