学习一门语言,那么我们必然要涉及到网络通信,而谈到网络通信却又离不开tcp,这里我们利用go标准库net来模拟一个服务端、客户端的流程,从而深入学习其中的代码流程(深入其中解析本质)
func main() {
server, err := net.Listen("tcp", "127.0.0.1:8080") // 开启服务端
if err != nil {
log.Fatal(err)
}
defer server.Close()
go func() { // 模拟客户端往服务端发送数据
coon, err := net.Dial("tcp", "127.0.0.1:8080")
if err != nil {
log.Fatal(err)
}
defer coon.Close()
coon.Write([]byte("hello world")) // 客户端发送数据
}()
client, _ := server.Accept() // 接受客户端
defer client.Close()
fmt.Println("client addr: ", client.LocalAddr().String()) // 打印服务端地址
var recv []byte = make([]byte, 100)
client.Read(recv)
//recv, _ := io.ReadAll(client)
fmt.Println("server recv from client: ", string(recv)) // 打印客户端发送的数据
}
以上代码完成了一个简单的网络通信过程,那么我们就先从服务端入手吧,这里显而易见的是net.Listen("tcp", "127.0.0.1:8080")
这个函数,我们可以猜想这个函数的处理流程应该是极其重要的,跟进看看:
func Listen(network, address string) (Listener, error) {
var lc ListenConfig
return lc.Listen(context.Background(), network, address) // 返回一个Listener接口
}
Listener接口必然映射这一个底层的结构体,那么我们就找到这个结构体分析一下:(跟入lc.Listen(context.Background(), network, address)
函数
func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) {
addrs, err := DefaultResolver.resolveAddrList(ctx, "listen", network, address, nil)
if err != nil {
return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
}
sl := &sysListener{ // 实例化一个sysListener结构体
ListenConfig: *lc,
network: network,
address: address,
}
var l Listener
la := addrs.first(isIPv4)
switch la := la.(type) { // 检查监听的协议
case *TCPAddr:
l, err = sl.listenTCP(ctx, la) // 监听tcp,并设置l为TCPListener结构体
case *UnixAddr:
l, err = sl.listenUnix(ctx, la) // 设置l为UnixListener结构体
default:
return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
}
if err != nil {
return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: err} // l is non-nil interface containing nil pointer
}
return l, nil
}
而最终返回的结构体则是从sl.listenTCP(ctx, la)
或者sl.listenUnix(ctx, la)
得到的,那我们就由sl.listenTCP(ctx, la)
来分析底层的结构体:
func (sl *sysListener) listenTCP(ctx context.Context, laddr *TCPAddr) (*TCPListener, error) {
fd, err := internetSocket(ctx, sl.network, laddr, nil, syscall.SOCK_STREAM, 0, "listen", sl.ListenConfig.Control) // 相当于C语言之中的socket函数
if err != nil {
return nil, err
}
return &TCPListener{fd: fd, lc: sl.ListenConfig}, nil // 返回的是TCPListener结构体
}
其实际流程为初始化一个socket套接字,并封装于TCPListener结构体之中
type TCPListener struct {
fd *netFD // 套接字
lc ListenConfig // TCP配置
}
type ListenConfig struct {
Control func(network, address string, c syscall.RawConn) error
KeepAlive time.Duration
}
那么上层使用的Accpet函数实际为:
func (l *TCPListener) Accept() (Conn, error) {
if !l.ok() {
return nil, syscall.EINVAL
}
c, err := l.accept() // 开始监听,调用内部函数
if err != nil {
return nil, &OpError{Op: "accept", Net: l.fd.net, Source: nil, Addr: l.fd.laddr, Err: err}
}
return c, nil
}
func (ln *TCPListener) accept() (*TCPConn, error) {
fd, err := ln.fd.accept() // 开始监听,对于netFD结构体的一层封装
if err != nil {
return nil, err
}
tc := newTCPConn(fd) // 返回tcp客户端,设置tc为TCPConn结构体
if ln.lc.KeepAlive >= 0 { // 如果持续存活
setKeepAlive(fd, true)
ka := ln.lc.KeepAlive
if ln.lc.KeepAlive == 0 {
ka = defaultTCPKeepAlive
}
setKeepAlivePeriod(fd, ka)
}
return tc, nil
}
而返回结果则是tcp对于的一个客户端会话:
type TCPConn struct { // 实际上就是一个套接字,经过了层层(二层)封装
conn
}
// TCPConn 继承于 conn,而conn是套接字的封装
type conn struct { // 对于套接字的一层封装,具有有读写等功能
fd *netFD
}
同理,客户端对于服务端的连接流程大致一样,同样从net.Dial("tcp", "127.0.0.1:8080")
开始分析:
func Dial(network, address string) (Conn, error) {
var d Dialer // 拨号器
return d.Dial(network, address) // connect封装
}
func (d *Dialer) Dial(network, address string) (Conn, error) {
return d.DialContext(context.Background(), network, address)
}
// 关键函数如下
func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) {
...
c, err := sd.dialParallel(ctx, primaries, fallbacks) // 解析 `整个连接`
...
}
关键函数sd.dialParallel(ctx, primaries, fallbacks)
,如下:
func (sd *sysDialer) dialParallel(ctx context.Context, primaries, fallbacks addrList) (Conn, error) {
if len(fallbacks) == 0 {
return sd.dialSerial(ctx, primaries)
}
returned := make(chan struct{})
defer close(returned)
type dialResult struct {
Conn
error
primary bool
done bool
}
results := make(chan dialResult) // unbuffered
startRacer := func(ctx context.Context, primary bool) {
ras := primaries
if !primary {
ras = fallbacks
}
c, err := sd.dialSerial(ctx, ras) // 开始尝试连接
select {
case results <- dialResult{Conn: c, error: err, primary: primary, done: true}: // 传输连接成功返回的结果
case <-returned: //如果该函数应该返回,则释放资源
if c != nil {
c.Close()
}
}
}
var primary, fallback dialResult
// Start the main racer.
primaryCtx, primaryCancel := context.WithCancel(ctx)
defer primaryCancel()
go startRacer(primaryCtx, true)
// Start the timer for the fallback racer.
fallbackTimer := time.NewTimer(sd.fallbackDelay())
defer fallbackTimer.Stop()
for { // 循环等待
select {
case <-fallbackTimer.C:
fallbackCtx, fallbackCancel := context.WithCancel(ctx)
defer fallbackCancel()
go startRacer(fallbackCtx, false)
case res := <-results: // 等待结果返回,实际为dialResult结构体
if res.error == nil {
return res.Conn, nil
}
if res.primary {
primary = res
} else {
fallback = res
}
if primary.done && fallback.done {
return nil, primary.error
}
if res.primary && fallbackTimer.Stop() {
// If we were able to stop the timer, that means it
// was running (hadn't yet started the fallback), but
// we just got an error on the primary path, so start
// the fallback immediately (in 0 nanoseconds).
fallbackTimer.Reset(0)
}
}
}
}
好了,现在我们可以看出来,net
实际上就是对于socket
的层层封装罢了