package pingo

import (
    "bufio"
    "bytes"
    "flag"
    "fmt"
    "io"
    "math/rand"
    "net"
    "net/rpc"
    "os"
    "path"
    "path/filepath"
    "reflect"
    "strings"
    "time"
)

// Register a new object this plugin exports. The object must be
// an exported symbol and obey all rules an object in the standard
// "rpc" module has to obey.
//注册的一个对象作为可以导出对象。这个对象必须符合RPC规则
//               - exported method of exported type
//    - two arguments, both of exported type
//    - the second argument is a pointer
//    - one return value, of type error
// Register will panic if called after Run.
//如果在运行中  注册对象 就会报错 
func Register(obj interface{}) {
    if defaultServer.running {
        panic("Do not call Register after Run")
    }
    defaultServer.register(obj)//注册可导出的对象
}

// Run will start all the necessary steps to make the plugin available.
//调用Run函数是必须的来保证插件的可用性
func Run() error {
    if !flag.Parsed() {//判断参数是否解析
        flag.Parse()//解析当前参数
    }
    return defaultServer.run()
}

// Internal object for plugin control
type PingoRpc struct{}

// Default constructor for interal object. Do not call manually.
func NewPingoRpc() *PingoRpc {
    return &PingoRpc{}
}

// Internal RPC call to shut down a plugin. Do not call manually.
func (s *PingoRpc) Exit(status int, unused *int) error {
    os.Exit(status)
    return nil
}

type config struct {
    proto   string
    addr    string
    prefix  string
    unixdir string
}

func makeConfig() *config {
    c := &config{}
    flag.StringVar(&c.proto, "pingo:proto", "unix", "Protocol to use: unix or tcp")
    flag.StringVar(&c.unixdir, "pingo:unixdir", "", "Alternative directory for unix socket")
    flag.StringVar(&c.prefix, "pingo:prefix", "pingo", "Prefix to output lines")
    return c
}

type rpcServer struct {
    *rpc.Server
    secret  string
    objs    []string
    conf    *config
    running bool
}

func newRpcServer() *rpcServer {
    rand.Seed(time.Now().UTC().UnixNano())
    r := &rpcServer{
        Server: rpc.NewServer(),
        secret: randstr(64),
        objs:   make([]string, 0),
        conf:   makeConfig(), // conf remains fixed after this point
    }
    r.register(&PingoRpc{})
    return r
}

var defaultServer = newRpcServer()

type bufReadWriteCloser struct {
    *bufio.Reader
    r io.ReadWriteCloser
}

func newBufReadWriteCloser(r io.ReadWriteCloser) *bufReadWriteCloser {
    return &bufReadWriteCloser{Reader: bufio.NewReader(r), r: r}
}

func (b *bufReadWriteCloser) Write(data []byte) (int, error) {
    return b.r.Write(data)
}

func (b *bufReadWriteCloser) Close() error {
    return b.r.Close()
}

func readHeaders(brwc *bufReadWriteCloser) ([]byte, error) {
    var buf bytes.Buffer
    var headerEnd bool

    for {
        b, err := brwc.ReadByte()
        if err != nil {
            return []byte(""), err
        }

        buf.WriteByte(b)

        if b == '\n' {
            if headerEnd {
                break
            }
            headerEnd = true
        } else {
            headerEnd = false
        }
    }

    return buf.Bytes(), nil
}

func parseHeaders(brwc *bufReadWriteCloser, m map[string]string) error {
    headers, err := readHeaders(brwc)
    if err != nil {
        return err
    }

    r := bytes.NewReader(headers)
    scanner := bufio.NewScanner(r)

    for scanner.Scan() {
        parts := strings.SplitN(scanner.Text(), ": ", 2)
        if parts[0] == "" {
            continue
        }
        m[parts[0]] = parts[1]
    }

    return nil
}

func (r *rpcServer) authConn(token string) bool {
    if token != "" && token == r.secret {
        return true
    }
    return false
}

func (r *rpcServer) serveConn(conn io.ReadWriteCloser, h meta) {
    bconn := newBufReadWriteCloser(conn)
    defer bconn.Close()

    headers := make(map[string]string)
    if err := parseHeaders(bconn, headers); err != nil {
        h.output("error", err.Error())
        return
    }

    if r.authConn(headers["Auth-Token"]) {
        r.Server.ServeConn(bconn)
    }

    return
}

func (r *rpcServer) register(obj interface{}) {
    element := reflect.TypeOf(obj).Elem() //获取对象元素类型
    r.objs = append(r.objs, element.Name()) //添加到objs中  获取元素类型名称
    r.Server.Register(obj)//调用rpc 的注册方法
}

type connection interface {
    addr() string
    retries() int
}

type tcp int

func (t *tcp) addr() string {
    if *t < 1024 {
        // Only use unprivileged ports
        *t = 1023
    }

    *t = *t + 1
    return fmt.Sprintf("127.0.0.1:%d", *t)
}

func (t *tcp) retries() int {
    return 500
}

type unix string

func (u *unix) addr() string {
    name := randstr(8)
    if *u != "" {
        name = filepath.FromSlash(path.Join(string(*u), name))
    }
    return name
}

