GO学习笔记(6)通信协议概要

1.模型分层

基本上大学的计算机基础都有学过,7层就是物数网传会表应,5层就是把会表应合并为应用层


image.png

1.1Socket抽象层

两个进程进行通信的前提:

  • 本地:只需要知道进程唯一id-pid(本地进程唯一标识符)即可
  • 网络:IP地址+协议+端口号可以确定网络中的一个进程

能唯一标识网络中的进程后就可以利用socket通信,socket是应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口给系统调用,实现进程在网络中的通信
image.png

回想一下之前的GMP模型中的P也是抽象出来帮助M调度G的,那我们利用Socket主要的操作是帮助应用层建立、接受连接,读写“文件”、关闭接口、超时,还要获取对方的地址和端口。

Socket起源于UNIX,在Unix一切皆文件哲学的思想下,Socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

2.TCP连接建立

2.1TCP服务端准备连接过程

我们需要使用socket套接字来帮助我们建立连接,在C语言中

int socket(int domain, int type, int protocol)
//domain指PF_INET、PF_INET6等表示是什么样的套接字
//type指SOCK_STREAM(tcp)/SOCK_DGRAM(UDP)/SOCK_RAM(原始套接字)
//最后一个被废弃为0
//返回的int就是套接字的文件句柄

我们知道在网络中确定一个进程需要ip和port,我们将我们的信息传入socket,让别让来发现我们的服务

bind(int fd, sockaddr * addr, socklen_t len)

一个人建立连接是不可能的,我们需要等待其他人和我们建立连接

int listen (int socketfd, int backlog)

注意第二个参数backlog在Linux中表示已完成(ESTABLISHED)并且未accept的队列大小,即可以接受的并发数目。
此时listen只是让套接字从“主动”转为“被动”状态,告诉内核这个套接字是用来等待连接请求的,并未开始真正等待,需要调用accept函数

int accept(int listensockfd, struct sockaddr *cliaddr, socklen_t *addrlen)

我们能通过这个函数获取到对方的地址(存入cliaddr,addrlen为地址大小)
监听套接字是阻塞的是一直存在的

2.2TCP客户端发起连接请求

bind之后直接connect

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)

调用connect函数触发TCP三次握手
image.png

2.3利用GO搭建一个简单的TCP服务器和客户端,只需listen即可完成bind和listen

server.go

package main

import (
        "bufio"
        "fmt"
        "net"
        "os"
        "strings"
        "time"
)

func main() {
        arguments := os.Args
        if len(arguments) == 1 {
                fmt.Println("Please provide port number")
                return
        }

        PORT := ":" + arguments[1]
        l, err := net.Listen("tcp", PORT)
        if err != nil {
                fmt.Println(err)
                return
        }
        defer l.Close()

        c, err := l.Accept()
        if err != nil {
                fmt.Println(err)
                return
        }

        for {
                netData, err := bufio.NewReader(c).ReadString('\n')
                if err != nil {
                        fmt.Println(err)
                        return
                }
                if strings.TrimSpace(string(netData)) == "STOP" {
                        fmt.Println("Exiting TCP server!")
                        return
                }

                fmt.Print("-> ", string(netData))
                t := time.Now()
                myTime := t.Format(time.RFC3339) + "\n"
                c.Write([]byte(myTime))
        }
}
    

服务端使用Dial函数完成bind和connect
client.go

package main

import (
        "bufio"
        "fmt"
        "net"
        "os"
        "strings"
)

func main() {
        arguments := os.Args
        if len(arguments) == 1 {
                fmt.Println("Please provide host:port.")
                return
        }

        CONNECT := arguments[1]
        c, err := net.Dial("tcp", CONNECT)
        if err != nil {
                fmt.Println(err)
                return
        }

        for {
                reader := bufio.NewReader(os.Stdin)
                fmt.Print(">> ")
                text, _ := reader.ReadString('\n')
                fmt.Fprintf(c, text+"\n")

                message, _ := bufio.NewReader(c).ReadString('\n')
                fmt.Print("->: " + message)
                if strings.TrimSpace(string(text)) == "STOP" {
                        fmt.Println("TCP client exiting...")
                        return
                }
        }
}

两者编译后使用tcpdump抓包(注意我这是一台机上使用环回网络)

pts1执行命令
tcpdump -i lo port 8080
pts2启动server
./server 8080
pts3 启动客户端并输入STOP
./client 127.0.0.1:8080
STOP 

观察tcpdump执行结果

22:08:21.107664 IP localhost.50442 > localhost.webcache: Flags [S], seq 2024884550, win 43690, options [mss 65495,sackOK,TS val 1340202011 ecr 0,nop,wscale 7], length 0
22:08:21.107673 IP localhost.webcache > localhost.50442: Flags [S.], seq 607092787, ack 2024884551, win 43690, options [mss 65495,sackOK,TS val 1340202011 ecr 1340202011,nop,wscale 7], length 0
22:08:21.107680 IP localhost.50442 > localhost.webcache: Flags [.], ack 607092788, win 342, options [nop,nop,TS val 1340202011 ecr 1340202011], length 0

这是三次握手过程,首先客户端发起建立请求
1.客户端第一次握手:
localhost.50424(client随机占用端口)->localhost.webcache
Flags [S],S:SYN标志位表示同步,开始会话请求
seq 2024884550表示发送了这么多字节的数据
2.服务端第二次握手
localhost.webcache > localhost.50424
Flags [S.] 点实际表示ACK确认,这是对SYN的确认
seq 607092787, ack 2024884551
这里我们看到ack对上一个包的seq序号+1确认
3.客户端第三次握手
localhost.50442 > localhost.webcache
Flags [.], ack 607092788
对上一个包的序号+1
至此连接完成
(这个demo例子客户端的断开没写,因此不分析四次挥手了)

你可能感兴趣的:(GO学习笔记(6)通信协议概要)