Golang Socket原理

参考:

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))

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值