go语言socket

 : jank    :   : 533    : 2016-11-20 23:43  go

Socket编程

1.常用的Socket类型有两种:

1.流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;

2.数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。

TCP Socket和UDP Socket,TCP和UDP是协议,而要确定一个进程的需要三元组,需要IP地址和端口。

2.目前而言,几乎所有的应用程序都是采用socket而现在又是网络时代,网络中进程通信是无处不在,这就是为什么说一切皆Socket”。

3.ipv4

目前的全球因特网所采用的协议族是TCP/IP协议。IP是TCP/IP协议中网络层的协议,是TCP/IP协议族的核心协议。

IPv4的地址位数为32位,也就是最多有2的32次方的网络设备可以联到Internet上。近十年来由于互联网的蓬勃发展,IP位址的需求量愈来愈大,使得IP位址的发放愈趋紧张,前一段时间,据报道IPV4的地址已经发放完毕,我们公司目前很多服务器的IP都是一个宝贵的资源。

地址格式:127.0.0.1 172.122.121.111

4.ipv6

IPv6可以说是下一代的ip互联网协议,采用128位地址长度,几乎可以不受限制地提供地址。按保守方法估算IPv6实际可分配的地址,整个地球的每平方米面积上仍可分配1000多个地址。在IPv6的设计过程中除了一劳永逸地解决了地址短缺问题以外,还考虑了在IPv4中解决不好的其它问题,主要有端到端IP连接、服务质量(QoS)、安全性、多播、移动性、即插即用等。

地址格式:2002:c0e8:82e7:0:0:0:c0e8:82e7

5.TCP Socket

作为客户端来说,我们可以通过向远端某台机器的的某个网络端口发送一个请求,然后得到在机器的此端口上监听的服务反馈的信息。作为服务端,我们需要把服务绑定到某个指定端口,并且在此端口上监听,当有客户端来访问时能够读取信息并且写入反馈信息。

在Go语言的net包中有一个类型TCPConn,这个类型可以用来作为客户端和服务器端交互的通道,他有两个主要的函数:

func (c *TCPConn) Write(b []byte) (n int, err os.Error)

func (c *TCPConn) Read(b []byte) (n int, err os.Error)

TCPConn可以用在客户端和服务器端来读写数据。

6.TCPAddr类型,他表示一个TCP的地址信息,他的定义如下:

type TCPAddr struct {

   IP IP

   Port int

}

7.在Go语言中通过ResolveTCPAddr获取一个TCPAddr

func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)

net参数是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only),TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个).

addr表示域名或者IP地址,例如"www.google.com:80" 或者"127.0.0.1:22".

8.TCP Client

Go语言中通过net包中的DialTCP函数来建立一个TCP连接,并返回一个TCPConn类型的对象,

当连接建立时服务器端也创建一个同类型的对象,此时客户端和服务器段通过各自拥有的TCPConn对象来进行数据交换。

一般而言,客户端通过TCPConn对象将请求信息发送到服务器端,读取服务器端响应的信息。服务器端读取并解析来自客户端的请求,并返回应答信息,这个连接只有当任一端关闭了连接之后才失效,不然这连接可以一直在使用。建立连接的函数定义如下:

func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)


net参数是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only)、TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个)

laddr表示本机地址,一般设置为nil

raddr表示远程的服务地址

例:


9.TCP Server

1.执行简单的监听

            package main            
            import (
                "fmt"
                "net"
                "os"
                "time"
            )
            func main() {
                service := ":7777"
                tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
                checkError(err)
                listener, err := net.ListenTCP("tcp", tcpAddr)//监听7777端口
                checkError(err)
                for {
                    conn, err := listener.Accept()
                    if err != nil {//当出现错误的时候continue,不退出
                        continue
                    }
                    daytime := time.Now().String()
                    conn.Write([]byte(daytime)) // web访问7777端口时,输出当前时间
                    conn.Close()                // we're finished with this client
                }
            }
            func checkError(err error) {
                if err != nil {
                    fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
                    os.Exit(1)
                }
            }

2.上面的代码有个缺点,执行的时候是单任务的,不能同时接收多个请求,那么该如何改造以使它支持多并发呢?