func (u *unix) retries() int {
    return 4
}
//
func (r *rpcServer) run() error {
    var conn connection
    var err error
    var listener net.Listener

    r.running = true  //设置默认运行状态为true

    h := meta(r.conf.prefix)//设置自定数据类型  参数值为config 的前缀
    h.output("objects", strings.Join(r.objs, ", "))//方法参数 key 为常量 objects  参数 val 值为注册服务对象元素名称  。使用,链接的字符串
       //协议判断
    switch r.conf.proto {
    case "tcp":
        conn = new(tcp)
    default:
        r.conf.proto = "unix"
        conn = new(unix)
    }
        //获取尝试连接500次 但是只要有一次执行成功 立刻返回
    for i := 0; i < conn.retries(); i++ {
        r.conf.addr = conn.addr()
        listener, err = net.Listen(r.conf.proto, r.conf.addr)
        if err == nil {
            break
        }
    }

    if err != nil {
        h.output("fatal", fmt.Sprintf("%s: Could not connect in %d attemps, using %s protocol", errorCodeConnFailed, conn.retries(), r.conf.proto))
        return err
    }

    h.output("auth-token", defaultServer.secret)
    h.output("ready", fmt.Sprintf("proto=%s addr=%s", r.conf.proto, r.conf.addr))
    for {
        var conn net.Conn
        conn, err = listener.Accept()
        if err != nil {
            h.output("fatal", fmt.Sprintf("err-http-serve: %s", err.Error()))
            continue
        }
        go r.serveConn(conn, h)
    }
}

server.go 源码阅读的更多相关文章

  1. 【原】AFNetworking源码阅读(一)

    [原]AFNetworking源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 AFNetworking版本:3.0.4 由于我平常并没有经常使用AFNetw ...

  2. Android源码阅读 – Zygote

    @Dlive 本文档: 使用的Android源码版本为:Android-4.4.3_r1 kitkat (源码下载: http://source.android.com/source/index.ht ...

  3. CI框架源码阅读笔记3 全局函数Common.php

    从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap ...

  4. CI框架源码阅读笔记1 - 环境准备、基本术语和框架流程

    最开始使用CI框架的时候,就打算写一个CI源码阅读的笔记系列,可惜虎头蛇尾,一直没有行动.最近项目少,总算是有了一些时间去写一些东西.于是准备将之前的一些笔记和经验记录下来,一方面权作备忘,另一方面时 ...

  5. 转-OpenJDK源码阅读导航跟编译

    OpenJDK源码阅读导航 OpenJDK源码阅读导航 博客分类: Virtual Machine HotSpot VM Java OpenJDK openjdk 这是链接帖.主体内容都在各链接中.  ...

  6. Netty源码阅读(一) ServerBootstrap启动

    Netty源码阅读(一) ServerBootstrap启动 转自我的Github Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速 ...

  7. Spark源码阅读之存储体系--存储体系概述与shuffle服务

    一.概述 根据<深入理解Spark:核心思想与源码分析>一书,结合最新的spark源代码master分支进行源码阅读,对新版本的代码加上自己的一些理解,如有错误,希望指出. 1.块管理器B ...

  8. Rpc框架dubbo-client(v2.6.3) 源码阅读(二)

    接上一篇 dubbo-server 之后,再来看一下 dubbo-client 是如何工作的. dubbo提供者服务示例, 其结构是这样的!dubbo://192.168.11.6:20880/com ...

  9. InfluxDB源码阅读之snapshotter服务

    操作系统 : CentOS7.3.1611_x64 go语言版本:1.8.3 linux/amd64 InfluxDB版本:1.1.0 服务模块介绍 源码路径: github.com/influxda ...

随机推荐

  1. Apache Kafka简介与安装(二)

    Kafka在Windows环境上安装与运行 简介 Apache kafka 是一个分布式的基于push-subscribe的消息系统,它具备快速.可扩展.可持久化的特点.它现在是Apache旗下的一个 ...

  2. html5中的全局属性

    在html5中,新增了一个"全局属性"的概念,所谓全局属性,是指可以对任何属性都使用的属性.下面列出常用的全局属性. 1.contentEditable属性,是微软开发的,该属性主 ...

  3. 如何用Python网络爬虫爬取网易云音乐歌曲

    今天小编带大家一起来利用Python爬取网易云音乐,分分钟将网站上的音乐down到本地. 跟着小编运行过代码的筒子们将网易云歌词抓取下来已经不再话下了,在抓取歌词的时候在函数中传入了歌手ID和歌曲名两 ...

  4. permutations II(全排列 2)

    题目要求 Given a collection of numbers that might contain duplicates, return all possible unique permuta ...

  5. 使用nginx sticky实现基于cookie的负载均衡

    在多台后台服务器的环境下,我们为了确保一个客户只和一台服务器通信,我们势必使用长连接.使用什么方式来实现这种连接呢,常见的有使用nginx自带的ip_hash来做,我想这绝对不是一个好的办法,如果前端 ...

  6. spring3.1文档目录翻译

    整理google共享磁盘找到了2014年翻译的spring官方文档的目录,分享出来可能会对英语不好的同学有些帮助吧. spring3.1官方文档目录-中文 spring3.1官方文档-英文 关于作者

  7. OkHttp上传文件,服务器端请求解析找不到文件信息的问题

    长话短说,不深入解释了,官方给的上传案例代码: private static final String IMGUR_CLIENT_ID = "..."; private stati ...

  8. pc端页面打包成安卓apk

    一.phoneGap PhoneGap是一个采用HTML,CSS和JavaScript的技术,创建移动跨平台移动应用程序的快速开发平台.它使开发者能够在网页中调用IOS,Android,Palm,Sy ...

  9. 利用Hive分析nginx日志

    这里用到的nginx日志是网站的访问日志,比如日志格式: 180.173.250.74 - - [08/Jan/2015:12:38:08 +0800] "GET /avatar/xxx.p ...

  10. Java面向对象(一、封装)

    Java 封装 封装的概念 在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装.隐藏起来的方法. 封装可以被认为是一个保护屏障,防止该类的代码 ...