参考:
https://www.cnblogs.com/yinzhengjie/p/7261584.html
https://blog.csdn.net/tianlongtc/article/details/80163661
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装;
TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。
常规socket连接api模型如下:
但在go语言中:
服务端是一个标准的Listen + Accept的结构(可参考上面的代码),而在客户端Go语言使用net.Dial或DialTimeout进行连接建立:
1、 DialTCP
函数:
客户端向服务端的拨号操作,需要指定协议,来建立一个TCP连接,并返回一个TCPConn类型的对象;当连接建立时服务器端也创建一个同类型的对象,此时客户端和服务器段通过各自拥有的TCPConn对象来进行数据交换。
func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)
- net参数是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only)、TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个)
- laddr表示本机地址,一般设置为nil
- raddr表示远程的服务地址
2、Listen函数
服务端函数,使用协议是tcp,监听的地址是addr:
listener,err := net.Listen("tcp",addr)
3、Accept函数
服务端函数,用于接收客户端的连接,
4、 Read函数
n,err = conn.Read(buf) //接收到客户端或者服务端发送的的内容,放入buf缓冲区;
5、Write函数
n,err := conn.Write([]byte("xxxx"))
向服务端或者客户端发送数据。用n接受返回的数据大小,用err接受错误信息。
6、Close函数
conn.Close() //服务端 或者 客户端断开TCP链接。
Socket客户端:
package main
import (
"io"
"net"
"log"
"fmt"
)
var (
cmd string
line string
)
func main() {
addr := "192.168.0.108:8080" //定义主机名
conn, err := net.Dial("tcp", addr) //拨号操作,用于连接服务端,需要指定协议。
if err != nil {
log.Fatal(err)
}
line = "Iam 客户端clint!"
sender(conn, line)
conn.Close() //断开TCP链接。
}
func sender(conn net.Conn, line string) {
n, err := conn.Write([]byte(line)) //向服务端发送数据。用n接受返回的数据大小,用err接受错误信息。
if err != nil {
log.Fatal(err)
}
buf := make([]byte, 10) //定义一个切片的长度是1024。
for {
n, err = conn.Read(buf) //接收到的内容大小。
if err == io.EOF {
conn.Close()
}
fmt.Print(string(buf[:n]))
}
return
}
Socket服务端:
package main
import (
"fmt"
"log"
"net"
"time"
)
func main() {
addr := "0.0.0.0:8080" //表示监听本地所有ip的8080端口,也可以这样写:addr := ":8080"
listener, err := net.Listen("tcp", addr) //使用协议是tcp,监听的地址是addr
if err != nil {
log.Fatal(err)
}
defer listener.Close() //关闭监听的端口23
buf := make([]byte, 128)
for {
fmt.Println("222")
conn, err := listener.Accept() //用conn接收链接
fmt.Println("33")
if err != nil {
log.Fatal(err)
}
for {
read_len, err := conn.Read(buf)
fmt.Printf("n=%d,buf=%s \n", read_len, string(buf))
if err != nil {
fmt.Println(err)
break
}
if read_len == 0 {
break
}
buf = make([]byte, 128) // clear last read content
}
conn.Write([]byte("Yinzhengjie\n")) //通过conn的wirte方法将这些数据返回给客户端。
conn.Write([]byte("hello Golang\n"))
time.Sleep(time.Second*3) //在结束这个链接之前需要睡一分钟在结束当前循环。
conn.Close() //与客户端断开连接。
}
}
控制TCP连接
TCP有很多连接控制函数,我们平常用到比较多的有如下几个函数:
func DialTimeout(net, addr string, timeout time.Duration) (Conn, error)
设置建立连接的超时时间,客户端和服务器端都适用,当超过设置时间时,连接自动关闭。
func (c *TCPConn) SetReadDeadline(t time.Time) error
func (c *TCPConn) SetWriteDeadline(t time.Time) error
用来设置写入/读取一个连接的超时时间。当超过设置时间时,连接自动关闭。
func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error
设置keepAlive属性,是操作系统层在tcp上没有数据和ACK的时候,会间隔性的发送keepalive包,操作系统可以通过该包来判断一个tcp连接是否已经断开,在windows上默认2个小时没有收到数据和keepalive包的时候人为tcp连接已经断开,这个功能和我们通常在应用层加的心跳包的功能类似。
conn.Read的行为特点:
1、Socket中无数据
连接建立后,如果对方未发送数据到socket,接收方(Server)会阻塞在Read操作上,这和前面提到的“模型”原理是一致的。执行该Read操作的goroutine也会被挂起。runtime会监视该socket,直到其有数据才会重新
调度该socket对应的Goroutine完成read。由于篇幅原因,这里就不列代码了,例子对应的代码文件:go-tcpsock/read_write下的client1.go和server1.go。
2、Socket中有部分数据
如果socket中有部分数据,且长度小于一次Read操作所期望读出的数据长度,那么Read将会成功读出这部分数据并返回,而不是等待所有期望数据全部读取后再返回。
3、Socket中有足够数据
如果socket中有数据,且长度大于等于一次Read操作所期望读出的数据长度,那么Read将会成功读出这部分数据并返回。这个情景是最符合我们对Read的期待的了:Read将用Socket中的数据将我们传入的slice填满后返回:n = 10, err = nil。
4、Socket关闭
“有数据关闭”是指在client关闭时,socket中还有server端未读取的数据,server会继续read数据,直到Read返回EOF error。
“无数据关闭”情形下的结果,那就是Read直接返回EOF error。
5、读取操作超时
func (c *TCPConn) SetReadDeadline(t time.Time) error
Write特点:
1、成功写
前面例子着重于Read,client端在Write时并未判断Write的返回值。所谓“成功写”指的就是Write调用返回的n与预期要写入的数据长度相等,且error = nil。这是我们在调用Write时遇到的最常见的情形,这里不再举例了。
2、写阻塞
TCP连接通信两端的OS都会为该连接保留数据缓冲,一端调用Write后,实际上数据是写入到OS的协议栈的数据缓冲的。TCP是全双工通信,因此每个方向都有独立的数据缓冲。当发送方将对方的接收缓冲区以及自身的发送缓冲区写满后,Write就会阻塞。我们来看一个例子:client5.go和server.go。
3、写入部分数据
Write并非在655360这个地方阻塞的,而是后续又写入24108后发生了阻塞,server端socket关闭后,我们看到Wrote返回er != nil且n = 24108,程序需要对这部分写入的24108字节做特定处理。
4、写入超时
conn.SetWriteDeadline(time.Now().Add(time.Microsecond * 10))