Go里面有一个goroutine机制,

            package main            
            import (
                "fmt"
                "net"
                "os"
                "time"
            )
            func main() {
                service := ":7777"
                tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
                checkError(err)
                listener, err := net.ListenTCP("tcp", tcpAddr)//监听7777端口
                checkError(err)
                for {
                    conn, err := listener.Accept()
                    if err != nil {//当出现错误的时候continue,不退出
                        continue
                    }
                    go handleClient(conn) //实现支持多并发
                }
            }
            //处理客服端
            func handleClient(conn net.Conn) {
            defer conn.Close()
            daytime := time.Now().String()
            conn.Write([]byte(daytime))
            }
            func checkError(err error) {
                if err != nil {
                    fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
                    os.Exit(1)
                }
            }

3.通过从客户端发送不同的请求来获取不同的时间格式,而且需要一个长连接,例:

            package main            
            import (
                "fmt"
                "net"
                "os"
                "time"
                "strconv"
                "strings"
            )
            func main() {
                service := ":1200"
                tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
                checkError(err)
                listener, err := net.ListenTCP("tcp", tcpAddr)
                checkError(err)
                for {
                    conn, err := listener.Accept()
                    if err != nil {
                        continue
                    }
                    go handleClient(conn)
                }
            }
            func handleClient(conn net.Conn) {
                conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // 写入读取两分钟过期
                request := make([]byte, 128) // set maxium request length to 128B to prevent flood attack
                defer conn.Close()  // close connection before exit
                for {
                    read_len, err := conn.Read(request)
                    if err != nil {
                        fmt.Println(err)
                        break
                    }
                    if read_len == 0 {
                        break // connection already closed by client
                    } else if strings.TrimSpace(string(request[:read_len])) == "timestamp" {
                        daytime := strconv.FormatInt(time.Now().Unix(), 10)
                        conn.Write([]byte(daytime))
                    } else {
                        daytime := time.Now().String()
                        conn.Write([]byte(daytime))
                    }
                    request = make([]byte, 128) // clear last read content
                }
            }
            func checkError(err error) {
                if err != nil {
                    fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
                    os.Exit(1)
                }
            }

10.控制TCP连接

TCP有很多连接控制函数,我们平常用到比较多的有如下几个函数:


1, func DialTimeout(net, addr string, timeout time.Duration) (Conn, error)

设置建立连接的超时时间,客户端和服务器端都适用,当超过设置时间时,连接自动关闭。


2, func (c *TCPConn) SetReadDeadline(t time.Time) error

  func (c *TCPConn) SetWriteDeadline(t time.Time) error

用来设置写入/读取一个连接的超时时间。当超过设置时间时,连接自动关闭。


3, func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error

设置客户端是否和服务器端保持长连接,可以降低建立TCP连接时的握手开销,对于一些需要频繁交换数据的应用场景比较适用。

11.UDP Socket

1.Go语言包中处理UDP Socket和TCP Socket不同的地方就是在服务器端处理多个客户端请求数据包的方式不同,UDP缺少了对客户端连接请求的Accept函数。其他基本几乎一模一样,只有TCP换成了UDP而已。UDP的几个主要函数如下所示:


func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error)

func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error)

func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error)

func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error)

func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error)

2.一个UDP的客户端代码如下所示,我们可以看到不同的就是TCP换成了UDP而已:

                package main
                import (
                    "fmt"
                    "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]
                    udpAddr, err := net.ResolveUDPAddr("udp4", service)
                    checkError(err)
                    conn, err := net.DialUDP("udp", nil, udpAddr)
                    checkError(err)
                    _, err = conn.Write([]byte("anything"))
                    checkError(err)
                    var buf [512]byte
                    n, err := conn.Read(buf[0:])
                    checkError(err)
                    fmt.Println(string(buf[0:n]))
                    os.Exit(0)
                }
                func checkError(err error) {
                    if err != nil {
                        fmt.Fprintf(os.Stderr, "Fatal error ", err.Error())
                        os.Exit(1)
                    }
                }

12.go语言websocket

1.获取websocket包:

go get code.google.com/p/go.net/websocket //官方的报错

go get github.com/gorilla/websocket    //文档:https://gowalker.org/github.com/gorilla/websocket 

2.定义flag参数,有三种方式

