Go语言基础07——网络概述、socket编程

网络概述

网络协议介绍:
从应用的角度出发,协议可理解为“规则”,是数据传输和数据的解释的规则。

假设,A、B双方欲传输文件。规定:
l 第一次,传输文件名,接收方接收到文件名,应答OK给传输方;
l 第二次,发送文件的尺寸,接收方接收到该数据再次应答一个OK;
l 第三次,传输文件内容。同样,接收方接收数据完成后应答OK表示文件内容接收成功。

由此,无论A、B之间传递何种文件,都是通过三次数据传输来完成。A、B之间形成了一个最简单的数据传输规则。双方都按此规则发送、接收数据。A、B之间达成的这个相互遵守的规则即为协议。

这种仅在A、B之间被遵守的协议称之为原始协议。

当此协议被更多的人采用,不断的增加、改进、维护、完善。最终形成一个稳定的、完整的文件传输协议,被广泛应用于各种文件传输过程中。该协议就成为一个标准协议。最早的ftp协议就是由此衍生而来。

分层模型介绍:
为了减少协议设计的复杂性,大多数网络模型均采用分层的方式来组织。每一层都有自己的功能,就像建筑物一样,每一层都靠下一层支持。每一层利用下一层提供的服务来为上一层提供服务,本层服务的实现细节对上层屏蔽。


image.png

越下面的层,越靠近硬件;越上面的层,越靠近用户。至于每一层叫什么名字,其实并不重要(面试的时候,面试官可能会问每一层的名字)。只需要知道,互联网分成若干层即可。

  1. 物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后再转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特。
  2. 数据链路层:定义了如何让格式化数据以帧为单位进行传输,以及如何让控制对物理介质的访问。这一层通常还提供错误检测和纠正,以确保数据的可靠传输。如:串口通信中使用到的115200、8、N、1
  3. 网络层:在位于不同地理位置的网络中的两个主机系统之间提供连接和路径选择。Internet的发展使得从世界各站点访问信息的用户数大大增加,而网络层正是管理这种连接的层。
  4. 传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段。
  5. 会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名)。
  6. 表示层:可确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取。例如,PC程序与另一台计算机进行通信,其中一台计算机使用扩展二一十进制交换码(EBCDIC),而另一台则使用美国信息交换标准码(ASCII)来表示相同的字符。如有必要,表示层会通过使用一种通格式来实现多种数据格式之间的转换。
  7. 应用层:是最靠近用户的OSI层。这一层为用户的应用程序(例如电子邮件、文件传输和终端仿真)提供网络服务。

层和协议
每一层都是为了完成一种功能,为了实现这些功能,就需要大家都遵守共同的规则。大家都遵守这规则,就叫做“协议”(protocol)。

网络的每一层,都定义了很多协议。这些协议的总称,叫“TCP/IP协议”。TCP/IP协议是一个大家族,不仅仅只有TCP和IP协议,它还包括其它的协议,如下图:


image.png
  1. 链路层
    以太网规定,连入网络的所有设备,都必须具有“网卡”接口。数据包必须是从一块网卡,传送到另一块网卡。通过网卡能够使不同的计算机之间连接,从而完成数据通信等功能。网卡的地址——MAC 地址,就是数据包的物理发送地址和物理接收地址。

  2. 网络层
    网络层的作用是引进一套新的地址,使得我们能够区分不同的计算机是否属于同一个子网络。这套地址就叫做“网络地址”,这是我们平时所说的IP地址。这个IP地址好比我们的手机号码,通过手机号码可以得到用户所在的归属地。

网络地址帮助我们确定计算机所在的子网络,MAC 地址则将数据包送到该子网络中的目标网卡。网络层协议包含的主要信息是源IP和目的IP。

于是,“网络层”出现以后,每台计算机有了两种地址,一种是 MAC 地址,另一种是网络地址。两种地址之间没有任何联系,MAC 地址是绑定在网卡上的,网络地址则是管理员分配的,它们只是随机组合在一起。

网络地址帮助我们确定计算机所在的子网络,MAC 地址则将数据包送到该子网络中的目标网卡。因此,从逻辑上可以推断,必定是先处理网络地址,然后再处理 MAC 地址。

  1. 传输层
    当我们一边聊QQ,一边聊微信,当一个数据包从互联网上发来的时候,我们怎么知道,它是来自QQ的内容,还是来自微信的内容?

