Golang 词法分析器浅析

     浅析 Go 语言的词法分析器

章节目录

  1. 简介
  2. Token
  3. Scanner
  4. 例子

作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者再学习错误的知识.谢谢!

简介##

在本文我们将简单的走读 Go 语言的词法分析器实现(go/scanner/scanner.go).

本文基于 Go 1.11.4.

对于 Scanner 的作用, 就像 Java 中的 StringTokenizer 类型, 负责将一个输入字符串按照特定的分隔符划分为一个个独立的单元. 不同的地方在于, 词法分析器在划分单元时依照的是 Go 语言的规范, 而不是指定的分隔符.

那么为什么要将源代码进行划分呢? 说来话长, 建议阅读编译原理相关书籍-.

在写下本文之前, 本人有幸刚刚阅读了 Writing an interpreter in go , 感谢该书作者, 这本书对于我写下这篇文章有很多的帮助, 在这儿分享给大家.

Token##

维基百科中对词法分析描述如下:

词法分析(英语:lexical analysis)是计算机科学中将字符序列转换为标记(token)序列的过程。进行词法分析的程序或者函数叫作词法分析器(lexical analyzer,简称lexer),也叫扫描器(scanner)。词法分析器一般以函数的形式存在,供语法分析器调用。

对于编译原理有了解的读者肯定深知, 词法分析是编译的第一步. 它的主要作用就是将源代码(这里就是我们编写的 Go 语言源文件)进行扫描, 将源代码解析为一个个的词法单元. 而词法单元就对应于我们源代码中的变量,函数,关键字等.

举个例子, 当我们使用 Go 语言编程时, 写下如下代码:

var a bool = false

通过词法解析器的解析, 我们将会得到如下 Token:

var   // Go 语言关键字
a     // 变量
bool  // Go语言内置类型, 也属于关键字
=     // 赋值关键字
false // 常量关键字 

正如你所看到的那样, 我们在解析过程中不仅仅解析出了不同的程序组件, 而且每个组件都有特定的类型标记, 这些信息将被语法分析器使用. 比如可以用来检测语法错误.

好了, 本人知识有限,概述到此结束, 我们来看一下 token.go 的实现(该文件位于 Go 源码目录 go/token/token.go).

为了减少篇幅, 这里省略了部分代码.