1.通过flag.String(), Bool(), Int() 等flag.Xxx()方法,该种方式返回一个相应的指针

import "flag"

var ip = flag.Int("flagname", 1234, "help message for flagname")

2.通过flag.XxxVar()方法将flag绑定到一个变量,该种方式返回值类型,如

var flagvar int

func init() {

   flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")

}

3.通过flag.Var()绑定自定义类型,自定义类型需要实现Value接口(Receives必须为指针),如

flag.Var(&flagVal, "name", "help message for flagname")

对于这种类型的flag,默认值为该变量类型的初始值

1.调用flag.Parse()解析命令行参数到定义的flag

flag.Parse()

2.解析函数将会在碰到第一个非flag命令行参数时停止,非flag命令行参数是指不满足命令行语法的参数,如命令行参数为cmd --flag=true abc则第一个非flag命令行参数为“abc”

3.调用Parse解析后,就可以直接使用flag本身(指针类型)或者绑定的变量了(值类型)

fmt.Println("ip has value ", *ip)

fmt.Println("flagvar has value ", flagvar)

还可通过flag.Args(), flag.Arg(i)来获取非flag命令行参数

3.例:

            package main            
            import (
            "flag"
            "html/template"
            "log"
            "net/http"
            "github.com/gorilla/websocket"
            )
            var addr = flag.String("addr", "120.27.37.185:8080", "http service address")//返回一个相应的指针
            var upgrader = websocket.Upgrader{} // use default options
            func echo(w http.ResponseWriter, r *http.Request) {
            c, err := upgrader.Upgrade(w, r, nil)
            if err != nil {
            log.Print("upgrade:", err)
            return
            }
            defer c.Close()
            for {
            mt, message, err := c.ReadMessage()
            if err != nil {
            log.Println("read:", err)
            break
            }
            log.Printf("recv: %s", message)
            err = c.WriteMessage(mt, message)
            if err != nil {
            log.Println("write:", err)
            break
            }
            }
            }
            func home(w http.ResponseWriter, r *http.Request) {
            homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
            }
            func main() {
            flag.Parse()
            log.SetFlags(0)
            http.HandleFunc("/echo", echo)
            http.HandleFunc("/", home)
            log.Fatal(http.ListenAndServe(*addr, nil))
            }
            var homeTemplate = template.Must(template.New("").Parse(`
            <!DOCTYPE html>
            <head>
            <meta charset="utf-8">
            <script>  
            window.addEventListener("load", function(evt) {
                var output = document.getElementById("output");
                var input = document.getElementById("input");
                var ws;
                function print(message) {
                    var d = document.createElement("div");
                    d.innerHTML = message;
                    output.appendChild(d);
                };
                document.getElementById("open").onclick = function(evt) {
                    if (ws) {
                        return false;
                    }
                    ws = new WebSocket("{{.}}");
                    ws.onopen = function(evt) {
                        print("OPEN");
                    }
                    ws.onclose = function(evt) {
                        print("CLOSE");
                        ws = null;
                    }
                    ws.onmessage = function(evt) {
                        print("RESPONSE: " + evt.data);
                    }
                    ws.onerror = function(evt) {
                        print("ERROR: " + evt.data);
                    }
                    return false;
                };
                document.getElementById("send").onclick = function(evt) {
                    if (!ws) {
                        return false;
                    }
                    print("SEND: " + input.value);
                    ws.send(input.value);
                    return false;
                };
                document.getElementById("close").onclick = function(evt) {
                    if (!ws) {
                        return false;
                    }
                    ws.close();
                    return false;
                };
            });
            </script>
            </head>
            <body>
            <table>
            <tr><td valign="top" width="50%">
            <p>Click "Open" to create a connection to the server, 
            "Send" to send a message to the server and "Close" to close the connection. 
            You can change the message and send multiple times.
            <p>
            <form>
            <button id="open">Open</button>
            <button id="close">Close</button>
            <p><input id="input" type="text" value="Hello world!">
            <button id="send">Send</button>
            </form>
            </td><td valign="top" width="50%">
            <div id="output"></div>
            </td></tr></table>
            </body>
            </html>
            `))

4.RPC

1.RPC(Remote Procedure Call Protocol)——远程过程调用协议,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。它假定某些传输协议的存在,如TCP或UDP,以便为通信程序之间携带信

