http协议与gin

socket编程与tcp

由于tcp/ip协议都封装在操作系统的底层上,为了避免应用层直接去调用操作系统的方法,所以操作系统提供了一个socket接口,应用层去调用这个接口,而socket接口又分为tcpSocket和udpSocket,可以理解为socket其实就是应用层和传输层的一个门面,调用socket方法只是去调用我们tcp/udp协议的传输
在这里插入图片描述
tcp特点:

  1. 可靠传输(校验和,超时重试机制,流量控制,拥塞控制)
  2. 面向字节流
  3. 三次握手,四次挥手

go的socket编程

server端:

func main()  {

	//监听端口
	listen,err:=net.Listen("tcp","localhost:20000")
	if(err!=nil){
		fmt.Println("err:",err)
		return
	}

	
	for {
	//接收到请求
		coon, err := listen.Accept()
		if (err != nil) {
			fmt.Println("err:", err)
			return
		}
	//一个请求启动一个携程
		go slove(coon)
	}

}

func slove(conn net.Conn) {

	defer conn.Close()
	for {
		reader := bufio.NewReader(conn)
		var buf [128]byte
		n, err := reader.Read(buf[:])
		if (err != nil) {
			fmt.Println("err:", err)
			return
		}
		s := string(buf[:n])
		fmt.Println(s)
		conn.Write([]byte(s))
	}
}

client:

func main()  {
	//连接服务器
	conn, err := net.Dial("tcp", "localhost:20000")
	if(err!=nil){
		fmt.Println(err)
		return
	}
	for{
	//读写数据
		var s string
		fmt.Scanln(&s)
		//只是发在缓冲区中,并不会立即发送到客户端
		_, err := conn.Write([]byte(s))
		if(err!=nil){
			return
		}
		reader := bufio.NewReader(conn)
		var buf [128]byte

		n, err := reader.Read(buf[:])
		if(err!=nil){
			fmt.Printf(err.Error())
			return

		}
		fmt.Println(string(buf[:n]))
	}
}

上面socket编程已经能实现并发请求了,而如果直接用socket编程去传输我们的应用层消息会出现情况呢。
1.粘包:如果客户端发送的数据很小的消息,而且客户端发送好几个消息,而这几个消息并不会立马发送到服务端,而是先会堆积到客户端的缓存中,而同样服务端也是同理,当收到消息ack确认后并不会立马送到应用层,而是会堆积到我们的缓冲区中。上述情况服务端将会把客户端所有的消息全读出来。
2.断包:客户端发送的数据很大,超过缓冲区了,将会分几次写,而服务端将会读好几次,服务端以为客服端发了好几个信息。

造成上面原因是服务端不知道客户端消息的边界是怎么样的,一种简单解决办法是客户端在消息前加个头部,告诉消息到底有多长,而服务端更加这种方式去读消息,这就是双方的一种协议,发消息前客户端序列化消息成可识别的二进制流,而服务端去反序列化成消息,这种序列化和反序列化的消息传送,是rpc协议或应用层协议必须做到流程
在这里插入图片描述

go的http包

http应用程序非常简洁

func main()  {
//路由注册
	http.HandleFunc("/", func(re http.ResponseWriter, rs *http.Request) {
		re.Write([]byte("Hello World"))
	})
//绑定端口,接收请求连接,然后处理处理请求连接
	http.ListenAndServe(":8080",nil)
}

这里将路径注册到http默认路由上

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

监听和接收http请求

func ListenAndServe(addr string, handler Handler) error {
//封装一个server类
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}
func (srv *Server) ListenAndServe() error {
	if srv.shuttingDown() {
		return ErrServerClosed
	}
	addr := srv.Addr
	if addr == "" {
		addr = ":http"
	}
	//以tcp方式监听请求端口
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	//处理请求连接
	return srv.Serve(ln)
}
func (srv *Server) Serve(l net.Listener) error {
    // ... 省略代码
    for {
      rw, e := l.Accept() //看这里accept
      ctx := context.WithValue(baseCtx, ServerContextKey, srv)
      //省略一大堆
     c := srv.newConn(rw)
	 c.setState(c.rwc, StateNew, runHooks) // before Serve can return
	 //启动一个协程去处理请求
	 go c.serve(connCtx) 
    }
}
func (c *conn) serve(ctx context.Context) {
    // ... 省略代码,这里主要是处理请求信息,将其转化一个httpRequest和httpResponse对象
    serverHandler{c.server}.ServeHTTP(w, w.req)
    w.cancelCtx()
    if c.hijacked() {
      return
    }
    w.finishRequest()
    // ... 省略代码
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler
	if handler == nil {
	//如果没有其他路由,就用http自定义路由
		handler = DefaultServeMux
	}
	if req.RequestURI == "*" && req.Method == "OPTIONS" {
		handler = globalOptionsHandler{}
	}
	//用路由处理http信息
	handler.ServeHTTP(rw, req)
}