type Token int// 所有 Go 语言支持的 Token 类型列表
const (// 该类型的 Token 标识源文件中存在词法错误, 词法分析无法成功的解析源文件.// 也就是说我们编写了不符合 Go 语言语法的源代码ILLEGAL Token = iota// 该类型的 Token 表示源文件已经被遍历完成// 在遇到这种类型的 Token 时, 表示着词法分析的完成EOF COMMENT // 这个 Token 不必多说, 标识源代码中的注释literal_beg// IDENT 标识一个标识符, 比如方法名称, 类型名称, 变量名称等. // 常量, 关键字都不属于这个类型IDENT  // mainINT    // 12345FLOAT  // 123.45IMAG   // 123.45iCHAR   // 'a'STRING // "abc"literal_endoperator_beg// Operators and delimitersADD // +SUB // -MUL // *QUO // /REM // %// AND, OR, +=, -=, &=, ^= 等省略ARROW // <-// 这里省略了各种括号对应的 Token 类型定义operator_endkeyword_beg// 以下声明 Go 语言关键字 Token 类型, 这里省略了绝大部分FUNCGOGOTOIFIMPORTSELECTSTRUCTSWITCHTYPEVARkeyword_end
)// tokens map 的用处在于将词法分析过程中解析出来的单词或者词组
// 与上面刚刚定义的 Token 类型对应起来
// 比如, 当词法分析器从源代码中解析出一个单词 func 时, 他将创建一个 Token, 
// 而该 Token 的类型将是 FUNC. 这一项就存在于下面这个 tokens map 中.
// 值得一提的时, 这个 map 中包含了几项 Token 类型, 这些类型仅仅在用来
// 辅助词法分析器, 程序中并不会出现这样的词法单元.这几个类型: 
//    ILLEFAL: 用来标识不符合 Go 语言语法的词法单元出现在源程序中
//    EOF: 用来标识源程序解析完毕
var tokens = [...]string{ILLEGAL: "ILLEGAL",EOF:     "EOF",COMMENT: "COMMENT",FUNC:   "func",GO:     "go",GOTO:   "goto",IF:     "if",IMPORT: "import",SELECT: "select",STRUCT: "struct",SWITCH: "switch",TYPE:   "type",VAR:    "var",
}// 该方法返回 Token 的字符串表示形式
func (tok Token) String() string {s := ""if 0 <= tok && tok < Token(len(tokens)) {s = tokens[tok]}if s == "" {s = "token(" + strconv.Itoa(int(tok)) + ")"}return s
}const (LowestPrec  = 0 // non-operatorsUnaryPrec   = 6HighestPrec = 7
)// 获取当前 Token 的优先级
// 对于非操作符的 Token, 它的优先级最低, 为 LowestPrec.
// 举个例子说明以下. 比如我们在解析如下代码: a := b + c * d
// 我们会得到七个 Token. 分别对应 a, :=, b, +, c, *, d
// 在语法分析中, 当我们要评估 a 在执行完该语句时,它的值是多少, 
// 我们就需要知道每个操作符 Token (:=, +, *) 的优先级, 以决定哪个操作将被优先执行, 
// 哪个操作实在另一个操作执行完之后执行.
func (op Token) Precedence() int {switch op {case LOR:return 1case LAND:return 2case EQL, NEQ, LSS, LEQ, GTR, GEQ:return 3case ADD, SUB, OR, XOR:return 4case MUL, QUO, REM, SHL, SHR, AND, AND_NOT:return 5}return LowestPrec
}var keywords map[string]Tokenfunc init() {keywords = make(map[string]Token)for i := keyword_beg + 1; i < keyword_end; i++ {keywords[tokens[i]] = i}
}// 该方法用来判别一个解析出来的 Identifier 到底是一个 Identifier 还是一个关键字.
// 关键字 map 在上一步已经被初始化了.
func Lookup(ident string) Token {if tok, is_keyword := keywords[ident]; is_keyword {return tok}return IDENT
}// 下面几个方法很好理解, 不在赘述func (tok Token) IsLiteral() bool { return literal_beg < tok && tok < literal_end }func (tok Token) IsOperator() bool { return operator_beg < tok && tok < operator_end }func (tok Token) IsKeyword() bool { return keyword_beg < tok && tok < keyword_end }

Scanner##

了解了 Token 之后, 我们就可以来看看 Scanner 的实现了.

就如简述中所述, 词法分析器的作用是将源程序分解为一个个 Token, 以便于语法分析器使用. 它的输入肯定就是源程序了, 输出自然是一个 Token 的集合.