息数据。通过它可以使函数调用模式网络化。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发

包括网络分布式多程序在内的应用程序更加容易。

2.Go标准包中已经提供了对RPC的支持,而且支持三个级别的RPC:TCP、HTTP、JSONRPC。但Go的RPC包是独一无二的RPC,它和传统的RPC系统不同,它只支持Go开发的服务器与客户端之间的交互,

因为在内部,它们采用了Gob来编码。

Go RPC的函数只有符合下面的条件才能被远程访问,不然会被忽略,详细的要求如下:

函数必须是导出的(首字母大写)

必须有两个导出类型的参数,

第一个参数是接收的参数,第二个参数是返回给客户端的参数,第二个参数必须是指针类型的

函数还要有一个返回值error

1.http的服务端代码实现如下:

                        package main                        
                        import (
                            "errors"
                            "fmt"
                            "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 {//rpc函数,第一个参数是接收的参数,第二个参数是返回给客户端的参数,第二个参数必须是指针类型的函数还要有一个返回值error
                            *reply = args.A * args.B
                            return nil
                        }
                        func (t *Arith) Divide(args *Args, quo *Quotient) error {//rpc函数,第一个参数是接收的参数,第二个参数是返回给客户端的参数,第二个参数必须是指针类型的函数还要有一个返回值error
                            if args.B == 0 {
                                return errors.New("divide by zero")
                            }
                            quo.Quo = args.A / args.B
                            quo.Rem = args.A % args.B
                            return nil
                        }
                        func main() {
                            arith := new(Arith)
                            rpc.Register(arith)
                            rpc.HandleHTTP()
                            err := http.ListenAndServe(":1234", nil)
                            if err != nil {
                                fmt.Println(err.Error())
                            }
                        }

通过上面的例子可以看到,我们注册了一个Arith的RPC服务,然后通过rpc.HandleHTTP函数把该服务注册到了HTTP协议上,然后我们就可以利用http的方式来传递数据了。

