golang-实现自己的事件驱动

2023-10-27

golang实现自己的事件驱动

众所周知,go中的异步操作都已经封装在了运行时的过程中,有关socket的网络的异步操作都封装到了go的netpoll中,从而简化了编程形式。本文也就根据evio库总结而来。

golang跨平台库

如何编写golang的跨平台库,现在主流的方式如下:

evserver
│   go.mod
│   main.go    
│
└───show
│   │   show.go
│   │   show_darwin.go
│   │   show_linux.go

通过创建一个package,然后通过命名文件后缀为_{平台后缀}.go的形式,在不同平台运行的时候会自动编译对应的代码。

main.go代码

package main

import (
	"evserver/show"
)


func main() {
	show := show.GetDefault()
	show.ShowHello()
}

show.go

package show


type Show interface {
	ShowHello()
}

show_linux.go

package show


import "fmt"

type osDefaulter struct {
}

func GetDefault() osDefaulter{
	return osDefaulter{}
}


func(s osDefaulter) ShowHello(){
	fmt.Println("linux show")
}

show_darwin.go

package show


import "fmt"

type osDefaulter struct {
}

func GetDefault() osDefaulter{
	return osDefaulter{}
}


func(s osDefaulter) ShowHello(){
	fmt.Println("darwin show")
}

常见的设计的方式就是通过在package中定义一个接口,然后通过不同平台的后缀文件去调用不同的方式去实现,从而完成package统一的对外提供服务的方式,当然跨平台的库也可以有另外一种方式,即如下:

func ShowHello() string {
    if runtime.GOOS == "windows" {
        return "windows hello"
    } else {
        return "other paltform hello"
    }
}

但是这种方式针对简单的跨平台性能还可以,针对复杂的跨平台的功能就对代码侵入比较严重。

https://techblog.steelseries.com/2014/04/08/multi-platform-development-go.html

https://blog.gopheracademy.com/advent-2013/day-23-multi-platform-applications/

golang事件驱动-Kqueue

总体的代码目录如下:

evserver
│   go.mod
│   main.go    
│
└───poll
│   │   poller.go
│   │   poller_darwin.go
│   │   poller_linux.go


main.go
package main

import (
	"evserver/poll"
	"fmt"
)

func main() {
	IP := "127.0.0.1"
	Port := 6667
	s := &poll.Server{
		Ip:IP,
		Port: Port,
	}
	s.Init()
	s.Data = func(c *poll.Conn, in []byte) (out []byte) {
		out = in
		out = append(out, []byte("back")...)
		return
	}
	fmt.Printf(" running in %s:%d\n", IP, Port)
	poll.LoopRun(s)
}

poller.go
package poll

import (
	"log"
	"net"
	"syscall"
)

const READ_FLAG  = 1
const WRITE_FLAG  = 2


type Conn struct {
	fd         int              // file descriptor
	lnidx      int              // listener index in the server lns list
	out        []byte           // write buffer
	sa         syscall.Sockaddr // remote socket address
	reuse      bool             // should reuse input buffer
	opened     bool             // Connection opened event fired
	ctx        interface{}      // user-defined context
	loop       *loop            // Connected loop
}

func (c *Conn) Context() interface{}       { return c.ctx }
func (c *Conn) SetContext(ctx interface{}) { c.ctx = ctx }
func (c *Conn) Wake() {
	//if c.loop != nil {
	//	c.loop.poll.Trigger(c)
	//}
}


type loop struct {
	idx     int            // loop index in the server loops list
	poll    *Poll // epoll or kqueue
	packet  []byte         // read packet buffer
	fdConns map[int]*Conn  // loop Connections fd -> Conn
}


type Server struct {
	Ip string
	Port int
	fd int
	Data func(c *Conn, in []byte)(out []byte)
}

func (s *Server)Init()  {
	fd ,err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
	if err != nil {
		panic(err)
	}

	var serverAddr [4]byte

	ip := s.Ip
	IP := net.ParseIP(ip)
	if IP == nil {
		log.Fatal("Unable to process IP: ", ip)
	}

	copy(serverAddr[:], IP[12:16])

	if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
		syscall.Close(fd)
		panic(err)
	}

	err = syscall.Bind(fd, &syscall.SockaddrInet4{Port: s.Port, Addr:serverAddr})
	if err != nil {
		panic(err)
	}

	err = syscall.Listen(fd, 1024)
	if err != nil {
		panic(err)
	}
	s.fd = fd
}

