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 ...
随机推荐
- 年度盘点,四年的精华合集「GitHub 热点速览」
今年是 GPT 年,无论是 GitHub 还是朋友圈还是技术平台,即便你不关心 GPT 的发展情况,同大模型.AI 相关的项目总能进入你的信息流.到这期为止,热度速览也连载了四年,从一开始习惯看 Gi ...
- Cisco 交换机利用CDP数据自动绘制网络拓扑图[drawio]-实践
进行网络运维,必须对网络拓扑情况进行详细的掌握,但是网络改动后,更新网络拓扑比较繁琐,维护人员容易懈怠,久而久之,通过人工绘制的网络拓扑很容易与现有网络出现偏差. 现在,可以通过python 丰富的库 ...
- Odoo接口说明
通过使用PostMan调用Odoo - POST { "jsonrpc":"2.0", "params":{ "db": ...
- 内网& 公网
内.外网是相对于防火墙而言的,在防火墙内部叫做内网,反之就是外网.在一定程度上外网等同于公网,内网等同于私网. 内网IP是什么? 内网IP简单理解就是局域网IP地址.内网地址即局域网(LAN),内网的 ...
- Python——第五章:Zipfile模块
Zipfile模块 zipfile 模块是 Python 中用于处理 ZIP 文件的标准库模块.它提供了创建.读取和写入 ZIP 文件的功能. zipfile 模块在写入文件时,如果要将文件写入子目录 ...
- Python——第二章:字典(dictionary)以及 添、删、改、查
首先, 字典是以键值对的形式进行存储数据的,必须有键[key],有值[value] 字典的表示方式: {key:value, key2:value, key3:value} 举例: dic = {&q ...
- 直击云栖|践行数据化运维,云掣重新解读MSP
2020年云栖大会百城汇·杭州站,云掣MSP专场圆满落幕! 本次云栖大会·云掣MSP专场以"数据智能,智能运维"为主题,主要聚焦企业云化转型演进趋势,云上运维全景监控以及云原生云环 ...
- Fpga开发笔记(一):高云FPGA芯片介绍,入手开发板套件、核心板和底板介绍
前言 FPGA作为一种逻辑芯片,硬件架构独特,具有并行性.低延时性和灵活性等特性,应用领域广泛. FPGA市场主要玩家是英特尔.AMD.莱迪思.Microchip.Achronix等:国内厂商包 ...
- Java使用OSS实现上传文件
阿里云OSS使用 1.准备OSS 访问阿里云官网,注册并登录账户(支付宝方便一些,顺便冲点钱) 在产品分类中,找到阿里云oss 然后创建一个bucket 2.使用OSS(上传文件) 2.1在阿里云网站 ...
- CodeForces 808G Anthem of Berland 前缀函数 KMP DP
原题链接 题意 第一行给我们一串长为s,只包含小写字母与问号的字符串A,第二行给我们一个长为t只有小写字母的字符串B, 同时满足 $ s * t \le 1e7 $ 我们可以把问号变成任意的字母,我们 ...