在上一节中介绍了 socket 的 Listen 方法,这里进一步介绍 AcceptReadWrite 方法。

1. Accept

Accept 的核心逻辑在于:

func (ln *TCPListener) accept() (*TCPConn, error) {
fd, err := ln.fd.accept()
if err != nil {
return nil, err
}
tc := newTCPConn(fd)
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
}

通过 socket 返回的 fd 调用 accept 方法从 socket 上接收数据。accept 返回新 fd,通过该新 fd 建立 tcp 连接。并且通过 setKeepAlivesetKeepAlivePeriod 函数添加对应该新 fd 的 KeepAlive 属性:tcp_keepalive_time, tcp_keepalive_intvltcp_keepalive_probes

在 KeepAlive 函数中有一段函数 runtime.KeepAlive 比较有意思:

func setKeepAlive(fd *netFD, keepalive bool) error {
err := fd.pfd.SetsockoptInt(syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, boolint(keepalive))
runtime.KeepAlive(fd)
return wrapSyscallError("setsockopt", err)
}

它的存在是为了让 fd 不会被 GC 回收,更多信息可参考 issue_21402go 变量逃逸分析

继续看 accept 方法:

func (fd *netFD) accept() (netfd *netFD, err error) {
d, rsa, errcall, err := fd.pfd.Accept()
...
if netfd, err = newFD(d, fd.family, fd.sotype, fd.net); err != nil {
poll.CloseFunc(d)
return nil, err
} netfd.setAddr(netfd.addrFunc()(lsa), netfd.addrFunc()(rsa))
return netfd, nil
}

netFD 包的是 pfd poll.FD,调用 pfd 的 Accept 方法返回 socket 上的系统文件描述符 d。将 d 包装成 netfd,接着通过 setAddr 设置 netfd 的本地地址 laddr 和 client 端地址 raddr。

poll.FDAccept 是重头戏了,接着看:

func (fd *FD) Accept() (int, syscall.Sockaddr, string, error) {
for {
s, rsa, errcall, err := accept(fd.Sysfd)
if err == nil {
return s, rsa, "", err
}
switch err {
case syscall.EINTR:
continue
case syscall.EAGAIN:
if fd.pd.pollable() {
if err = fd.pd.waitRead(fd.isFile); err == nil {
continue
}
}
...
}
} func accept(s int) (int, syscall.Sockaddr, string, error) {
ns, sa, err := Accept4Func(s, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC)
switch err {
case nil:
return ns, sa, "", nil
...
}
} func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
fd = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}

poll.FD 的 accept 方法中做了下面几件事:

  1. accept 函数经过 Accept4Func, accept4 到系统调用,通过系统调用号 SYS_ACCEPT4 和文件描述符 fd.Sysfd 返回作用在 socket 上的系统文件描述符和远端 socket 地址。
  2. 这里 accept 是非阻塞的,意味着即使没有 client 连接也会返回。此时返回的 err 类型为 syscall.EAGAIN
  3. 进入到 EAGAIN 错误类型中,会通过 fd.pd.pollable 方法判断是否为 true。如果为 true 阻塞当前 goroutine 直到有新的可读数据。

Accept 的实现简单介绍基本告一段落了,下面继续看 socket 的 ReadWrite 实现。

2. Read 和 Write

2.1 Read

Read 经过层层调用到 poll.FD 的 Read 方法:

func (fd *FD) Read(p []byte) (int, error) {
...
if err := fd.pd.prepareRead(fd.isFile); err != nil {
return 0, err
}
if fd.IsStream && len(p) > maxRW {
p = p[:maxRW]
}
for {
n, err := ignoringEINTRIO(syscall.Read, fd.Sysfd, p)
if err != nil {
n = 0
if err == syscall.EAGAIN && fd.pd.pollable() {
if err = fd.pd.waitRead(fd.isFile); err == nil {
continue
}
}
}
err = fd.eofError(n, err)
return n, err
}
}