func LoopRun(s *Server){
	l := &loop{
		poll:OpenPoll(),
		packet:  make([]byte, 0xFFFF),
		fdConns: make(map[int]*Conn),
	}

	l.poll.AddRead(s.fd)

	err := l.poll.Wait(func(fd int, note interface{}) error {
		if fd == s.fd {
			err := LoopAccept(s, l, fd)
			if err != nil {
				panic(err)
			}
		} else {
			c := l.fdConns[fd]
			flag := note.(int)
			if flag == READ_FLAG {
				LoopRead(s, l, c)
			} else {
				LoopWrite(s, l, c)
			}
		}

		return nil
	})
	if err != nil {
		panic(err)
	}
}


func LoopAccept(s *Server, l *loop, fd int) error{
	nfd, sa, err := syscall.Accept(fd)
	if err != nil {
		if err != nil {
			if err == syscall.EAGAIN {
				return nil
			}
			return err
		}
	}
	if err := syscall.SetNonblock(nfd, true); err != nil {
		return err
	}

	c := &Conn{fd: nfd, sa: sa, loop: l}
	c.out = []byte{}
	l.fdConns[c.fd] = c
	l.poll.AddReadWrite(c.fd)
	return nil
}


func LoopRead(s *Server, l *loop, c *Conn)error {
	var in []byte
	n, err := syscall.Read(c.fd, l.packet)
	if n == 0 || err != nil {
		if err == syscall.EAGAIN {
			return nil
		}
		return LoopClose(s, l, c)
	}
	in = l.packet[:n]
	if !c.reuse {
		in = append([]byte{}, in...)
	}
	if s.Data != nil {
		out := s.Data(c, in)
		if len(out) > 0 {
			c.out = append(c.out[:0], out...)
		}
	}
	if len(c.out) != 0 {
		l.poll.ModReadWrite(c.fd)
	}
	return nil
}


func LoopWrite(s *Server, l *loop, c *Conn)error {
	if c == nil {
		return nil
	}
	if c.out == nil || len(c.out) == 0 {
		return nil
	}
	n, err := syscall.Write(c.fd, c.out)
	if err != nil {
		if err == syscall.EAGAIN {
			return nil
		}
		return LoopClose(s, l, c)
	}
	if n == len(c.out) {
		// release the connection output page if it goes over page size,
		// otherwise keep reusing existing page.
		if cap(c.out) > 4096 {
			c.out = nil
		} else {
			c.out = c.out[:0]
		}
	} else {
		c.out = c.out[n:]
	}
	if len(c.out) == 0{
		l.poll.ModRead(c.fd)
	}
	return nil
}


func LoopClose(s *Server, l *loop, c *Conn)error{
	delete(l.fdConns, c.fd)
	err := syscall.Close(c.fd)
	return err
}
poller_darwin.go
package poll

import (
	"syscall"
)

// Poll ...
type Poll struct {
	fd      int
	changes []syscall.Kevent_t
}

// OpenPoll ...
func OpenPoll() *Poll {
	l := new(Poll)
	p, err := syscall.Kqueue()
	if err != nil {
		panic(err)
	}
	l.fd = p
	_, err = syscall.Kevent(l.fd, []syscall.Kevent_t{{
		Ident:  0,
		Filter: syscall.EVFILT_USER,
		Flags:  syscall.EV_ADD | syscall.EV_CLEAR,
	}}, nil, nil)
	if err != nil {
		panic(err)
	}

	return l
}

// Close ...
func (p *Poll) Close() error {
	return syscall.Close(p.fd)
}