词法分析器仅能检测出很少部分的程序错误, 比如 if 语句后未使用花括号’{’, 非法的操作符’…’ 等. 对于类型或变量重定义, 函数调用参数个数不正确等错误都需要在编译器后续过程中才能发现.

这里我们先简述一下词法分析器的工作原理, 这样将有助于学习源码.
词法分析器往往是一个一个字符的读取输入的代码, 通过当前读取到的字符, 搭配一个解析词法的状态机来决定当前读取到的 Token 的类型.有时, 一个字符并不能提供足够的信息来做出这种判断, 此时就需要预先读取下一个或多个字符来辅助词法分析器做出判断.
正如 <> 中所说的一样, 它的工作原理与 JSON 解析器或者 XML 解析器的工作原理大体上是一致的, 只是得到的结果略有不同而已.
而这里提到的解析词法的状态机就是我们将要学习的核心了.

下来我们就来看看 scanner.go 源代码.
和上一小节相同, 我们只留下程序的主干部分, 细枝末节的代码我们将省略掉以换取相对的清晰整洁.
我们同时也根据需要调整了方法或者变量的声明位置.

type Scanner struct {file *token.File  // source file handledir  string       // directory portion of file.Name()src  []byte       // 输入字节数组// 词法分析器使用的核心变量ch         rune // 记录当前字符offset     int  // 记录当前读取到了输入字节的位置rdOffset   int  // reading offset (position after current character)lineOffset int  // 记录当前读取到的字符在输入字节的哪一行// 这里我们省略了记录错误信息的相关变量或者方法, 我们不关注它
}// 初始化词法分析器
// 正如你所看到的, 这里并没有很多值得关注的地方
// 唯一值得一看的就是 next 方法
func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) {// Explicitly initialize all fields since a scanner may be reused.if file.Size() != len(src) {panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src)))}s.file = files.dir, _ = filepath.Split(file.Name())s.src = srcs.err = errs.mode = modes.ch = ' 's.offset = 0s.rdOffset = 0s.lineOffset = 0s.insertSemi = falses.ErrorCount = 0s.next()if s.ch == bom {s.next() // ignore BOM at file beginning}
}// 读取下一个字符.
// 词法分析器实质上就一个状态机, 而该状态机总是需要以当前字符作为输入.
// 这个方法的作用就是读取输入字节中下一个字符.
// 因为 Go 支持 Unicode 编码格式的源程序, 
// 所以我们将会看到这个方法读取的是下一个字符, 而不是下一个字节.
// 这个方法并没有返回值, 而是更新了 Scanner 类型中的相关变量(ch, offset等)
func (s *Scanner) next() {// 如果当前还未读取到输入字节的结束位置, 则读取下一个字符到 ch, // 并更新 offset, rdOffset, lineOffset, if s.rdOffset < len(s.src) {s.offset = s.rdOffsetif s.ch == '\n' {s.lineOffset = s.offsets.file.AddLine(s.offset)}r, w := rune(s.src[s.rdOffset]), 1switch {case r == 0:s.error(s.offset, "illegal character NUL")case r >= utf8.RuneSelf:// not ASCIIr, w = utf8.DecodeRune(s.src[s.rdOffset:])if r == utf8.RuneError && w == 1 {s.error(s.offset, "illegal UTF-8 encoding")} else if r == bom && s.offset > 0 {s.error(s.offset, "illegal byte order mark")}}s.rdOffset += ws.ch = r} else {s.offset = len(s.src)if s.ch == '\n' {s.lineOffset = s.offsets.file.AddLine(s.offset)}s.ch = -1 // 当读取到源程序的结束位置时, 将 ch 标记为 -1, 这代表了 EOF.}
}// Scan 方法就是词法分析器的核心实现了, 正如上面所说, 它是一个状态机.// 从它的返回值我们可以看到, 对于 Scan 的每一次调用, 该方法将会返回一个 Token// 如果返回的 Token 是 literal (token.IDENT, token.INT, token.FLOAT,
// token.IMAG, token.CHAR, token.STRING), 或者 Token.COMMENT,
// 该方法的返回值 lit 将会包含该 Token 的值. 
// 这里需要注意的是, 返回值 Token 仅仅代表了当前解析出来的 Token 的类型, 并未包含
// Token 的值. 一些 Token 的类型就代表了它的值. 比如 '{', '+'等, 
// 而另外一些 Token 需要额外的返回值表示它的值. 比如当读取到的 Token 是 INT 时,
// 我们就需要使用返回值 lit 来取得读取到的到底是 0 还是 1000 或者其他的合法整数值.// 如果当前读取到的 Token 是 ILLEGAL, 那么返回值 lit 将会是未能成功解析的字符// 这里返回值 pos 可以暂时忽略func (s *Scanner) Scan() (pos token.Pos, tok token.Token, lit string) {
scanAgain:// 很容易理解, 我们总是习惯于在代码中插入空格空行等对于编译器来说没有意义的字符, // 这里使用 skipWhitespace 方法过滤掉.s.skipWhitespace() pos = s.file.Pos(s.offset)// 判断当前 token 的类型, 根据当前字符 ch.switch ch := s.ch; {// 如果当前读取到的是一个字母.(a-z, A-Z或者utf8编码的字母), // 我们就将他解析为标识符(Identifier) token// 当然这个标识符可能是一个关键字,因此使用 token.Lookup // 来判断当前标识符是否是关键字case isLetter(ch): lit = s.scanIdentifier()if len(lit) > 1 { // 关键字的长度都大于1, 因此小于1的情况下, 必然是标识符tok = token.Lookup(lit)} else {tok = token.IDENT}// 如果当前读取到的是一个数字, 那就将他解析为数字, 具体是 INT, FLOAT // 在scanNumber方法中进行判断, 我们回过头来再来看该方法case '0' <= ch && ch <= '9':tok, lit = s.scanNumber(false)default:s.next() // 将 s.ch 更新为下一个字符, 我们将依赖于下一个判断当前 token 类型switch ch { // 此处的 ch 是 s.ch 的前一个字符case -1:tok = token.EOFcase '\n':  return pos, token.SEMICOLON, "\n"case '"': // 当前 token 是 string. 形式如 "abc..."tok = token.STRINGlit = s.scanString()case '\'': // 当前 token 是 char. 形式如 'a'tok = token.CHARlit = s.scanRune()case '`': // 当前 token 是 raw string. 形式如 `abc...`tok = token.STRINGlit = s.scanRawString()case ':': // 遇到 ':', 具体token 类型将和下一个字符有关. // 如果下一个字符是 '=', 那么当前 token 将是 ":=", 否则就是简单的 ':'tok = s.switch2(token.COLON, token.DEFINE)case '.':// 当前 ch 是 '.', 且 s.ch 是数字, 那么我们目前所处的// token 的格式为 "*.1"形式, 只能是小数.if '0' <= s.ch && s.ch <= '9' {tok, lit = s.scanNumber(true)} else if s.ch == '.' { // 如果当前 ch 是 '.', 且 s.ch 也是 '.', 那么我们当前 token 的格式为 ".."// 因此当前 token 应为 "...". 否则为非法 token(但是这里并没有处理这种情况).s.next()if s.ch == '.' {s.next()tok = token.ELLIPSIS}} else { // 如果不是 小数,':', ':=', 也不是 '...', 那么我们认为它是 '.'.// 比如 foo.Prop 中的点tok = token.PERIOD}case ',':tok = token.COMMAcase ';':tok = token.SEMICOLONlit = ";"case '(':tok = token.LPARENcase ')':tok = token.RPARENcase '[':tok = token.LBRACKcase ']':tok = token.RBRACKcase '{':tok = token.LBRACEcase '}':tok = token.RBRACEcase '+':// 如果当前 ch 是 '+', 那么所有可能结果是 '+', '+=', '++'. // 具体 token 类型仍然取决于 s.ch 的值tok = s.switch3(token.ADD, token.ADD_ASSIGN, '+', token.INC)case '-':tok = s.switch3(token.SUB, token.SUB_ASSIGN, '-', token.DEC)case '*':tok = s.switch2(token.MUL, token.MUL_ASSIGN)case '/':// 如果当前 ch 是 '/', 且 s.ch 是 '/' 或者 '*', 当前 token 是注释.if s.ch == '/' || s.ch == '*' {comment := s.scanComment()tok = token.COMMENTlit = comment} else {// 如果不是注释, 那么可能结果为除法操作符或者'/='.tok = s.switch2(token.QUO, token.QUO_ASSIGN)}case '%':// 可能结果为取模操作符或者 '%=' 操作符.tok = s.switch2(token.REM, token.REM_ASSIGN)case '^': tok = s.switch2(token.XOR, token.XOR_ASSIGN)case '<':if s.ch == '-' {s.next()tok = token.ARROW} else {tok = s.switch4(token.LSS, token.LEQ, '<', token.SHL, token.SHL_ASSIGN)}case '>':tok = s.switch4(token.GTR, token.GEQ, '>', token.SHR, token.SHR_ASSIGN)case '=':tok = s.switch2(token.ASSIGN, token.EQL)case '!':tok = s.switch2(token.NOT, token.NEQ)case '&':if s.ch == '^' {s.next()tok = s.switch2(token.AND_NOT, token.AND_NOT_ASSIGN)} else {tok = s.switch3(token.AND, token.AND_ASSIGN, '&', token.LAND)}case '|':tok = s.switch3(token.OR, token.OR_ASSIGN, '|', token.LOR)default:// next reports unexpected BOMs - don't repeatif ch != bom {s.error(s.file.Offset(pos), fmt.Sprintf("illegal character %#U", ch))}tok = token.ILLEGALlit = string(ch)}}return
}func (s *Scanner) scanComment() string {// initial '/' already consumed; s.ch == '/' || s.ch == '*'offs := s.offset - 1 // position of initial '/'next := -1           // position immediately following the comment; < 0 means invalid commentnumCR := 0if s.ch == '/' {//-style comment// (the final '\n' is not considered part of the comment)s.next()for s.ch != '\n' && s.ch >= 0 {if s.ch == '\r' {numCR++}s.next()}// if we are at '\n', the position following the comment is afterwardsnext = s.offsetif s.ch == '\n' {next++}goto exit}/*-style comment */s.next()for s.ch >= 0 {ch := s.chif ch == '\r' {numCR++}s.next()if ch == '*' && s.ch == '/' {s.next()next = s.offsetgoto exit}}s.error(offs, "comment not terminated")exit:lit := s.src[offs:s.offset]// On Windows, a (//-comment) line may end in "\r\n".// Remove the final '\r' before analyzing the text for// line directives (matching the compiler). Remove any// other '\r' afterwards (matching the pre-existing be-// havior of the scanner).if numCR > 0 && len(lit) >= 2 && lit[1] == '/' && lit[len(lit)-1] == '\r' {lit = lit[:len(lit)-1]numCR--}// interpret line directives// (//line directives must start at the beginning of the current line)if next >= 0 /* implies valid comment */ && (lit[1] == '*' || offs == s.lineOffset) && bytes.HasPrefix(lit[2:], prefix) {s.updateLineInfo(next, offs, lit)}if numCR > 0 {lit = stripCR(lit, lit[1] == '*')}return string(lit)
}func isLetter(ch rune) bool {return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch)
}func isDigit(ch rune) bool {return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch)
}// 读取一个标识符. 
// 标识符的合法表示为以字符开头可以包含数字的字符串
func (s *Scanner) scanIdentifier() string {offs := s.offsetfor isLetter(s.ch) || isDigit(s.ch) {s.next()}return string(s.src[offs:s.offset])
}func digitVal(ch rune) int {switch {case '0' <= ch && ch <= '9':return int(ch - '0')case 'a' <= ch && ch <= 'f':return int(ch - 'a' + 10)case 'A' <= ch && ch <= 'F':return int(ch - 'A' + 10)}return 16 // larger than any legal digit val
}// 读取一个字符串
// 这里的字符串是双引号格式的. 对于``形式的字符串由scanRawString()方法解析
func (s *Scanner) scanString() string {// '"' opening already consumedoffs := s.offset - 1for {ch := s.chif ch == '\n' || ch < 0 {s.error(offs, "string literal not terminated")break}s.next()if ch == '"' {break}if ch == '\\' {s.scanEscape('"')}}return string(s.src[offs:s.offset])
}func (s *Scanner) scanRawString() string {// '`' opening already consumedoffs := s.offset - 1hasCR := falsefor {ch := s.chif ch < 0 {s.error(offs, "raw string literal not terminated")break}s.next()if ch == '`' {break}if ch == '\r' {hasCR = true}}lit := s.src[offs:s.offset]if hasCR {lit = stripCR(lit, false)}return string(lit)
}func (s *Scanner) skipWhitespace() {for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' && !s.insertSemi || s.ch == '\r' {s.next()}
}

