HTTP, QUIC, TCP 在 5G 中的优化

概念及问题梳理

TCP

介绍

算法

问题

UDP

介绍

算法

问题

HTTP 2.0

介绍

  1. 二进制传输
  2. 头部压缩
  3. 多路复用
  4. 服务器推送(server push)
    • 服务器推送(server push)是 HTTP/2 协议里面,唯一一个需要开发者自己配置的功能.
      “`json
      server {
      listen 443 ssl http2;
      server_name localhost;

      ssl on;
      ssl_certificate /etc/nginx/certs/example.crt;
      ssl_certificate_key /etc/nginx/certs/example.key;

      ssl_session_timeout 5m;

      ssl_ciphers HIGH:!aNULL:!MD5;
      ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
      ssl_prefer_server_ciphers on;

      <p>location / {
      root /usr/share/nginx/html;
      index index.html index.htm;
      http2_push /style.css;
      http2_push /example.png;
      }
      }</p></li>
      </ul></li>
      </ol>

      <pre><code class=""> * 服务端接收到客户端主请求,能够预测主请求,在响应主请求的同事,主动并发推动依赖资源到客户端。客户端解析主请求响应后,可以无延时从本地缓存获取依赖资源,减少访问演示,提高访问体验,也加大了链路并发能力。
      * 服务器端配置http2_push,可以实现。但导致应用和配置混在一起,每次配置需要重新启动。可采用**后端应用产生 HTTP 回应的头信息Link命令**服务器发现有这个头信息,就会进行服务器推送。
      “`html
      Link: </styles.css>; rel=preload; as=style, </example.png>; rel=preload; as=image

      * 此时nginx需要配置
      ```json
          server {
              listen 443 ssl http2;
              # ...
              root /var/www/html;
      
              location = / {
                  proxy_pass http://upstream;
                  http2_push_preload on;
              }
          }
      </code></pre>
      
      <pre><code class="">    如果服务器或者浏览器不支持 HTTP/2,那么浏览器就会按照 preload 来处理这个头信息,预加载指定的资源文件。事实上,这个头信息就是 preload 标准提出的,它的语法和as属性的值都写在了标准里面。
          * 如果本地缓存已经有,服务器还要推送,则会浪费带宽。 一种解决方法是通过判断是否是第一次访问。
          ```json
              server {
                  listen 443 ssl http2 default_server;
      
                  ssl_certificate ssl/certificate.pem;
                  ssl_certificate_key ssl/key.pem;
      
                  root /var/www/html;
                  http2_push_preload on;
      
                  location = /demo.html {
                      add_header Set-Cookie "session=1";
                      add_header Link $resources;
                  }
              }
              map $http_cookie $resources {
                  "~*session=1" "";
                  default "</style.css>; as=style; rel=preload";
              }
      

      算法

      问题

      QUIC

      介绍

      算法

      问题

      方案梳理

      结论

5G名词解释

5G相关名词解释

  1. PSA (PDU Session Anchor PDU):会话锚点
  2. ADC (Application Data Center):业务检测
  3. TAI (Tracking Area Identity):有PLMN和TAC组成。TAI=PLMN+TAC(Tracking Area Code)
  4. GBR(Guaranteed Bit Rate):保证比特速率 5Mbps
  5. MBR(Max Bit Rate):最大比特速率
  6. SPN(spn slicing packet network):切片
  7. 游戏3A (相对概念):高质量、高投入、高成本。

Go 语言集训(六):安全编程

数据加密

  • 单密钥加密算法,对称加密。需要加密的明文、加密算法和密钥。 DES,AES,RC4等
  • 双密钥加密算法,非对称加密。需要加密的明文、加密算法、私钥和公钥。该系统中,私钥和公钥可以用来加密或者解密,但是私钥加密的明文,必须要用对应的公钥解密。用公钥加密的明文,必须用对应的私钥解密。

数字签名

数字签名,是指用于标记数字文件拥有者、创造者、分发者身份的字符串。数字前民更拥有标记文件身份、分发的不可抵赖性等作用。
如:A公司发布了一个可执行文件,成为AProduct.exe, A在Aproduct.exe中加入A公司的数字签名。A公司的数字签名是A公司的私钥加密了AProduct.exe文件的哈希值,我们得到打过数字签名的AProduct.exe后,可以查看数字签名。这个过程就是A公司的公钥解密了文件哈希值。从而验证两个问题:Aproduct.exe是否A公司发布,AProduct.exe是否被篡改。

数字证书

数字证书中包含了银行的公钥,有了公钥后,网银就可以用公钥加密我们提供给银行的信息,这样银行才能用对应的私钥得到我们信息,确保安全。

PKI体系

PKI全称公钥基础设施,使用非对称加密理论,提供数字签名、加密、数字证书等服务体系。一般包括权威认证机构CA,数字证书库,密钥备份及恢复系统、证书作废系统、应用接口API等。

Go语言集训(五):网络编程

网络编程

在Go语言中编写网络程序时,我们看不到传统的那种编码形式。原来使用socket编程是,会按照以下步骤:
1. 建立socket : socket()
2. 绑定socket: bind()
3. 监听: listen() 或者链接:connect()
4. 接受连接: accept()
5. 接收: receive() 或者发送:send
在GO语言中,只需要调动net.Dial()即可。

Dial 函数

func Dial(net, addr string) (Conn, error)
* net 是网络协议的名字
* addr 是IP地址或者域名,端口号以“:”的形式跟随在抵制或者域名的后面,端口号可选。
* 目前支持tcp,tcp4,tcp6,udp,udp4,udp6,ip,ip4,ip6
* 如果ICMP可以使用:ip4:ICMP 或者 ip4:1
成功建立连接后,我们可以进行数据的发送和接收。发送数据时,使用conn的write(),接收是采用Read()

TCP 实例

package main