从上述代码可以发现:

  • 网络处理逻辑通过层层封装走到 poll 的 Read,poll 是不区分文件还是网络数据的。因此,在 prepareRead 中需要通过 fd.isFile 判断。
  • maxRW 是能读取数据的最大字节,这里是 1G。原因分析在注释中:
// Darwin and FreeBSD can't read or write 2GB+ files at a time,
// even on 64-bit systems.
// The same is true of socket implementations on many systems.
// See golang.org/issue/7812 and golang.org/issue/16266.
// Use 1GB instead of, say, 2GB-1, to keep subsequent reads aligned.
  • ignoringEINTRIO 中通过 syscall.Read 函数,作用在系统调用上,通过系统调用号和文件描述符 fd.Sysfd 读取 socket 的数据到 p。
  • 类似 Accept,如果 ignoringEINTRIO 返回错误 syscall.EAGAIN,并且 fd.pd.pollable 是 true 的话,会阻塞当前 goroutine 等待读取数据。
  • 进入到 eofError 逻辑。对于文件,如果读到 EOF 则说明文件结束。对于网络数据,err 返回为 nil。

Write

类似于 ReadWrite 的核心逻辑在:

// Write implements io.Writer.
func (fd *FD) Write(p []byte) (int, error) {
...
for {
max := len(p)
if fd.IsStream && max-nn > maxRW {
max = nn + maxRW
}
n, err := ignoringEINTRIO(syscall.Write, fd.Sysfd, p[nn:max])
if n > 0 {
nn += n
}
if nn == len(p) {
return nn, err
}
if err == syscall.EAGAIN && fd.pd.pollable() {
if err = fd.pd.waitWrite(fd.isFile); err == nil {
continue
}
}
if err != nil {
return nn, err
}
if n == 0 {
return nn, io.ErrUnexpectedEOF
}
}
}

通过 syscall.Write 函数进入系统调用,执行 Write 调用作用于系统文件描述符 fd.Sysfd 写数据到 p。如果返回 EAGAINpollabletrue 的话则阻塞当前 goroutine 进入 waitWrite。直到数据写完,跳出 for 循环。


Go socket 编程源码解析(下)的更多相关文章

  1. Hadoop中Yarnrunner里面submit Job以及AM生成 至Job处理过程源码解析

    参考 http://blog.csdn.net/caodaoxi/article/details/12970993 Hadoop中Yarnrunner里面submit Job以及AM生成 至Job处理 ...

  2. Spring IOC容器启动流程源码解析(四)——初始化单实例bean阶段

    目录 1. 引言 2. 初始化bean的入口 3 尝试从当前容器及其父容器的缓存中获取bean 3.1 获取真正的beanName 3.2 尝试从当前容器的缓存中获取bean 3.3 从父容器中查找b ...

  3. Dubbo服务调用过程源码解析④

    目录 0.服务的调用 1.发送请求 2.请求编码 3.请求的解码 4.调用具体服务 5.返回调用结果 6.接收调用结果 Dubbo SPI源码解析① Dubbo服务暴露源码解析② Dubbo服务引用源 ...

  4. Android开发——View绘制过程源码解析(二)

    0. 前言   View的绘制流程从ViewRoot的performTraversals开始,经过measure,layout,draw三个流程,之后就可以在屏幕上看到View了.上一篇已经介绍了Vi ...

  5. Spring bean 创建过程源码解析

    在上一篇文件 Spring 中 bean 注册的源码解析 中分析了 Spring 中 bean 的注册过程,就是把配置文件中配置的 bean 的信息加载到内存中,以 BeanDefinition 对象 ...

  6. Spark作业执行流程源码解析

    目录 相关概念 概述 源码解析 作业提交 划分&提交调度阶段 提交任务 执行任务 结果处理 Reference 本文梳理一下Spark作业执行的流程. Spark作业和任务调度系统是其核心,通 ...

  7. java架构之路-(SpringMVC篇)SpringMVC主要流程源码解析(下)注解配置,统一错误处理和拦截器

    我们上次大致说完了执行流程,也只是说了大致的过程,还有中间会出错的情况我们来处理一下. 统一异常处理 比如我们的运行时异常的500错误.我们来自定义一个类 package com.springmvcb ...

  8. Tars-Java网络编程源码分析

    作者:vivo 互联网服务器团队- Jin Kai 本文从Java NIO网络编程的基础知识讲到了Tars框架使用NIO进行网络编程的源码分析. 一.Tars框架基本介绍 Tars是腾讯开源的支持多语 ...

  9. redis启动过程源码解析

    redis整个程序的入口函数在server.c中的main函数,函数调用关系如下图1,调用顺序为从上到下,从左至右. 图1 redis启动函数调用图 main函数源码如下,1-55行根据配置文件和启动 ...

  10. dubbo系列四、dubbo服务暴露过程源码解析

    一.代码准备 1.示例代码 参考dubbo系列二.dubbo+zookeeper+dubboadmin分布式服务框架搭建(windows平台) 2.简单了解下spring自定义标签 https://w ...