// Wait ...
func (p *Poll) Wait(iter func(fd int, note interface{}) error) error {
	events := make([]syscall.Kevent_t, 128)
	for {
		n, err := syscall.Kevent(p.fd, p.changes, events, nil)
		if err != nil && err != syscall.EINTR {
			return err
		}
		p.changes = p.changes[:0]
		for i := 0; i < n; i++ {
			if fd := int(events[i].Ident); fd != 0 {
				var flag int
				if events[i].Filter == syscall.EVFILT_READ {
					flag = READ_FLAG
				} else {
					flag = WRITE_FLAG
				}
				if err := iter(fd, flag); err != nil {
					return err
				}
			}
		}
	}
}

// AddRead ...
func (p *Poll) AddRead(fd int) {
	p.changes = append(p.changes,
		syscall.Kevent_t{
			Ident: uint64(fd), Flags: syscall.EV_ADD, Filter: syscall.EVFILT_READ,
		},
	)
}

// AddReadWrite ...
func (p *Poll) AddReadWrite(fd int) {
	p.changes = append(p.changes,
		syscall.Kevent_t{
			Ident: uint64(fd), Flags: syscall.EV_ADD, Filter: syscall.EVFILT_READ,
		},
		syscall.Kevent_t{
			Ident: uint64(fd), Flags: syscall.EV_ADD, Filter: syscall.EVFILT_WRITE,
		},
	)
}

// ModRead ...
func (p *Poll) ModRead(fd int) {
	p.changes = append(p.changes, syscall.Kevent_t{
		Ident: uint64(fd), Flags: syscall.EV_DELETE, Filter: syscall.EVFILT_WRITE,
	})
}

// ModReadWrite ...
func (p *Poll) ModReadWrite(fd int) {
	p.changes = append(p.changes, syscall.Kevent_t{
		Ident: uint64(fd), Flags: syscall.EV_ADD, Filter: syscall.EVFILT_WRITE,
	})
}

// ModDetach ...
func (p *Poll) ModDetach(fd int) {
	p.changes = append(p.changes,
		syscall.Kevent_t{
			Ident: uint64(fd), Flags: syscall.EV_DELETE, Filter: syscall.EVFILT_READ,
		},
		syscall.Kevent_t{
			Ident: uint64(fd), Flags: syscall.EV_DELETE, Filter: syscall.EVFILT_WRITE,
		},
	)
}
运行测试
import socket


sock = socket.socket()
sock.connect(("127.0.0.1", 6667))

print(sock.send(b"data123121231232"))
print(sock.recv(4096))

此时运行程序go run main.go和测试客户端程序。

此时就可以看出简单的事件驱动的过程。

压测

将main.go进行修改,修改为http的处理方式。

package main

import (
	"evserver/poll"
	"fmt"
	"strconv"
	"strings"
	"time"
)

func main() {
	IP := "127.0.0.1"
	Port := 6667
	s := &poll.Server{
		Ip:IP,
		Port: Port,
	}
	s.Init()
	s.Data = func(c *poll.Conn, in []byte) (out []byte) {
		if in == nil {
			return
		}
		data := in
		//if noparse && bytes.Contains(data, []byte("\r\n\r\n")) {
		//	// for testing minimal single packet request -> response.
		//	out = appendresp(nil, "200 OK", "", res)
		//	return
		//}
		// process the pipeline
		var req request
		for {
			leftover, err := parsereq(data, &req)
			if err != nil {
				// bad thing happened
				out = appendresp(out, "500 Error", "", err.Error()+"\n")
				break
			} else if len(leftover) == len(data) {
				// request not ready, yet
				break
			}
			// handle the request
			out = appendhandle(out, &req)
			data = leftover
		}
		return
	}

	res = "Hello World!\r\n"

	fmt.Printf(" running in %s:%d\n", IP, Port)
	poll.LoopRun(s)
}


var res string

type request struct {
	proto, method string
	path, query   string
	head, body    string
	remoteAddr    string
}


// appendhandle handles the incoming request and appends the response to
// the provided bytes, which is then returned to the caller.
func appendhandle(b []byte, req *request) []byte {
	return appendresp(b, "200 OK", "", res)
}