import (
    "bytes"
    "fmt"
    "io"
    "net"
    "os"
)

func main() {

    if len(os.Args) != 2 {
        fmt.Fprintf(os.Stderr, "usage:%s host:port", os.Args[0])
        os.Exit(1)
    }

    service := os.Args[1]

    conn, err := net.Dial("tcp", service)
    checkError(err)

    _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
    checkError(err)

    result, err := readFully(conn)
    checkError(err)

    fmt.Println(string(result))

    os.Exit(0)

}

func checkError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error :%s", err.Error())
        os.Exit(1)
    }
}

func readFully(conn net.Conn) ([]byte, error) {
    defer conn.Close()

    result := bytes.NewBuffer(nil)
    var buf [512]byte
    for {
        n, err := conn.Read(buf[0:])
        result.Write(buf[0:n])
        if err != nil {
            if err == io.EOF {
                break
            }
            return nil, err
        }
    }
    return result.Bytes(), nil
}

HTTP客户端

Go内置的net/http提供了最简单的http客户端实现,我们不需要借助第三方网络通信库如libcurl就可以直接使用http中用的最多的GET和POST方式请求数据
1. 基本方法
net /http包的Client类型提供了如下几个方法:

* func(c *Client) Get (url string) (r *Response, err error)
* func(c *Client) Post (url string, bodyType string, body io.Reader) (r *Response, err error)
* func(c *Client) PostForm(url string, data url.Values) (r *Response, err error)
* func(c *Client) Head(url string) (r *Response , err error)
* func(c *Client) Do(req *Request) (resp *Response, err error)
  1. 概要介绍

* http.Get()

resp, err := http.Get("http://www.baidu.com")
if err != nil {
    // handle error
    return
}
defer resp.Body.close()
io.Copy(os.Stdout, resp.Body)
// 上面这段代码请求一个网页首页,并将网页内容打印到标准输出上。
  • http.Post()
resp, err := http.Post("http://www.baidu.com/upload","image/jpeg",&imageDataBuf)
if err != nil {
   //handle error
   return
}
if resp.StatusCode != http.StatusOK {
  //handle error
  return
}
  • http.PostForm()
// 实现了标准编码格式为application/x-www-form-urlencoded的表单提交
resp, err := http.PostForm("http://www.baidu.com/posts", url.Values("title":{"article title"},"content":{"article body"}))
if err != nil {
   //handle error
   return
}
  • http.Head()
//请求目标URL的头部信息
resp, err := http.Head("www.baidu.com")
  • (*http.Client).Do()
//在多数情况下http.Get()和http.PostForm()能够满足需求,但是需要我们发起的HTTP请求需要更多的定制信息。比如传递cookie,设置自定义的“User-Agent”
req, err := http.NewRequest("GET","http://www.baidu.com",nil)
req.Header.Add("User-Agent", "Gobook Custom User-Agent")
client := & http.Client{}
resp, err := client.Do(req)

HTTP服务端

  1. 处理HTTP请求
    func ListenAndServe(add string, handler Handler) error

* addr即坚挺地址
* handle为服务端处理程序。通常为空,意味着调用http.DefaultServeMux处理。服务端编写业务逻辑http.Handle() 或者http.HandleFunc()默认是诸如到http.DefaultServeMux中。

http.Handle("/foo",fooHandler)
http.HandleFunc("/bar",func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServer(":8080",nil))
//如果更多控制服务端的行为,可以自动以http.Server

s:= & http.Server {
    Addr: ":8080",
    Handler: myHandler,
    ReadTimeout: 10*time.Second,
    WriteTimeout: 10*time.Second,
    MaxHeaderBytes: 1<<20,
}
log.Fatal(s.ListenAndServer())
  1. 处理HTTPS请求
    func ListenAndServeTLS(add string,certFile string, keyFile string, handler Handler) error

* addr即坚挺地址
* certFile对应SSL证书存放的路径
* keyFile对应证书私钥文件路径
* handle为服务端处理程序。通常为空,意味着调用http.DefaultServeMux处理。服务端编写业务逻辑http.Handle() 或者http.HandleFunc()默认是诸如到http.DefaultServeMux中。

http.Handle("/foo",fooHandler)
http.HandleFunc("/bar",func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServerTLS(":10443","cert.pem", "key.pem",nil))

//如果更多控制服务端的行为,可以自动以http.Server

s:= & http.Server {
    Addr: ":10443",
    Handler: myHandler,
    ReadTimeout: 10*time.Second,
    WriteTimeout: 10*time.Second,
    MaxHeaderBytes: 1<<20,
}
log.Fatal(s.ListenAndServerTLS("cert.pem","key.pem"))

RPC编程

RPC协议构架于TCP或者UDP,或者HTTP上,允许开发者直接调用另一台计算机上的程序,而无需额外调用编写网络通信相关代码。使得开发包括网络分布式程序在内的应用程序更加容易
net/rpc允许RPC客户端通过网络或者IO连接调用一个远端对象的公开方法(必须是大写字母开头、可外部调用的)。在RPC服务端,可将一个对象注册为可访问的服务,之后该对象的公开方法就能能够以远程的方式提供访问。一个RPC服务端可以注册多个不同类型的对象,但不允许注册同一类型的多个对象。
一个对象需要满足以下条件,才能够提供远程访问:
1. 必须是在对象外部可公开调用的方法
2. 必须有两个参数,且参数的类型都必须是包外部可以访问的类型,或者Go内建支持的类型
3. 第二个参数必须是一个指针
4. 方法必须返回一个error类型的值
func (t *T) MethodName(argType T1, replyType *T2) error

