Golang 同時監聽 TCP 與 HTTP 多個端口 提供服務

https://javasgl.github.io/golang-listen-tcp-and-http-port/

项目入口 main.go 中,之前是监听 TCP 连接,代码大致如下:

func main(){
 server,err:= net.Listen("tcp","host:port")
 if err!=nil{
  return
 }
 defer server.Close()

 for{
  conn,err:= server.Accept() 
  if err!=nil{
   continue
  }
  go handleConn(conn)
 }
}

func handleConn(conn net.Conn){
 //do somethings
}

现在需要在此基础之上监听 http 连接,由于 http.ListenAndServe() 方法是阻塞的,所以需要新开goroutine进行监听,代码示意如下:

func main(){

 http.HandleFunc("/hi", Router)
 //因为会阻塞,所以需要新开goroutine进行监听
 go http.ListenAndServe("host:http_port", nil)

 server,err:= net.Listen("tcp","host:tcp_port")
 if err!=nil{
  return
 }
 defer server.Close()

 for{
  conn,err:= server.Accept() 
  if err!=nil{
   continue
  }
  go handleConn(conn)
 }
}

func handleConn(conn net.Conn){
 //do somethings
}



Golang 学习笔记:HTTP, TCP/IP, UDP
http://blog.wuxu92.com/go-lang-notebook-2/

應詳閱出處,相當詳細

import (
 . "fmt"
 "net/http"
)

const (
 PORT = ":1024"
 MSG = "hello, gopher"
)
func main() {
 http.HandleFunc("/hello", Hello)
 go func() {
  http.ListenAndServe(PORT, nil)
 }()
 
 go func() {
  http.ListenAndServeTLS(":443","cert.pem", "key.pem", nil)
 }()
 for {}
}
func Hello(w http.ResponseWriter, r *http.Request) {
 w.Header().Set("Content-Type", "text/plain")
 Fprintf(w, MSG)
}

这段代码启动了一个server,开启了http和https支持;分别监听在1024端口和443端口。
有几个需要注意的地方。
  1. http.HandleFunc(string, func(http.ResponseWriter, *http.Request)) 绑定某个请求路径的处理方法
  2. 使用goroutine执行监听与服务动作。启动了两个goroutine
  3. 最后使用一个for循环保证主线程不会退出。因为主线程退出的话goroutine也会结束。
  4. 其中的TLS支持的HTTPS服务的cert和key文件可以使用Go源码中带的文件生成go run $GOROOT/src/crypto/tls/generate_cert.go -ca=true -host="localhost"
最后一个无限循环可以使用channel代替,可以使用sync.WaitGroup实现, sync.WaitGroup持有一个计数器,其Add(int)方法增加计数器,其Done()方法减小计数器,其Wait()方法会让线程阻塞等待计数器恢复0。其实现如下。
import (
 . "fmt"
 "net/http"
 "sync"
)

const (
 PORT = ":1024"
 MSG = "hello, gopher"
)
var servers sync.WaitGroup

func main() {
 http.HandleFunc("/hello", Hello)
 Launch(func() {
  http.ListenAndServe(PORT, nil)
 })
 
 Launch(func() {
  http.ListenAndServeTLS(":443","cert.pem", "key.pem", nil)
 })
 
 servers.Wait()
}

func Launch(f func()) {
 servers.Add(1)
 go func() {
  defer servers.Done()
  f()
 }()
}

func Hello(w http.ResponseWriter, r *http.Request) {
 w.Header().Set("Content-Type", "text/plain")
 Fprintf(w, MSG)
}
使用上面的方法可以添加任意多的监听端口。可以通过Server做更多的配置。
s := &http.Server{
 Addr:           ":8080",
 Handler:        myHandler,
 ReadTimeout:    10 * time.Second,
 WriteTimeout:   10 * time.Second,
 MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())

HTTP与Go程结合

我们对Goroutine和HTTP都比较熟悉了,我们可以设置一个任务队列,让每一个请求都进入这个队列然后使用一定数量的Goroutine去执行逻辑。这样效率比上面的实现要高很多。

发送GET/POST请求

