=Start=
缘由:
学习、整理一下如何用Go语言进行TCP socket编程,方便以后需要的时候进行参考和使用。
正文:
参考解答:
基于TCP(面向连接)的socket编程,分为客户端和服务器端。
客户端的流程如下:
(1)创建套接字(socket)
(2)向服务器发出连接请求(connect)
(3)和服务器端进行通信(send/recv)
(4)关闭套接字(close)
服务器端的流程如下:
(1)创建套接字(socket)
(2)将套接字绑定到一个本地地址和端口上(bind)
(3)将套接字设为监听模式,准备接收客户端请求(listen)
(4)等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)
(5)用返回的套接字和客户端进行通信(send/recv)
(6)返回,等待另一个客户请求
(7)关闭套接字(close)
从TCP socket诞生后,网络编程架构模型也几经演化,大致是:“每进程一个连接” –> “每线程一个连接” –> “Non-Block + I/O多路复用(linux epoll/windows iocp/freebsd darwin kqueue/solaris Event Port)”。伴随着模型的演化,服务程序愈加强大,可以支持更多的连接,获得更好的处理性能。
目前主流Web Server一般均采用的都是”Non-Block + I/O多路复用”(有的也结合了多线程、多进程)。不过I/O多路复用也给使用者带来了不小的复杂度,以至于后续出现了许多高性能的I/O多路复用框架, 比如libevent、libev、libuv等,以帮助开发者简化开发复杂性,降低心智负担。不过Go的设计者似乎认为I/O多路复用的这种通过回调机制割裂控制流 的方式依旧复杂,且有悖于“一般逻辑”设计,为此Go语言将该“复杂性”隐藏在Runtime中了:Go开发者无需关注socket是否是 non-block的,也无需亲自注册文件描述符的回调,只需在每个连接对应的goroutine中以“block I/O”的方式对待socket处理即可,这可以说大大降低了开发人员的心智负担。一个典型的Go Server端程序大致如下:
func handleConn(c net.Conn) { 
    defer c.Close() 
    for { 
        // read from the connection 
        // ... ... 
        // write to the connection 
        //... ... 
    } 
} 
func main() { 
    l, err := net.Listen("tcp", ":8888") 
    if err != nil { 
        fmt.Println("listen error:", err) 
        return 
    }
    defer l.Close()
    for { 
        c, err := l.Accept() 
        if err != nil { 
            fmt.Println("accept error:", err) 
            break 
        } 
        // start a new goroutine to handle 
        // the new connection. 
        go handleConn(c) 
    } 
}
上面的内容可能更偏概念性的描述,下面通过几个简单的例子帮助建立起直观的认识。
客户端代码:
package main
import (
    "bufio"
    "fmt"
    "net"
    "os"
    "strconv"
)
const (
    SERVER_IP       = "127.0.0.1"
    SERVER_PORT     = 10006
    SERVER_RECV_LEN = 10
)
func checkError(err error) {
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}
func main() {
    address := SERVER_IP + ":" + strconv.Itoa(SERVER_PORT)
    fmt.Printf("value of address: %v (%T)\n\n", address, address)
    conn, err := net.Dial("tcp", address)
    checkError(err)
    defer conn.Close()
    input := bufio.NewScanner(os.Stdin)
    for input.Scan() {
        line := input.Text()
        lineLen := len(line)
        n := 0
        for written := 0; written < lineLen; written += n {
            var toWrite string
            if lineLen-written > SERVER_RECV_LEN {
                toWrite = line[written : written+SERVER_RECV_LEN]
            } else {
                toWrite = line[written:]
            }
            n, err = conn.Write([]byte(toWrite))
            checkError(err)
            fmt.Println("Write:", toWrite)
            msg := make([]byte, SERVER_RECV_LEN)
            n, err = conn.Read(msg)
            checkError(err)
            fmt.Println("Response:", string(msg))
        }
    }
}
服务端(单进程/单线程/单goroutine)代码:
package main
import (
    "fmt"
    "net"
    "os"
    "strconv"
    "strings"
)
const (
    SERVER_IP       = "127.0.0.1"
    SERVER_PORT     = 10006
    SERVER_RECV_LEN = 10
)
func main() {
    address := SERVER_IP + ":" + strconv.Itoa(SERVER_PORT)
    listener, err := net.Listen("tcp", address)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    defer listener.Close()
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println(err)
            continue
        }
        defer conn.Close()
        fmt.Println("Client Info:", conn.RemoteAddr().String())
        for {
            data := make([]byte, SERVER_RECV_LEN)
            _, err = conn.Read(data)
            if err != nil {
                fmt.Println(err)
                break
            }
            strData := string(data)
            fmt.Println("Received:", strData)
            upper := strings.ToUpper(strData)
            _, err = conn.Write([]byte(upper))
            if err != nil {
                fmt.Println(err)
                break
            }
            fmt.Println("Send:", upper)
        }
    }
}
上面的服务端程序存在一个问题,那就是一次只能为一个客户端提供服务(for循环),如果一个客户端连接时间过长,可能会让服务器无法服务于其他的客户端,这就是拒绝服务攻击,最简单的解决这种问题的方式就是将conn的处理单独启动线程,在Go 语言中我们只需要借助于 go 方法启动一个routine即可。
package main
import (
    "fmt"
    "net"
    "os"
    "strconv"
    "strings"
)
const (
    SERVER_IP       = "127.0.0.1"
    SERVER_PORT     = 10006
    SERVER_RECV_LEN = 10
)
func main() {
    address := SERVER_IP + ":" + strconv.Itoa(SERVER_PORT)
    listener, err := net.Listen("tcp", address)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    defer listener.Close()
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println(err)
            continue
        }
        go handleConn(conn)
    }
}
func handleConn(conn net.Conn) {
    defer conn.Close()
    fmt.Println("Client Info:", conn.RemoteAddr().String())
    for {
        data := make([]byte, SERVER_RECV_LEN)
        _, err := conn.Read(data)
        if err != nil {
            fmt.Println(err)
            break
        }
        strData := string(data)
        fmt.Println("Received:", strData)
        upper := strings.ToUpper(strData)
        _, err = conn.Write([]byte(upper))
        if err != nil {
            fmt.Println(err)
            break
        }
        fmt.Println("Send:", upper)
    }
}
将客户端数据处理的部分封装成了一个handleConn函数,在与客户端连接建立时,通过Go 语言的go关键字调用handleConn函数,启动一个新的协程进行处理,这样,main函数会立刻重新阻塞在Accept上,从而保证服务器可以时刻监听客户端连接的建立,提高了处理性能。
参考链接:
- golang TCP Socket编程
 - 利用golang通道优化TCP Socket服务器
 - Golang Socket编程
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.1.md - Go语言TCP Socket编程
https://github.com/bigwhite/experiments/tree/master/go-tcpsock - golang之TCP开发
 - https://golang.org/pkg/net/#example_Listener