在RPC客户端,GO的net/rpc包提供便利的rpc.Dial()和rpc.DialHTTP()方法来于指定RPC服务端建立连接。建立后,go的net/rpc包允许我们使用同步或者异步的方式接收rpc服务端的结果。调用rpc客户端的call方法进行同步处理,这时候客户端程序按顺序执行,只有接收完RPC服务端的处理结果之后,才可能继续执行后面的程序。当调用RPC客户端的Go方法,进行异步处理,RPC客户端无需等待服务端的结果即可执行后面的程序,而当接收到RPC服务端的处理结果时,再对其进行相应的处理。无论是调用RPC客户端的Call还是Go,都必须制定要调用的服务及其方法名称,以及一个客户端传入参数的引用,还有一个用于接收处理结果参数的指针。
如果没有明确制定RPC传输过程中采用何种编码,默认将使用encoding/gob包进行数据传输。

package server

import (
    "log"
    "net"
    "net/http"
    "net/rpc"
)

type Args struct {
    A, B int
}

type Quotient struct {
    Quo, Rem int
}

type Arith int

func (t *Arith) Multiply(args *Args, reply *int) error {
    *reply = args.A * args.B
    return nil
}

func (t *Arith) Divide(args *Args, reply *Quotient) error {
    reply.Quo = args.A / args.B
    reply.Rem = args.A % args.B
    return nil
}

// 服务端侦听
func main() {
    arith := new(Arith)
    rpc.Register(arith)
    rpc.HandleHTTP()

    l, e := net.Listen("tcp", ":1234")
    if e != nil {
        log.Fatal("Listen error: ", e)
    }
    go http.Serve(l, nil)
}

//客户端连接调用

func client() {
    client, err := rpc.DialHTTP("tcp", serverAddress+":1234")

    args := &server.Args{7, 8}
    var reply int
    err = client.Call("Arith.Multiply", args, &reply)

    quotient := new(Quotient)
    divCall := client.Go("Arith.Divid", args, "ient, nil)
    replyCall := <-divCall.Done
}

json编解码

空接口是通用类型。如果要解码一段位置结构的JSON,只需要将这段json数据解码输出到一个空接口接口。
* JSON中布尔值会专为Go中bool
* 数值–float64
* 字符串–string
* json数组会转换为[]interface{}
* json对象会转换为map[string]interface{}
* null — nil



func json() { b := []byte(`{ "Title": "Go语言编程", "Authors": ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan", "XuDaoli"], "Publisher": "ituring.com.cn", "IsPublished": true, "Price": 9.99, "Sales": 1000000 }`) var r interface{} err := json.Unmarshal(b, &r) // //map[string]interface{}{ // "Title": "Go语言编程", // "Authors": ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan", // "XuDaoli"], // "Publisher": "ituring.com.cn", // "IsPublished": true, // "Price": 9.99, // "Sales": 1000000 //} }

简单web界面

目录结构:
uploads:上传文件目的地
views: html文件 包含两个list.html 和 upload.html
photoweb.go

//photoweb.go
package main

import (
    "fmt"
    "html/template"
    "io"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "path"
    "runtime/debug"
    "strings"
)

const (
    ListDir      = 0x0001
    UPLOAD_DIR   = "./uploads"
    TEMPLATE_DIR = "./views"
)

var templates = make(map[string]*template.Template)

func init() {
    fileInfoArr, err := ioutil.ReadDir(TEMPLATE_DIR)
    check(err)
    var templateName, templatePath string
    for _, fileInfo := range fileInfoArr {
        templateName = fileInfo.Name()
        if ext := path.Ext(templateName); ext != ".html" {
            continue
        }
        templatePath = TEMPLATE_DIR + "/" + templateName
        log.Println("Loading template:", templatePath)
        t := template.Must(template.ParseFiles(templatePath))
        if strings.Contains(templateName, "upload") {
            fmt.Println("upload....")
            templates["upload"] = t
        }
        if strings.Contains(templateName, "list") {
            fmt.Println("list....")
            templates["list"] = t
        }
    }
}
func check(err error) {
    if err != nil {
        panic(err)
    }
}
func renderHtml(w http.ResponseWriter, tmpl string, locals map[string]interface{}) {
    err := templates[tmpl].Execute(w, locals)
    check(err)
}
func isExists(path string) bool {
    _, err := os.Stat(path)
    if err == nil {
        return true
    }
    return os.IsExist(err)
}
func uploadHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
        renderHtml(w, "upload", nil)
    }
    if r.Method == "POST" {
        f, h, err := r.FormFile("image")
        check(err)
        filename := h.Filename
        defer f.Close()
        imagePath := UPLOAD_DIR + "/" + filename
        t, err := os.OpenFile(imagePath, os.O_WRONLY|os.O_CREATE, 0666)
        check(err)
        defer t.Close()
        _, err1 := io.Copy(t, f)
        check(err1)
        http.Redirect(w, r, "/view?id="+filename, http.StatusFound)
    }
}
func viewHandler(w http.ResponseWriter, r *http.Request) {
    imageId := r.FormValue("id")
    imagePath := UPLOAD_DIR + "/" + imageId
    if exists := isExists(imagePath); !exists {
        http.NotFound(w, r)
        return
    }
    w.Header().Set("Content-Type", "image")
    http.ServeFile(w, r, imagePath)
}
func listHandler(w http.ResponseWriter, r *http.Request) {
    fileInfoArr, err := ioutil.ReadDir("./uploads")
    check(err)
    locals := make(map[string]interface{})
    images := []string{}
    for _, fileInfo := range fileInfoArr {
        images = append(images, fileInfo.Name())
        fmt.Println(images)
    }
    locals["images"] = images
    renderHtml(w, "list", locals)
}
func safeHandler(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if e, ok := recover().(error); ok {
                http.Error(w, e.Error(), http.StatusInternalServerError)
                // 或者输出自定义的50x错误页面
                // w.WriteHeader(http.StatusInternalServerError)
                // renderHtml(w, "error", e)
                // logging
                log.Println("WARN: panic in %v. - %v", fn, e)
                log.Println(string(debug.Stack()))
            }
        }()
        fn(w, r)
    }
}
func staticDirHandler(mux *http.ServeMux, prefix string, staticDir string, flags int) {
    mux.HandleFunc(prefix, func(w http.ResponseWriter, r *http.Request) {
        file := staticDir + r.URL.Path[len(prefix)-1:]
        if (flags & ListDir) == 0 {
            if exists := isExists(file); !exists {
                http.NotFound(w, r)
                return
            }
        }
        http.ServeFile(w, r, file)
    })
}
func main() {
    mux := http.NewServeMux()
    staticDirHandler(mux, "/assets/", "./public", 0)
    mux.HandleFunc("/", safeHandler(listHandler))
    mux.HandleFunc("/view", safeHandler(viewHandler))
    mux.HandleFunc("/upload", safeHandler(uploadHandler))
    err := http.ListenAndServe(":8080", mux)
    if err != nil {
        log.Fatal("ListenAndServe: ", err.Error())
    }
}

