Go socket 编程源码解析(下)
在上一节中介绍了 socket 的 Listen
方法,这里进一步介绍 Accept
和 Read
,Write
方法。
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 连接。并且通过 setKeepAlive
和 setKeepAlivePeriod
函数添加对应该新 fd 的 KeepAlive 属性:tcp_keepalive_time
, tcp_keepalive_intvl
和 tcp_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_21402 和 go 变量逃逸分析。
继续看 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.FD
的 Accept
是重头戏了,接着看:
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
方法中做了下面几件事:
accept
函数经过Accept4Func
,accept4
到系统调用,通过系统调用号SYS_ACCEPT4
和文件描述符fd.Sysfd
返回作用在 socket 上的系统文件描述符和远端 socket 地址。- 这里 accept 是非阻塞的,意味着即使没有 client 连接也会返回。此时返回的 err 类型为
syscall.EAGAIN
。 - 进入到 EAGAIN 错误类型中,会通过
fd.pd.pollable
方法判断是否为true
。如果为true
阻塞当前 goroutine 直到有新的可读数据。
Accept 的实现简单介绍基本告一段落了,下面继续看 socket 的 Read
和 Write
实现。
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
类似于 Read
,Write
的核心逻辑在:
// 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
。如果返回 EAGAIN
且 pollable
为 true
的话则阻塞当前 goroutine 进入 waitWrite
。直到数据写完,跳出 for 循环。
Go socket 编程源码解析(下)的更多相关文章
- Hadoop中Yarnrunner里面submit Job以及AM生成 至Job处理过程源码解析
参考 http://blog.csdn.net/caodaoxi/article/details/12970993 Hadoop中Yarnrunner里面submit Job以及AM生成 至Job处理 ...
- Spring IOC容器启动流程源码解析(四)——初始化单实例bean阶段
目录 1. 引言 2. 初始化bean的入口 3 尝试从当前容器及其父容器的缓存中获取bean 3.1 获取真正的beanName 3.2 尝试从当前容器的缓存中获取bean 3.3 从父容器中查找b ...
- Dubbo服务调用过程源码解析④
目录 0.服务的调用 1.发送请求 2.请求编码 3.请求的解码 4.调用具体服务 5.返回调用结果 6.接收调用结果 Dubbo SPI源码解析① Dubbo服务暴露源码解析② Dubbo服务引用源 ...
- Android开发——View绘制过程源码解析(二)
0. 前言 View的绘制流程从ViewRoot的performTraversals开始,经过measure,layout,draw三个流程,之后就可以在屏幕上看到View了.上一篇已经介绍了Vi ...
- Spring bean 创建过程源码解析
在上一篇文件 Spring 中 bean 注册的源码解析 中分析了 Spring 中 bean 的注册过程,就是把配置文件中配置的 bean 的信息加载到内存中,以 BeanDefinition 对象 ...
- Spark作业执行流程源码解析
目录 相关概念 概述 源码解析 作业提交 划分&提交调度阶段 提交任务 执行任务 结果处理 Reference 本文梳理一下Spark作业执行的流程. Spark作业和任务调度系统是其核心,通 ...
- java架构之路-(SpringMVC篇)SpringMVC主要流程源码解析(下)注解配置,统一错误处理和拦截器
我们上次大致说完了执行流程,也只是说了大致的过程,还有中间会出错的情况我们来处理一下. 统一异常处理 比如我们的运行时异常的500错误.我们来自定义一个类 package com.springmvcb ...
- Tars-Java网络编程源码分析
作者:vivo 互联网服务器团队- Jin Kai 本文从Java NIO网络编程的基础知识讲到了Tars框架使用NIO进行网络编程的源码分析. 一.Tars框架基本介绍 Tars是腾讯开源的支持多语 ...
- redis启动过程源码解析
redis整个程序的入口函数在server.c中的main函数,函数调用关系如下图1,调用顺序为从上到下,从左至右. 图1 redis启动函数调用图 main函数源码如下,1-55行根据配置文件和启动 ...
- dubbo系列四、dubbo服务暴露过程源码解析
一.代码准备 1.示例代码 参考dubbo系列二.dubbo+zookeeper+dubboadmin分布式服务框架搭建(windows平台) 2.简单了解下spring自定义标签 https://w ...
随机推荐
- 【纯手工打造】时间戳转换工具(python)
1.背景 最近发现一个事情,如果日志中的时间戳,需要我们转换成时间,增加可读性.或者将时间转换成时间戳,来配置时间.相信大多人和我一样,都是打开网页,搜索在线时间戳转换工具,然后复制粘贴进去.个人认为 ...
- 从 ECMAScript 6 角度谈谈执行上下文
大家好,我是归思君 起因是最近了解JS执行上下文的时候,发现很多书籍和资料,包括<JavaScript高级程序设计>.<JavaScript权威指南>和网上的一些博客专栏,都是 ...
- NetSuite 开发日记:批量增删改
一.批量插入/创建 使用 record.create() 插入数据时,一次只能插入一条,有多条数据需要插入时只能通过循环的方式,这样效率非常慢,耗时会随着数据量的增大而呈线性增长,有一种巧妙的方式可以 ...
- 基于源码去理解Iterator迭代器的Fail-Fast与Fail-Safe机制
原创/朱季谦 在Java编程当中,Iterator迭代器是一种用于遍历如List.Set.Map等集合的工具.这类集合部分存在线程安全的问题,例如ArrayList,若在多线程环境下,迭代遍历过程中存 ...
- Android SDK Manager 报错“加载 SDK 组件信息失败”。(Android SDK Manager complains with "Loading SDK component information failed."
[解决方案]: 将存储库设置更改为 Google .因此,在 android SDK 管理器上点击齿轮图标(设置),然后点击 Repository -> Google.
- React 类组件转换为函数式
函数式的 React 组件更加现代,并支持有用的 hooks,现在流行把旧式的类组件转换为函数式组件.这篇文章总结了转换的一些通用的步骤和陷阱. 通用替换 定义 从 class (\w+) exten ...
- linux内核initcall放置在各个section中函数执行流程
前言 linux以及嵌入式一些代码,我们看到core_initcall.device_initcall等等需要链接器分配各个section,并且在启动该模块时候执行.下面我们详细追溯一下执行过程. 作 ...
- ChatGPT的中转站(欧派API) oupuapi,不扶墙也能上楼
开启智能生活新篇章:oupo中转站(欧派)--引领人工智能大模型的枢纽 在人工智能技术日新月异的今天,我们荣幸地向您推介oupo中转站(欧派)--这一汇聚各类顶尖人工智能大模型的平台.它不仅为技术研发 ...
- 从传统行业到半导体行业开发(YMS,DMS,EAP,EDA)
一线开发人: 今天半导体YMS 项目快要收尾了,我的心情有点高兴,多年来我一直保持着写作的习惯,总是想写一些什么,今天但是又不知道从何说起.自己从传统的行业转向左半导体行业开发.从电*机如软件开发到电 ...
- nacos系列:spring cloud使用nacos实现配置管理和服务发现
目录 版本说明 创建项目 版本说明 IDEA:2021.3 Maven:3.6.3 Jdk:17 Spring-Boot:2.6.13 Spring-Cloud:2021.0.5 Spring-Clo ...