Golang 提供 net 库用来处理网络相关协议
基础示例
原生 net
实现
package main
import (
"fmt"
"net"
)
func main() {
host := "0.0.0.0"
port := "8888"
addr := net.JoinHostPort(host, port)
fmt.Println(addr) // 0.0.0.0:8888
fmt.Println(net.SplitHostPort(addr)) // 0.0.0.0 8888 <nil>
// 获取本地所有 IP 信息
fmt.Println("Interfaces:")
ifaces, err := net.Interfaces()
for _, iface := range ifaces {
fmt.Println(iface.Index, iface.Name, iface.MTU, iface.Flags, iface.HardwareAddr)
fmt.Println(iface.Addrs())
fmt.Println(iface.MulticastAddrs())
}
fmt.Println("InterfaceAddrs:")
addrs, err := net.InterfaceAddrs()
for _, addr := range addrs {
fmt.Println(addr.String(), addr.Network())
}
host = "www.xiexianbin.cn"
// 根据域名查找 IP
var ips []net.IP
ips, err = net.LookupIP(host)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(ips)
fmt.Println(net.LookupHost(host))
// 解析 txt 记录
mxs, err := net.LookupMX("xiexianbin.cn")
if err != nil {
fmt.Println(err)
}
for _, mx := range mxs {
//&net.MX{Host:"mxn.mxhichina.com.", Pref:0x5}
//&net.MX{Host:"mxw.mxhichina.com.", Pref:0xa}
fmt.Printf("%#v\n", mx)
}
txts, err := net.LookupTXT("_dnsauth.ca.xiexianbin.cn ")
if err != nil {
fmt.Println(err)
}
for _, txt := range txts {
fmt.Printf("%#v\n", txt)
}
// cidr
ip, ipnet, err := net.ParseCIDR("172.20.0.1/24")
fmt.Println(ip, ipnet, err) // 172.20.0.1 172.20.0.0/24 <nil>
fmt.Println(ipnet.Contains(net.ParseIP("172.20.0.10"))) // true
fmt.Println(ipnet.Contains(net.ParseIP("172.20.1.1"))) // false
fmt.Println(ipnet.Network()) // ip+net
for _, s := range []string{"172.20.0.1", "::1", "foo.ip"} {
ip := net.ParseIP(s)
fmt.Println(ip) // [172.20.0.1 ::1 <nil>]
if ip != nil {
fmt.Println(ip.IsLinkLocalMulticast())
fmt.Println(ip.IsLoopback())
fmt.Println(ip.IsGlobalUnicast())
fmt.Println(ip.IsPrivate())
}
}
// 根据 IP 查找域名
names, err := net.LookupAddr(ips[0].String())
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(names)
}
服务端
package main
import (
"bufio"
"fmt"
"io"
"net"
"strconv"
"strings"
"time"
)
func response1(conn net.Conn) {
defer conn.Close()
n, err := conn.Write([]byte(time.Now().Format("2006-01-02 15:04:06")))
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("sent to client", conn.RemoteAddr(), n, "bytes.")
}
func response2(conn net.Conn) {
defer conn.Close()
var err error
var n int
bs := make([]byte, 1024)
for {
n, err = conn.Read(bs)
if err != nil {
fmt.Println()
return
}
req := strings.Join([]string{"receive from", conn.RemoteAddr().String(), "data is", string(bs[:n]), ",len is", strconv.Itoa(n), "bytes."}, " ")
fmt.Println(req)
resp := strings.Join([]string{time.Now().Format("2006-01-02 15:04:06"), req}, " ")
n, err = conn.Write([]byte(resp))
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("sent to client", conn.RemoteAddr(), n, "bytes.")
}
}
func response3(conn net.Conn) {
defer conn.Close()
defer func() {
fmt.Println("conn", conn.RemoteAddr(), "is closed.")
}()
reader := bufio.NewReader(conn)
writer := bufio.NewWriter(conn)
for {
_, err := writer.WriteString(fmt.Sprintf("client from %s\n", conn.RemoteAddr().String()))
if err != nil {
fmt.Println(err.Error())
}
_ = writer.Flush()
str, err := reader.ReadString('\n')
if err != nil {
fmt.Println(err.Error())
if err == io.EOF {
break
}
return
}
fmt.Printf("receive from %s, data is %s.\n", conn.RemoteAddr(), str)
}
}
func main() {
addr := "0.0.0.0:8888" // ":8888"
var err error
listen, err := net.Listen("tcp", addr)
if err != nil {
fmt.Println(err.Error())
return
}
defer listen.Close()
fmt.Println("listen on", addr)
for {
// 等待客户端连接
conn, err := listen.Accept()
if err != nil {
return
}
//go response1(conn)
//go response2(conn)
go response3(conn)
}
}
客户端
package main
import (
"bufio"
"fmt"
"net"
"os"
)
func main() {
var err error
addr := "127.0.0.1:8888" // ":8888"
conn, err := net.Dial("tcp", addr)
if err != nil {
fmt.Println(err.Error())
os.Exit(-1)
}
defer conn.Close()
var send string
reader := bufio.NewReader(conn)
writer := bufio.NewWriter((conn))
//for {
// fmt.Print("please input which to send:")
// _, _ = fmt.Scan(&send)
// n, err = conn.Write([]byte(send + "\n"))
// if err != nil {
// fmt.Println(err.Error())
// return
// }
// fmt.Println("success send", n, "bytes to", conn.RemoteAddr())
//
// bs := make([]byte, 1024)
// n, err := conn.Read(bs)
// if err != nil {
// fmt.Println(err.Error())
// return
// }
// fmt.Println("success receive:", string(bs[:n]))
//}
for {
str, _ := reader.ReadString('\n')
fmt.Println("success receive:", str)
fmt.Print("please input which to send(q:exit):")
// 从命令行读取数据
// 方法一
//_, _ = fmt.Scan(&send)
// 方法二:只读一行数据,否则要使用 for
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
send = scanner.Text()
if send == "q" {
break
}
_, _ = writer.WriteString(fmt.Sprintf("%s\n", send))
_ = writer.Flush()
fmt.Println("success send to", conn.RemoteAddr())
}
}
httpclient
package main
import (
"bufio"
"fmt"
"io"
"net"
)
func main() {
addr := "www.baidu.com:80"
//addr := "www.xiexianbin.cn:80"
//addr := "127.0.0.1:8888"
dial, err := net.Dial("tcp", addr)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("connect to", dial.RemoteAddr())
reqStr := fmt.Sprintf("GET /index.html HTTP/1.1\r\n:authority: www.baidu.com\r\n:method: GET\r\n:path: /index.html\r\naccept: text/html,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nuser-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36\r\n\r\n")
fmt.Fprintf(dial, reqStr)
//bs := make([]byte, 1024)
//for {
// n, err := dial.Read(bs)
// if err != nil {
// if err == io.EOF {
// break
// }
// fmt.Println(err.Error())
// return
// }
// fmt.Print(string(bs[:n]))
reader := bufio.NewReader(dial)
for {
str, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
fmt.Println(err)
return
}
fmt.Print(str)
}
}
跳过 SSL 证书认证
package main
import (
"fmt"
"net/http"
"crypto/tls"
)
func main() {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
_, err := client.Get("https://golang.org/")
if err != nil {
fmt.Println(err)
}
}
netClient 公共方法
// WebhookClient exists to mock the client in tests.
type WebhookClient interface {
Do(req *http.Request) (*http.Response, error)
}
var netTransport = &http.Transport{
TLSClientConfig: &tls.Config{
Renegotiation: tls.RenegotiateFreelyAsClient,
},
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
}
var netClient WebhookClient = &http.Client{
Timeout: time.Second * 30,
Transport: netTransport,
}
// 其他方法内使用
func x() {
request, err := http.NewRequestWithContext(ctx, "Get", "URL", bytes.NewReader([]byte("{}")))
resp, err := netClient.Do(request)
...
}
Web 示例
基于 net/http
实现
httpserver
纯原生实现 Http Server:go-httpserver
package main
import (
"bytes"
"fmt"
"io"
"log"
"net/http"
"time"
)
// Home Page, 实现 handler
func home(w http.ResponseWriter, r *http.Request) {
//n, err := resp.Write([]byte("home page"))
n, err := io.WriteString(w, "home page")
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("send to client", n, "bytes.")
}
// Post 处理器,实现 http.Handler interface
type Post struct{}
func (p Post) ServeHTTP(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, time.Now().Format("2006-01-02 15:04:05"))
}
type Info struct{}
func (info Info) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Printf("%#v\n", r.URL)
fmt.Println(r.RemoteAddr)
w.WriteHeader(200)
_, _ = io.WriteString(w, fmt.Sprintf("%s %s %s\r\n", r.Method, r.RequestURI, r.Proto))
for k, v := range r.Header {
_, _ = io.WriteString(w, fmt.Sprintf("%s: %s\r\n", k, v))
}
io.WriteString(w, "\r\n")
fmt.Println("req body is:")
//bs := make([]byte, 1024)
//for {
// n, err := r.Body.Read(bs)
// if n != 0 {
// fmt.Print(string(bs[:n]))
// _, _ = w.Write(bs[:n])
// }
// if err != nil {
// if err != io.EOF {
// fmt.Println(err.Error())
// }
// break
// }
//}
//io.Copy(os.Stdout, r.Body)
//io.Copy(w, r.Body)
buf := bytes.NewBuffer(nil)
io.Copy(buf, r.Body)
fmt.Println(buf.String())
io.Copy(w, buf)
}
func main() {
addr := "0.0.0.0:8000"
// 面向过程实现
http.HandleFunc("/", home)
// 面向对象实现
//http.Handle("/post/", Post{})
http.Handle("/post/", &Post{})
// curl -XPOST http://0.0.0.0:8000/info -d "hello world"
http.Handle("/info", &Info{})
fmt.Println("start server, listen on", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}
fileserver
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
addr := "0.0.0.0:8000"
// http.Dir(".") 为类型转换
http.Handle("/", http.FileServer(http.Dir(".")))
fmt.Println("start server, listen on", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}
httpclient
package main
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"net/http"
"net/url"
"os"
)
func testGet() {
url := "https://www.xiexianbin.cn"
resp, err := http.Get(url)
if err != nil {
fmt.Println(err)
return
}
buf := bytes.NewBuffer(nil)
buf.WriteString(fmt.Sprintf("%s %s\r\n", resp.Proto, resp.Status))
for k, v := range resp.Header {
buf.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
}
buf.WriteString("\r\n")
io.Copy(buf, resp.Body)
fmt.Println(buf.String())
}
// self sign ca
func testGet2() {
u := "https://s1.dddd.dev"
req, err := http.NewRequest("GET", u, nil)
if err != nil {
fmt.Println(err)
return
}
transport := http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
client := &http.Client{Transport: &transport}
//client.Get()
//client.Post()
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(resp.StatusCode)
io.Copy(os.Stdout, resp.Body)
}
func testPost() {
// curl -v -XPOST "https://api.github.com/gists" -H "content-type: application/json" -H "Accept: application/vnd.github.v3+json" -d "{}"
url := "https://api.github.com/gists"
body := bytes.NewReader([]byte("{}"))
resp, err := http.Post(url, "application/json", body)
if err != nil {
return
}
buf := bytes.NewBuffer(nil)
buf.WriteString(fmt.Sprintf("%s %s\r\n", resp.Proto, resp.Status))
for k, v := range resp.Header {
buf.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
}
buf.WriteString("\r\n")
io.Copy(buf, resp.Body)
fmt.Println(buf.String())
}
func testPostForm() {
// curl -v -XPOST "https://api.github.com/gists" -H "content-type: application/json" -H "Accept: application/vnd.github.v3+json" -d "{}"
u := "https://api.github.com/gists"
//var data url.Values = url.Values{}
data := make(url.Values)
data.Add("username", "xianbin")
data.Add("password", "123456")
resp, err := http.PostForm(u, data)
if err != nil {
return
}
buf := bytes.NewBuffer(nil)
buf.WriteString(fmt.Sprintf("%s %s\r\n", resp.Proto, resp.Status))
for k, v := range resp.Header {
buf.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
}
buf.WriteString("\r\n")
io.Copy(buf, resp.Body)
fmt.Println(buf.String())
}
func testDelete() {
u := "https://api.github.com/gists"
req, err := http.NewRequest("DELETE", u, nil)
if err != nil {
fmt.Println(err)
return
}
client := &http.Client{Transport: http.DefaultTransport}
//client.Get()
//client.Post()
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(resp.StatusCode, resp.Body)
}
func main() {
testGet()
testGet2()
testPost()
testPostForm()
testDelete()
}
package main
import (
"bytes"
"io"
"log"
"mime/multipart"
"net/http"
"os"
"strings"
"time"
)
func main() {
call("http://localhost:8080/employee", "POST")
}
func call(urlPath, method string) error {
client := &http.Client{
Timeout: time.Second * 10,
}
// New multipart writer.
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
fw, err := writer.CreateFormField("name")
if err != nil {
}
_, err = io.Copy(fw, strings.NewReader("John"))
if err != nil {
return err
}
fw, err = writer.CreateFormField("age")
if err != nil {
}
_, err = io.Copy(fw, strings.NewReader("23"))
if err != nil {
return err
}
fw, err = writer.CreateFormFile("photo", "test.png")
if err != nil {
}
file, err := os.Open("test.png")
if err != nil {
panic(err)
}
_, err = io.Copy(fw, file)
if err != nil {
return err
}
// Close multipart writer.
writer.Close()
req, err := http.NewRequest("POST", "http://localhost:8080/employee", bytes.NewReader(body.Bytes()))
if err != nil {
return err
}
req.Header.Set("Content-Type", writer.FormDataContentType())
rsp, _ := client.Do(req)
if rsp.StatusCode != http.StatusOK {
log.Printf("Request failed with response code: %d", rsp.StatusCode)
}
return nil
}
package main
import (
"bytes"
"flag"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
)
var (
filePath string
addr string
)
func init() {
flag.StringVar(&filePath, "file", "", "the file to upload")
flag.StringVar(&addr, "addr", "localhost:8080", "the addr of file server")
flag.Parse()
}
func main() {
if filePath == "" {
fmt.Println("file must not be empty")
return
}
err := doUpload(addr, filePath)
if err != nil {
fmt.Printf("upload file [%s] error: %s", filePath, err)
return
}
fmt.Printf("upload file [%s] ok\n", filePath)
}
func createReqBody(filePath string) (string, io.Reader, error) {
var err error
buf := new(bytes.Buffer)
bw := multipart.NewWriter(buf) // body writer
f, err := os.Open(filePath)
if err != nil {
return "", nil, err
}
defer f.Close()
// text part1
p1w, _ := bw.CreateFormField("name")
p1w.Write([]byte("Tony Bai"))
// text part2
p2w, _ := bw.CreateFormField("age")
p2w.Write([]byte("15"))
// file part1
_, fileName := filepath.Split(filePath)
fw1, _ := bw.CreateFormFile("file1", fileName)
io.Copy(fw1, f)
bw.Close() //write the tail boundry
return bw.FormDataContentType(), buf, nil
}
func doUpload(addr, filePath string) error {
// create body
contType, reader, err := createReqBody(filePath)
if err != nil {
return err
}
url := fmt.Sprintf("http://%s/upload", addr)
req, err := http.NewRequest("POST", url, reader)
// add headers
req.Header.Add("Content-Type", contType)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("request send error:", err)
return err
}
resp.Body.Close()
return nil
}
F&Q
loc := resp.Header.Get("Location")
if loc == "" {
// While most 3xx responses include a Location, it is not
// required and 3xx responses without a Location have been
// observed in the wild. See issues #17773 and #49281.
return resp, nil
}
loc := resp.Header.Get("Location")
if loc == "" {
resp.closeBody()
return nil, uerr(fmt.Errorf("%d response missing Location header", resp.StatusCode))
}