// appendresp will append a valid http response to the provide bytes.
// The status param should be the code plus text such as "200 OK".
// The head parameter should be a series of lines ending with "\r\n" or empty.
func appendresp(b []byte, status, head, body string) []byte {
	b = append(b, "HTTP/1.1"...)
	b = append(b, ' ')
	b = append(b, status...)
	b = append(b, '\r', '\n')
	b = append(b, "Server: error\r\n"...)
	b = append(b, "Date: "...)
	b = time.Now().AppendFormat(b, "Mon, 02 Jan 2006 15:04:05 GMT")
	b = append(b, '\r', '\n')
	if len(body) > 0 {
		b = append(b, "Content-Length: "...)
		b = strconv.AppendInt(b, int64(len(body)), 10)
		b = append(b, '\r', '\n')
	}
	b = append(b, head...)
	b = append(b, '\r', '\n')
	if len(body) > 0 {
		b = append(b, body...)
	}
	return b
}

// parsereq is a very simple http request parser. This operation
// waits for the entire payload to be buffered before returning a
// valid request.
func parsereq(data []byte, req *request) (leftover []byte, err error) {
	sdata := string(data)
	var i, s int
	var top string
	var clen int
	var q = -1
	// method, path, proto line
	for ; i < len(sdata); i++ {
		if sdata[i] == ' ' {
			req.method = sdata[s:i]
			for i, s = i+1, i+1; i < len(sdata); i++ {
				if sdata[i] == '?' && q == -1 {
					q = i - s
				} else if sdata[i] == ' ' {
					if q != -1 {
						req.path = sdata[s:q]
						req.query = req.path[q+1 : i]
					} else {
						req.path = sdata[s:i]
					}
					for i, s = i+1, i+1; i < len(sdata); i++ {
						if sdata[i] == '\n' && sdata[i-1] == '\r' {
							req.proto = sdata[s:i]
							i, s = i+1, i+1
							break
						}
					}
					break
				}
			}
			break
		}
	}
	if req.proto == "" {
		return data, fmt.Errorf("malformed request")
	}
	top = sdata[:s]
	for ; i < len(sdata); i++ {
		if i > 1 && sdata[i] == '\n' && sdata[i-1] == '\r' {
			line := sdata[s : i-1]
			s = i + 1
			if line == "" {
				req.head = sdata[len(top)+2 : i+1]
				i++
				if clen > 0 {
					if len(sdata[i:]) < clen {
						break
					}
					req.body = sdata[i : i+clen]
					i += clen
				}
				return data[i:], nil
			}
			if strings.HasPrefix(line, "Content-Length:") {
				n, err := strconv.ParseInt(strings.TrimSpace(line[len("Content-Length:"):]), 10, 64)
				if err == nil {
					clen = int(n)
				}
			}
		}
	}
	// not enough data
	return data, nil
}

此时运行go run main.go,并将前文编写的原生的http和evio的http进行对比。同样进行wrk进行测试。

原生http

 wrk -t8 -c200 -d60s --latency  http://127.0.0.1:8000
Running 1m test @ http://127.0.0.1:8000
  8 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     4.60ms    6.67ms 229.63ms   93.36%
    Req/Sec     7.41k     1.73k   14.17k    72.51%
  Latency Distribution
     50%    2.86ms
     75%    4.06ms
     90%    7.82ms
     99%   35.12ms
  3537128 requests in 1.00m, 431.78MB read
  Socket errors: connect 0, read 27, write 0, timeout 0
Requests/sec:  58849.98
Transfer/sec:      7.18MB

evio的http(开启3个loop)

wrk -t8 -c200 -d60s --latency  http://127.0.0.1:7979
Running 1m test @ http://127.0.0.1:7979
  8 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.19ms  730.09us  36.98ms   93.56%
    Req/Sec    11.39k     1.37k   17.97k    68.33%
  Latency Distribution
     50%    2.02ms
     75%    2.37ms
     90%    2.68ms
     99%    4.24ms
  5444990 requests in 1.00m, 540.05MB read
Requests/sec:  90669.19
Transfer/sec:      8.99MB

自己实现的http

wrk -t8 -c200 -d60s --latency  http://127.0.0.1:6667
Running 1m test @ http://127.0.0.1:6667
  8 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.32ms    1.38ms  47.39ms   95.27%
    Req/Sec    11.06k     1.98k   31.58k    74.97%
  Latency Distribution
     50%    2.04ms
     75%    2.41ms
     90%    2.90ms
     99%    6.85ms
  5286393 requests in 1.00m, 529.36MB read
