Socket 起源于 Unix,而 Unix 基本哲学之一就是“一切皆文件”,都可以用“打开 open –> 读写 write/read –> 关闭 close”模式来操作。Socket 就是该模式的一个实现,网络的 Socket 数据传输是一种特殊的 I/O,Socket 也是一种文件描述符。Socket 也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的 Socket 描述符,随后的连接建立、数据传输等操作都是通过该 Socket 实现的。
常用的 Socket 类型有两种:流式 Socket(SOCK_STREAM)和数据报式 Socket(SOCK_DGRAM)。流式是一种面向连接的 Socket,针对于面向连接的 TCP 服务应用;数据报式 Socket 是一种无连接的 Socket,对应于无连接的 UDP 服务应用。
使用 TCP/IP 协议的应用程序通常采用应用编程接口:UNIX BSD 的套接字(socket)和 UNIX System V 的 TLI(已经被淘汰),来实现网络进程之间的通信。
Socket 基础知识
Socket 有两种:TCP Socket 和 UDP Socket,而要确定一个进程的需要三元组:IP 地址、端口和协议(TCP 和 UDP)。
IPv4 地址
全球因特网所采用的协议族是 TCP/IP 协议,IP 是 TCP/IP 协议中网络层的协议(核心协议)。IPv4 的地址位数为 32 位,地址格式类似这样:127.0.0.1、172.122.121.111。
IPv6 地址
IPv6 的地址位数为 128 位,在 IPv6 的设计过程中除了一劳永逸地解决了地址短缺问题以外,还考虑了在 IPv4 中解决不好的其它问题,主要有端到端 IP 连接、服务质量(QoS)、安全性、多播、移动性、即插即用等。地址格式类似这样:2002:c0e8:82e7:0:0:0:c0e8:82e7。
go 支持的 ip 类型
type IP []byte
name := os.Args[1]
// 把一个 IPv4 或者 IPv6 的地址转化成 IP 类型
addr := net.ParseIP(name)
if addr == nil {
fmt.Println("Invalid address")
} else {
fmt.Println("The address is ", addr.String())
}
TCP Socket
TCPConn 可以用在客户端和服务器端来读写数据,可以通过 DialTCP 获取,当连接建立时服务器端也创建一个同类型的对象。
func (c *TCPConn) Write(b []byte) (int, error)
func (c *TCPConn) Read(b []byte) (int, error)
// network 参数是 tcp4、tcp6、tcp 中的任意一个
// laddr 表示本机地址,一般设置为 nil
// raddr 表示远程的服务地址
func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)
TCPAddr 表示一个 TCP 的地址信息,可以通过 ResolveTCPAddr 获取。
type TCPAddr struct {
IP IP
Port int
Zone string // IPv6 scoped addressing zone
}
// net 参数是 tcp4、tcp6、tcp 中的任意一个
// addr 表示域名或者 IP 地址,例如 www.google.com:80 或者 127.0.0.1:22
func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)
TCP client
模拟一个基于 HTTP 协议的客户端请求去连接一个 Web 服务端。
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0])
os.Exit(1)
}
service := os.Args[1]
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
checkError(err)
conn, err := net.DialTCP("tcp", nil, tcpAddr)
checkError(err)
_, err = conn.Write([]byte("timestamp"))
checkError(err)
// result, err := ioutil.ReadAll(conn)
result := make([]byte, 256)
_, err = conn.Read(result)
checkError(err)
fmt.Println(string(result))
os.Exit(0)
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
TCP server
可以通过 net 包来创建一个服务器端程序,在服务器端需要绑定服务到指定的非激活端口,并监听此端口。
func ListenTCP(network string, laddr *TCPAddr) (*TCPListener, error)
func (l *TCPListener) Accept() (Conn, error)
func main() {
service := ":1200"
tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
for {
conn, err := listener.Accept()
if err != nil {
continue
}
go handleClient(conn)
}
}
func handleClient(conn net.Conn) {
// 一定时间内客户端无请求发送,conn 便会自动关闭
conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // set 2 minutes timeout
request := make([]byte, 128) // set maxium request length to 128B to prevent flood attack
defer conn.Close() // close connection before exit
// 保持与客户端的长连接
for {
// 读取客户端发来的请求
read_len, err := conn.Read(request)
if err != nil {
fmt.Println(err)
break
}
if read_len == 0 {
break // connection already closed by client
} else if strings.TrimSpace(string(request[:read_len])) == "timestamp" {
daytime := strconv.FormatInt(time.Now().Unix(), 10)
conn.Write([]byte(daytime))
} else {
daytime := time.Now().String()
conn.Write([]byte(daytime))
}
// 每次读取到请求处理完毕后,需要清理 request,因为 conn.Read() 会将新读取到的内容 append 到原内容之后
request = make([]byte, 128) // clear last read content
}
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
UDP Socket
UDP Socket 不同的地方就是在服务器端处理多个客户端请求数据包的方式不同,UDP 缺少对客户端连接请求的 Accept 方法。
func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error)
func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error)
func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error)
func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error)
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error)
UDP client
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0])
os.Exit(1)
}
service := os.Args[1]
udpAddr, err := net.ResolveUDPAddr("udp4", service)
checkError(err)
conn, err := net.DialUDP("udp", nil, udpAddr)
checkError(err)
_, err = conn.Write([]byte("anything"))
checkError(err)
var buf [512]byte
n, err := conn.Read(buf[0:])
checkError(err)
fmt.Println(string(buf[0:n]))
os.Exit(0)
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error %s", err.Error())
os.Exit(1)
}
}
UDP server
func main() {
service := ":1200"
udpAddr, err := net.ResolveUDPAddr("udp4", service)
checkError(err)
conn, err := net.ListenUDP("udp", udpAddr)
checkError(err)
for {
handleClient(conn)
}
}
func handleClient(conn *net.UDPConn) {
var buf [512]byte
_, addr, err := conn.ReadFromUDP(buf[0:])
if err != nil {
return
}
daytime := time.Now().String()
conn.WriteToUDP([]byte(daytime), addr)
}
func checkError(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error %s", err.Error())
os.Exit(1)
}
}