Golang网络编程

670次阅读  |  发布于2年以前

网络编程

  1. TCP socket 编程,是网络编程的主流。之所以叫 Tcp socket 编程,是因为底层是基于 Tcp/ip 协议的.
  2. b/s 结构的 http 编程,我们使用浏览器去访问服务器时,使用的就是 http 协议,而 http 底层依旧是用 tcp socket 实现的。

协议(tcp/ip)

TCP/IP(Transmission Control Protocol/Internet Protocol)的简写,中文译名为传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议是 Internet 最基本的协议、Internet 国际互联网络的基础,简单地说,就是由网络层的 IP 协议和传输层的 TCP 协议组成的。

OSI参考模型 (理论)

TCP/IP模型 (现实)

  1. 物理层设备,屏蔽双绞线,非屏蔽双绞线,集线器,转换器,中继器。转换器和调解制调器都是用来数/模转换的。中继器是用来放大传输信号的。集线器是将多台计算机连接在一起构成局域网。连接在集线器上的任何一个设备发送数据时,其它所有设备必须等待,因为集线器内部使用的是总线型的网络结构;集线器内部没有操作系统,没有mac地址缓存,所以它不能判断数据包的目的地,故它以广播的方式把数据包发送到每个设备,接收方再根据数据包内的mac信息判断是不是发给自己的,不是丢掉;随着交换机价格的降低,集线器不再常见。
  2. 数据链路层设备,交换机,网桥,网卡。网桥已不多见,网桥是端口少的交换机,交换机是端口多的网桥。交换机,差错校验,出错的帧不会被转发。交换机给某个端口发送数据,第一次发送的是广播,因为它有操作系统,有学习能力,所以第一次后会把这个端口的mac地址缓存起来,这样再次发往这个设备的时候,就不发送广播了。
  3. 网络层设备,路由器。路由器本身有三层结构,物理层,数据链路层,网络层。
  4. 防火墙工作在网络层和传输层,它根据管理员设定的网络策略进行网络访问控制。防火墙有硬件的,软件的;硬件的价格昂贵。

交换机不识别IP,交换机只能通过mac地址通信,仅能在局域网内通信,一旦跨了网段就不能通过交换机通信了。

跨网段要通过路由器通信。

网关,整个局域网的出口。局域网内处理不了的数据包都交给网关处理。如果和外网通信,源mac是发送方的mac,目标mac是局域网的网关。如果是内网通信外网,因为内网IP不能访问外网,当数据包发送到网关的时候,网关会把自己的公网IP包裹在内网IP的外面,叫NAT网络地址转换。如果是公网通信外网,就不会有NAT网络地址转换。再经过多个路由,IP不再改变。

在网络中传输数据时,源mac一直在变,只要经过一层路由就会改变。

域名解析系统DNS既使用了UDP、也使用了TCP。DNS服务器有主从服务器,它们之间同步数据时使用TCP;我们在浏览器输入域名时,需要解析为IP地址,这时使用的是UDP。

局域网内A用户连接B用户,是通过IP来连接的;但交换机不识别IP,不过交换机里有该局域网中所有计算机的mac和ip的对应表格。arp协议就是在局域网中通信时把内网ip转换为mac地址的协议。

在局域网内,windows 在命令行输入 arp -a,可以查看局域网中mac和ip的对应关系。

数据链路层非常模糊,很难划分到底属于哪一层。

模型层级 数据结构
应用层 上层数据
传输层 tcp头部 上层数据
网络层 ip头部 tcp头部 上层数据
数据链路层 mac头部 ip头部 tcp头部 上层数据
  1. 每个 internet 上的主机和路由器都有一个 ip 地址,它包括网络号和主机号, 地址有 ipv4(32ip位)或者 ipv6(128 位). 可以通过 ipconfig 来查看
  2. 端口(port)
   0 号是保留端口
    1-1024 是固定端口(程序员不要使用),又叫有名端口,即被某些程序固定使用,一般程序员不使用.
        22: SSH 远程登录协议 23: telnet 使用
        25: smtp 服务使用
        21: ftp 使用
        80: iis 使用 7: echo 服务
    1025-65535 是动态端口,这些端口,程序员可以使用.