list.html

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>List</title>
</head>
<body>
<ol>
{{range $.images}}
<li><a href="/view?id={{.|urlquery}}">{{.|html}}</a></li>
{{end}}
</ol>
</body>
</html>

upload.html

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Upload</title>
</head>
<body>
<form method="POST" action="/upload" enctype="multipart/form-data">
Choose an image to upload: <input name="image" type="file" />
<input type="submit" value="Upload" />
</form>
</body>
</html>

Go 语言集训(四):并发

并发编程一般采用共享内存系统和消息传递系统。Go语言采用消息传递系统。即“不要通过共享内存来通信,而应该通过通信来共享内存

Channel

Channel 是Go语言提供的goroutine间的通信方式。可以使用channel在两个或者多个goroutine之间传递消息。channel是进程内的通信方式,因此通过channel传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针。如果需要跨进程通信,我们建议用分布式系统的方法来解决,比如使用Socket或者HTTP等通信协议。

Channel是类型相关的。也就是说,一个channel智能传递一种类型的值,这个烈性需要在声明channel时制定。可以将其认为是一种类型安全的管道。

select

通过调用select()函数来监控一系列的文件句柄,一旦其中一个文件句柄发生了IO动作,该select()调用就会被返回。GO语言在语言级别支持select关键字,用于处理异步IO问题。
select与switch非常类似,由select开始一个新的选择快,每个选择条件由case语句来描述。与switch语句可以选择任何可使用相等比较的条件相比,select有比较多的限制,其中最大的一条限制是每个case语句里必须是一个IO操作。

select {
    case <- chan1:
    //如果chanl成功读到数据,则进行该case语句
    case chan2 <- 1:
    //如果成功向chan2写入数据,则进行case处理
    default:
    //如果上面都没有成功,则进入default处理。
}

缓冲机制

之前都是传递单个数据场景,如果持续传输大量数据的场景,就有些问题。需要给channel带上缓冲,从而达到消息队列的效果。
创建一个带缓冲的channel:
c := make(chan int, 1024)
调用make()时,缓冲区大小作为第二个参数传入即可。即使没有读取方,写入方也可以一直往channel里写入,在缓冲区被填写完之前,都不会被阻塞。

超时机制

如果发现channle已满,或者从channel读取数据时发现channel为空。如果不正确处理这些情况,很容易导致goroutine锁死。 Go语言没有提供直接的超时处理机制,但我们可以利用select机制。虽然select不是专为超时而设计的,却能很方便解决超时问题。因为select特点是只要其中一个case已经完成,程序会继续往下执行,而不会考虑其他case情况。

timeout := make(chan bool, 1)
go func() {
    time.Sleep(1e9)
    timeout <- true
}()
select {
    case <- ch:
    //从ch中读取到了数据
    case <- timeout:
    //一直没有从ch读取到数据,但从timeout读取到了数据
}

channel的传递

管道是一种非常广泛的一种设计模式,比如在处理数据时,我们采用管道设计,这样可以比较容易以插件的方式增加数据的处理流。

type PipeData struct {
    value int
    handler func(int) int
    next chan int
}

func handle(queue chan *PipeData) {
    for data := range queue {
        data.next <- data.handler(data.value)
    }
}

单向channel

单向channel 智能用于发送或者接受数据。channel本身必然是同时支持读写的,否则根本无法使用。加入一个channle真的只能读,那么只能为空,所以这是没有意义的。单向channle,其实只是对channel的一种使用限制。

我们在讲一个chanel变量传递到一个函数时,可以通过其指定为单向channel变量,从而限制该函数对此channel的操作。比如只能往这个channle写,或者读。

单向channel声明很简单。

var ch1 chan int //ch1是一个正常的chan,不是单向的
var ch2 chan<- float64 //ch2是一个单向channle,只能用于写float64数据
var ch3 <- chan int //ch3是一个只能读的channle,只能读int数据。

//初始化
ch4 := make(chan int)
ch5 := <-chan int(ch4) //ch5是一个单向读取的
ch6 := cha <- int(ch4) //ch6是一个单向写的

close(ch) //关闭channel

多核并行化

在执行一些昂贵的计算任务时,我们希望能够尽量利用现代服务器普遍具备的多核特性来尽量将任务并行化,从而降低总计算时间的目的。此时我们需要了解CPU核心的数量,并针对性地分解计算任务到多个goroutine中去并行运行。


