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) {
}
现在需要在此基础之上监听 http 连接,由于 http.ListenAndServe() 方法是阻塞的,所以需要新开goroutine进行监听,代码示意如下:
func main () {
http.HandleFunc("/hi" , Router)
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) {
}
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端口。
有几个需要注意的地方。
http.HandleFunc(string, func(http.ResponseWriter, *http.Request)) 绑定某个请求路径的处理方法
使用goroutine执行监听与服务动作。启动了两个goroutine
最后使用一个for循环保证主线程不会退出。因为主线程退出的话goroutine也会结束。
其中的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()
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 )
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 {
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()
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))
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) {
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的回调的思路去理解