socket编程与tcp
由于tcp/ip协议都封装在操作系统的底层上,为了避免应用层直接去调用操作系统的方法,所以操作系统提供了一个socket接口,应用层去调用这个接口,而socket接口又分为tcpSocket和udpSocket,可以理解为socket其实就是应用层和传输层的一个门面,调用socket方法只是去调用我们tcp/udp协议的传输
tcp特点:
- 可靠传输(校验和,超时重试机制,流量控制,拥塞控制)
- 面向字节流
- 三次握手,四次挥手
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)
- 建立tcpSocket连接,并监听8080端口
- 接受到请求连接后(Accept)后,启动一个协程去处理这个请求.
- 处理请求信息,并封装成httpReqeust和httpResponse对象,由serverHandler{c.server}.ServeHTTP(w, w.req)处理
- 然后找到真正的http路由(没有就用默认的),根据请求路径找到相应的handler处理
- 响应请求
http总结
- http包已经帮我们实现socket的封装,请求消息的封装成httpRequest对象给予路由器去处理请求,等路由处理完后帮我们响应请求
- 但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的优势
- gin只是http的一个自定义路由器,继承了http高性能的特性,天生支撑高并发
- gin有routeGroup的概念,例如(user),在这个路由组下的全部路由注册都会继承当前路由组的全部中间件。
- 引入中间件的概念,方便对gin做二次扩展,例如我们项目用中间件做权限认证和登录认证
- 友好的参数绑定,只需要通过标签 form,和json对参数进行绑定到结构体中。
- 支撑restful风格,采用多颗基数树做路由匹配,对内存压缩到极致,采用基数树好处还可以处理路径通用匹配符“*”和“:”,例如(user/:id),(user/*id)
- 集成了Validator做参数验证
上面是只是介绍了http引流到gin上,还没介绍gin是怎么注册路由树和通过路由树去查找handler,还没弄懂。可以先简单看看长啥样的