使用注意

  1. 在计算机(尤其是做服务器)要尽可能的少开端口
  2. 一个端口只能被一个程序监听
  3. 如果使用 netstat –an 可以查看本机有哪些端口在监听
  4. 可以使用 netstat –anb 来查看监听端口的 pid,在结合任务管理器关闭不安全的端口

server.go

    package main
    import (
        "fmt"
        "net" //做网络socket开发时,net包含有我们需要所有的方法和函数
        _"io"
    )

    func process(conn net.Conn) {

        //这里我们循环的接收客户端发送的数据
        defer conn.Close() //关闭conn

        for {
            //创建一个新的切片
            buf := make([]byte, 1024)
            //conn.Read(buf)
            //1. 等待客户端通过conn发送信息
            //2. 如果客户端没有wrtie[发送],那么协程就阻塞在这里
            //fmt.Printf("服务器在等待客户端%s 发送信息\n", conn.RemoteAddr().String())
            n , err := conn.Read(buf) //从conn读取
            if err != nil {

                fmt.Printf("客户端退出 err=%v", err)
                return //!!!
            }
            //3. 显示客户端发送的内容到服务器的终端
            fmt.Print(string(buf[:n])) 
        }
    }

    func main() {
        fmt.Println("服务器开始监听....")
        //net.Listen("tcp", "0.0.0.0:8888")
        //1. tcp 表示使用网络协议是tcp
        //2. 0.0.0.0:8888 表示在本地监听 8888端口
        listen, err := net.Listen("tcp", "0.0.0.0:8888")
        if err != nil {
            fmt.Println("listen err=", err)
            return 
        }
        defer listen.Close() //延时关闭listen

        //循环等待客户端来链接我
        for {
            //等待客户端链接
            fmt.Println("等待客户端来链接....")
            conn, err := listen.Accept()
            if err != nil {
                fmt.Println("Accept() err=", err)

            } else {
                fmt.Printf("Accept() suc con=%v 客户端ip=%v\n", conn, conn.RemoteAddr().String())
            }
            //这里准备其一个协程,为客户端服务
            go process(conn)
        }
    }

client.go

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

    func main() {
        conn, err := net.Dial("tcp", "192.168.20.253:8888")
        if err != nil {
            fmt.Println("client dial err=", err)
            return 
        }
        //功能一:客户端可以发送单行数据,然后就退出
        reader := bufio.NewReader(os.Stdin) //os.Stdin 代表标准输入[终端]

        for {
            //从终端读取一行用户输入,并准备发送给服务器
            line, err := reader.ReadString('\n')
            if err != nil {
                fmt.Println("readString err=", err)
            }
            //如果用户输入的是 exit就退出
            line = strings.Trim(line, " \r\n")
            if line == "exit" {
                fmt.Println("客户端退出..")
                break
            }

            //再将line 发送给 服务器
            _, err = conn.Write([]byte(line + "\n"))
            if err != nil {
                fmt.Println("conn.Write err=", err)  
            }
        }
    }

UDP协议

UDP协议(User Datagram Protocol)中文名称是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP协议的实时性比较好,通常用于视频直播相关领域。

// UDP/server/main.go

// UDP server端
func main() {
  listen, err := net.ListenUDP("udp", &net.UDPAddr{
    IP:   net.IPv4(0, 0, 0, 0),
    Port: 30000,
  })
  if err != nil {
    fmt.Println("listen failed, err:", err)
    return
  }
  defer listen.Close()
  for {
    var data [1024]byte
    n, addr, err := listen.ReadFromUDP(data[:]) // 接收数据
    if err != nil {
      fmt.Println("read udp failed, err:", err)
      continue
    }
    fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n)
    _, err = listen.WriteToUDP(data[:n], addr) // 发送数据
    if err != nil {
      fmt.Println("write to udp failed, err:", err)
      continue
    }
  }
}

// UDP 客户端
func main() {
  socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
    IP:   net.IPv4(0, 0, 0, 0),
    Port: 30000,
  })
  if err != nil {
    fmt.Println("连接服务端失败,err:", err)
    return
  }
  defer socket.Close()
  sendData := []byte("Hello server")
  _, err = socket.Write(sendData) // 发送数据
  if err != nil {
    fmt.Println("发送数据失败,err:", err)
    return
  }
  data := make([]byte, 4096)
  n, remoteAddr, err := socket.ReadFromUDP(data) // 接收数据
  if err != nil {
    fmt.Println("接收数据失败,err:", err)
    return
  }
  fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8