随机推荐

  1. IDEA 激活码全家桶 webStorm亲测可用【更新日期2021.11.30】

    3MRUAPM31O-eyJsaWNlbnNlSWQiOiIzTVJVQVBNMzFPIiwibGljZW5zZWVOYW1lIjoi5rC45LmF5r+A5rS7IHd3d8K3YWppaHVvw ...

  2. 3D 高斯喷溅 🤗 为什么图形永远不会相同

    高斯喷溅 (Gaussian Splatting) 技术是一种翻天覆地的渲染手段,能够以 144 帧每秒的速度渲染出高质量的场景,这和传统的图形处理流程截然不同 这种将高斯数据转换成图像的过程,与训练 ...

  3. 小市值选股策略代码分享(附python源码)

    小市值选股策略的核心在于通过综合分析公司的基本面.行业定位.财务健康状况以及市场趋势, 来寻找那些被市场低估但具备显著成长潜力的股票,同时也要重视风险管理和投资组合的多样化. 今天来给大家分享下小市值 ...

  4. Celery将任务分发到不同的队列,交给不同的Worker处理

    https://docs.celeryq.dev/en/stable/userguide/routing.html#routing-tasks https://blog.csdn.net/wangle ...

  5. Linux的文件系统,根目录rootfs结构

    文件系统fs 文件系统时操作系统用来管理文件的.fs=filesystem... 在linux中,一切皆为文件,这句话够经典了吧... linux中每个分区都是一个fs. FHS Linux下的Fil ...

  6. 牛客刷Java记录第四天

    第一题,单选题 class Car extends Vehicle { public static void main (String[] args) { new Car(). run(); } pr ...

  7. 三种方式MD5加密

    1.直接生成(标准的MD5加密) 选择函数助手,选择__digest,填写算法和密码,点击生成 2.开发直接给的jar包 步骤:a.从测试计划导入jar包 或 放在jmeter的lib\ext目录读取 ...

  8. Python——第四章:函数的递归调用

    递归:  函数自己调用自己 递归如果没有任何东西拦截的话. 它默认就是一个死循环 def func() func() func() 因此递归调用的时候需要有判断,来退出循环 def func() if ...

  9. 计算机网络分层结构--OSI模型、TCP/IP 模型、五层模型

    计算机网络分层结构 OSI参考模型与TCP/IP参考模型 五层参考模型

  10. 基于 Docker 打造前端持续集成开发环境

    基于 Docker 打造前端持续集成开发环境 写在前面: 这不是一篇有新意的文章,因为这篇文章半年前我就发布在其他平台上过,为什么在知乎上旧文新发? 除了想要把发布在其他地方的文章统一放到知乎上之外, ...