<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[shengsheng的博客]]></title>
  <link href="http://www.shengsheng.net/atom.xml" rel="self"/>
  <link href="http://www.shengsheng.net/"/>
  <updated>2022-12-03T17:08:25+08:00</updated>
  <id>http://www.shengsheng.net/</id>
  <author>
    <name><![CDATA[]]></name>
    
  </author>
  <generator uri="http://www.mweb.im/">MWeb</generator>
  
  <entry>
    <title type="html"><![CDATA[diy之显示器灯]]></title>
    <link href="http://www.shengsheng.net/16715406373981.html"/>
    <updated>2022-12-20T20:50:37+08:00</updated>
    <id>http://www.shengsheng.net/16715406373981.html</id>
    <content type="html"><![CDATA[
<p>双12降100元，入手个明基显示器灯（BenQ ScreenBar Halo)</p>

<p><img src="media/16715406373981/16715408263896.jpg" alt=""/></p>

<p><img src="media/16715406373981/16715408360266.jpg" alt=""/></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[diy之rgb风扇]]></title>
    <link href="http://www.shengsheng.net/16700583557297.html"/>
    <updated>2022-12-03T17:05:55+08:00</updated>
    <id>http://www.shengsheng.net/16700583557297.html</id>
    <content type="html"><![CDATA[
<p>风扇要有颜色，要多接个主板的5v线</p>

<p><img src="media/16700583557297/16700584743982.jpg" alt=""/></p>

<p><img src="media/16700583557297/16700584844122.jpg" alt=""/></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[diy之cpu]]></title>
    <link href="http://www.shengsheng.net/16684834710508.html"/>
    <updated>2022-11-15T11:37:51+08:00</updated>
    <id>http://www.shengsheng.net/16684834710508.html</id>
    <content type="html"><![CDATA[
<p>由于i5还是比较慢，主板只支持11代，刚好双11，入手I9</p>

<p><img src="media/16684834710508/16684835239072.jpg" alt=""/></p>

<p><img src="media/16684834710508/16684835369417.jpg" alt=""/></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[diy之显示器]]></title>
    <link href="http://www.shengsheng.net/16666613105868.html"/>
    <updated>2022-10-25T09:28:30+08:00</updated>
    <id>http://www.shengsheng.net/16666613105868.html</id>
    <content type="html"><![CDATA[
<p>在高sir购买之下，看了款参数比较高的显示器，正好老婆做设计可以用上</p>

<p><img src="media/16666613105868/16666633853742.jpg" alt=""/></p>

<p><img src="media/16666613105868/16666633936071.jpg" alt=""/></p>

<p><img src="media/16666613105868/16666634030115.jpg" alt=""/></p>

<p>LG-27EP950效果还不错，赞</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[超龙3080ti]]></title>
    <link href="http://www.shengsheng.net/16417795468295.html"/>
    <updated>2022-01-10T09:52:26+08:00</updated>
    <id>http://www.shengsheng.net/16417795468295.html</id>
    <content type="html"><![CDATA[
<p>新入手玩具</p>

<p><img src="media/16417795468295/16417796348965.jpg" alt=""/></p>

<p>手掌那么大<br/>
<img src="media/16417795468295/16417796432544.jpg" alt=""/></p>

<h3 id="toc_0">主机效果图</h3>

<p><img src="media/16417795468295/16417797006378.jpg" alt=""/></p>

<p><img src="media/16417795468295/16417797115849.jpg" alt=""/></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[高端路由器]]></title>
    <link href="http://www.shengsheng.net/16361114600575.html"/>
    <updated>2021-11-05T19:24:20+08:00</updated>
    <id>http://www.shengsheng.net/16361114600575.html</id>
    <content type="html"><![CDATA[
<p>在kinbun的推荐下，入手一款网络专业级路由器asus ax88u，正好遇上双11<br/>
<img src="media/16361114600575/16361115276719.jpg" alt=""/></p>

<p><img src="media/16361114600575/16361115345094.jpg" alt=""/></p>

<p><img src="media/16361114600575/16361115414285.jpg" alt=""/><br/>
<img src="media/16361114600575/16361115503013.jpg" alt=""/><br/>
<img src="media/16361114600575/16361115570931.jpg" alt=""/></p>

<p>高达联名款，一个字“型”</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[gin框架的使用]]></title>
    <link href="http://www.shengsheng.net/16345450428439.html"/>
    <updated>2021-10-18T16:17:22+08:00</updated>
    <id>http://www.shengsheng.net/16345450428439.html</id>
    <content type="html"><![CDATA[
<p>项目使用gin框架作为项目开发，主要此框架特别精简，官方文档<a href="https://gin-gonic.com/zh-cn/docs/">https://gin-gonic.com/zh-cn/docs/</a><br/>
最简单的go服务</p>

<pre><code class="language-go">package main
import (
    &quot;github.com/gin-gonic/gin&quot;
)
func main() {
    d := gin.Default()
    d.GET(&quot;/index&quot;, func(c *gin.Context) {
        c.JSON(200, gin.H{&quot;message&quot;:&quot;hello world&quot;,&quot;data&quot;:&quot;&quot;})
    })
    d.Run(&quot;127.0.0.1:8080&quot;)
}
</code></pre>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[golang快速上手]]></title>
    <link href="http://www.shengsheng.net/16345370325648.html"/>
    <updated>2021-10-18T14:03:52+08:00</updated>
    <id>http://www.shengsheng.net/16345370325648.html</id>
    <content type="html"><![CDATA[
<h2 id="toc_0">第一本go的书籍</h2>

<p>2013年买的第一本go语言的书，至今还是比较好的入门书籍<br/>
<img src="media/16345370325648/16345370946578.jpg" alt=""/></p>

<p>距离现在好几年了，这两年go生态相对比较好，从而线上服务转去golang。<br/>
实际就是现代C语言，没有class面向对象，只有使用函数来做面向对象。</p>

<h2 id="toc_1">go常用命令</h2>

<h3 id="toc_2">go get</h3>

<p>go get <a href="http://github.com/123/abc">http://github.com/123/abc</a><br/>
$GOPATH<br/>
获取组件</p>

<h3 id="toc_3">go build</h3>

<p>bin/</p>

<h3 id="toc_4">go run</h3>

<h3 id="toc_5">go mod</h3>

<p>dep</p>

<p>module<br/>
如果Go的版本太低不能使用，建议将Go的版本升级到最新。<br/>
环境变量中可以增加GOPROXY=<a href="https://goproxy.io">https://goproxy.io</a> 这样没有梯子的情况下可以正确的加载相应的包文件。<br/>
环境变量GO111MODULE不要设置，如果已经增加了这个变量请务必设置为GO111MODULE=auto。<br/>
在项目的根目录下使用命令go mod init projectName。</p>

<h3 id="toc_6">go tool</h3>

<p>go tool nm ./example</p>

<h2 id="toc_7">基本类型</h2>

<p>类型  长度(字节)  默认值 说明<br/>
bool    1   false<br/><br/>
byte    1   0   uint8<br/>
rune    4   0   Unicode Code Point, int32<br/>
int, uint   4或8 0   32 或 64 位<br/>
int8, uint8 1   0   -128 ~ 127, 0 ~ 255，byte是uint8 的别名<br/>
int16, uint16   2   0   -32768 ~ 32767, 0 ~ 65535<br/>
int32, uint32   4   0   -21亿~ 21亿, 0 ~ 42亿，rune是int32 的别名<br/>
int64, uint64   8   0<br/><br/>
float32 4   0.0 <br/>
float64 8   0.0 <br/>
complex64   8<br/><br/>
complex128  16<br/><br/>
uintptr 4或8     以存储指针的 uint32 或 uint64 整数<br/>
array           值类型<br/>
struct          值类型<br/>
string      &quot;&quot;  UTF-8 字符串<br/>
slice       nil 引用类型<br/>
map     nil 引用类型<br/>
channel     nil 引用类型<br/>
interface       nil 接口<br/>
function        nil 函数</p>

<h2 id="toc_8">数组与切片</h2>

<h3 id="toc_9">数组</h3>

<p>Don’t communicate by sharing memory; share memory by communicating.</p>

<pre><code class="language-go">var arr0 [5]int = [5]int{1, 2, 3}
var arr1 = [5]int{1, 2, 3, 4, 5}
var arr2 = [...]int{1, 2, 3, 4, 5, 6}
var str = [5]string{3: &quot;hello world&quot;, 4: &quot;tom&quot;}
</code></pre>

<p>深拷贝是指将值类型的数据进行拷贝的时候，拷贝的是数值本身，所以值类型的数据默认都是深拷贝。浅拷贝指的是拷贝的引用地址，修改拷贝过后的数据,原有的数据也被修改。 那么如何做到引用类型的深拷贝？也就是需要将引用类型的值进行拷贝。修改拷贝的值不会对原有的值造成影响。</p>

<pre><code class="language-go">// copy(目标切片,数据源)  深拷贝数据函数
s2 := []int{1, 2, 3, 4}
s3 := []int{7, 8, 9}

copy(s2, s3)        //将s3拷贝到s2中    
fmt.Println(s2)     //结果 [7 8 9 4]
fmt.Println(s3)     //结果 [7 8 9]

copy(s3, s2[2:])    //将s2中下标为2的位置 到结束的值 拷贝到s3中 
fmt.Println(s2)     //结果 [1 2 3 4]
fmt.Println(s3)     //结果 [3 4 9]

copy(s3, s2)        //将s2拷贝到s3中
fmt.Println(s2)     //结果 [1 2 3 4]
fmt.Println(s3)     //结果 [1 2 3]
</code></pre>

<p>二维数据</p>

<p>数组长度是固定的，如果要扩展数组，得使用切片</p>

<h3 id="toc_10">切片</h3>

<p>make()是Go语言中的内置函数，主要用于创建并初始化slice切片类型，或者map字典类型，或者channel通道类型数据。他与new方法的区别是。new用于各种数据类型的内存分配，在Go语言中认为他返回的是一个指针。指向的是一个某种类型的零值。make 返回的是一个有着初始值的非零值。</p>

<p>使用make函数</p>

<pre><code class="language-go">var s3 []int = make([]int, 0)
</code></pre>

<p>or</p>

<pre><code class="language-go">var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[start:end] 
1:3
var slice1 []int = arr[:end]        
var slice2 []int = arr[start:]        
var slice3 []int = arr[:] 
var slice4 = arr[:len(arr)-1]
</code></pre>

<p>切片是可索引的，并且可以由 len() 方法获取长度。<br/>
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。<br/>
切片是面向地址的，修改数值会影响相关变量。</p>

<h3 id="toc_11">map</h3>

<p>定义语法</p>

<pre><code class="language-go">map[KeyType]ValueType
    
//1, 声明map 默认值是nil
var m1 map[key_data_type]value_data_type
声明  变量名称 map[key的数据类型]value的数据类型
//2，使用make声明
m2:=make(map[key_data_type]value_data_type)
//3,直接声明并初始化赋值map方法
m3:=map[string]int{&quot;语文&quot;:89,&quot;数学&quot;:23,&quot;英语&quot;:90}
</code></pre>

<p>demo:</p>

<pre><code class="language-go">func main() {
    scoreMap := make(map[string]int, 8)
    scoreMap[&quot;张三&quot;] = 90
    scoreMap[&quot;小明&quot;] = 100
    fmt.Println(scoreMap)
    fmt.Println(scoreMap[&quot;小明&quot;])
    fmt.Printf(&quot;type of a:%T\n&quot;, scoreMap)
}


</code></pre>

<h2 id="toc_12">结构体</h2>

<p>基本上和C的struct一样<br/>
在Go语言中，使用type关键字可以定义出新的自定义类型，有了自定义类型之后我们就可以为自定义类型添加各种方法了。</p>

<pre><code class="language-go">type person1 struct {
        name, city string
        age        int8
}

package main

import (
    &quot;fmt&quot;
)

//定义结构体
type Person struct {
    name    string
    age     int
    sex     string
    address string
}

</code></pre>

<p>实例：</p>

<pre><code class="language-go">func main() {
    //实例化后并使用结构体
    p := Person{} //使用简短声明方式，后面加上{}代表这是结构体
    
    p.age = 2     //给结构体内成员变量赋值
    p.address = &quot;陕西&quot;
    p.name = &quot;好家伙&quot;
    p.sex = &quot;女&quot;
    
    fmt.Println(p.age, p.address, p.name, p.sex)//使用点.来访问结构体内成员的变量的值。

}

</code></pre>

<h2 id="toc_13">指针</h2>

<p>基本上和C一致<br/>
指针是存储另一个变量的内存地址的变量。 例如： 变量B的值为100， 地址为0x1122。变量A的值为变量B的地址0x1122，那么A就拥有了变量B的地址，则A就成为指针。Go语言中通过&amp;获取变量的地址。通过* 获取指针所对应的变量存储的数值。</p>

<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
)

func main() {

    //定义一个变量
    a := 2
    fmt.Printf(&quot;变量A的地址为%p&quot;, &amp;a) //通过%p占位符, &amp;符号获取变量的内存地址。
    //变量A的地址为0xc000072090
    

    //创建一个指针
    // 指针的声明 通过 *T 表示T类型的指针
    var i *int     //int类型的指针
    var f *float64 //float64类型的指针
    fmt.Println(i) // &lt; nil &gt;空指针
    fmt.Println(f)

    //因为指针存储的变量的地址 所以指针存储值
    i = &amp;a
    fmt.Println(i)  //i存储a的内存地址0xc000072090
    fmt.Println(*i) //i存储这个指针存储的变量的数值2
    *i = 100
    fmt.Println(*i) //100
    fmt.Println(a)  //100通过指针操作 直接操作的是指针所对应的数值

}
</code></pre>

<p>指针的指针，也就是存储的不是具体的数值了，而是另一个指针的地址。</p>

<pre><code class="language-go">func main(){
    a := 2
    var i *int         //声明一个int类型的指针
    fmt.Println(&amp;a)    //0xc00000c1c8
    i = &amp;a             //将a的地址取出来放到i里面
    fmt.Println(&amp;i)    //0xc000006028
    var a2 **int       //声明一个指针类型的指针
    a2 = &amp;i            //再把i的地址放进a2里面
    fmt.Println(a2)    //获取的是a2所对应的数值0xc000006028也就是i的地址
}
</code></pre>

<p>指针属于引用类型的数据， 所以在传递过程中是将参数的地址传给函数，将指针作为参数传递时，只有值类型的数据，需要传递指针，而引用类型的数据本身就是传递的地址，所以数组传递可以使用指针，切片是引用类型数据，则不需要传递指针传递。</p>

<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
)

func main() {
    s := 10
    fmt.Println(s) //调用函数之前数值是10
    fun1(&amp;s)
    fmt.Println(s) //调用函数之后再访问则被修改成2
}

//接收一个int类型的指针作为参数
func fun1(a *int) {
    *a = 2
}
</code></pre>

<h3 id="toc_14">使用结构体调用指针</h3>

<pre><code class="language-go">//1 使用结构体指针
var p *Person
p = &amp;p2 //将p2 的地址赋给p
import &quot;encoding/json&quot;
package main

import (
    &quot;encoding/json&quot;
    &quot;fmt&quot;
)
//结构体
type Prescription struct {
    Name     string        
    Unit     string        
    Additive *Prescription 
}

func main() {
    p := Prescription{}
    p.Name = &quot;鹤顶红&quot;
    p.Unit = &quot;1.2kg&quot;
    p.Additive = &amp;Prescription{
        Name: &quot;砒霜&quot;,
        Unit: &quot;0.5kg&quot;,
    }

    buf, err := json.Marshal(p) //转换为json返回两个结果
    if err != nil {
        fmt.Println(&quot;err = &quot;, err)
        return
    }

    fmt.Println(&quot;json = &quot;, string(buf))
}

package main

import (
    &quot;encoding/json&quot;
    &quot;fmt&quot;
)

//结构体
type Prescription struct {
    Name     string        `json:&quot;name&quot;` //重新指定json字段为小写输出
    Unit     string        `json:&quot;unit&quot;`
    Additive *Prescription `json:&quot;additive,omitempty&quot;`
}

func main() {
    jsonstr := `{&quot;name&quot;:&quot;鹤顶红&quot;,&quot;unit&quot;:&quot;1.2kg&quot;,&quot;additive&quot;:{&quot;name&quot;:&quot;砒霜&quot;,&quot;unit&quot;:&quot;0.5kg&quot;}}`
    var p Prescription
    if err := json.Unmarshal([]byte(jsonstr), &amp;p); err != nil {
        fmt.Println(err)
    }
    fmt.Println(p)
}

</code></pre>

<h2 id="toc_15">流程控件</h2>

<p>日常语言的if、for range、switch不作具体描述，比较特殊的用法是select<br/>
while(true)<br/>
{</p>

<p>}<br/>
select 语句类似于 switch 语句，但是select会随机执行一个可运行的case。如果没有case可运行，它将阻塞，直到有case可运行。</p>

<p>select 是Go中的一个控制结构，类似于用于通信的switch语句。每个case必须是一个通信操作，要么是发送要么是接收。 select 随机执行一个可运行的case。如果没有case可运行，它将阻塞，直到有case可运行。一个默认的子句应该总是可运行的。</p>

<pre><code class="language-go">select {
    case communication clause  :
       statement(s);      
    case communication clause  :
       statement(s);
    /* 你可以定义任意数量的 case */
    default : /* 可选 */
       statement(s);
}
</code></pre>

<h1 id="toc_16">函数与方法</h1>

<p>如果你想让一个方法可以被别的包访问的话，你需要把这个方法的第一个字母大写。这是一种约定。</p>

<h3 id="toc_17">面向对象</h3>

<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
)

type Phone interface {
    call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
    fmt.Println(&quot;I am Nokia, I can call you!&quot;)
}

type IPhone struct {
}

func (iPhone IPhone) call() {
    fmt.Println(&quot;I am iPhone, I can call you!&quot;)
}

func main() {
    var phone Phone

    phone = new(NokiaPhone)
    phone.call()

    phone = new(IPhone)
    phone.call()

}
</code></pre>

<h3 id="toc_18">反射工具类</h3>

<p>reflect</p>

<h2 id="toc_19">通道</h2>

<p>通道（channel）是用来传递数据的一个数据结构。</p>

<pre><code class="language-go">//通道的声明
var channel chan int
//如果通道时nil 则要通过make创建通道
channel= make(chan int)
</code></pre>

<p>通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 &lt;- 用于指定通道的方向，发送或接收。如果未指定方向，则为双向通道。</p>

<p>在函数或者方法前面加上关键字go，就会同时运行一个新的goroutine。</p>

<p>与函数不同的是goroutine调用之后会立即返回，不会等待goroutine的执行结果，所以goroutine不会接收返回值。 把封装main函数的goroutine叫做主goroutine，main函数作为主goroutine执行，如果main函数中goroutine终止了，程序也将终止，其他的goroutine都不会再执行。</p>

<p>虽然说Go编译器将Go的代码编译成本地可执行代码。不需要像java或者.net那样的语言需要一个虚拟机来运行，但其实go是运行在runtime调度器上的，它主要负责内存管理、垃圾回收、栈处理等等。也包含了Go运行时系统交互的操作，控制goroutine的操作，Go程序的调度器可以很合理的分配CPU资源给每一个任务。</p>

<p>Go1.5版本之前默认是单核执行的。从1.5之后使用可以通过runtime.GOMAXPROCS()来设置让程序并发执行，提高CPU的利用率。</p>

<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
    &quot;runtime&quot;
    &quot;time&quot;
)

func main() {
    //获取当前GOROOT目录
    fmt.Println(&quot;GOROOT:&quot;, runtime.GOROOT())
    //获取当前操作系统
    fmt.Println(&quot;操作系统:&quot;, runtime.GOOS)
    //获取当前逻辑CPU数量
    fmt.Println(&quot;逻辑CPU数量：&quot;, runtime.NumCPU())

    //设置最大的可同时使用的CPU核数  取逻辑cpu数量
    n := runtime.GOMAXPROCS(runtime.NumCPU())
    fmt.Println(n) //一般在使用之前就将cpu数量设置好 所以最好放在init函数内执行

    //goexit 终止当前goroutine
    //创建一个goroutine
    go func() {
        fmt.Println(&quot;start...&quot;)
        runtime.Goexit() //终止当前goroutine
        fmt.Println(&quot;end...&quot;)
    }()
    time.Sleep(3 * time.Second) //主goroutine 休眠3秒 让子goroutine执行完
    fmt.Println(&quot;main_end...&quot;)
}
</code></pre>

<h2 id="toc_20">延迟函数</h2>

<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
)

func main() {
    defer test(1) //第一个被defer的，函数后执行
    defer test(2) //第二个被defer的，函数先执行
    test(3)       //没有defer的函数，第一次执行
    
    //执行结果
    //3
    //2
    //1
}

func test(s int) {
    fmt.Println(s)
}
</code></pre>

<h2 id="toc_21">错误处理</h2>

<p>实际每个函数都可以定义二个参数的返回，error类型<br/>
例如<br/>
txt, err = test(&quot;test.txt&quot;);</p>

<h3 id="toc_22">错误类型</h3>

<pre><code class="language-go">package main

import (
    &quot;errors&quot;
    &quot;fmt&quot;
)

func main() {
    err := errors.New(&quot;错误信息...&quot;)
    fmt.Println(err)

    num, err2 := Calculation(0)
    fmt.Println(num, err2)
}

//通过内置errors包创建错误对象来返回
func Calculation(divisor int) (int, error) {
    if divisor == 0 {
        return 0, errors.New(&quot;错误:除数不能为零.&quot;)
    }
    return 100 / divisor, nil
}
</code></pre>

<pre><code class="language-go">panic和recover
中断信息，recover接收信息

package main

import (
    &quot;fmt&quot;
)

func main() {
    Test1()
}

func Test1() {
    defer func() {
        ms := recover()//这里执行恢复操作
        fmt.Println(ms, &quot;恢复执行了..&quot;) //恢复程序执行,且必须在defer函数中执行
    }()
    defer fmt.Println(&quot;第1个被defer执行&quot;)
    defer fmt.Println(&quot;第2个被defer执行&quot;)
    for i := 0; i &lt;= 6; i++ {
        fmt.Println(i);
        if i == 4 {
            panic(&quot;中断操作&quot;) //让程序进入恐慌 终端程序操作
        }
    }
    
    defer fmt.Println(&quot;第3个被defer执行&quot;) //恐慌之后的代码是不会被执行的
}
</code></pre>

<h2 id="toc_23">常用工具包</h2>

<p>官方常用工具包有如下<br/>
fmt、strings、strconv、os、io、errors</p>

<h1 id="toc_24">导入包</h1>

<p>引入每个工具包，都先要先导入<br/>
使用import 导入包。go自己会默认从GO的安装目录和GOPATH环境变量中的目录，检索src下的目录进行检索包是否存在。所以导入包的时候路径要从src目录下开始写。GOPATH 就是我们自己定义的包的目录。</p>

<pre><code class="language-go">// 每行一个写法
import fmt
// 导入多个，还可以重命名
import (
    &quot;fmt&quot;
    &quot;sync&quot;
    &quot;os&quot; &quot;test&quot;
    _ &quot;test.com/test&quot;  // 只调用里面的init，不调用里面的方法
)
</code></pre>

<h2 id="toc_25">闭包</h2>

<pre><code class="language-go">闭包：
package main
import (
    &quot;fmt&quot;
)
func main() {
    res := closure()
    fmt.Println(res) //0x49a880  返回内层函数函数体地址
    r1 := res()      //执行closure函数返回的匿名函数
    fmt.Println(r1)  //1
    r2 := res()
    fmt.Println(r2) //2 
    //普通的函数应该返回1，而这里存在闭包结构所以返回2 。
    //一个外层函数当中有内层函数，这个内层函数会操作外层函数的局部变量,并且外层函数把内层函数作为返回值,则这里内层函数和外层函数的局部变量,统称为闭包结构。这个外层函数的局部变量的生命周期会发生改变，不会随着外层函数的结束而销毁。
    //所以上面打印的r2 是累计到2 。

    res2 := closure() //再次调用则产生新的闭包结构 局部变量则新定义的
    fmt.Println(res2)
    r3 := res2()
    fmt.Println(r3)
}

//定义一个闭包结构的函数 返回一个匿名函数
func closure() func() int { //外层函数
    //定义局部变量a
    a := 0 //外层函数的局部变量
    //定义内层匿名函数 并直接返回
    return func() int { //内层函数
        a++ //在匿名函数中将变量自增。内层函数用到了外层函数的局部变量，此变量不会随着外层函数的结束销毁
        return a
    }
}
</code></pre>

<h3 id="toc_26">sync工具</h3>

<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
    &quot;sync&quot;
)

//创建一个同步等待组的对象
var wg sync.WaitGroup

func main() {
    wg.Add(3) //设置同步等待组的数量
    go Relief1()
    go Relief2()
    go Relief3()
    wg.Wait() //主goroutine进入阻塞状态
    fmt.Println(&quot;main end...&quot;)
}

func Relief1() {
    fmt.Println(&quot;func1...&quot;)
    wg.Done() //执行完成 同步等待数量减1
}
func Relief2() {
    defer wg.Done()
    fmt.Println(&quot;func2...&quot;)
}
func Relief3() {
    defer wg.Done() //推荐使用延时执行的方法来减去执行组的数量
    fmt.Println(&quot;func3...&quot;)
}
</code></pre>

<h3 id="toc_27">互斥锁</h3>

<p>互斥锁，当一个goroutine获得锁之后其他的就只能等待当前goroutine执行完成之后解锁后才能访问资源。对应的方法有上锁Lock()和解锁Unlock()。</p>

<h3 id="toc_28">读写锁</h3>

<p>互斥锁是用来控制多个协程在访问同一个资源的时候进行加锁控制,保证了数据安全，但同时也降低了性能，如果说多个goroutine同时访问一个数据，只是读取一下数据，并没有对数据进行任何修改操作，那么不管多少个goroutine来读取都应该是可以的。主要问题在于修改。修改的数据就需要加锁操作，来保证数据在多个goroutine读取的时候统一。 读取和读取之间是不需要互斥操作的，所以我们用读写锁专门针对读操作和写操作的互斥锁。</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[服务器迁移注意事项]]></title>
    <link href="http://www.shengsheng.net/16278746836691.html"/>
    <updated>2021-08-02T11:24:43+08:00</updated>
    <id>http://www.shengsheng.net/16278746836691.html</id>
    <content type="html"><![CDATA[
<p>迁移一般使用scp或rsync比较多，此文记录迁移遇到的坑。</p>

<h1 id="toc_0">rsync的使用</h1>

<p>因为不使用rsync://协议，不走配置文件。配置文件太麻烦了，我们直接使用ssh的22端口进行传输。<br/>
首先要同步的机上都安装上rsync.<br/>
<code><br/>
yum install -y rsync<br/>
</code><br/>
同步命令</p>

<pre><code class="language-text">rsync -av --progress --exclude=&quot;vendor/*&quot;  源目录/  目标目录/


rsync -av --progress --exclude=&quot;vendor/*&quot; --exclude=&quot;Runtime/log/*&quot; root@163.163.163.163:/webwww/www/slplat /webwww/www/
</code></pre>

<h3 id="toc_1">参数解释</h3>

<p>--exclude 排除要同步的目录<br/>
-a 参数表示存档模式，保存所有的元数据，比如修改时间（modification time）、权限、所有者等，并且软链接也会同步过去。<br/>
-v 输出细节<br/>
--progress  输出同步信息</p>

<p>每次运行即可同步信息</p>

<h2 id="toc_2">同步好文件之后，一般要停掉源服务器的写入，因为要同步数据库迁移。</h2>

<p>这里可以使用腾讯云的数据库迁移工具。</p>

<p><img src="media/16278746836691/16278751728151.jpg" alt=""/><br/>
或使用navicat mysql的迁移工具。<br/>
这里要注意同步的话要同版本迁移，例如mysql5.7，目标数据库最好是5.7，如使用8.0.22可能有问题，因为mysql8.0.22去掉int类型的位长。</p>

<h1 id="toc_3">dns与cdn的更新</h1>

<p>因为dns可能存在缓存的问题，并不能及时更新到用户层，所以源来的服务器最好开启，把数据库等连接填写新外网的数据库。</p>

<h1 id="toc_4">测试</h1>

<p>最后一步测试流程，例如有些接口做了ip白名单的，要主动加上。</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[ab测试php接口]]></title>
    <link href="http://www.shengsheng.net/16269522469797.html"/>
    <updated>2021-07-22T19:10:46+08:00</updated>
    <id>http://www.shengsheng.net/16269522469797.html</id>
    <content type="html"><![CDATA[
<p>接口测试，我们一般会使用ab、wrk等工具。公司培训了一下php服务端人员，故记录一下。</p>

<h2 id="toc_0">ab命令详解</h2>

<pre><code class="language-text">-n 即requests，用于指定压力测试总共的执行次数。
-c 即concurrency，用于指定的并发数。
-t 即timelimit，等待响应的最大时间(单位：秒)。
-b 即windowsize，TCP发送/接收的缓冲大小(单位：字节)。
-p 即postfile，发送POST请求时需要上传的文件，此外还必须设置-T参数。
-u 即putfile，发送PUT请求时需要上传的文件，此外还必须设置-T参数。
-T 即content-type，用于设置Content-Type请求头信息，例如：application/x-www-form-urlencoded，默认值为text/plain。
-v 即verbosity，指定打印帮助信息的冗余级别。
-w 以HTML表格形式打印结果。
-i 使用HEAD请求代替GET请求。
-x 插入字符串作为table标签的属性。
-y 插入字符串作为tr标签的属性。
-z 插入字符串作为td标签的属性。
-C 添加cookie信息，例如：&quot;Apache=1234&quot;(可以重复该参数选项以添加多个)。
-H 添加任意的请求头，例如：&quot;Accept-Encoding: gzip&quot;，请求头将会添加在现有的多个请求头之后(可以重复该参数选项以添加多个)。
-A 添加一个基本的网络认证信息，用户名和密码之间用英文冒号隔开。
-P 添加一个基本的代理认证信息，用户名和密码之间用英文冒号隔开。
-X 指定使用的和端口号，例如:&quot;126.10.10.3:88&quot;。
-V 打印版本号并退出。
-k 使用HTTP的KeepAlive特性。
-d 不显示百分比。
-S 不显示预估和警告信息。
-g 输出结果信息到gnuplot格式的文件中。
-e 输出结果信息到CSV格式的文件中。
-r 指定接收到错误信息时不退出程序。
-h 显示用法信息，其实就是ab -help。

</code></pre>

<p>模拟并发请求100次，总共请求10000次<br/>
命令模板：<br/>
ab -c 100 -n 10000 待测试网站（建议完整路径）<br/>
内容解释：</p>

<pre><code class="language-text">Server Software: nginx/1.10.2 (服务器软件名称及版本信息)
Server Hostname: 192.168.1.106(服务器主机名)
Server Port: 80 (服务器端口)
Document Path: /index1.html. (供测试的URL路径)
Document Length: 3721 bytes (供测试的URL返回的文档大小)
Concurrency Level: 1000 (并发数)
Time taken for tests: 2.327 seconds (压力测试消耗的总时间)
Complete requests: 5000 (的总次数)
Failed requests: 688 (失败的请求数)
Write errors: 0 (网络连接写入错误数)
Total transferred: 17402975 bytes (传输的总数据量)
HTML transferred: 16275725 bytes (HTML文档的总数据量)
Requests per second: 2148.98 [#/sec] (mean) (平均每秒的请求数) 这个是非常重要的参数数值，服务器的吞吐量
Time per request: 465.338 [ms] (mean) (所有并发用户(这里是1000)都请求一次的平均时间)
Time request: 0.247 [ms] (mean, across all concurrent requests) (单个用户请求一次的平均时间)
Transfer rate: 7304.41 [Kbytes/sec] received 每秒获取的数据长度 (传输速率，单位：KB/s)
</code></pre>

<h1 id="toc_1">模式的测试</h1>

<p><img src="media/16269522469797/16269529874477.jpg" alt=""/></p>

<p>php-fpm、workerman http、swoole http、swoole协程http, 主要围绕这几种来展开</p>

<h2 id="toc_2">swoole模式</h2>

<p><img src="media/16269522469797/16269524233041.jpg" alt=""/></p>

<h1 id="toc_3">workerman模式</h1>

<p><img src="media/16269522469797/16269525030649.jpg" alt=""/></p>

<h2 id="toc_4">php-fpm模式</h2>

<p><img src="media/16269522469797/16269525696896.jpg" alt=""/></p>

<h1 id="toc_5">总结</h1>

<p>这里可以查看得知php-fpm进程每次要ast一下代码过程会减慢速度，简单业务的情况下，workerman会更快一些。</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[ansible极速入门]]></title>
    <link href="http://www.shengsheng.net/16268654750373.html"/>
    <updated>2021-07-21T19:04:35+08:00</updated>
    <id>http://www.shengsheng.net/16268654750373.html</id>
    <content type="html"><![CDATA[
<p>之前使用pssh来做快速管理，ansible会比pssh加强，所以用他一下</p>

<h1 id="toc_0">安装</h1>

<p>首先我们在主要的服务器上使用yum来安装一下</p>

<pre><code class="language-text">yum -y install ansible
</code></pre>

<p>安装完之后，更新一下主机配置<br/>
vim /etc/ansible/hosts</p>

<p>例如我们配置一组ip</p>

<pre><code class="language-text">[mg]
1.55.239.140
1.71.34.97
1.75.220.150
1.75.207.240
</code></pre>

<p>生成密钥（这个可以详看我写的openssh通讯）</p>

<pre><code class="language-text">ssh-keygen -t rsa
</code></pre>

<p>将pub加到其它机器上，实现ssh通讯</p>

<h1 id="toc_1">具体命令</h1>

<p>Ansible的命令格式为， ansible 主机群组名 -m 命令模块名 -a &quot;批量执行的操作&quot;<br/>
其中-m不是必须的，默认为command模块，-a也不是必须的，表示命令模块的参数，比如前面的ping模块就没有参数。<br/>
可以使用 ansible-doc -l 列出所有可用的命令模块， ansible-doc -s 模块名 查看指定模块的参数信息</p>

<pre><code class="language-text">ping # 主机连通性测试
command # 在远程主机上执行命令,不支持管道
shell # 在远程主机上调用shell解析器,支持管道命令个
copy # 用于将文件复制到远程主机,支持设定内容和修改权限.
file # 创建文件,创建连接文件,删除文件等
fetch # 从远程复制文件到本地
cron # 管理cron计划任务
yum # 用于模块的安装
service # 管理服务
user # 管理用户账号
group # 用户组管理
script # 将本地的脚本在远端服务器运行
setup # 该模块主要用于收集信息，是通过调用facts组件来实现的,以变量形式存储主机上的信息

-------------------------------------------------------------------

ansible mg -m command -a &quot;free -g&quot;
ansible mg -m shell -a &quot;free -g|grep Mem&quot;
ansible mg -m script -a &quot;/root/echo.sh&quot;
ansible mg -m copy -a &quot;src=/root/echo.sh dest=/tmp/ owner=root group=root mode=0755&quot;
ansible mg -m yum -a &quot;name=wget state=latest&quot;
ansible mg -m service -a &quot;name=nginx state=stoped&quot;
ansible mg -m get_url -a &quot;url=http://www.baidu.com dest=/tmp/index.html mode=0440 force=yes&quot;
ansible mg -m setup
ansible mg -m file -a &quot;dest=/opt/test state=touch&quot;
directory：创建目录
file：如果文件不存在，则创建
link：创建symbolic link
absent：删除文件或目录
touch： 创建一个不存在的空文件
ansible mg -m cron -a &quot;name=&#39;backup servcie&#39; minute=*/5 job=&#39;/usr/sbin/ntpdate  time.nist.gov &gt;/dev/null 2&gt;&amp;1&#39;&quot;
</code></pre>

<p>暂时使用得最多的是command和shell</p>

<h3 id="toc_2">检测指定ip</h3>

<p><code><br/>
ansible 127.0.0.1 -m ping<br/>
</code></p>

<h3 id="toc_3">特定组</h3>

<p><code><br/>
ansible mg -m ping<br/>
</code></p>

<h3 id="toc_4">所有配置</h3>

<p><code><br/>
ansible all -m ping<br/>
</code></p>

<h1 id="toc_5">批量操作示例</h1>

<pre><code class="language-text">ansible mg -m shell -a &quot;yum install -y yum-utils device-mapper-persistent-data lvm2&quot;

ansible mg -m shell -a &quot;yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo&quot;

ansible mg -m shell -a &quot;yum -y install docker-ce docker-ce-cli containerd.io&quot;

ansible mg -m shell -a &quot;systemctl start docker&quot;
</code></pre>

<h1 id="toc_6">总结</h1>

<p>ansible更高级的功能暂时没用上，但批量处理命令的方式实在是方便！</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[GatewayWorker源码分析]]></title>
    <link href="http://www.shengsheng.net/16260545730464.html"/>
    <updated>2021-07-12T09:49:33+08:00</updated>
    <id>http://www.shengsheng.net/16260545730464.html</id>
    <content type="html"><![CDATA[
<p>GatewayWorker官方介绍是为了开发长连接的应用。<br/>
<img src="media/16260545730464/16260552306177.jpg" alt=""/></p>

<p>register在worker进程和gateway进程中间</p>

<h1 id="toc_0">运行</h1>

<p>我们下载运行一下<br/>
<img src="media/16260545730464/16260547552327.jpg" alt=""/></p>

<p>主要由3个worker组成，对应文件里的3个<br/>
<img src="media/16260545730464/16260548072167.jpg" alt=""/></p>

<p>我们主要将逻辑写在Events.php事件逻辑里即可，其它交给布署的人员。<br/>
跟websocket三核心方法一样<br/>
onConnect // 客户连接<br/>
onMessage // 处理客户消息<br/>
onClose  // 客户关闭</p>

<h1 id="toc_1">核心文件</h1>

<p>因为GatewayWorker依赖workerman的原因，源码文件并不多，主要我们要掌握思路<br/>
.<br/>
├── BusinessWorker.php   用户事件处理<br/>
├── Gateway.php           gateway<br/>
├── Lib<br/>
│   ├── Context.php        上下文管理<br/>
│   ├── Db.php              db类<br/>
│   ├── DbConnection.php    db连接，作池使用<br/>
│   └── Gateway.php         gateway方法，主要学习这个类<br/>
├── Protocols<br/>
│   └── GatewayProtocol.php  通讯协议<br/>
└── Register.php             Register类</p>

<p>Protocols和Lib暂且可以不分析，除了Lib/Gateway.php文件</p>

<h2 id="toc_2">Register</h2>

<p><img src="media/16260545730464/16260552047947.jpg" alt=""/></p>

<p>主要是管理gateway连接和worker连接, 然后广播一下地址broadcastAddresses</p>

<h2 id="toc_3">BusinessWorker</h2>

<p>BusinessWorker主要是调用用户Events.php的逻辑, 并向GateWay保持通讯<br/>
<img src="media/16260545730464/16260554002541.jpg" alt=""/></p>

<h2 id="toc_4">GatewayWorker</h2>

<p>主要是入口，例如每个浏览器先访问GatewayWorker, GatewayWorker再到找Register进程，连接到用户逻辑模块。<br/>
<img src="media/16260545730464/16260555234794.jpg" alt=""/></p>

<h2 id="toc_5">Lib\Gateway</h2>

<p>很多核心逻辑处理，主要看这个类<br/>
<img src="media/16260545730464/16260557258793.jpg" alt=""/></p>

<p>方法比较多<br/>
sendToAll 广播信息<br/>
sendToClient 发送给指定客户<br/>
isUidOnline  判断某个客户端是否在线<br/>
isOnline    是否在线<br/>
getClientInfoByGroup    获取某个组信息<br/>
getAllClientIdCount     获取所有在线client_id数<br/>
getUidListByGroup   获取某个群组在线uid<br/>
getAllUidList   获取全局在线uid<br/>
等等<br/>
方法比较多，需每个认真查看逻辑，分析作者设计思想</p>

<h1 id="toc_6">总结</h1>

<p>总的来说GatewayWorker的设计思想还是比较好的，可以横向扩展分布式的思路。如果不关心是怎么运作，倒是写好Events.php就够用，学习好Lib\Gateways的方法。</p>

<h1 id="toc_7">附录</h1>

<p><a href="http://doc2.workerman.net/">GatewayWorker手册</a></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[workerman源码分析_简略篇]]></title>
    <link href="http://www.shengsheng.net/16254492208395.html"/>
    <updated>2021-07-05T09:40:20+08:00</updated>
    <id>http://www.shengsheng.net/16254492208395.html</id>
    <content type="html"><![CDATA[
<p>使用workerman在写一个cronjob项目，出现问题引发的wm分析。</p>

<p><img src="media/16254492208395/16254493083277.jpg" alt=""/><br/>
配置个web的http worker</p>

<p>在代码里再请求curl 8089端口，即<a href="http://127.0.0.1:8089">http://127.0.0.1:8089</a><br/>
为了调试，我们开启curl的debug功能</p>

<pre><code class="language-text">curl_setopt($ch, CURLOPT_VERBOSE, true); // curl debug
curl_setopt($ch, CURLOPT_STDERR, fopen(CRONPATH.&#39;/Application/Log/curl.log&#39;, &#39;w+&#39;));
$response = curl_getinfo( $ch );
</code></pre>

<p>测试正常连接百度是正常的，但连接127.0.0.1:8089一直在等待中<br/>
Warning: file_get_contents(<a href="http://127.0.0.1:8089/?op=cl_list&amp;secret=b61e757422497ccaef7dab5d59b180dd">http://127.0.0.1:8089/?op=cl_list&amp;secret=b61e757422497ccaef7dab5d59b180dd</a>): Failed to open stream: HTTP request failed! in /webwww/y/cronshow/Application/Library/Web.php on line 174</p>

<pre><code class="language-text">curl_start
exec
* Expire in 0 ms for 6 (transfer 0x558eac35eb10)
* Expire in 2000 ms for 8 (transfer 0x558eac35eb10)
* Expire in 2000 ms for 2 (transfer 0x558eac35eb10)
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0x558eac35eb10)
* Connected to 127.0.0.1 (127.0.0.1) port 8089 (#0)
&gt; GET /?op=cl_list&amp;secret=b61e757422497ccaef7dab5d59b180dd HTTP/1.1
Host: 127.0.0.1:8089
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.5
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded; charset=utf-8
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:28.0) Gecko/20100101 Firefox/28.0
X-MicrosoftAjax: Delta=true

* Operation timed out after 2000 milliseconds with 0 bytes received
* Closing connection 0
</code></pre>

<p>显示2秒之后断开</p>

<p>docker里开启strace调试</p>

<pre><code class="language-text">docker run --cap-add=SYS_PTRACE -it -v ~/code:/webwww --name php-fpm-8033 show/php-fpm:8.0.3
</code></pre>

<p>进入docker里查看</p>

<pre><code class="language-text">strace -tt -p 对应进程端口
可使用php job.php status查看workerman的worker端口
</code></pre>

<p>可以监测到连接8089的时候是-1</p>

<pre><code class="language-text">socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 8

connect(8, {sa_family=AF_INET, sin_port=htons(8089), sin_addr=inet_addr(&quot;127.0.0.1&quot;)}, 16) = -1 EINPROGRESS (Operation now in progress)

getpeername(8, {sa_family=AF_INET, sin_port=htons(8089), sin_addr=inet_addr(&quot;127.0.0.1&quot;)}, [128-&gt;16]) = 0
getsockname(8, {sa_family=AF_INET, sin_port=htons(53752), sin_addr=inet_addr(&quot;127.0.0.1&quot;)}, [128-&gt;16]) = 0

</code></pre>

<p>正常的情况下是0</p>

<pre><code class="language-text">connect(9, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr(&quot;192.168.65.1&quot;)}, 16) = 0

</code></pre>

<p>这时候思考到workerman的count参数只开启了一个阻塞进程，故而改成3个进程即可。</p>

<p>通过这个事故，下定决定分析workerman源码</p>

<h2 id="toc_0">分析</h2>

<p>我们先查看一下workerman的目录</p>

<pre><code class="language-text">.
├── _webwww_y_cronshow_job.php.pid
├── workerman
│   ├── Autoloader.php
│   ├── Connection
│   │   ├── AsyncTcpConnection.php
│   │   ├── AsyncUdpConnection.php
│   │   ├── ConnectionInterface.php
│   │   ├── TcpConnection.php
│   │   └── UdpConnection.php
│   ├── Events
│   │   ├── Ev.php
│   │   ├── Event.php
│   │   ├── EventInterface.php
│   │   ├── Libevent.php
│   │   ├── React
│   │   │   ├── Base.php
│   │   │   ├── ExtEventLoop.php
│   │   │   ├── ExtLibEventLoop.php
│   │   │   └── StreamSelectLoop.php
│   │   ├── Select.php
│   │   └── Swoole.php
│   ├── Lib
│   │   ├── Constants.php
│   │   └── Timer.php
│   ├── MIT-LICENSE.txt
│   ├── Protocols
│   │   ├── Frame.php
│   │   ├── Http
│   │   │   ├── Chunk.php
│   │   │   ├── Request.php
│   │   │   ├── Response.php
│   │   │   ├── ServerSentEvents.php
│   │   │   ├── Session
│   │   │   │   ├── FileSessionHandler.php
│   │   │   │   └── RedisSessionHandler.php
│   │   │   ├── Session.php
│   │   │   └── mime.types
│   │   ├── Http.php
│   │   ├── ProtocolInterface.php
│   │   ├── Text.php
│   │   ├── Websocket.php
│   │   └── Ws.php
│   ├── README.md
│   ├── Timer.php
│   ├── Worker.php
│   └── composer.json
└── workerman.log

</code></pre>

<p>上来来说<br/>
Connection 连接类<br/>
Events 网络事件类<br/>
Protocols 请求协议类<br/>
主要这三部分组成<br/>
核心文件主要是Worker.php，不得不佩服workerman作者，简单即精华。<br/>
所以我们从Worker.php文件开始说起</p>

<p><img src="media/16254492208395/16254501275579.jpg" alt=""/><br/>
其实我们所有服务都是从runAll开始<br/>
<img src="media/16254492208395/16254501531720.jpg" alt=""/></p>

<p>一层层来分析一下runAll所做的事</p>

<h2 id="toc_1">checkSapiEnv</h2>

<p>主要是检查环境符不符合要求</p>

<h2 id="toc_2">init</h2>

<p>初始化主要生成master的pid文件和对应的log文件<br/>
Timer::init(); 初始化事件<br/>
<img src="media/16254492208395/16254503025591.jpg" alt=""/><br/>
注册并signal事件<br/>
这里要了解一下php的pcntl库</p>

<h2 id="toc_3">parseCommand</h2>

<p>主要解析命令行的输入<br/>
登录php job.php start <br/>
即对&#39;start&#39;,&#39;stop&#39;,&#39;restart&#39;,&#39;reload&#39;,&#39;status&#39;,&#39;connections&#39;等做接收处理 </p>

<h2 id="toc_4">daemonize</h2>

<p>判断是否需要守护进程，如约需要pcntl_fork当前一下</p>

<h2 id="toc_5">initWorkers</h2>

<p>初始化worker<br/>
如果你定义了三组worker就三次<br/>
例如</p>

<pre><code class="language-text">$jobworker = new JobWorker();
$cronworker = new CronWorker();
$webworker = new WebWorker();
</code></pre>

<p><img src="media/16254492208395/16254505578046.jpg" alt=""/></p>

<p>最近监听各worker的端口</p>

<p><img src="media/16254492208395/16254505897418.jpg" alt=""/></p>

<p>设置监听端口并设置接收监听resumeAccept</p>

<p>这里主要使用到了php的stream和socket库</p>

<p><img src="media/16254492208395/16254506654332.jpg" alt=""/><br/>
resumeAccept会增加Event事件，监听每个用户请求的connection</p>

<h2 id="toc_6">installSignal</h2>

<p>注册Signal库，即触发各种网络进程事件<br/>
例如ctrl-c关闭进程，kill -9关闭进程等<br/>
这里要了解每个sig的含义<br/>
SIGINT,SIGTERM,SIGHUP,SIGUSR1,SIGQUIT,SIGUSR2,SIGIO,SIGPIPE</p>

<h2 id="toc_7">saveMasterPid</h2>

<p>保存Master进程的pid</p>

<h2 id="toc_8">displayUI</h2>

<p>设置要显示的UI<br/>
运行成功之后会有个界面<br/>
<img src="media/16254492208395/16254508530042.jpg" alt=""/></p>

<h2 id="toc_9">forkWorkers</h2>

<p>这里主要是查看定义worker里的个数，例如我们的webworker定义count=3，会生成webworker相关的3个子进程。</p>

<h2 id="toc_10">resetStd</h2>

<p>设置定义的输入输出</p>

<h2 id="toc_11">monitorWorkers</h2>

<p>使用pcntl_signal_dispatch事件，监控每个子进程的情况。<br/>
Worker类就大概这样的情况了</p>

<h1 id="toc_12">网络相关</h1>

<p>因为在stream和socket里，并没有协议做解释，所以协议的处理我们会在Protocols文件夹里做相对就的处理<br/>
<img src="media/16254492208395/16254511266103.jpg" alt=""/></p>

<p>例如我们定义的是text://127.0.0.1:123, 就会找到Text做弄对应的处理<br/>
<a href="http://0.0.0.0:456,%E5%8D%B3%E6%8C%87%E5%AE%9AHttp.php%E5%81%9A%E7%9B%B8%E5%85%B3%E5%A4%84%E7%90%86">http://0.0.0.0:456,即指定Http.php做相关处理</a><br/>
协议的定义在workerman手册能找到</p>

<h1 id="toc_13">结尾</h1>

<p>workerman为我们带来了异步，提升并发，这是比php-fpm带来更多的好处。但我们也要了解一下进程内是怎么一回事，因为mysql长链接断开等问题。<br/>
workerman主要的难度是多进程和网络事件，这部分需要我们学习select、poll、epoll等。了解网络解释的一些过程，此篇比较精简说明一下workerman，后续更新每个模块的详解。</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[nginx前后端分离的一些配置]]></title>
    <link href="http://www.shengsheng.net/16240150077955.html"/>
    <updated>2021-06-18T19:16:47+08:00</updated>
    <id>http://www.shengsheng.net/16240150077955.html</id>
    <content type="html"><![CDATA[
<p>之前的都是多个域名，现在打算采用分离的形式</p>

<h2 id="toc_0">fpm模式</h2>

<pre><code class="language-text">server {
        index index.html index.htm index.php;
        root /webwww/www/landpage/Public/dist;
        server_name landpage.xx.com;
  listen 443;
  listen 80;
  ssl on;
  ssl_certificate /webwww/ssl/1_.cn_bundle.crt;
  ssl_certificate_key /webwww/ssl/2_.cn.key;
  ssl_session_timeout 5m;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #按照这个协议配置
  ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;#按照这个套件配置
  ssl_prefer_server_ciphers on;
        location  / {
                #alias /webwww/www/landpage/Public/dist;             #项目路径
                index index.html;
                try_files $uri $uri/ /index.html;
        }
        location /api {
          root /webwww/www/landpage/Public;
          if (!-e $request_filename){
             rewrite ^/(.*)$ /index.php?s=$1 last;
             break;
          }
          try_files $uri $uri/ /index.php;
        }
        location ~ [^/]\.php(/|$) {
                root  /webwww/www/landpage/Public;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass 172.17.0.7:9000;
                fastcgi_index index.php;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                #fastcgi_param   SCRIPT_FILENAME         $request_filename;
                fastcgi_param PATH_INFO       $fastcgi_path_info;
        }
}
</code></pre>

<h1 id="toc_1">nginx查看php-fpm状态</h1>

<p>php-fpm需要配置一下</p>

<pre><code class="language-text">[www]
user = www-data
group = www-data
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
;pm.process_idle_timeout = 300
pm.max_requests = 10000
pm.status_path = /status_show
slowlog = /webwww/log/php-fpm-slow.log
</code></pre>

<p>nginx增加location</p>

<pre><code class="language-text">location ~^/status_show$ {
        fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
        include fastcgi_params;
        fastcgi_pass 172.17.0.2:9000;
}
</code></pre>

<p>查看本域名/status_show?full即所有连接<br/>
/status_show还可以指定json、html和xml的方式显示</p>

<p><img src="media/16240150077955/16240153140050.jpg" alt=""/></p>

<h1 id="toc_2">proxy模式</h1>

<pre><code class="language-text">server {
        listen 443;
        listen 80;
        ssl on;
        ssl_certificate /webwww/ssl/1_.cn_bundle.crt;
        ssl_certificate_key /webwww/ssl/2_.cn.key;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #按照这个协议配置
        server_name jumpuser.xxx.cn;
        root /webwww/www/jumpuser/Public/dist;
        location  / {
                index index.html;
                try_files $uri $uri/ /index.html;
        }
       location /api {
                proxy_pass http://172.19.0.2:8080;
                proxy_http_version 1.1;
                proxy_set_header X-Real-IP $remote_addr;
        }
        index index.html index;
}
</code></pre>

<p>以上配置都是访问/api开头走后台，默认其它路径走前端， 后端项目必须使用强路由的形式。</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[websocket踩坑记]]></title>
    <link href="http://www.shengsheng.net/16238418040587.html"/>
    <updated>2021-06-16T19:10:04+08:00</updated>
    <id>http://www.shengsheng.net/16238418040587.html</id>
    <content type="html"><![CDATA[
<h2 id="toc_0">基本配置</h2>

<p>为了解决知道用户时长，我们采用了websocket作为通讯,php采用了swoole的websocket功能。<br/>
<img src="media/16238418040587/16238419003179.jpg" alt=""/></p>

<p>主要有三个事件<br/>
Open 建立链接的处理<br/>
Message 收到客户端的处理<br/>
Close 关闭与客户端的处理</p>

<p>前端配置基本和后端一样, http页面能访问ws和wss,但https只能访问wss。</p>

<pre><code class="language-text">var ws = new WebSocket(&quot;wss://xxx/websocket&quot;)
ws.onopen = function () { 
    ws.send(&quot;hello&quot;); 
}; 
ws.onmessage = function (resp) { 
    document.getElementById(&#39;total&#39;).innerHTML = resp.data; 
};
ws.onclose = function () {}; 
</code></pre>

<p>nginx配置：</p>

<pre><code class="language-text">upstream websocket {
    server 172.17.0.2:9507; # 如果只有一条可以直接放在proxy_pass里
}
location /websocket {
          proxy_pass http://websocket;
          proxy_read_timeout 60s;
          proxy_send_timeout 60s;
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection &quot;upgrade&quot;;
}
</code></pre>

<p>链接的时候可以可以参数放在后面，服务器能从get里获取token参数进行鉴权<br/>
wss://test.com/websocket?token=abcdefg</p>

<p><img src="media/16238418040587/16238421223263.jpg" alt=""/></p>

<p>需要注意，如果是启用swoole里面的ssl功能，需将proxy_pass的 http://websocket 改成https://websocket</p>

<p>因为我们默认wss走443端口，而我们443端口已经有配置证书，故直接走http://websocket即可。</p>

<h2 id="toc_1">心跳</h2>

<p>因为我们默认服务器配置是60秒时候，所以前端或后端定时发送数据来维护链接即可。</p>

<pre><code class="language-text"> proxy_read_timeout 60s;
 proxy_send_timeout 60s;
</code></pre>

<p>前端</p>

<pre><code class="language-text">setInterval(wx.send(&#39;1&#39;),100000); 
</code></pre>

<h2 id="toc_2">腾讯云的坑</h2>

<p>如果是从CDN走websocket,默认也是支持的，默认根据源站的配置，走80端口。<br/>
链接是能链接，但是wss的时候会出现ws.send不了数据。 如果要使用websocket建议使用ECDN(全站加速）</p>

<h3 id="toc_3">官方说明</h3>

<p>cdn是提供网站静态内容的加速分发处理能力，ECDN 为纯动态网站与动静态混合型网站提供高可靠、低延时一站式加速服务。具体您可以了解下对应产品的功能介绍<br/>
CDN的应用场景：<a href="https://cloud.tencent.com/document/product/228/37846">https://cloud.tencent.com/document/product/228/37846</a><br/>
ECDN的功能介绍：<a href="https://cloud.tencent.com/document/product/570/10359">https://cloud.tencent.com/document/product/570/10359</a></p>

<h3 id="toc_4">其它</h3>

<p>附：<br/>
cdn的协议跟随引发的问题<br/>
WARNING ssl_accept: bad SSL client[127.0.0.1:35418], reason=156, error_string=http request #4068<br/>
这个错误说明有明文 HTTP 请求发送到了 SSL 的 HTTPS 端口。<br/>
<a href="https://github.com/swoole/swoole-src/issues/4068#event-4585154187">https://github.com/swoole/swoole-src/issues/4068#event-4585154187</a></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[laravel database源码分析]]></title>
    <link href="http://www.shengsheng.net/16227343785198.html"/>
    <updated>2021-06-03T23:32:58+08:00</updated>
    <id>http://www.shengsheng.net/16227343785198.html</id>
    <content type="html"><![CDATA[
<p>公司框架采用了laravel的Eloquent用为数据库的orm</p>

<pre><code class="language-text">// 使用Manager管理数据库
use Illuminate\Database\Capsule\Manager as Db;
self::$db = new Db;
            foreach($dbconfig as $connection_name =&gt; $config)
            {
                $db_arr = [
                    &#39;driver&#39;    =&gt; $config[&#39;dbtype&#39;] ?? &#39;mysql&#39;, 
                    &#39;host&#39;      =&gt; $config[&#39;host&#39;],
                    &#39;port&#39;      =&gt; $config[&#39;port&#39;],
                    &#39;database&#39;  =&gt; $config[&#39;dbname&#39;],
                    &#39;username&#39;  =&gt; $config[&#39;username&#39;],
                    &#39;password&#39;  =&gt; $config[&#39;password&#39;],
                    &#39;charset&#39;   =&gt; $config[&#39;charset&#39;] ?? &#39;utf8&#39;,
                    &#39;collation&#39; =&gt; $config[&#39;collation&#39;] ?? &#39;utf8_general_ci&#39;,
                    &#39;prefix&#39;    =&gt; &#39;&#39;,
                ];
                self::$db-&gt;addConnection($db_arr,$connection_name);
            }
            // self::$db-&gt;setEventDispatcher(new Dispatcher(new Container));
            // 设置fetch的方式
            self::$db-&gt;setFetchMode(\PDO::FETCH_ASSOC);
            self::$db-&gt;setAsGlobal();
            self::$db-&gt;bootEloquent();
</code></pre>

<p><img src="media/16227343785198/D74AAFF0-9A24-4363-A137-1DE400BDAAC2.png" alt="D74AAFF0-9A24-4363-A137-1DE400BDAA"/></p>

<p>setAsGlobal更新instance对象<br/>
bootEloquent开始启用。</p>

<p>常用原生用法，使用connection类<br/>
query()<br/>
select()<br/>
cursor()<br/>
prepared()<br/>
getPdoForSelect()<br/>
unprepared()<br/>
run()<br/>
runQueryCallback()<br/>
logQuery()<br/>
getQueryLog()</p>

<p><img src="media/16227343785198/16227346790325.jpg" alt=""/></p>

<p>Manger作为facade Db的使用<br/>
调用conneciton()</p>

<p>Manager::connection(&#39;default&#39;)-&gt;select(&quot;select * from test&quot;);<br/>
<img src="media/16227343785198/9EA336CE-166B-46F9-94A9-BFC18FB5CB04.png" alt="9EA336CE-166B-46F9-94A9-BFC18FB5CB04"/><br/>
查看databae\PDO\Connection.php和databse\Conneciton查看实现的方法。</p>

<p><img src="media/16227343785198/6B3646A2-BEA9-4479-888A-BEC4976E11CB.png" alt="6B3646A2-BEA9-4479-888A-BEC4976E11CB"/></p>

<p><img src="media/16227343785198/62B486D9-97E8-4605-A65E-71FB73D4AC4A.png" alt="62B486D9-97E8-4605-A65E-71FB73D4AC4A"/></p>

<p>其实比较好用的游标，使用原生协程来跟进多数据fetch</p>

<p>另一个值得学习的databse\Eloquent\Model类，orm神器<br/>
<img src="media/16227343785198/9164A66F-0560-4CC0-AA23-BB36CF094644.png" alt="9164A66F-0560-4CC0-AA23-BB36CF094644"/></p>

<p>可以查看出优美地调用__call和__callStatic</p>

<p>在databse\Eloquent\Builder可以发现更多orm的使用方法<br/>
例如<br/>
updateOrCreate<br/>
firstOrFail<br/>
<img src="media/16227343785198/90CAC82C-CC07-4E6D-82FE-24B30AEB6F51.png" alt="90CAC82C-CC07-4E6D-82FE-24B30AEB6F51"/></p>

<p>查看源码，值得学习大神处理的逻辑。<br/>
基本上都是静态的方式调用</p>

<p><img src="media/16227343785198/68ED5D5A-2B51-4A67-99DC-5B806E956D1A.png" alt="68ED5D5A-2B51-4A67-99DC-5B806E956D1A"/></p>

<p>相对应有些失败之后还有重连的机制。</p>

<p>大概源码查看到此，基本上学习此源码，orm足够使用的了。</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[git管理迁移]]></title>
    <link href="http://www.shengsheng.net/16219577161349.html"/>
    <updated>2021-05-25T23:48:36+08:00</updated>
    <id>http://www.shengsheng.net/16219577161349.html</id>
    <content type="html"><![CDATA[
<p>由于之前gitlab管理是使用k8s来弄，但没把数据挂载在本地硬盘，所以可能会有故障，因此为了减了运维压力。<br/>
jenkins采用腾讯去的cloudbase。<br/>
git管理由私有gitlab改到gitee上。</p>

<p>cloudbase jenkins应用:<br/>
<img src="media/16219577161349/0D79715F-C247-43F0-AD38-80FF7B0E94A8.png" alt="0D79715F-C247-43F0-AD38-80FF7B0E94A8"/></p>

<p>gitee:<br/>
<img src="media/16219577161349/80F68D19-D3F6-4A5A-874E-9AF1329FEC79.png" alt="80F68D19-D3F6-4A5A-874E-9AF1329FE"/></p>

<p><img src="media/16219577161349/16219577181650.jpg" alt=""/></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[现有安卓手机]]></title>
    <link href="http://www.shengsheng.net/16217270801027.html"/>
    <updated>2021-05-23T07:44:40+08:00</updated>
    <id>http://www.shengsheng.net/16217270801027.html</id>
    <content type="html"><![CDATA[
<p>现在的安卓手机其实相比之前，cpu和内存增大就肯定不用说了，主要是快速，从0到充满也就用了15分钟。和苹果相等的价钱，现有的安卓手机并不差。<br/>
ad一下，iqoo7。</p>

<p><img src="media/16217270801027/16217272147047.jpg" alt=""/></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[elasticsearch做服务端监控]]></title>
    <link href="http://www.shengsheng.net/16217265079986.html"/>
    <updated>2021-05-23T07:35:07+08:00</updated>
    <id>http://www.shengsheng.net/16217265079986.html</id>
    <content type="html"><![CDATA[
<p>在kibana里其实很容易查看到具体的使用情况<br/>
<img src="media/16217265079986/9490AE60-49A0-4DBE-98A1-ADBA30DC3944.png" alt="9490AE60-49A0-4DBE-98A1-ADBA30D"/></p>

<p>但发觉没报警机制，例如发送短信和钉钉报警(钉钉主要拉机器人，这里不做详细介绍)。</p>

<p><code><br/>
&quot;elasticsearch/elasticsearch&quot;: &quot;^7.0&quot;<br/>
</code><br/>
首先在composer里加载elasticsearch的开源组件</p>

<p>核心代码如下：</p>

<pre><code class="language-text"> public static function getSearch($type = &#39;&#39;, $hostname = &#39;k8s-slave02&#39;)
    {
        $searchtype = &#39;&#39;;
        switch($type)
        {
            case &#39;diskio&#39;:
                $searchtype = &#39;system.diskio&#39;;
                break;
            case &#39;network&#39;:
                $searchtype = &#39;system.network&#39;;
                break;
            case &#39;cpu&#39;:
                $searchtype = &#39;system.cpu&#39;;
                break;
            case &#39;memory&#39;:
                $searchtype = &#39;system.memory&#39;;
                break;
            case &#39;filesystem&#39;:
                $searchtype = &#39;system.filesystem&#39;;
                break;
            default:
                $searchtype = &#39;&#39;;
                break;
        }
        if(empty($client))
        {
            $hosts = \SilangPHP\Config::get(&quot;Db.elasticsearch&quot;);
            self::$client = ClientBuilder::create()-&gt;setHosts($hosts)-&gt;build();
        }
        $params = [
            &#39;index&#39; =&gt; &#39;metricbeat-7.5.1&#39;,
            &#39;type&#39; =&gt; &#39;_doc&#39;,
            &#39;sort&#39; =&gt; &#39;@timestamp:desc&#39;,
            &#39;size&#39; =&gt; 1,
            &#39;body&#39; =&gt; [
                &#39;query&#39; =&gt; [
                    &#39;bool&#39; =&gt; [
                        &#39;must&#39; =&gt; [
                            [ &#39;match&#39; =&gt; [ &#39;host.hostname&#39; =&gt; $hostname ] ],
                            // system.diskio | docker.cpu system.network load  system.cpu system.process.summary system.process.summary
                            [ &#39;match&#39; =&gt; [ &#39;event.dataset&#39; =&gt; $searchtype ] ],
                        ]
                    ]
                ]
            ]
        ];
        $results = self::$client-&gt;search($params);
        $milliseconds = $results[&#39;took&#39;];
        $maxScore     = $results[&#39;hits&#39;][&#39;max_score&#39;];
        $score = $results[&#39;hits&#39;][&#39;hits&#39;][0][&#39;_score&#39;];
        $doc   = $results[&#39;hits&#39;][&#39;hits&#39;][0][&#39;_source&#39;];
        return $doc;
    }
</code></pre>

<p>主要通过匹配搜索host.hostname主机名称和event.dataset事件来获取相关信息，为了获取最新的来报警，排序使用@timestamp:desc时间倒序来获取。</p>

<p>汇总发送效果<br/>
<img src="media/16217265079986/16217269052143.jpg" alt=""/></p>

<p>规则报警得大家自己去定义，例如内存高于80%的时候触发报警。<br/>
使用过某些开源的监控工具，其实也没精力去学习更多，也相对复杂，简单有简单的好处。<br/>
相比第一版报警系统，现在的其实可观很多<sup>_^</sup></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[关于doge]]></title>
    <link href="http://www.shengsheng.net/16207910317257.html"/>
    <updated>2021-05-12T11:43:51+08:00</updated>
    <id>http://www.shengsheng.net/16207910317257.html</id>
    <content type="html"><![CDATA[
<p>其实并没有很多人口中实现了财务自由，只是2017年的时候买了些狗币，忘记售出去了，刚好这段时间火，上去看了一下。倒是有台思域车。<br/>
<img src="media/16207910317257/4701620791137_.pic.jpg" alt="4701620791137_.pic"/><br/>
<img src="media/16207910317257/4711620791138_.pic.jpg" alt="4711620791138_.pic"/></p>

<p>马克斯的神助攻，动物币都有所上升。<sup>_^</sup></p>

]]></content>
  </entry>
  
</feed>
