本文内容纲要:
- 一、简介
- 二、socket编程
- 三、go中HTTP服务处理流程
- 简介
- Handler
- ServeMux
- Server
- 创建HTTP服务
- 路由注册
- 总结
- 四、HTTP客户端工具
- GET请求示例
- 使用http.Do设置请求头、cookie等
- POST请求示例
- PostForm请求示例
-
一、简介
go语言中的网络编程主要通过net包实现,net包提供了网络I/O接口,包括HTTP、TCP/IP、UDP、域名解析和Unix域socket等。和大多数语言一样go可以使用几行代码便可以启动一个服务器,但是得益于goroutine的配合go实现的服务器拥有强大并发处理能力。
二、socket编程
Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求。
socket本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。 我们知道网络 通信 都 是基于 ip+port 方能定位到目标的具体机器上的具体服务,操作系统有0-65535个端口,每个端口都可以独立对外提供服务,如果 把一个公司比做一台电脑 ,那公司的总机号码就相当于ip地址, 每个员工的分机号就相当于端口, 你想找公司某个人,必须 先打电话到总机,然后再转分机 。
go中socket编程实现起来非常方便,下面是处理流程
服务器端:
- 监听端口
- 接受客户端连接
- 创建goroutine处理连接
客户端:
- 建立连接
- 收发数据
- 关闭连接
服务端示例:
package main
import (
"fmt"
"net"
)
func handle(conn net.Conn) { //处理连接方法
defer conn.Close() //关闭连接
for{
buf := make([]byte,100)
n,err := conn.Read(buf) //读取客户端数据
if err!=nil {
fmt.Println(err)
return
}
fmt.Printf("read data size %d msg:%s", n, string(buf[0:n]))
msg := []byte("hello,world\n")
conn.Write(msg) //发送数据
}
}
func main() {
fmt.Println("start server....")
listen,err := net.Listen("tcp","0.0.0.0:3000") //创建监听
if err != nil{
fmt.Println("listen failed! msg :" ,err)
return
}
for{
conn,errs := listen.Accept() //接受客户端连接
if errs != nil{
fmt.Println("accept failed")
continue
}
go handle(conn) //处理连接
}
}
客户端示例:
package main
import (
"bufio"
"fmt"
"net"
"os"
"strings"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:3000")
if err != nil {
fmt.Println("err dialing:", err.Error())
return
}
defer conn.Close()
inputReader := bufio.NewReader(os.Stdin)
for {
str, _ := inputReader.ReadString('\n')
data := strings.Trim(str, "\n")
if data == "quit" { //输入quit退出
return
}
_, err := conn.Write([]byte(data)) //发送数据
if err != nil {
fmt.Println("send data error:", err)
return
}
buf := make([]byte,512)
n,err := conn.Read(buf) //读取服务端端数据
fmt.Println("from server:", string(buf[:n]))
}
}
conn示例还提供其他方法:
type Conn interface {
// Read reads data from the connection.
// Read can be made to time out and return an Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetReadDeadline.
Read(b []byte) (n int, err error) //读取连接中数据
// Write writes data to the connection.
// Write can be made to time out and return an Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
Write(b []byte) (n int, err error) //发送数据
// Close closes the connection.
// Any blocked Read or Write operations will be unblocked and return errors.
Close() error //关闭链接
// LocalAddr returns the local network address.
LocalAddr() Addr //返回本地连接地址
// RemoteAddr returns the remote network address.
RemoteAddr() Addr //返回远程连接的地址
// SetDeadline sets the read and write deadlines associated
// with the connection. It is equivalent to calling both
// SetReadDeadline and SetWriteDeadline.
//
// A deadline is an absolute time after which I/O operations
// fail with a timeout (see type Error) instead of
// blocking. The deadline applies to all future and pending
// I/O, not just the immediately following call to Read or
// Write. After a deadline has been exceeded, the connection
// can be refreshed by setting a deadline in the future.
//
// An idle timeout can be implemented by repeatedly extending
// the deadline after successful Read or Write calls.
//
// A zero value for t means I/O operations will not time out.
SetDeadline(t time.Time) error //设置链接读取或者写超时时间
// SetReadDeadline sets the deadline for future Read calls
// and any currently-blocked Read call.
// A zero value for t means Read will not time out.
SetReadDeadline(t time.Time) error //单独设置读取超时时间
// SetWriteDeadline sets the deadline for future Write calls
// and any currently-blocked Write call.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
// A zero value for t means Write will not time out.
SetWriteDeadline(t time.Time) error//单独设置写超时时间
}
三、go中HTTP服务处理流程
简介
routerMultiplexerDefautServeMuxhandler
最后简化的请求处理流程为:
Clinet -> Requests -> [Multiplexer(router) -> handler -> Response -> Clinet
ServeMuxHandler
对象说明:
func(w http.ResponseWriter, r *http.Requests)HandlerFunchandler函数
handler处理器和handler对象的差别在于,一个是函数,另外一个是结构,它们都有实现了ServeHTTP方法。很多情况下它们的功能类似,下文就使用统称为handler。
Handler
Golang没有继承,类多态的方式可以通过接口实现。所谓接口则是定义声明了函数签名,任何结构只要实现了与接口函数签名相同的方法,就等同于实现了接口。go的http服务都是基于handler进行处理。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
任何结构体,只要实现了ServeHTTP方法,这个结构就可以称之为handler对象。ServeMux会使用handler并调用其ServeHTTP方法处理请求并返回响应。
ServeMux
源码部分:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool
}
type muxEntry struct {
explicit bool
h Handler
pattern string
}
m
Server
http.ListenAndServe
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
查看server的结构如下:
type Server struct {
Addr string
Handler Handler
ReadTimeout time.Duration
WriteTimeout time.Duration
TLSConfig *tls.Config
MaxHeaderBytes int
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
disableKeepAlives int32 nextProtoOnce sync.Once
nextProtoErr error
}
server结构存储了服务器处理请求常见的字段。其中Handler字段也保留Handler接口。如果Server接口没有提供Handler结构对象,那么会使用DefautServeMux做multiplexer,后面再做分析。
创建HTTP服务
创建一个http服务,大致需要经历两个过程,首先需要注册路由,即提供url模式和handler函数的映射,其次就是实例化一个server对象,并开启对客户端的监听。
http.HandleFunc("/", indexHandler)
http.ListenAndServe("127.0.0.1:8000", nil)
或
server := &Server{Addr: addr, Handler: handler}
server.ListenAndServe()
示例:
package main
import (
"fmt"
"net/http"
)
func Hello(w http.ResponseWriter, r *http.Request) {
fmt.Println("Hello World.")
fmt.Fprintf(w, "Hello World.\n")
}
func main() {
http.HandleFunc("/", Hello)
err := http.ListenAndServe("0.0.0.0:6000", nil)
if err != nil {
fmt.Println("http listen failed.")
}
}
//curl http://127.0.0.1:6000
// 结果:Hello World
路由注册
net/http包暴露的注册路由的api很简单,http.HandleFunc选取了DefaultServeMux作为multiplexer:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
DefaultServeMux是ServeMux的一个实例。当然http包也提供了NewServeMux方法创建一个ServeMux实例,默认则创建一个DefaultServeMux:
// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
DefaultServeMux的HandleFunc(pattern, handler)方法实际是定义在ServeMux下的:
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
HandlerFunc是一个函数类型。同时实现了Handler接口的ServeHTTP方法。使用HandlerFunc类型包装一下路由定义的indexHandler函数,其目的就是为了让这个函数也实现ServeHTTP方法,即转变成一个handler处理器(函数)。
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
我们最开始写的例子中
http.HandleFunc("/",Indexhandler)
这样 IndexHandler 函数也有了ServeHTTP方法。ServeMux的Handle方法,将会对pattern和handler函数做一个map映射:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
// If srv.Addr is blank, ":http" is used.
// ListenAndServe always returns a non-nil error.
func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
Server的ListenAndServe方法中,会初始化监听地址Addr,同时调用Listen方法设置监听。最后将监听的TCP对象传入Serve方法:
// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
//
// For HTTP/2 support, srv.TLSConfig should be initialized to the
// provided listener's TLS Config before calling Serve. If
// srv.TLSConfig is non-nil and doesn't include the string "h2" in
// Config.NextProtos, HTTP/2 support is not enabled.
//
// Serve always returns a non-nil error. After Shutdown or Close, the
// returned error is ErrServerClosed.
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
if fn := testHookServerServe; fn != nil {
fn(srv, l)
}
var tempDelay time.Duration // how long to sleep on accept failure
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
srv.trackListener(l, true)
defer srv.trackListener(l, false)
baseCtx := context.Background() // base is always background, per Issue 16220
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, e := l.Accept()
if e != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}
监听开启之后,一旦客户端请求到底,go就开启一个协程处理请求,主要逻辑都在serve方法之中。
serve方法比较长,其主要职能就是,创建一个上下文对象,然后调用Listener的Accept方法用来 获取连接数据并使用newConn方法创建连接对象。最后使用goroutine协程的方式处理连接请求。因为每一个连接都开起了一个协程,请求的上下文都不同,同时又保证了go的高并发。serve也是一个长长的方法:
serve方法
serverHandler{c.server}.ServeHTTP(w, w.req)
// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
这里DefaultServeMux的ServeHTTP方法其实也是定义在ServeMux结构中的,相关代码如下:
// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// Check for longest valid match.
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// CONNECT requests are not canonicalized.
if r.Method == "CONNECT" {
return mux.handler(r.Host, r.URL.Path)
}
// All other requests have any port stripped and path cleaned
// before passing to mux.handler.
host := stripHostPort(r.Host)
path := cleanPath(r.URL.Path)
if path != r.URL.Path {
_, pattern = mux.handler(host, path)
url := *r.URL
url.Path = path
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
return mux.handler(host, r.URL.Path)
}
// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
mux的ServeHTTP方法通过调用其Handler方法寻找注册到路由上的handler函数,并调用该函数的ServeHTTP方法,本例则是IndexHandler函数。 mux的Handler方法对URL简单的处理,然后调用handler方法,后者会创建一个锁,同时调用match方法返回一个handler和pattern。 在match方法中,mux的m字段是map[string]muxEntry图,后者存储了pattern和handler处理器函数,因此通过迭代m寻找出注册路由的patten模式与实际url匹配的handler函数并返回。 返回的结构一直传递到mux的ServeHTTP方法,接下来调用handler函数的ServeHTTP方法,即IndexHandler函数,然后把response写到http.RequestWirter对象返回给客户端。 上述函数运行结束即`serverHandler{c.server}.ServeHTTP(w, w.req)`运行结束。接下来就是对请求处理完毕之后上希望和连接断开的相关逻辑。 至此,Golang中一个完整的http服务介绍完毕,包括注册路由,开启监听,处理连接,路由处理函数。
总结
多数的web应用基于HTTP协议,客户端和服务器通过request-response的方式交互。一个server并不可少的两部分莫过于路由注册和连接处理。Golang通过一个ServeMux实现了的multiplexer路由多路复用器来管理路由。同时提供一个Handler接口提供ServeHTTP用来实现handler处理其函数,后者可以处理实际request并构造response。 ServeMux和handler处理器函数的连接桥梁就是Handler接口。ServeMux的ServeHTTP方法实现了寻找注册路由的handler的函数,并调用该handler的ServeHTTP方法。ServeHTTP方法就是真正处理请求和构造响应的地方。 回顾go的http包实现http服务的流程,可见大师们的编码设计之功力。学习有利提高自身的代码逻辑组织能力。更好的学习方式除了阅读,就是实践,接下来,我们将着重讨论来构建http服务。尤其是构建http中间件函数。
四、HTTP客户端工具
net/http不仅提供了服务端处理,还提供了客户端处理功能。
http包中提供了Get、Post、Head、PostForm方法实现HTTP请求:
//GET
func Get(url string) (resp *Response, err error) {
return DefaultClient.Get(url)
}
//POST
func Post(url string, contentType string, body io.Reader) (resp *Response, err error) {
return DefaultClient.Post(url, contentType, body)
}
//HEAD
func Head(url string) (resp *Response, err error) {
return DefaultClient.Head(url)
}
//POSTFORM
func PostForm(url string, data url.Values) (resp *Response, err error) {
return DefaultClient.PostForm(url, data)
}
GET请求示例
package main
import (
"fmt"
"net/http"
"log"
"reflect"
"bytes"
)
func main() {
resp, err := http.Get("http://www.baidu.com")
if err != nil {
// 错误处理
log.Println(err)
return
}
defer resp.Body.Close() //关闭链接
headers := resp.Header
for k, v := range headers {
fmt.Printf("k=%v, v=%v\n", k, v) //所有头信息
}
fmt.Printf("resp status %s,statusCode %d\n", resp.Status, resp.StatusCode)
fmt.Printf("resp Proto %s\n", resp.Proto)
fmt.Printf("resp content length %d\n", resp.ContentLength)
fmt.Printf("resp transfer encoding %v\n", resp.TransferEncoding)
fmt.Printf("resp Uncompressed %t\n", resp.Uncompressed)
fmt.Println(reflect.TypeOf(resp.Body))
buf := bytes.NewBuffer(make([]byte, 0, 512))
length, _ := buf.ReadFrom(resp.Body)
fmt.Println(len(buf.Bytes()))
fmt.Println(length)
fmt.Println(string(buf.Bytes()))
}
使用http.Do设置请求头、cookie等
package main
import (
"net/http"
"strings"
"io/ioutil"
"log"
"fmt"
)
func main() {
client := &http.Client{}
req, err := http.NewRequest("POST", "http://www.baidu.com",
strings.NewReader("name=xxxx&passwd=xxxx"))
if err != nil {
fmt.Println(err)
return
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") //设置请求头信息
resp, err := client.Do(req)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
return
}
var res string
res = string(body[:])
fmt.Println(res)
}
POST请求示例
package main
import (
"net/http"
"strings"
"fmt"
"io/ioutil"
)
func main() {
resp, err := http.Post("http://www.baidu.com",
"application/x-www-form-urlencoded",
strings.NewReader("username=xxx&password=xxxx"))
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
PostForm请求示例
package main
import (
"net/http"
"fmt"
"io/ioutil"
"net/url"
)
func main() {
postParam := url.Values{
"name": {"wd"},
"password": {"1234"},
}
resp, err := http.PostForm("https://cn.bing.com/", postParam)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
本文内容总结:一、简介,二、socket编程,三、go中HTTP服务处理流程,简介,Handler,ServeMux,Server,创建HTTP服务,路由注册,总结,四、HTTP客户端工具,GET请求示例,使用http.Do设置请求头、cookie等,POST请求示例,PostForm请求示例,,