http客户端代码:

                        package main                        
                        import (
                            "fmt"
                            "log"
                            "net/rpc"
                            "os"
                        )
                        type Args struct {
                            A, B int
                        }
                        type Quotient struct {
                            Quo, Rem int
                        }
                        func main() {
                            if len(os.Args) != 2 {
                                fmt.Println("Usage: ", os.Args[0], "server")
                                os.Exit(1)
                            }
                            serverAddress := os.Args[1] //接收客户端输入的数据
                            client, err := rpc.DialHTTP("tcp", serverAddress+":1234")
                            if err != nil {
                                log.Fatal("dialing:", err)
                            }
                            // Synchronous call
                            args := Args{17, 8}
                            var reply int
                            err = client.Call("Arith.Multiply", args, &reply)//发送数据
                            if err != nil {
                                log.Fatal("arith error:", err)
                            }
                            fmt.Printf("Arith: %d*%d=%d
", args.A, args.B, reply)//打印结果
                            var quot Quotient
                            err = client.Call("Arith.Divide", args, &quot)//发送数据
                            if err != nil {
                                log.Fatal("arith error:", err)
                            }
                            fmt.Printf("Arith: %d/%d=%d remainder %d
", args.A, args.B, quot.Quo, quot.Rem)
                        }


2.tcp的服务端代码如下:

                        package main                        
                        import (
                            "errors"
                            "fmt"
                            "net"
                            "net/rpc"
                            "os"
                        )
                        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, quo *Quotient) error {
                            if args.B == 0 {
                                return errors.New("divide by zero")
                            }
                            quo.Quo = args.A / args.B
                            quo.Rem = args.A % args.B
                            return nil
                        }
                        func main() {
                            arith := new(Arith)
                            rpc.Register(arith)
                            tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
                            checkError(err)
                            listener, err := net.ListenTCP("tcp", tcpAddr)
                            checkError(err)
                            for {
                                conn, err := listener.Accept()
                                if err != nil {
                                    continue
                                }
                                rpc.ServeConn(conn)
                            }
                        }
                        func checkError(err error) {
                            if err != nil {
                                fmt.Println("Fatal error ", err.Error())
                                os.Exit(1)
                            }
                        }

上面这个代码和http的服务器相比,不同在于:在此处我们采用了TCP协议,然后需要自己控制连接,当有客户端连接上来后,我们需要把这个连接交给rpc来处理。

如果你留心了,你会发现这它是一个阻塞型的单用户的程序,如果想要实现多并发,那么可以使用goroutine来实现,

tcp的客户端代码:

                            package main                            
                            import (
                                "fmt"
                                "log"
                                "net/rpc"
                                "os"
                            )
                            type Args struct {
                                A, B int
                            }
                            type Quotient struct {
                                Quo, Rem int
                            }
                            func main() {
                                if len(os.Args) != 2 {
                                    fmt.Println("Usage: ", os.Args[0], "server:port")
                                    os.Exit(1)
                                }
                                service := os.Args[1]
                                client, err := rpc.Dial("tcp", service)
                                if err != nil {
                                    log.Fatal("dialing:", err)
                                }
                                // Synchronous call
                                args := Args{17, 8}
                                var reply int
                                err = client.Call("Arith.Multiply", args, &reply)
                                if err != nil {
                                    log.Fatal("arith error:", err)
                                }
                                fmt.Printf("Arith: %d*%d=%d
", args.A, args.B, reply)
                                var quot Quotient
                                err = client.Call("Arith.Divide", args, &quot)
                                if err != nil {
                                    log.Fatal("arith error:", err)
                                }
                                fmt.Printf("Arith: %d/%d=%d remainder %d
", args.A, args.B, quot.Quo, quot.Rem)
                            }
                            //这个客户端代码和http的客户端代码对比,唯一的区别一个是DialHTTP,一个是Dial(tcp),其他处理一模一样


3.JSON RPC是数据编码采用了JSON,而不是gob编码,其他和上面介绍的RPC概念一模一样,下面我们来演示一下,如何使用Go提供的json-rpc标准包,请看服务端代码的实现:

                        package main                        
                        import (
                            "errors"
                            "fmt"
                            "net"
                            "net/rpc"
                            "net/rpc/jsonrpc"
                            "os"
                        )
                        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, quo *Quotient) error {
                            if args.B == 0 {
                                return errors.New("divide by zero")
                            }
                            quo.Quo = args.A / args.B
                            quo.Rem = args.A % args.B
                            return nil
                        }
                        func main() {
                            arith := new(Arith)
                            rpc.Register(arith)
                            tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
                            checkError(err)
                            listener, err := net.ListenTCP("tcp", tcpAddr)
                            checkError(err)
                            for {
                                conn, err := listener.Accept()
                                if err != nil {
                                    continue
                                }
                                jsonrpc.ServeConn(conn)
                            }
                        }
                        func checkError(err error) {
                            if err != nil {
                                fmt.Println("Fatal error ", err.Error())
                                os.Exit(1)
                            }
                        }

通过示例我们可以看出 json-rpc是基于TCP协议实现的,目前它还不支持HTTP方式。


json客户端的实现代码:


                        package main                        
                        import (
                            "fmt"
                            "log"
                            "net/rpc/jsonrpc"
                            "os"
                        )
                        type Args struct {
                            A, B int
                        }
                        type Quotient struct {
                            Quo, Rem int
                        }
                        func main() {
                            if len(os.Args) != 2 {
                                fmt.Println("Usage: ", os.Args[0], "server:port")
                                log.Fatal(1)
                            }
                            service := os.Args[1]
                            client, err := jsonrpc.Dial("tcp", service)
                            if err != nil {
                                log.Fatal("dialing:", err)
                            }
                            // Synchronous call
                            args := Args{17, 8}
                            var reply int
                            err = client.Call("Arith.Multiply", args, &reply)
                            if err != nil {
                                log.Fatal("arith error:", err)
                            }
                            fmt.Printf("Arith: %d*%d=%d
", args.A, args.B, reply)
                            var quot Quotient
                            err = client.Call("Arith.Divide", args, &quot)
                            if err != nil {
                                log.Fatal("arith error:", err)
                            }
                            fmt.Printf("Arith: %d/%d=%d remainder %d
", args.A, args.B, quot.Quo, quot.Rem)
                        }


   

备案编号:赣ICP备15011386号

联系方式:qq:1150662577    邮箱:1150662577@qq.com