例子##

上面我们已经简单的了解了整个解析器的工作原理, 下面我们来跑几个测试来验证一下.

测试用例位于 go/scanner/scanner_test.go

输入输出对照表:

输入Token 类型Token 值
/* a comment */COMMENT/* a comment */
// a commentCOMMENT// a comment
foobarIDENTfoobar
01234567INT01234567
0xcafebabeINT0xcafebabe
3.14159265FLOAT3.14159265
2.71828e-1000FLOAT2.71828e-1000
‘a’CHARa
`foobar`STRING`foobar`
+ADDEMPTY
/QUOEMPTY
%=REM_ASSIGNEMPTY
,COMMAEMPTY
{LBRACKEMPTY
breakBREAKbreak
caseCASEcase
fallthroughFALLTHROUGHEMPTY

省略部分测试用例

END!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499688.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何读懂 C 语言复杂的声明

如何读懂 C 语言复杂的声明 作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者再学习错误的知识.谢谢! 参考<<C专家编程>> 废话 虽说 C 语言相比于其他更高级的语言来讲&#xff0c;有着非常精简的语法结构&#xff0c;对…

C 语言笔记: 链表节点实现技巧--struct的妙用

链表节点实现技巧–struct的妙用 作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者再学习错误的知识.谢谢! 废话 C 语言虽然只提供了非常简单的语法&#xff0c;但是丝毫不影响 C 语言程序员使用 C 来实现很多让人叹为观止的高级功能…

协议簇: Media Access Control(MAC) Frame 解析

Media Access Control(MAC) Frame 解析 前言 千里之行&#xff0c;始于足下。 因为个人从事网络协议开发&#xff0c;一直想深入的学习一下协议族&#xff0c;从这篇开始&#xff0c;我将开始记录分享我学习到的网络协议相关的知识 简介 引用百度百科的描述&#xff1a; 数…

协议簇:Ethernet Address Resolution Protocol (ARP) 解析

简介 前面的文章中&#xff0c;我们介绍了 MAC Frame 的帧格式。我们知道&#xff0c;在每个 Ethernet Frame 中都分别包含一个 48 bit 的源物理地址和目的物理地址. 对于源地址很容易理解&#xff0c;该地址可以直接从硬件上读取. 但是对于一个网络节点&#xff0c;他怎么知道…

协议簇:IPv4 解析

简介 IP 是一种无连接的协议. 操作在使用分组交换的链路层&#xff08;如以太网&#xff09;上。此协议会尽最大努力交付数据包。 尽最大努力意味着&#xff1a; IP 协议不保证数据的可靠传输, 没有流量控制机制, 不保证传输序列(意味着 IP 数据包会在传输过程中乱序), 没有…

协议簇:ICMP 解析

简介 ICMP 是 Internet Control Message Protocol 的简写. 它主要用来调试网络通信环境中存在的问题. 比如&#xff0c;当 IP 数据包总是无法正常的发送到目的地址, 当网关没有足够的 buffer 来转发对应的数据包 等问题. 值得一提的是&#xff0c;它属于网络层&#xff0c;不属…

协议簇:TCP 解析: 基础

简介 本文我们将从 RFC 学习一下 RFC793 中描述的 TCP 协议. 这将区别于通常讲解计算机网络书籍中所描述的 TCP. 但他们必然是相统一的&#xff0c;不会互相冲突. 系列文章 协议簇&#xff1a;TCP 解析&#xff1a;基础 协议簇&#xff1a;TCP 解析&#xff1a;建立连接 协议…

协议簇:TCP 解析: 建立连接

简介 接前文 协议簇&#xff1a;TCP 解析: 基础&#xff0c; 我们这篇文章来看看 TCP 连接建立的过程&#xff0c;也就是众所周知的”三次握手“的具体流程. 系列文章 协议簇&#xff1a;TCP 解析&#xff1a;基础 协议簇&#xff1a;TCP 解析&#xff1a;建立连接 协议簇&a…

协议簇:TCP 解析: 连接断开

简介 接前文 协议簇&#xff1a;TCP 解析: 建立连接&#xff0c; 我们这篇文章来看看 TCP 连接断开的过程&#xff0c;也就是众所周知的”四次挥手“的具体流程. 系列文章 协议簇&#xff1a;TCP 解析&#xff1a;基础 协议簇&#xff1a;TCP 解析&#xff1a;建立连接 协议…

协议簇:TCP 解析: Sequence Number

简介 序列号&#xff08;Sequence Number&#xff09; 是 TCP 协议中非常重要的一个概念&#xff0c;以至于不得不专门来学习一下。这篇文章我们就来解开他的面纱. 在 TCP 的设计中&#xff0c;通过TCP协议发送的每个字节都对应于一个序列号. 由于每个字节都有自己的序列号&a…

协议簇:TCP 解析:TCP 数据传输

简介 前面&#xff0c;我们分别介绍了 TCP 基础知识以及连接的建立和关闭&#xff0c;以及最重要的 Sequence Number 的概念. 本篇文章&#xff0c;我们来介绍一下 TCP 如何传输数据. 系列文章 协议簇&#xff1a;TCP 解析&#xff1a;基础 协议簇&#xff1a;TCP 解析&…

CodeTank iOS App Technical Support

CodeTank iOS App Technical Support For All Email: z253951598outlook.com TEL: 86-17782749061 App Screen Shoots

CentOS 7 防火墙命令

查看防火墙状态 systemctl status firewalld如果已经开启&#xff0c;状态为 active 如果未开启&#xff0c;状态为 inactive 开启防火墙 systemctl start firewalld关闭防火墙 systemctl stop firewalld查看当前防火墙的配置 firewall-cmd --list-all这里&#xff0c;我…

Go Concurrency Patterns: Context

Go Concurrency Patterns: Context 原文地址&#xff1a;https://blog.golang.org/context Introduction 在 Go 语言实现的服务器上&#xff0c;我们总是使用 goroutine 来处理与客户端建立的连接&#xff0c; 给每个连接分配一个独立的 goroutine. 在请求的 handler 中也通常…

Go Concurrency Patterns: Timing out, moving on

原文地址&#xff1a;https://blog.golang.org/concurrency-timeouts 并发变成有它自己的风格. 一个非常好的例子就是 timeout. 虽然 go 的 channel 没有直接支持 timeout 机制&#xff0c;但是要实现它非常容易. 比如说&#xff0c;我们想从一个 channel ch 中接收数据&#…

Go Concurrency Patterns: Pipelines and cancellation

原文地址&#xff1a; https://blog.golang.org/pipelines 简介 Go 语言提供的并发原语使得可以很方便的构建数据流 pipeline&#xff0c;使用这样的 pipeline 可以高效的利用 I/O 和多 cpu 的优势. 这篇文章我们将展示如何构建并使用 pipeline. 什么是 pipeline ? 在 go 语…

QTcpSocket connectToHost 建立连接速度慢问题

问题场景 在使用 QT 开发一个客户端 App 的时候&#xff0c;我们通过 QTcpSocket 与后台服务器进程通信。 后台程序使用其他语言编写。 问题&#xff1a; 在客户端启用之后尝试建立与后台程序的 TCP 连接的时候&#xff0c;发现连接速度非常慢&#xff08;肉眼可见的慢&#x…

WinSock I/O 模型 -- Select 模型

简介 Select 模型是 WinSock 中最常见的 I/O 模型&#xff0c;这篇文章我们就来看看如何使用 Select api 来实现一个简单的 TCP 服务器. API 基础 Select 模型依赖 WinSock API Select 来检查当前 Socket 是否可写或者可读。 使用这个 API 的优点是我们不需要使用阻塞的 So…

WinSock I/O 模型 -- WSAEventSelect 模型

简介 WSAEventSelect 模型也是 WinSock 中最常见的异步 I/O 模型。 这篇文章我们就来看看如何使用 WSAEventSelect api 来实现一个简单的 TCP 服务器. API 基础 WSAEventSelect WSAEventSelect 用来把一个 SOCKET 对象和一个 WSAEVENT 对象关联起来。 lNetworkEvents 表示…

WinSock I/O 模型 -- WSAAsyncSelect 模型

简介 WSAAsyncSelect 模型也是 WinSock 中常见的异步 I/O 模型。 使用这个模型&#xff0c;网络应用程序通过接收以 Windows 消息为基础的网络事件通知来处理网络请求。 这篇文章我们就来看看如何使用 WSAAsyncSelect api 来实现一个简单的 TCP 服务器. API 基础 要使用 W…