Requests/sec:  87953.74
Transfer/sec:      8.81MB
原生http evio处理http 手工实现http
Qps 58849.98 90669.19 87953.74

总结

本文主要就是梳理了一下go的跨平台的主流方式,并简单实现了有关mac的kqueue的过程,后续大家有兴趣可自行开发事件驱动的框架,当前比较火热的是evio,gevgnet。由于本人才疏学浅,如有错误请批评指正。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

golang-实现自己的事件驱动 的相关文章

  • 【Golang入门】Golang第一天心得

    生活所迫 入门一下Go 很奇葩的第一点 接口 package main import fmt 定义一个接口 type Shape interface Area float64 定义一个矩形类型 type Rectangle struct W
  • Go开发命令行程序指南

    近期在Twitter上看到一个名为 Command Line Interface Guidelines 的站点 1 这个站点汇聚了帮助大家编写出更好命令行程序的哲学与指南 这份指南基于传统的Unix编程原则 2 又结合现代的情况进行了 与时
  • go-zero使用Etcd进行服务注册代码分析

    代码分析 github com tal tech go zero v1 2 3 core discov publisher go package discov import github com tal tech go zero core
  • go字符串详解

    文章目录 摘要 1 byte和rune类型 2 字符串 string 3 练习 反转字符串 摘要 go字符串结构体包含 指向底层存储数组的指针 字符串长度 字符串按utf 8将字符编码成二进制数 然后存储在byte数组中 因为utf 8编码
  • Go Web编程实战(6)----反射

    目录 反射 反射的3大原则 接口类型变量 转换为 反射类型对象 反射类型对象 转换为 接口类型变量 反射类型对象 修改 值必 可写的 反射 与其他语言一样 Go语言的反射同样是指 计算机程序在运行时 可以访问 检测和修改它本身状态或行为的一
  • Golang适合高并发场景的原因分析

    典型的两个现实案例 我们先看两个用Go做消息推送的案例实际处理能力 360消息推送的数据 16台机器 标配 24个硬件线程 64GB内存 Linux Kernel 2 6 32 x86 64 单机80万并发连接 load 0 2 0 4 C
  • 带你使用Golang快速构建出命令行应用程序

    在日常开发中 大家对命令行工具 CLI 想必特别熟悉了 如果说你不知道命令工具 那你可能是个假开发 每天都会使用大量的命令行工具 例如最常用的Git Go Docker等 不管是做技术开发还是业务开发 都会有开发命令行程序的场景 例如如果是
  • golang:环境变量GOPROXY和GO111MODULE设置

    我们安装完golang后 我们在windows的cmd命令下就可以直接查看和使用go命令和环境变量了 同样的在linux下可以在控制台使用 如下图所示 C Users lijie1 gt go env set GO111MODULE set
  • go 进阶 gin实战相关: 五. gin_scaffold 企业脚手架

    目录 一 gin scaffold 企业级脚手架 二 gin scaffold 脚手架安装及使用演示 文件分层解释 开始使用 1 配置开启go mod 功能 2 下载 安装 gin scaffold 3 整合 golang common 4
  • Golang协程与通道整理

    协程goroutine 不由OS调度 而是用户层自行释放CPU 从而在执行体之间切换 Go在底层进行协助实现 涉及系统调用的地方由Go标准库协助释放CPU 总之 不通过OS进行切换 自行切换 系统运行开支大大降低 通道channel 并发编
  • Go语言包管理(一)

    Go语言中的包 我们在使用其他语言 比如Java Python 都有类似包的概念 Go也不例外 其核心思想即为分组和模块化 人的大脑对庞大和复杂的事情很难掌控 可以对其采用分而治之的策略 使其模块化 从而更容易管理 如下是标准库中net包的
  • Go 程序编译过程(基于 Go1.21)

    版本说明 Go 1 21 官方文档 Go 语言官方文档详细阐述了 Go 语言编译器的具体执行过程 Go1 21 版本可以看这个 https github com golang go tree release branch go1 21 sr
  • 48.Go简要实现令牌桶限流与熔断器并集成到Gin框架中

    文章目录 一 简介 二 限流器与熔断器在微服务中的作用 1 限流器 对某个接口单位时间内的访问量做限制 2 熔断器 当服务连续报错 超过一定阈值时 打开熔断器使得服务不可用 三 具体实现 1 限流器实现逻辑 以令牌桶算法为例 2 限流器集成
  • 【go语言开发】loglus日志框架的使用

    本文将简单介绍loglus框架的基本使用 并给出demo 文章目录 前言 Loglus常见用法 自定义日志级别 使用字段钩子 输出到多个位置 使用钩子实现自定义日志处理 demo
  • go-zero开发入门之网关往rpc服务传递数据2

    go zero 的网关服务实际是个 go zero 的 API 服务 也就是一个 http 服务 或者说 rest 服务 http 转 grpc 使用了开源的 grpcurl 库 当网关需要往 rpc 服务传递额外的数据 比如鉴权数据的时候
  • go-zero目录结构和说明

    code of conduct md 行为准则 CONTRIBUTING md 贡献指南 core 框架的核心组件 bloom 布隆过滤器 用于检测一个元素是否在一个集合中 breaker 熔断器 用于防止过多的请求导致系统崩溃 cmdli
  • GoLong的学习之路,进阶,Viper(yaml等配置文件的管理)

    本来有今天是继续接着上一章写微服务的 但是这几天有朋友说 再写Web框架的时候 遇到一个问题 就是很多的中间件 redis 微信 mysql mq 的配置信息写的太杂了 很不好管理 希望我能写一篇有管理配置文件的 所以这篇就放到今天写吧 微
  • 协程-单线程内的异步执行

    1 仿协程实例 不同事件依次顺序执行 coding utf 8 import time def calculate 1 step event name for index in range step print This is s even
  • Golang拼接字符串性能对比

    g o l a n g golang g o l an g
  • go cannot find package “github.com/gorilla/websocket“解读

    Go无法找到包 github com gorilla websocket 的解决方案 在Go开发过程中 我们经常会依赖第三方库来简化开发工作 而使用 go get 命令安装这些库时 有时候我们可能会遇到类似于以下错误的情况 plaintex