type Vector []float64 func (v Vector) DoSome(i, n int, u Vector, c chan int) { for ; i < n; i++ { v[i] += u.Op(v[i]) } c <- 1 //任务执行完毕 } const NCPU = 16 func (v Vector) DoAll(u Vector) { c := make(chan int, NCPU) //用于接收每个CPU的任务完成信号 for i := 0; i < NCPU; i++ { go v.DoSome(i*len(v)/NCPU, (i+1)*len(v)/NCPU, u, c) } for i := 0; i < NCPU; i++ { <-c //获取每一个数据,表示一个CPU计算完成 } } //并没有发现有明显的加快,可以采用设置GOMAXPROCS的参数,设置并发。

同步

GO语言中的sync采用两种类型的锁。sync.Mutex 和 sync.RWMutex. Mutex是最简单的的锁,goroutine获得后,其他只能等待。RWMutex相对友好,是经典的单写多读模型。从实现上来看,RWMutex类型组合了Mutex。

type RWMutex struct {
    w Mutex
    writeSem unit32
    readSem unit32
    readCount int32
    readerWait int32
}

全局唯一性操作

对于从全局角度值需要运行一次代码,比如全局初始化操作,go语言提供了一个Once类型保证全局的唯一性操作。具体代码如下

var a string
var once sync.Once

func setup() {
    a = "hello world"
}

func doprint() {
    once.Do(setup)
    print(a)
}
func twopoint() {
    go doprint()
    go doprint()
}
// 没有引入once,则setup将会被每个goroutine先调用一次,至少对于这个例子是多余的。 once的Do方法可以保证全局范围内之只调用制定的函数次,而且所有其他goroutine在调用此举是,将会被阻塞。直到全剧唯一的once.Do()调用结束后才继续

案例

src
-cg:center.go centerclient.go player.go
-ipc:client.go server.og ipc_test.og
-cgss.go

cgss.go

package main

import (
    "bufio"
    "cg"
    "fmt"
    "ipc"
    "os"
    "strconv"
    "strings"
)

var centerClient *cg.CenterClient

func startCenterServer() error {
    server := ipc.NewIpcServer(&cg.CenterServer{})
    client := ipc.NewIpcClient(server)
    centerClient = &cg.CenterClient{client}
    return nil
}

func Help(args []string) int {
    fmt.Println(`
    Command:
    login <user><leve><exp>
    logout <user>
    send<mess>
    listplayer
    quit(q)
    help(h)
    `)
    return 0
}

func Quit(args []string) int {
    return 1
}

func Logout(args []string) int {
    if len(args) != 2 {
        fmt.Println("Usage : logout <username>")
        return 0
    }
    centerClient.RemovePlayer(args[1])
    return 0
}

func Login(args []string) int {
    if len(args) != 4 {
        fmt.Println("Usage:login...")
        return 0
    }
    level, err := strconv.Atoi(args[2])

    if err != nil {
        return 0
    }
    exp, err := strconv.Atoi(args[3])
    if err != nil {
        return 0
    }
    player := cg.NewPlayer()
    player.Name = args[1]
    player.Level = level
    player.Exp = exp
    err = centerClient.AddPlayer(player)
    return 0
}

func ListPlayer(args []string) int {
    ps, err := centerClient.ListPlayer("")
    if err != nil {
        fmt.Println("Failed", err)
    } else {
        for i, v := range ps {
            fmt.Println(i+1, ":", v)
        }
    }
    return 0
}

func Send(args []string) int {
    message := strings.Join(args[1:], " ")
    err := centerClient.Broadcast(message)
    if err != nil {
        fmt.Println("Failed", err)
    }
    return 0
}

func GetCommandHandlers() map[string]func(args []string) int {
    return map[string]func([]string) int{
        "help":       Help,
        "h":          Help,
        "login":      Login,
        "logout":     Logout,
        "listplayer": ListPlayer,
        "send":       Send,
        "quit":       Quit,
    }
}

func main() {
    fmt.Println("Game server soltuion")
    startCenterServer()
    Help(nil)
    r := bufio.NewReader(os.Stdin)

    handlers := GetCommandHandlers()

    for {

        fmt.Println("Command >")
        b, _, _ := r.ReadLine()
        line := string(b)
        tokens := strings.Split(line, " ")

        if handlers, ok := handlers[tokens[0]]; ok {
            ret := handlers(tokens)
            if ret != 0 {
                break
            }
        } else {
            fmt.Println("Unknow command", tokens[0])
        }
    }
}

center.go

package cg

import (
    "encoding/json"
    "errors"
    "fmt"
    "ipc"
    "sync"
)

var _ ipc.Server = &CenterServer{}

type Message struct {
    From    string "from"
    To      string "to"
    Content string "content"
}

type CenterServer struct {
    servers map[string]ipc.Server
    players []*Player
    //  rooms   []*Room
    mutex sync.RWMutex
}

func NewCenterServer() *CenterServer {
    servers := make(map[string]ipc.Server)
    players := make([]*Player, 0)
    return &CenterServer{servers: servers, players: players}
}

func (server *CenterServer) addPlayer(params string) error {
    player := NewPlayer()

    err := json.Unmarshal([]byte(params), &player)
    if err != nil {
        return err
    }
    server.mutex.Lock()
    defer server.mutex.Unlock()

    server.players = append(server.players, player)
    return nil
}

func (server *CenterServer) removePlayer(params string) error {
    server.mutex.Lock()
    defer server.mutex.Unlock()

    for i, v := range server.players {
        if v.Name == params {
            fmt.Println("dddd", len(server.players))
            if len(server.players) == 1 {
                server.players = make([]*Player, 0)
                fmt.Println("1111")
            } else if i == len(server.players)-1 {
                server.players = server.players[:i-1]
            } else if i == 0 {
                server.players = server.players[1:]
            } else {
                server.players = append(server.players[:i-1], server.players[:i+1]...)
            }
            return nil
        }
    }
    return errors.New("Plyaer not found")
}

func (server *CenterServer) listPlayer(params string) (players string, err error) {
    server.mutex.Lock()
    defer server.mutex.Unlock()

    if len(server.players) > 0 {
        b, _ := json.Marshal(server.players)
        players = string(b)
    } else {
        err = errors.New("no player online")
    }
    return
}

func (server *CenterServer) broadcast(prams string) error {
    var message Message
    err := json.Unmarshal([]byte(prams), &message)
    if err != nil {
        return err
    }

    server.mutex.Lock()
    defer server.mutex.Unlock()

    if len(server.players) > 0 {
        for _, player := range server.players {
            player.mq <- &message
        }
    } else {
        err = errors.New("No player online")
    }
    return err
}

func (server *CenterServer) Handle(method, params string) *ipc.Response {
    switch method {
    case "addplayer":
        err := server.addPlayer(params)
        if err != nil {
            return &ipc.Response{Code: err.Error()}
        }
    case "removeplayer":
        err := server.removePlayer(params)
        if err != nil {
            return &ipc.Response{Code: err.Error()}
        }
    case "listplayer":
        players, err := server.listPlayer(params)
        if err != nil {
            return &ipc.Response{Code: err.Error()}
        }
        return &ipc.Response{"200", players}
    case "broadcast":
        err := server.broadcast(params)
        if err != nil {
            return &ipc.Response{Code: err.Error()}
        }
        return &ipc.Response{Code: "200"}
    default:
        return &ipc.Response{Code: "404", Body: method + ":" + params}
    }
    return &ipc.Response{Code: "200"}
}

// Name ..
func (server *CenterServer) Name() string {
    return "CenterServer"
}

player.go

package cg

import "fmt"

type Player struct {
    Name  string "name"
    Level int    "level"
    Exp   int    "exp"
    //  Room  int    "rom"
    mq chan *Message
}

func NewPlayer() *Player {
    m := make(chan *Message, 1024)
    player := &Player{"", 0, 0, m}
    go func(p *Player) {
        for {
            msg := <-p.mq
            fmt.Println(p.Name, "received message:", msg.Content)
        }
    }(player)
    return player
}

centerclient.go

package cg

import (
    "encoding/json"
    "errors"
    "ipc"
)

type CenterClient struct {
    *ipc.IpcClient
}

func (client *CenterClient) AddPlayer(player *Player) error {
    b, err := json.Marshal(*player)
    if err != nil {
        return err
    }

    resp, err := client.Call("addplayer", string(b))
    if err == nil && resp.Code == "200" {
        return nil
    }
    return err
}

func (client *CenterClient) RemovePlayer(name string) error {
    ret, _ := client.Call("removeplayer", name)
    if ret.Code == "200" {
        return nil
    }
    return errors.New(ret.Code)
}

func (client *CenterClient) ListPlayer(params string) (ps []*Player, err error) {
    resp, _ := client.Call("listplayer", params)
    if resp.Code != "200" {
        err = errors.New(resp.Code)
        return
    }
    err = json.Unmarshal([]byte(resp.Body), &ps)
    return
}

func (client *CenterClient) Broadcast(message string) error {
    m := &Message{Content: message} //构造message结构体
    b, err := json.Marshal(m)
    if err != nil {
        return nil
    }

    resp, _ := client.Call("broadcast", string(b))
    if resp.Code == "200" {
        return nil
    }
    return errors.New(resp.Code)
}

ipc:client.go

package ipc

import "encoding/json"

type IpcClient struct {
    conn chan string
}

func NewIpcClient(server *IpcServer) *IpcClient {
    c := server.Connect()
    return &IpcClient{c}
}

func (client *IpcClient) Call(method, params string) (resp *Response, err error) {
    req := &Request{method, params}

    var b []byte
    b, err = json.Marshal(req)
    if err != nil {
        return
    }

    client.conn <- string(b)
    str := <-client.conn

    var resp1 Response
    err = json.Unmarshal([]byte(str), &resp1)
    resp = &resp1

    return
}

func (client *IpcClient) Close() {
    client.conn <- "CLOSE"
}

server.og

package ipc

import (
    "encoding/json"
    "fmt"
)

type Request struct {
    Method string "method"
    Params string "params"
}

type Response struct {
    Code string "code"
    Body string "body"
}

type Server interface {
    Name() string
    Handle(method, params string) *Response
}

type IpcServer struct {
    Server
}

func NewIpcServer(server Server) *IpcServer {
    return &IpcServer{server}
}

func (server *IpcServer) Connect() chan string {
    session := make(chan string, 0)
    go func(c chan string) {
        for {
            request := <-c
            if request == "CLOSE" {
                break
            }
            var req Request
            err := json.Unmarshal([]byte(request), &req)
            if err != nil {
                fmt.Println("Invalid request format:", request)
            }

            resp := server.Handle(req.Method, req.Params)
            b, err := json.Marshal(resp)
            c <- string(b)
        }
        fmt.Println("Session closed")
    }(session)
    fmt.Println("A new session has been created successfully")
    return session
}


Go 语言集训(三):面向对象

在Go语言中,面向对象和面向过程是两种不同的方式。

func (a Integer) Less (b Integer) bool {  //面向对象
    return a < b
}

func Integer_Less(a Integer, b Integer) bool {//面向过程
    return a < b
}

GO语言中有4个类型比较特别,看起来像引用类型。

  1. 数组切片:指向数组的一个去见
  2. map:及其常见的数据结构,提供键值查询能力。Map本质是一个字典指针
  3. channel:执行体(goroutine)间的通信设施
  4. 接口(interface):对一组满足某个契约的类型的抽象
    Channel和Map类似,本质上是一个指针。将他们设计为引用类型而不是统一的值类型的原因是,完整复制一个channel或者map并不是常规需求。

匿名组合

type Job struct {
    Command String 
    *log.Logger
}

func(job *Job) Start() {
    job.Log("Starting now...")
    job.Log("started")
}

在合适赋值后,我们在JOB类型的所有成员方法中可以很舒适地借用所有log.Logger提供的方法。对于Job实现者来讲,他甚至根本不用意识到log.Logger类型的存在,这就是匿名组合的美丽。
需要注意的是,不管是非匿名的类型组合还是匿名组合,被组合的类型所包含的方法虽然都升级成外部这个组合类型的方法,但是其实他们被组合方法调用时接收者并没有改变。比如Job例子,即使这个组合后调用的方式变成了job.Log(…),但Log函数的接收者任然是log.Logger指针,因此在Log中不可能访问到Job的其他成员方法和变量。

type X struct {
    Name string
}

type Y struct {
    X
    Name string
}

接口组合中名字冲突时,所有Y类型的Name成员都只会访问到最外层的那个Name变量。X.Name相当于隐藏了起来。

type Logger struct{
    Level int
}
type Y struct{
    *Logger
    Name string
    *log.Logger
}//这里会有问题,可能收到编译错误,但如果定义后没有再用过,也不会产生错误。因为Y类型存在两个名为Logger的成员。虽然类型不同。

可见性

要使某个符号对其他包可见,需要将该符号定义为以答谢字母开头。

type Rect struct {
    X,Y float64
    Width,Height float64
}//这样Rect类型的成员全部被导出了

func (r *Rect) area() float64 {//包内可见
    return r.Width * r.Height 
}

Go语言中符号的可访问新是包一级的而不是类型一级的。在上面例子中,area是Rect内部方法,但同一个包中的其他类型也可以访问到它。这样做很粗犷,但很实用。如果GO语言的可访问性是类型一级的,少不了加上friend这样的关键字,标识两个类是朋友关系,可以访问彼此的私有成员。

接口赋值

  1. 将对象实例赋值给接口
  2. 将一个接口赋值给另外一个接口
    A(小) = B(大)

接口查询

var file1 Wirter = ..
if file5,ok := file.(two.IStream); ok {
    ...
}

if file6,ok := file.(*File); ok {
    ...
}

完整案例

Go 语言集训(二)

闭包

Go的匿名函数是一个闭包
* 基本概念
闭包是可以包含自由(为绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及他们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)
* 闭包的价值
闭包的机制在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要标识数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,这就是说这些函数可以存储到变量中作为参数传递给其他函数,最重要的是能够被函数动态创建和返回
* Go语言中的闭包
GO语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在。

package main

import "fmt"

func main() {
    var j int = 5
    a := func() func() {
        var i int = 10
        return func() {
            fmt.Printf("i,j: %d,%d\n", i, j)
        }
    }()
    a()
    j *= 2
    a()
    fmt.Println("闭包另外尝试")
    i := incr()
    fmt.Println(i())      //1
    fmt.Println(i())      //2
    fmt.Println(i())      //3
    fmt.Println(incr()()) //1 incr()返回一个func() int ;所以还需要incr()()l对 func()int进行执行
    fmt.Println(incr()()) //1

}

func incr() func() int { // func() int 作为 func的返回值
    var x int
    return func() int {  //这是返回的指针函数
        x++
        return x
    }
}

对于Map的 函数的使用

package main

import "fmt"

func f(p string) {
    fmt.Println("function f parameter:", p)
}

func g(p string, q int) {
    fmt.Println("function g parameters:", p, q)
}

func main() {
    m := map[string]interface{}{//使用interface更加安全,可以匹配不同的接口。
        "f": f,
        "g": g,
    }
    for k, v := range m {
        switch k {
        case "f":
            v.(func(string))("astring")
        case "g":
            v.(func(string, int))("astring", 42)
        }
    }
}

排序的案例

.
├── algorithms
│   ├── bubblesort
│   │   ├── bubblesort.go
│   │   └── buddlesort_test.go
│   └── qsort
│   ├── qsort.go
│   └── qsort_test.go
├── input.data
├── output.data
└── sorter.go

sourter.go

// sorter.go
package main

import "flag"
import "fmt"
import "io"
import "bufio"
import "os"
import "strconv"
import "time"

import "algorithms/bubblesort"
import "algorithms/qsort"

var infile *string = flag.String("i", "infile", "File contains values for sorting")
var outfile *string = flag.String("o", "outfile", "File to receive sorted values")
var algorithm *string = flag.String("a", "qsort", "Sort algorithm")

func main() {
    flag.Parse()

    if infile != nil {
        fmt.Println("inflie =", *infile, "outfile =", *outfile, "algortihm =", *algorithm)
    }
    values, err := readValues(*infile)
    if err == nil {
        t1 := time.Now()
        switch *algorithm {
        case "qsort":
            qsort.QuickSort(values)
        case "bubblesort":
            bubblesort.BubbleSort(values)
        default:
            fmt.Print("sorting algortim", *algorithm, "is either unkonw or unsported")
        }
        t2 := time.Now()
        fmt.Println("The sorting process costs", t2.Sub(t1), "to complete")
        writeValues(values, *outfile)
    } else {
        fmt.Println(err)
    }
}

func writeValues(value []int, outfile string) error {
    file, err := os.Create(outfile)
    if err != nil {
        fmt.Println("Failed to create the output file ", outfile)
        return err
    }

    defer file.Close()
    for _, value := range value {
        str := strconv.Itoa(value)
        file.WriteString(str + "\n")
    }
    return nil
}

func readValues(infile string) (values []int, err error) {
    file, err := os.Open(infile)
    if err != nil {
        fmt.Println("failed to open the input file ", infile)
        return
    }
    defer file.Close()

    br := bufio.NewReader(file)
    values = make([]int, 0)

    for {
        line, isPrefix, err1 := br.ReadLine()

        if err1 != nil {
            if err1 != io.EOF {
                err = err1
            }
            break
        }
        if isPrefix {
            fmt.Println("A too long line, seems unexpted")
            return
        }

        str := string(line) //转换字符数组为字符串

        value, err1 := strconv.Atoi(str)
        fmt.Println(err1)
        if err1 != nil {
            err = err1
            return
        }
        values = append(values, value)
    }
    return
}

sqort.go

package qsort

func quicSort(values []int, left, right int) {
    temp := values[left]
    p := left
    i, j := left, right
    for i <= j {
        for j >= p && values[j] >= temp {
            j--
        }
        if j >= p {
            values[p] = values[j]
            p = j
        }
        if values[i] <= temp && i <= p {
            i++
        }
        if i <= p {
            values[p] = values[i]
            p = i
        }
    }
    values[p] = temp
    if p-left > 1 {
        quicSort(values, left, p-1)
    }
    if right-p > 1 {
        quicSort(values, p+1, right)
    }
}

func QuickSort(values []int) {
    quicSort(values, 0, len(values)-1)
}

bubblesort.go

package bubblesort

func BubbleSort(values []int) {
    flag := true
    for i := 0; i < len(values)-1; i++ {
        flag = true
        for j := 0; j < len(values)-i-1; j++ { //存在交换就把flag变为false,没有的话,则说明排序完成
            if values[j] > values[j+1] {
                values[j], values[j+1] = values[j+1], values[j]
                flag = false
            }
        }
        if flag == true {
            break
        }
    }
}

bubblesort_test.go

package bubblesort

import "testing"

func TestBuddleSort1(t *testing.T) {
    values := []int{5, 4, 3, 2, 1}
    BubbleSort(values)
    if values[0] != 1 || values[2] != 3 || values[3] != 4 || values[4] != 5 {
        t.Error("BubbleSort() failed. Got", values, "exptected 1 2 3 4 5")
    }
}

func TestBuddleSort2(t *testing.T) {
    values := []int{5, 5, 3, 2, 1}
    BubbleSort(values)
    if values[0] != 1 || values[2] != 3 || values[3] != 5 || values[4] != 5 {
        t.Error("BubbleSort() failed. Got", values, "exptected 1 2 3 5 5")
    }
}

func TestBuddleSort3(t *testing.T) {
    values := []int{5}
    BubbleSort(values)
    if values[0] != 5 {
        t.Error("BubbleSort() failed. Got", values, "exptected 5")
    }
}

qsort_testing.go

package qsort

import "testing"

func TestQsort1(t *testing.T) {
    values := []int{5, 4, 3, 2, 1}
    QuickSort(values)
    if values[0] != 1 || values[2] != 3 || values[3] != 4 || values[4] != 5 {
        t.Error("QuickSort() failed. Got", values, "exptected 1 2 3 4 5")
    }
}

父母草原行

父母来京,小侄女全程护送。之前小侄女想到上海来看我,但在上海出差任务安排紧张,没有自己的时间。此行也是为小侄女考上一中的奖励。由于父母年高体弱,没有办法爬山等,草原之行是不错的选择。

行程计划

  • 8月2日: 北京–张家口(宿)
  • 8月3日:张家口–坝上(宿)
  • 8月4日:坝上-承德(宿)
  • 8月5日:承德-北京(宿)

备忘录

  1. 查看8月2-6号的天气
  2. 7月29日租车(迈锐宝)
  3. 购买返程火车
  4. 草原上的住宿提前预定

租车时间

  • 8月2日 13:00 取车
  • 8月6日 13:00 还车

活动安排

  1. 小妮子骑马
  2. 烟花
  3. 承德八大庙or避暑山庄
  4. 草原溜达

物资储备

由于老人和小孩都在,不能像往常一样,开车就走。需要储备一些物资,以防路上有什么情况发生。

  1. 保温箱:已下单
  2. 暖宝宝
  3. 保温杯
  4. 气罐和炉子:茶叶
  5. 防蚊虫
  6. 防晒霜
  7. 卫生纸
  8. 熟食
  9. 零食

小侄女的知识储备

  1. 坝上草原
  2. 木兰围场
  3. 承德
  4. 承德八大庙
  5. 冬奥会
  6. 张家口
  7. 丰宁

阿里行程计划(待更新)

行程目的

不到阿里,不算到西藏。阿里想了5年,等了5年。
* 转山!神圣的冈仁波齐,世界宗教的中心。宗教之美
* 古格王朝。历史之美。
* 羌塘-棕熊、野牦牛、狐狸、狼等等。野性之美。
* 玛旁雍错、当惹雍错、色林错、纳木错、拉䀚措 。 自然之美
能否走到文布南村,看具体时间和路线。

总体时间规划

总计15天。

日期 行程 距离 景点
9月27日 北京–西安 Z19 20:44-08:31 卧铺没得看
9月28日 西安–拉萨 15:05-18:25 空中雪山
9月29日 拉萨-日喀则 300KM 5H 租车、赶路
9月30日 日喀则-萨嘎 450KM 8H 赶路
10月1日 萨嘎-霍尔 485KM 8H 雅江源沙丘、五彩沙漠、公珠错
10月2日 霍尔-普兰-塔钦 200KM 4H 昂拉错、纳木那尼峰、玛旁雍错,孔雀河、边贸市场、科加寺、古宫寺
10月3日 转山 25KM 8H 冈仁波齐
10月4日 转山 25KM 8H 冈仁波齐
10月5日 塔钦-扎达 230KM 5H 扎达土林、苍穹银城
10月6日 扎达-狮泉河 485KM 8H 雅江源沙丘、五彩沙漠、公珠错
10月7日 狮泉河-仁多 各种措
10月8日 任多-措勤 各种错、扎布耶茶卡
10月9日 措勤-尼玛 280KM 6H 文部南北村、当惹雍错、象雄遗址
10月10日 尼玛-班戈 335KM 4.5H 色林错、各种错
10月11日 班戈-当雄 220KM 5.5H 巴林错、纳木错
10月12日 当雄-拉萨 210KM 4H 廓琼岗冰川
10月13日 拉萨-北京 16:10-22:15 空中雪山