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 ...
随机推荐
- 使用Visual Studio 2022 创建lib和dll并使用
对于一个经常写javaWeb的人来说,使用Visual Studio似乎没什么必要,但是对于使用ffi的人来说,使用c或c++编译器,似乎是必不可少的,下面我将讲述如何用Visual Studio 2 ...
- 解决swagger2 --> Illegal DefaultValue null for parameter type integer 保存问题
在pmo.xml中加入两个依赖 <!--增加两个配置--> <dependency> <groupId>io.swagger</groupId> < ...
- Java8函数式编程应用
我们经常提到,Java8是革命性的一个版本,原因就是正式引入了函数式编程,那Java的函数式编程在实际应用中到底有什么用呢?结合实际的应用,我整理出了函数式在Java的几个经典用途. 缓求值 惰性求值 ...
- Selenium-无头模式headless
无头模式适合的场景: 部署到没有gui界面的服务器,比如linux 开发环境测试完全没问题后可以使用无头模式,提高selenium速度. # 使用headless无界面浏览器模式 chrome_opt ...
- Go 泛型之泛型约束
Go 泛型之泛型约束 目录 Go 泛型之泛型约束 一.引入 二.最宽松的约束:any 三.支持比较操作的内置约束:comparable 四.自定义约束 五.类型集合(type set) 六.简化版的约 ...
- MinIO客户端之cat
MinIO提供了一个命令行程序mc用于协助用户完成日常的维护.管理类工作. 官方资料 mc cat 使用cat命令输出本地硬盘上的文本文件的内容至控制台. ./mc cat ./local.json ...
- 部署堡垒机2——安装MySQL8.0.32或8.0.28+
MySQL的三大版本 a)MySQL Enterprise Edition:企业版本(付费)b)MySQL Cluster CGE:高级集群版(收费)c)MySQL Community Server: ...
- 请注意,你的 Pulsar 集群可能有删除数据的风险
在上一篇 Pulsar3.0新功能介绍中提到,在升级到 3.0 的过程中碰到一个致命的问题,就是升级之后 topic 被删除了. 正好最近社区也补充了相关细节,本次也接着这个机会再次复盘一下,毕竟这是 ...
- QRCoder1.4.3生成二维码,不依赖System.Drawing,解决"未能找到类型或命名空间名QRCode","及ImageFormatPng仅在windows上受支持"
生成二维码1(简单) 包引用: <PackageReference Include="QRCoder" Version="1.4.3" /> usi ...
- CSS3学习笔记-字体属性
在CSS3中,可以使用字体属性来控制网页中文本的样式和排版.以下是常用的字体属性: font-family 该属性用于指定网页中的文本所使用的字体.我们可以通过使用通用的字体名称,或者直接使用字体名称 ...