什么是Socket?
Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。
Socket也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
Socket 是实现“打开–读/写–关闭”这样的模式,以使用 TCP 协议通讯的 socket 为例。如下图所示:
常用的Socket类型
- 流式Socket(SOCK_STREAM),流式是一种面向连接的Socket,针对于面向连接的TCP服务应用
- 数据报式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去组织符合指定的协议数据然后进行通信。
- 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.111IPv6地址
IPv6是下一版本的互联网协议,也可以说是下一代互联网的协议,它是为了解决IPv4在实施过程中遇到的各种问题而被提出的,IPv6采用128位地址长度,几乎可以不受限制地提供地址。按保守方法估算IPv6实际可分配的地址,整个地球的每平方米面积上仍可分配1000多个地址。在IPv6的设计过程中除了一劳永逸地解决了地址短缺问题以外,还考虑了在IPv4中解决不好的其它问题,主要有端到端IP连接、服务质量(QoS)、安全性、多播、移动性、即插即用等。
地址格式类似这样:2002:c0e8:82e7:0:0:0:c0e8:82e7
Go支持的IP类型
在Go的net包中定义了很多类型、函数和方法用来网络编程,其中IP的定义如下:
type IP []byte
在net包中有很多函数来操作IP,但是其中比较有用的也就几个,其中ParseIP(s string) IP函数会把一个IPv4或者IPv6的地址转化成IP类型,请看下面的例子:
package main
import (
"fmt"
"net"
"os"
)
func main() {
// 把一个 IPv4 或者 IPv6 的地址转化成 IP 类型
name := "192.168.10.105"
addr := net.ParseIP(name)
if addr == nil {
fmt.Println("Invalid address")
} else {
fmt.Println("The address is ", addr.String())
}
os.Exit(0)
}
Go TCP通信
TCP协议
TCP/IP(Transmission Control Protocol/Internet Protocol) 即传输控制协议/网间协议,是一种面向连接(连接导向)的、可靠的、基于字节流的传输层(Transport layer)通信协议。
因为是面向连接的协议,数据像水流一样传输,会存在粘包问题。
TCP服务端
一个TCP服务端可以同时连接很多个客户端,例如世界各地的用户使用自己电脑上的浏览器访问淘宝网。因为Go语言中创建多个goroutine实现并发非常方便和高效,所以我们可以每建立一次链接就创建一个goroutine去处理。
TCP服务端程序的处理流程:
- 监听端口
- 接收客户端请求建立链接
- 创建 goroutine 处理链接
package main
import (
"bufio"
"fmt"
"net"
)
// 处理函数
func process(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("read from client failed, err:", err)
break
}
recvStr := string(buf[:n])
fmt.Println("收到 client端发来的数据:", recvStr)
// 服务端发送数据
conn.Write([]byte("server: " + recvStr))
}
}
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:5173")
if err != nil {
fmt.Println("listen failed, err:", err)
return
}
for {
// 建立连接
conn, err := listen.Accept()
if err != nil {
fmt.Println("accept failed, err:", err)
continue
}
go process(conn)
}
}
TCP客户端
一个TCP客户端进行TCP通信的流程如下:
- 建立与服务端的链接
- 进行数据收发
- 关闭链接 ```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 }
defer conn.Close()
inputReader := bufio.NewReader(os.Stdin)
for {
input, _ := inputReader.ReadString('\n')
inputInfo := strings.Trim(input, "\r\n")
if strings.ToUpper(inputInfo) == "Q" {
return
}
// 发送数据
_, err := conn.Write([]byte(inputInfo))
if err != nil {
return
}
// 接收服务端数据
buf := [512]byte{}
// 读取整个buf
n, err := conn.Read(buf[:])
if err != nil {
fmt.Println("recv failed, err:", err)
return
}
// 从开始读取到n,不包括n
fmt.Println(string(buf[:n]))
}
}
go run server.go 收到 client端发来的数据: hello 收到 client端发来的数据: how are you?
go run client.go hello server: hello how are you? server: how are you? ```
参考