也就是说,我们还需要一个参数,表示这个数据包到底供哪个程序(进程)使用。这个参数就叫做“端口”(port),它其实是每一个使用网卡的程序的编号。每个数据包都发到主机的特定端口,所以不同的程序就能取到自己所需要的数据。

端口特点:
l 对于同一个端口,在不同系统中对应着不同的进程
l 对于同一个系统,一个端口只能被一个进程拥有

  1. 应用层
    应用程序收到“传输层”的数据,接下来就要进行解读。由于互联网是开放架构,数据来源五花八门,必须事先规定好格式,否则根本无法解读。“应用层”的作用,就是规定应用程序的数据格式。

cs模型介绍


cs模型介绍.png

TCP服务器代码

package main

import (
    "fmt"
    "net"
)

func main() {
    fmt.Println("TCP服务端代码演示案例")
    listener, err := net.Listen("tcp", "127.0.0.1:8000") //监听
    if err != nil {
        fmt.Println("err:", err)
        return
    }

    defer listener.Close() //关闭

    for {
        //阻塞,等待用户连接
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("err:", err)
            continue
        }
        // 接受用户请求
        buff := make([]byte, 1024)
        n, err1 := conn.Read(buff)
        if err1 != nil {
            fmt.Println("err1:", err1)
            continue
        }
        fmt.Println("buff = ", buff[:n])
        defer conn.Close() //关闭当前用户链接
    }

}

使用netcat工具,模拟发送数据

nc 127.0.0.1 8000

TCP客户端代码

package main

import (
    "fmt"
    "net"
)

func main() {
    fmt.Println("TCP服务端代码演示案例")
    listener, err := net.Listen("tcp", "127.0.0.1:8000") //监听
    if err != nil {
        fmt.Println("err:", err)
        return
    }

    defer listener.Close() //关闭

    for {
        //阻塞,等待用户连接
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("err:", err)
            continue
        }
        // 接受用户请求
        buff := make([]byte, 1024)
        n, err1 := conn.Read(buff)
        if err1 != nil {
            fmt.Println("err1:", err1)
            continue
        }
        fmt.Println("buff = ", buff[:n])
        defer conn.Close() //关闭当前用户链接
    }

}

先运行服务端,在运行客户端,然后在服务端发现输出内容:

buff = [104 101 108 108 111 44 105 32 97 109 32 99 108 105 101 110 116 33]

为此可以将服务端输出打印优化一下,输出字符串:

fmt.Println("buff = ", string(buff[:n]))

简单版并发服务器

要实现的效果:


image.png

并发服务器端代码:

package main

import (
    "fmt"
    "net"
    "strings"
)

func HandleConn(conn net.Conn) {
    // 获取客户端网络地址信息
    addr := conn.RemoteAddr().String()
    fmt.Println("addr:", addr)

    // 接受用户请求
    buff := make([]byte, 1024)
    for { //保持链接,不用每次都断开
        n, err1 := conn.Read(buff)
        if err1 != nil {
            fmt.Println("err1:", err1)
            return
        }

        fmt.Printf("addr:%s,content:%s\n", addr, string(buff[:n]))

        // 把数据转成大写然后返回过去
        conn.Write([]byte(strings.ToUpper(string(buff[:n]))))
    }

}
func main() {
    fmt.Println("TCP并发服务端代码演示案例")

    listener, err := net.Listen("tcp", "127.0.0.1:8000") //监听

    if err != nil {
        fmt.Println("err:", err)
        return
    }

    defer listener.Close() //关闭

    for {
        //阻塞,等待用户连接
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("err:", err)
            return
        }

        //处理多个用户请求,新建一个协程

        go HandleConn(conn)

    }

}

并发Client端代码:

package main

import (
    "fmt"
    "net"
    "os"
)

func main() {
    fmt.Println("TCP客户端代码演示案例")

    conn, err := net.Dial("tcp", "127.0.0.1:8000") //主动连接服务器
    if err != nil {
        fmt.Println("err:", err)
        return
    }

    defer conn.Close() //关闭

    go func() {
        buff := make([]byte, 1024)

        for {
            n, err := os.Stdin.Read(buff)
            if err != nil {
                fmt.Println("os.Stdin err:", err)
                return
            }
            conn.Write(buff[:n])
        }

    }()

    //接受服务器返回的信息

    buff := make([]byte, 1024)

    for {
        n, err := conn.Read(buff)
        if err != nil {
            fmt.Println("err:", err)
            return
        }
        fmt.Println("服务器返回内容:", string(buff[:n]))
    }

}

