什么是Socket?

Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。
Socket也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
Socket 是实现“打开–读/写–关闭”这样的模式,以使用 TCP 协议通讯的 socket 为例。如下图所示:

socket编程 - 图1

常用的Socket类型

  1. 流式Socket(SOCK_STREAM),流式是一种面向连接的Socket,针对于面向连接的TCP服务应用
  2. 数据报式Socket(SOCK_DGRAM),数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用
类型 SOCK_DGRAM SOCK_STREAM
数据形式 数据报 字节流
数据边界 没有
逻辑连接 没有
数据有序性 不能保证 能够保证
传输可靠性 不具备 具备

通过上面的介绍我们知道Socket有两种:TCP Socket和UDP Socket,TCP和UDP是协议,而要确定一个进程的需要三元组,需要IP地址和端口。

socket通信

Socket是BSD UNIX的进程通信机制,通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄。Socket是应用层与TCP/IP协议族通信的中间软件抽象层。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket后面,对用户来说只需要调用Socket规定的相关函数,让Socket去组织符合指定的协议数据然后进行通信。
image.png
image.png

  • Socket又称“套接字”,应用程序通常通过“套接字”向网络发出请求或者应答网络请求
  • 常用的Socket类型有两种:流式Socket和数据报式Socket,流式是一种面向连接的Socket,针对于面向连接的TCP服务应用,数据报式Socket是一种无连接的Socket,针对于无连接的UDP服务应用
  • TCP:比较靠谱,面向连接,比较慢
  • UDP:不是太靠谱,比较快

    Socket基础知识

    通过上面的介绍我们知道Socket有两种:TCP Socket和UDP Socket,TCP和UDP是协议,而要确定一个进程的需要三元组,需要IP地址和端口。

    IPv4地址

    目前的全球因特网所采用的协议族是TCP/IP协议。IP是TCP/IP协议中网络层的协议,是TCP/IP协议族的核心协议。目前主要采用的IP协议的版本号是4(简称为IPv4),发展至今已经使用了30多年。
    IPv4的地址位数为32位,也就是最多有2的32次方的网络设备可以联到Internet上。近十年来由于互联网的蓬勃发展,IP位址的需求量愈来愈大,使得IP位址的发放愈趋紧张,前一段时间,据报道IPV4的地址已经发放完毕,我们公司目前很多服务器的IP都是一个宝贵的资源。
    地址格式类似这样:127.0.0.1、 172.122.121.111

    IPv6地址

    IPv6是下一版本的互联网协议,也可以说是下一代互联网的协议,它是为了解决IPv4在实施过程中遇到的各种问题而被提出的,IPv6采用128位地址长度,几乎可以不受限制地提供地址。按保守方法估算IPv6实际可分配的地址,整个地球的每平方米面积上仍可分配1000多个地址。在IPv6的设计过程中除了一劳永逸地解决了地址短缺问题以外,还考虑了在IPv4中解决不好的其它问题,主要有端到端IP连接、服务质量(QoS)、安全性、多播、移动性、即插即用等。
    地址格式类似这样:2002:c0e8:82e7:0:0:0:c0e8:82e7

Go支持的IP类型

在Go的net包中定义了很多类型、函数和方法用来网络编程,其中IP的定义如下:

  1. type IP []byte

在net包中有很多函数来操作IP,但是其中比较有用的也就几个,其中ParseIP(s string) IP函数会把一个IPv4或者IPv6的地址转化成IP类型,请看下面的例子:

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. "os"
  6. )
  7. func main() {
  8. // 把一个 IPv4 或者 IPv6 的地址转化成 IP 类型
  9. name := "192.168.10.105"
  10. addr := net.ParseIP(name)
  11. if addr == nil {
  12. fmt.Println("Invalid address")
  13. } else {
  14. fmt.Println("The address is ", addr.String())
  15. }
  16. os.Exit(0)
  17. }

Go TCP通信

TCP协议

TCP/IP(Transmission Control Protocol/Internet Protocol) 即传输控制协议/网间协议,是一种面向连接(连接导向)的、可靠的、基于字节流的传输层(Transport layer)通信协议。
为是面向连接的协议,数据像水流一样传输,会存在粘包问题

image.pngimage.png
image.png

TCP服务端

一个TCP服务端可以同时连接很多个客户端,例如世界各地的用户使用自己电脑上的浏览器访问淘宝网。因为Go语言中创建多个goroutine实现并发非常方便和高效,所以我们可以每建立一次链接就创建一个goroutine去处理。
TCP服务端程序的处理流程:

  1. 监听端口
  2. 接收客户端请求建立链接
  3. 创建 goroutine 处理链接
  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "net"
  6. )
  7. // 处理函数
  8. func process(conn net.Conn) {
  9. defer conn.Close()
  10. for {
  11. reader := bufio.NewReader(conn)
  12. var buf [128]byte
  13. n, err := reader.Read(buf[:])
  14. if err != nil {
  15. fmt.Println("read from client failed, err:", err)
  16. break
  17. }
  18. recvStr := string(buf[:n])
  19. fmt.Println("收到 client端发来的数据:", recvStr)
  20. // 服务端发送数据
  21. conn.Write([]byte("server: " + recvStr))
  22. }
  23. }
  24. func main() {
  25. listen, err := net.Listen("tcp", "127.0.0.1:5173")
  26. if err != nil {
  27. fmt.Println("listen failed, err:", err)
  28. return
  29. }
  30. for {
  31. // 建立连接
  32. conn, err := listen.Accept()
  33. if err != nil {
  34. fmt.Println("accept failed, err:", err)
  35. continue
  36. }
  37. go process(conn)
  38. }
  39. }

TCP客户端

一个TCP客户端进行TCP通信的流程如下:

  1. 建立与服务端的链接
  2. 进行数据收发
  3. 关闭链接 ```go package main

import ( “bufio” “fmt” “net” “os” “strings” )

func main() { conn, err := net.Dial(“tcp”, “127.0.0.1:5173”) if err != nil { fmt.Println(“err :”, err) return }

  1. defer conn.Close()
  2. inputReader := bufio.NewReader(os.Stdin)
  3. for {
  4. input, _ := inputReader.ReadString('\n')
  5. inputInfo := strings.Trim(input, "\r\n")
  6. if strings.ToUpper(inputInfo) == "Q" {
  7. return
  8. }
  9. // 发送数据
  10. _, err := conn.Write([]byte(inputInfo))
  11. if err != nil {
  12. return
  13. }
  14. // 接收服务端数据
  15. buf := [512]byte{}
  16. // 读取整个buf
  17. n, err := conn.Read(buf[:])
  18. if err != nil {
  19. fmt.Println("recv failed, err:", err)
  20. return
  21. }
  22. // 从开始读取到n,不包括n
  23. fmt.Println(string(buf[:n]))
  24. }

}

go run server.go 收到 client端发来的数据: hello 收到 client端发来的数据: how are you?

go run client.go hello server: hello how are you? server: how are you? ```

参考