http保重提供了非常方便的发发送get请求和post请求的封装。
resp, err := http.Get("http://example.com/")
...
resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
...
resp, err := http.PostForm("http://example.com/form",
 url.Values{"key": {"Value"}, "id": {"123"}})

defer resp.Body.Close()  // 不要忘记defer关闭资源
body, err := ioutil.ReadAll(resp.Body)  // 读取资源
strCont := string(body)

环境变量

使用os包中的Getenv(string)Setenv(string, string)方法获得和设置环境变量。要注意Setenv的作用域只是当前运行进程,结束后或者重新运行后环境变量会重置为系统设置值。
Println(os.Getenv("PATH"))
os.Setenv("GOOO", "gofast")
os.Setenv("PATH", ".")
Println(os.Getenv("PATH"))

操作系统信号

当程序在命令行中运行的时候,一般使用Ctrl+c可以终结程序运行。对于操作系统有一定了解的话应该知道,Ctrl+c向运行的进程发送了一个结束进程的signal。这种方式很有用,但是有点时候暴力结束进程执行不是我们想要的逻辑,我们可以捕获这个termination signal,然后对他做一些自己的处理。注意,windows下不能捕获这些信号,只在*nix下有效。
使用os/signal包提供的封装。
package main

import (
 "fmt"
 "os"
 "os/signal"
 "time"
)
var i int

func init() {
 go SignalHandler(make(chan os.Signal, 1))
}
func main() {
 i = 0
 for {
  i++
  time.Sleep(time.Second)
 }
}

func SignalHandler(c chan os.Signal) {
 signal.Notify(c, os.Interrupt)
 for s:= <-c;; s= <-c {
  switch s {
   case os.Interrupt:
    fmt.Println("interrupt received", i)
    os.Exit(0)  // 去掉这一行,那么Ctrl+c的快捷键将不能中断进程运行
   default:
    fmt.Println("default case")
  }
 }
}
运行上面的代码,在Ctrl-c时可以看到输出 interrupt received 字符串。
除了接收系统的信号,还可以向其他的进行发送信号。
syscall.Kill(syscall.Getpid(), syscall.SIGABRT)
这一部分可能暂时用不上,只要知道有这种用法就可以了。

TCP/IP

使用net提供的TCP/IP封装可以很方便的监听与请求端口。
server(src/wuxu/tcpserver/main.go):
package main

import (
 "net"
 "fmt"
)
var i int
func main() {
 i = 0
 if listener, e := net.Listen("tcp", ":1024"); e == nil {
  for {
   if conn, e := listener.Accept(); e==nil {
    go func(c net.Conn) {
     defer c.Close()
     fmt.Fprintln(c, "hihi", i)
     i++
    }(conn)
   }
  }
 } else {
  fmt.Println("error")
 }
}
client(src/wuxu/tcpclient/main.go)
package main

import (
 "fmt"
 "net"
 "bufio"
)

func main() {
 if conn, e := net.Dial("tcp", "localhost:1024"); e == nil {
  defer conn.Close()
  if text, e := bufio.NewReader(conn).ReadString('\n'); e ==nil {
   fmt.Println(text)
  }
 } else {
  fmt.Println("error", e)
 }
}

加密的TCP通信

还可以很方便地简历加密的连接,为了加密连接我们需要两套密钥对,即服务器的公钥与私钥,客户端的公钥与私钥,使用之前的方法再在服务器和客户端的目录下,各生成一对密钥。go run $GOROOT/src/crypto/tls/generate_cert.go -ca=true -host="localhost"
修改代码如下:
server:
package main

import (
 "fmt"
 "crypto/tls"
 "crypto/rand"
)
var i int
func main() {
 i = 0
 var config tls.Config
 if certificate, e := tls.LoadX509KeyPair("cert.pem", "key.pem"); e ==nil {
  config = tls.Config{
   Certificates: []tls.Certificate{certificate},
   Rand: rand.Reader,
  }
 }
 if listener, e := tls.Listen("tcp", ":1024", &config); e == nil {
  for {
   if conn, e := listener.Accept(); e==nil {
    go func(c *tls.Conn) {
     defer c.Close()
     fmt.Fprintln(c, "secure hihi", i)
     i++
    }(conn.(*tls.Conn))
   }
  }
 } else {
  fmt.Println("error")
 }
}
可以看出,引入tls包后不需要net包了,tls实现了相关得了协议的加密版本。
client:
package main