文件传输原理分析


传输文件


并发聊天服务器

实现效果:


image.png

并发聊天服务器代码:

package main

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

type User struct {
    name string
    addr string
    C    chan string //用户发送的数据通道
}

// 保存在线用户
var onlineUserMap map[string]User
var message = make(chan string)

// 给用户发送消息
func WriteMsgToClient(user User, conn net.Conn) {
    fmt.Println("给当前客户端发送消息:", user.addr)
    for { //this
        for msg := range user.C { //给当前客户端发送消息
            conn.Write([]byte(msg + "\n"))
        }
    }

}

func MakeMsg(user User, msg string) (buff string) {
    buff = "[" + user.addr + "]" + user.name + ":" + msg
    return
}

func handleConn(conn net.Conn) {
    addr := conn.RemoteAddr().String()
    fmt.Printf("%s login!\n", addr)
    //把当前登录的用户信息存入到onlineUserMap当中
    user := User{addr, addr, make(chan string)}

    onlineUserMap[addr] = user

    isQuit := make(chan bool) //对方是否主动退出
    isLive := make(chan bool) //超时处理

    go WriteMsgToClient(user, conn) //给当前客户发送消息

    // 广播某个人上线
    //messaage <- MakeMsg(user, "login")
    user.C <- MakeMsg(user, "i am here!")
    // message <- "[" + user.addr + "]" + user.name + ":Login!"
    fmt.Println("广播某个人上线")

    // 新建一个协程用来接收用户发来的数据
    go func() {
        buff := make([]byte, 1024)
        for {
            n, err := conn.Read(buff)
            // if err != nil {
            //  fmt.Println("conn.Read1:", err)
            //  return
            // }
            if n == 0 {
                isQuit <- true
                fmt.Println("conn.Read2:", err)
                return
            }

            msg := string(buff[:n-1])

            fmt.Println("len(msg):", len(msg))
            fmt.Printf("%s send : %s", addr, msg)

            if len(msg) == 3 && msg == "who" {
                fmt.Println("who指令")
                conn.Write([]byte("user list:\n"))
                for _, user := range onlineUserMap {
                    msg = user.name + ":" + user.addr + "\n"
                    conn.Write([]byte(msg))
                }
            } else if len(msg) >= 8 && msg[:6] == "rename" {

                // rename|mike
                name := strings.Split(msg, "|")[1]
                user.name = name
                onlineUserMap[addr] = user
                conn.Write([]byte("rename ok\n"))

            } else {

                // 转发此内容:
                message <- MakeMsg(user, string(buff[:n]))
            }
            isLive <- false

        }
    }()

    for {
        //通过select检测channel的流动
        select {
        case <-isQuit:
            delete(onlineUserMap, addr)
            message <- MakeMsg(user, " login out!")
            return
        case <-isLive:
            fmt.Println(addr, "is live")
        case <-time.After(15 * time.Second): //30后
            delete(onlineUserMap, addr)
            message <- MakeMsg(user, " time out leave!")
            return
        }
    }

}

func sendMsgToAll() {
    for {
        //fmt.Println("sendMsgToAll没有消息前,这里会阻塞")
        msg := <-message //没有消息前,这里会阻塞
        for _, user := range onlineUserMap {
            user.C <- msg
        }
    }

}

func main() {
    fmt.Println("并发聊天服务器演示案例")
    listener, err := net.Listen("tcp", "127.0.0.1:8000")
    if err != nil {
        fmt.Println("Listen err:", err)
        return
    }

    defer listener.Close()

    //给map分配空间
    onlineUserMap = make(map[string]User)

    // 新开一个协程,转发消息,只要有消息来了,遍历map, 给map每个成员都发送此消息

    go sendMsgToAll()

    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Accept err:", err)
            return
        }
        go handleConn(conn)
    }

}

END.

你可能感兴趣的:(Go语言基础07——网络概述、socket编程)