https://golang.org/pkg/net/#TCPAddr
https://golang.org/pkg/net/#Dial
https://golang.org/pkg/net/#Listen 
=END=
《 “Go语言学习#17-如何进行TCP socket编程” 》 有 3 条评论
golang使用UDP进行网络通信
https://blog.csdn.net/yjp19871013/article/details/82316299
一个简单的Golang实现的Socket5 Proxy
http://www.flysnow.org/2016/12/26/golang-socket5-proxy.html
Go语言的变量、函数、Socks5代理服务器
https://blog.csdn.net/ithomer/article/details/78121431
Provides packet processing capabilities for Go
https://github.com/google/gopacket
Packet Capture, Injection, and Analysis with Gopacket
https://www.devdungeon.com/content/packet-capture-injection-and-analysis-gopacket
Go语言用GoPacket抓包分析
https://blog.csdn.net/u014762921/java/article/details/78275428
Golang调用gopacket库编写网络数据包捕获、注入、分析工具
https://blog.lfoder.cn/2018/06/17/Golang%E8%B0%83%E7%94%A8gopacket%E5%BA%93%E7%BC%96%E5%86%99%E7%BD%91%E7%BB%9C%E6%95%B0%E6%8D%AE%E5%8C%85%E6%8D%95%E8%8E%B7%E3%80%81%E6%B3%A8%E5%85%A5%E3%80%81%E5%88%86%E6%9E%90%E5%B7%A5%E5%85%B7/
Capture and parse http traffics
https://github.com/hsiafan/httpdump
Sniffing and parsing mysql,redis,http,mongodb etc protocol. 抓包截取项目中的数据库请求并解析成相应的语句。
https://github.com/40t/go-sniffer