随机推荐

  • 我在知乎学写作

    最懂技术的传播者 最懂传播的工程师 最近被知乎成功 忽悠 报名了一个 写作新人培养计划 的课程 写作新人 这老黄瓜未免也太能给自己刷绿漆了吧 心里头想着 还是得push自己 在内容创作的道路上 搞点事情啊 咋回事呢 为什么是知乎 从媒体江湖
  • Ubuntu20.04 ROS noetic安装教程

    注意 本贴是ros官网的方法 使用ros原版官方源 不是国内镜像 适合网络环境比较好的小伙伴 添加源 sudo sh c echo deb http packages ros org ros ubuntu lsb release sc ma
  • NUC980开源项目6-获取官方源码

    上面是我的微信和QQ群 欢迎新朋友的加入 项目码云地址 国内下载速度快 https gitee com jun626 nuc980 open source project 项目github地址 https github com Jun117
  • vulnhub靶机Thoth-Tech

    下载地址 https download vulnhub com thothtech Thoth Tech ova 主机发现 arp scan l 目标 192 168 21 148 端口扫描 nmap min rate 10000 p 19
  • 页面服务器不稳定因为什么,关于网站出现“该页面因服务器不稳定可能无法正常访问”的提示...

    TOC 网站的说法 原文链接 http bbs 360 cn thread 15235172 1 1 html 关于网站出现 该页面因服务器不稳定可能无法正常访问 的提示 近期我们接到一些用户反馈 网站出现 该页面因服务器不稳定可能无法正常
  • Istio是啥?一文带你彻底了解!

    Istio是啥 一文带你彻底了解 什么是 Istio 官方对 Istio 的介绍浓缩成了一句话 An open platform to connect secure control and observe services 翻译过来 就是
  • IDEA快速搭建SpringBoot项目

    项目搭建 创建项目 配置项目信息 依赖选择 可选可不选 根据实际需求来 主要都是后期导入 先选择的话 选择Web下的SpringWeb以及Template Englines下的Thymeleaf就够用了 SQL可以勾选MySQL Drive
  • Android蓝牙连接出现133的解决办法

    代码片段 出现连接133的问题找了很久的解决办法 尝试各种亦然不行 最终的解决办法就是下面标红代码 if status BluetoothGatt GATT SUCCESS if newState gatt STATE CONNECTED
  • redis配置文件详解

    redis配置文件详解 1 开头说明 这里没什么好说的 需要注意的是后面需要使用内存大小时 可以指定单位 通常是以 k gb m的形式出现 并且单位不区分大小写 2 INCLUDES 我们知道Redis只有一个配置文件 如果多个人进行开发维
  • 新建虚拟机与本机ping不通(一招解决)

    初始新建虚拟机或者复制虚拟机后 发现虚拟机能ping通内外网 但是本机无法ping通虚拟机 xshell也无法连接虚拟机 这时候就很头疼了 因为要上传很多文件到虚拟机上面 解决办法 1 关闭虚拟机后 打开虚拟机的虚拟网络编辑器 虚拟机 编辑
  • 抖音直播伴侣使用教程

    抖音直播伴侣使用教程分享 红色框区域 管理场景 添加素材 切换横竖屏 蓝色框区域 常用直播功能 绿色框区域 开关播控制 性能占用情况 官方公告 黄色框区域 直播榜单 白色框区域 弹幕窗口 中央区域 直播画面采集预览 抖音直播伴侣功能介绍 添
  • Vue入门

    npm install g cnpm registry https registry npm taobao org cnpm install global vue cli vue init webpack my project cd my
  • 大厂 H5 开发实战手册

    京东凹凸实验室 Aotu io 英文简称 O2 创立于 2015 年 10 月 为掘金最早一批联合编辑 拥有数千关注者 O2 对内负责京东 PC 端首页 多个频道页 小程序版本京东购物 微信手 Q 京东购物 M 端京东 各类营销活动场景 H
  • Java使用TreeSet来排序学生成绩

    Java TreeSet TreeSet是一个有序的集合 它的作用是提供有序的Set集合 在java中使用 下面我们来进行一个实例来操作一下 具体看看怎么使用 我们的要求是用TreeSet集合存储多个学生对象的姓名 语文和数学成绩 然后按照
  • 一道经典面试题透彻理解面向对象编程思想和简单工厂模式

    一道经典的面试题如下 用一种面向对象编程语言实现一个计算器来满足整数的加减乘除运算 大部分人的代码如下 1 0版本 include
  • 【教程&工具】微信同步文章到Bear

    在我日常工作中 我会将各种互联网以及生活中产出的信息汇总到Bear 再通过Bear的云同步使我各个终端的信息保持一致 以前在使用有道云笔记的时候 有个功能我很喜欢 就是当看到一篇想收藏的文章的话 就可以直接右上角发送到有道云笔记 如下图 顺
  • “蓝桥杯总结”

    历时一个学期第十四届蓝桥杯也算是落下帷幕了 我报的是java B组从什么都不懂到省一 自己都觉得不可思议 再到国优 优秀奖也就是安慰奖 这一次参赛虽然国赛等于没拿奖 但对我而言已经很满意了 正经总结可能还要说点经验什么的 但我备赛确实没什么
  • SpringBoot之一次关于bootstrap.yaml文件的思考

    一次关于bootstrap yaml文件的思考 1 简介 2 前言 3 BootstrapApplicationListener 4 ConfigFileApplicationListener 最后 1 简介 本文不是介绍yaml的语法 是
  • OpenCV t reshold函数

    threshold函数作用 去掉噪 例如过滤很小或很大像素值的图像点 threshold函数python版原型 retval dst cv threshold src thresh maxval type dst 参数说明 src 原图像
  • golang-实现自己的事件驱动

    golang实现自己的事件驱动 众所周知 go中的异步操作都已经封装在了运行时的过程中 有关socket的网络的异步操作都封装到了go的netpoll中 从而简化了编程形式 本文也就根据evio库总结而来 golang跨平台库 如何编写go