import (
 "fmt"
 "bufio"
 "crypto/tls"
)

func main() {
 var config tls.Config
 if certificate, e := tls.LoadX509KeyPair("cert.pem", "key.pem"); e == nil {
  config = tls.Config{
   Certificates: []tls.Certificate{certificate},
   InsecureSkipVerify: true,
  }
 }
 if conn, e := tls.Dial("tcp", "localhost:1024", &config); e == nil {
  defer conn.Close()
  if text, e := bufio.NewReader(conn).ReadString('\n'); e ==nil {
   fmt.Println(text)
  }
 } else {
  fmt.Println("error", e)
 }
}
和使用net包的整体流程差不多,使用tls包的实现替换net包。
这样我们就实现了一个使用tls密钥加密的传输通道了。

UDP

UDP的使用和TCP类似也包括一种加密的UDP传输。具体UDP和TCP的区别就不详细说了,这部分是考试面试的重点,一般都能说个123出来。
与TCP实现不一样的是,UDP要先使用net.ResolveUDPAddr(string, string)来获得一个*net.UDPAddr,然后使用net.ListenUDP("udp", addr)监听这个端口的UDP通信。一个简单实现如下:
import (
 . "fmt"
 "net"
 "os"
)

var MSG = ([]byte)("hello golong udp\n")

func main() {
 if addr, e := net.ResolveUDPAddr("udp", ":1026"); e == nil {
  if server, e := net.ListenUDP("udp", addr); e == nil {
   // infinit loop
   for buffer := MakeBuffer(); ; buffer = MakeBuffer() {
    if n, client, e := server.ReadFromUDP(buffer); e == nil {
     go func( c *net.UDPAddr, packet []byte) {
      if n, e := server.WriteToUDP(MSG, c); e == nil {
       Printf("%v bytes written to %v\n", n, c)
      }
     }(client, buffer[:n])
    } else {
     Println("error1", e)
    }
   }
  } else {
   Println("error2", e)
  }
 } else {
  Println("error3", e)
 }
}

func MakeBuffer() ([]byte) {
 return make([]byte, 1024)
}
作为server需要一个无限循环保持监听,每当收到一个客户端请求(ReadFromUDP),就启动一个Goroutine服务这个请求(WriteToUDP)。
UDP不想TCP服务可以用telnet连接,因为它是一个无连接的通信,我们需要自己写一个请求的客户端。client也比较简单,其实现如下:
import (
 . "fmt"
 "net"
 "bufio"
)

var CRLF = ([]byte)("\n")

func main() {
 if addr, e := net.ResolveUDPAddr("udp", "localhost:1026"); e == nil {
  if server, e := net.DialUDP("udp", nil, addr); e == nil {
   defer server.Close()
   // 随意发送一些内容到server等待回应
   // 这里可以多次(for 循环写都可以)写内容到服务器
   if _, e := server.Write(CRLF); e == nil {
    if text, e := bufio.NewReader(server).ReadString('\n'); e == nil {
     Printf("%v", text)
    }
   }
  } else {
   Println("error2", e)
  }
 } else {
  Println("error3", e)
 }
}
这里注意注释里面的话,使用Write()方法向UDP Server发送一些内容,然后才会受到Server的返回,返回使用bufio读取。一个UDP的Dial实例可以多起发送数据和读取返回。

使用加密的UDP

Golang提供了很方便的加密UDP连接,只需要提供一个密钥,就可以对传输的数据进行加密了,当然我们需要对server和client的数据都进行修改。
rsa encrypt server:
import (
 . "fmt"
 "bytes"
 "crypto/rand"
 "crypto/rsa"
 "crypto/sha1"
 "encoding/gob"
 . "net"
)

var MSG = []byte("hello go scure udp")
var RSA_LABEL = []byte("server")