回顾下http的调用流程
http.ListenAndServe(":8080",nil)

  1. 建立tcpSocket连接,并监听8080端口
  2. 接受到请求连接后(Accept)后,启动一个协程去处理这个请求.
  3. 处理请求信息,并封装成httpReqeust和httpResponse对象,由serverHandler{c.server}.ServeHTTP(w, w.req)处理
  4. 然后找到真正的http路由(没有就用默认的),根据请求路径找到相应的handler处理
  5. 响应请求

http总结

  1. http包已经帮我们实现socket的封装,请求消息的封装成httpRequest对象给予路由器去处理请求,等路由处理完后帮我们响应请求
  2. 但http默认路由匹配非常简单,相当于与一个map去匹配,不支持restful风格,也没有中间件和路由组的概念。设计巧妙的是,http不会优先使用默认路由,而是会使用我们自定义路由,实现自定义路由也非常简单,只需要去实现http包的Handler接口即可,例如像gin的Engine对象就是实现Handler接口,从而实现http的自定义路由
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

在这里插入图片描述

支持restful风格的gin

gin的基本使用

type Login struct {
	// binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
	User     *string `form:"username" json:"user" `
	Password *string `form:"password" json:"password"`
}

func main() {
	// 1.创建路由
	// 默认使用了2个中间件Logger(), Recovery()
	r := gin.Default()
	r.POST("/loginForm", func(c *gin.Context) {
		// 声明接收的变量
		var form Login
		// Bind()默认解析并绑定form格式
		if err := c.ShouldBind(&form); err != nil {
			fmt.Println(form)
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		fmt.Println(*form.Password)
		fmt.Println(*form.User)
		c.JSON(http.StatusOK, gin.H{})

	})
	r.Run(":8000")
}

启动服务

func (engine *Engine) Run(addr ...string) (err error) {
	defer func() { debugPrintError(err) }()

	address := resolveAddress(addr)
	debugPrint("Listening and serving HTTP on %s\n", address)
	err = http.ListenAndServe(address, engine)
	return
}

获取请求连接,http请求将请求这里

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    //获取对象池的context
	c := engine.pool.Get().(*Context)
	//重新设置context对象
	c.writermem.reset(w)
	c.Request = req
	c.reset()
	//通过路由树找到对应的handler调用链,执行handler调用链
	engine.handleHTTPRequest(c)
	//将context放入对象池中
	engine.pool.Put(c)
}

简单看一下handleHTTPRequest

func (engine *Engine) handleHTTPRequest(c *Context) {
	httpMethod := c.Request.Method
	rPath := c.Request.URL.Path
	unescape := false
	if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
		rPath = c.Request.URL.RawPath
		unescape = engine.UnescapePathValues
	}

	if engine.RemoveExtraSlash {
		rPath = cleanPath(rPath)
	}

	// Find root of the tree for the given HTTP method
	t := engine.trees
	for i, tl := 0, len(t); i < tl; i++ {
		if t[i].method != httpMethod {
			continue
		}
		root := t[i].root
		// Find route in tree
		value := root.getValue(rPath, c.Params, unescape)
		if value.handlers != nil {
			c.handlers = value.handlers
			c.Params = value.params
			c.fullPath = value.fullPath
			//执行handler
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
		if httpMethod != "CONNECT" && rPath != "/" {
			if value.tsr && engine.RedirectTrailingSlash {
				redirectTrailingSlash(c)
				return
			}
			if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
				return
			}
		}
		break
	}
	//省略一大堆
}

gin的优势

  1. gin只是http的一个自定义路由器,继承了http高性能的特性,天生支撑高并发
  2. gin有routeGroup的概念,例如(user),在这个路由组下的全部路由注册都会继承当前路由组的全部中间件。
  3. 引入中间件的概念,方便对gin做二次扩展,例如我们项目用中间件做权限认证和登录认证
  4. 友好的参数绑定,只需要通过标签 form,和json对参数进行绑定到结构体中。
  5. 支撑restful风格,采用多颗基数树做路由匹配,对内存压缩到极致,采用基数树好处还可以处理路径通用匹配符“*”和“:”,例如(user/:id),(user/*id)
  6. 集成了Validator做参数验证

上面是只是介绍了http引流到gin上,还没介绍gin是怎么注册路由树和通过路由树去查找handler,还没弄懂。可以先简单看看长啥样的
在这里插入图片描述

参考文献

官方文档
gin是怎么引流到http
gin基数树数据结构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值