func main() {
 Serve(":1025", func(conn *UDPConn, c *UDPAddr, packet *bytes.Buffer) (n int) {
  var key rsa.PublicKey
  if e := gob.NewDecoder(packet).Decode(&key); e==nil {
   if resp, e := rsa.EncryptOAEP(sha1.New(), rand.Reader, &key, MSG, RSA_LABEL); e==nil {
    n, _ = conn.WriteToUDP(resp, c)
   }
  }
  return
 })
}

func Serve(addr string, f func(*UDPConn, *UDPAddr, *bytes.Buffer) int) {
 Launch(addr, func(conn *UDPConn) {
  for {
   buffer := make([]byte, 1024)
   if n, client, e := conn.ReadFromUDP(buffer); e == nil {
    go func(c *UDPAddr, b []byte) {
     if n := f(conn, c, bytes.NewBuffer(b)); n!=0 {
      Println(n, "write to ", c)
     }
    }(client, buffer[:n])
   }
  }
 })
}

func Launch(addr string, f func(*UDPConn)) {
 if a, e := ResolveUDPAddr("udp", addr); e == nil {
  if server, e := ListenUDP("udp", a); e == nil {
   f(server)
  }
 }
}
rsa used client
import (
 . "fmt"
 "bytes"
 "crypto/rand"
 "crypto/rsa"
 "crypto/sha1"
 "crypto/x509"
 "encoding/gob"
 "encoding/pem"
 "io/ioutil"
 . "net"
)

var RSA_LABEL = []byte("server")

func main() {
 Connect("localhost:1025", func(server *UDPConn, priKey *rsa.PrivateKey) {
  cipherText := MakeBuffer()
  if n, e := server.Read(cipherText); e == nil {
   Println("cipher text", string(cipherText))
   // cipherText是加密的数据,需要解密
   if plainText, e := rsa.DecryptOAEP(sha1.New(), rand.Reader, priKey, cipherText[:n], RSA_LABEL); e == nil {
    Println("receive decrypt string:", string(plainText))
   }
  }
 })
}

func Connect(address string, f func(*UDPConn, *rsa.PrivateKey)) {
 LoadPrivateKey("key.pem", func(pk *rsa.PrivateKey) { // pk is private key
  if addr, e := ResolveUDPAddr("udp", address); e ==nil {
   if server, e := DialUDP("udp", nil, addr); e == nil {
    defer server.Close()
    SendKey(server, pk.PublicKey, func() {
     f(server, pk)
    })
   } else {
    Println("err1", e)
   }
  } else {
   Println("err2", e)
  }
 })
}

func LoadPrivateKey(file string, f func(*rsa.PrivateKey)) {
 if file, e := ioutil.ReadFile(file); e == nil {
  if block, _ := pem.Decode(file); block != nil {
   if block.Type == "RSA PRIVATE KEY" {
    if key, _ := x509.ParsePKCS1PrivateKey(block.Bytes); key !=nil {
     f(key)
    }
   }
  }
 } else {
  Println(e)
 }
 return
}

func SendKey(server *UDPConn, publicKey rsa.PublicKey, f func()) {
 var encodedKey bytes.Buffer
 if e := gob.NewEncoder(&encodedKey).Encode(publicKey); e == nil {
  if _, e = server.Write(encodedKey.Bytes()); e == nil {
   f()
  }
 }
}

func MakeBuffer() (r []byte) {
 return make([]byte, 1024)
}
需要注意的几点:
  • RSA_LABEL 是rsa加密使用的一个label,需要客户端和服务器端一致
  • 请求流程是客户端读取一个密钥,然后获取一个publickey,使用gob序列化后发送到server, server读取出key,使用这个publicKey对返回的数据进行加密
  • 客户端读取返回后,使用密钥进行解密,读取明文
  • 客户端代码使用了大量的回调,方法字面量,理解起来有点麻烦,可以用javascript的回调的思路去理解


程式語言編年史

程式語言編年史原文 下面這張圖片描繪了整個程式語言的歷史。包括各種程式語言的發明人、程式語言的特點和適用領域、被什麼網站或公司使用等 (檢視 完整高清圖 )。 之所以會有那麼多不同的程式語言是因為設計程式語言的初衷不同、對語言學習曲線的追求不同、不同程式之間的執行成本差異...