从socket到TCP协议,透彻理解网络编程

进行程序开发的同学,无论Web前端开发、Web后端开发,还是搜索引擎和大数据,几乎所有的开发领域都会涉及到网络编程。比如我们进行Web服务端开发,除了Web协议本身依赖网络外,通常还需要连接数据库,而数据库连接通常是通过网络连接数据库服务器,或者数据库集群,如果负载太高还要搞个缓存集群。

我们在上学的时候基本学了网络编程和网络协议。但两者之间的具体关系可能有些摸不到头脑。这里我们首先重点介绍2个概念,一个概念是网络编程,另外一个是协议。

我们知道网络协议是一个分层的协议族,也就是是有一组协议构成,从下往上各自负责各自的功能。那什么是协议呢?协议的字面意思是共同计议,商议。简单的理解其实就是多方进行沟通的规定。而网络协议其实就是在网络中多个计算节点进行交互、沟通的规定。如果根我们日常生活对比的话,协议可以理解为语言,比如汉语普通话。两个人交流如果都用不通话,那么彼此都能理解对方表达的意图。例如,一个人用四川话,而另外一个用浙江话,那沟通起来估计几乎不太可能。网络协议也是一样的,通过对数据格式的规范化,从而使计算机之间能够彼此明确对方的意图。

下面本文介绍一下网络编程,网络编程也称为socket编程,socket通常译作“套接字”,但原意其实意译应该为”接口“。也就是操作系统提供给开发人员进行网络开发的API接口。这套接口通常可以参数的调整支持多种协议,包括TCP、UDP和IP等等。下面本文从套接字编程和协议两方面分别详细的进行介绍。

网络编程

为了便于理解,本文先从具体的内容开始,也就是通过一个实例介绍一下网络编程是怎么回事。

本文将以TCP协议为例介绍网络编程和协议之前的关系。为了简单,便于理解,本文以Python为例进行介绍,如果不了解Python编程语言关系也不大,下面代码很容易理解。我们知道在网络通信中无论是BS架构还是CS架构,通常分为服务端和客户端,只不过BS架构中的浏览器就是客户端。因此,本文的示例也包含服务端和客户端2部分的代码。代码功能很简单,就是实现客户端和服务端发送字符串。

图1 客户端服务端通信模型

这个代码清单是服务端的代码,这段代码的作用就是在服务端的某个端口建立监听,并等待客户端建立连接。完成连接建立后,等待客户端发送数据,并将数据回传给客户端。

#!/usr/bin/env python3
#-*- coding:utf-8 -*-

from socket import *
from time import ctime

host = ''
port = 12345
buffsize = 2048
ADDR = (host,port)

# 创建一个基于TCP协议的套接字
tctime = socket(AF_INET,SOCK_STREAM)
tctime.bind(ADDR)
# 在指定的地址和端口监听
tctime.listen(3)

while True:
    print('Wait for connection ...')
    tctimeClient,addr = tctime.accept()
    print("Connection from :",addr)

    while True:
        data = tctimeClient.recv(buffsize).decode()
        if not data:
            break
        tctimeClient.send(('[%s] %s' % (ctime(),data)).encode())
    tctimeClient.close()
tctimeClient.close()

阅读服务端的代码可以看出主要包括,socket、bind、listen、accept、recv和send几个。其中值得关注的是listen和accept,两者分别用于监听端口和接受客户端的连接请求。

下面代码清单是客户端的实现,这里特别的地方是有一个connect函数,该函数实现与服务端建立连接。

#!/usr/bin/env python3
#-*- coding:utf-8 -*-

from socket import *

HOST ='localhost'

PORT = 12345

BUFFSIZE=2048

ADDR = (HOST,PORT)

tctimeClient = socket(AF_INET,SOCK_STREAM)

tctimeClient.connect(ADDR)

while True:
    data = input(">")
    if not data:
        break
    tctimeClient.send(data.encode())
    data = tctimeClient.recv(BUFFSIZE).decode()
    if not data:
        break
    print(data)
tctimeClient.close()

通过上述示例代码可以看出服务端通常是被动的,而客户端则要主动一些。服务端程序建立对某个端口的监听,等待客户端的连接请求。客户端向服务端发送连接请求,不出意外的情况下连接建立成功,这时客户端和服务端之前就可以互发数据了。当然,在实际生产环境中意外是经常的,因此从协议和接口层面,需要处理各种意外,本文在协议部分将详细介绍。

另外,本文实现了一个基本的客户端和服务端通信的程序,这个模式的通信在实际生产中几乎不再使用。在实际生产中为了提高数据传输和处理的效率,通常采用异步模式,这些内容超出了本文的介绍范围,后续文章会逐渐介绍。

TCP协议详解

前文说了网络协议是网络中不同计算机信息通信的语言,为了实现交互,这个语言就需要有一定的格式。本文以TCP协议为例进行介绍。

TCP协议是一个可靠的传输协议,其可靠性表现在2方面,一方面是保证数据包可以按照发送的顺序到达,另外一方面是保证数据包一定程度的正确性(后文详解为什么是一定程度上的正确性)。其可靠性的实现则基于2点技术,一点是具有一个CRC校验,这样如果数据包中的某些数据出现错误可以通过该校验和发现;另外一点是每个数据包都有一个序号,这样就能保证数据包的顺序性,如果出现错位的数据包可以请求重发。

既然说到了格式,那我们先看一下TCP数据包的数据格式。如下图是TCP数据包的格式,包括原端口、目的端口、序列号和标识位等等内容,内容有些多,看着可能有点眼花。但从大的方面理解,这个数据包其实只包含2部分内容,一个是包头,另外一个则是具体需要传输的数据。在TCP协议的控制逻辑中,包头起着最为关键的作用,它是TCP协议中诸如建立连接、断开连接、重传和错误校验等各种特性的基础。

图2 TCP数据包格式

包头的其它信息的含义都比较明了,本文仅仅介绍几个标志位(URG、ACK、PSH、RST、SYN和FIN)的含义。具体含义如下:

  • ACK: 确认序号有效。
  • RST:重置连接
  • SYN:发起一个新连接
  • FIN:释放一个连接

** 连接的建立 **
TCP在具体传输数据之前需要建立连接。这里的连接并不是物理连接,物理连接基于底层的协议已经建立完成,而且TCP建立连接也是要假设底层连接已经成功,TCP的连接其实是一个虚拟的,逻辑的连接。简单粗暴的理解,就是客户端和服务端分别记录了各自接受到的数据包的序号,并且将自身设置为某种状态。在TCP协议中,连接的建立通常成为3次握手,从字面的概念可以看出,连接的建立需要经过3次确认的过程。

图3 建立连接的3次握手

TCP协议3次握手的过程如图所示,初始状态客户端和服务端都处于关闭状态。主要过程分为3步:

  1. 客户端发送预连接数据包: TCP的连接是由客户端主动发起建立,客户端会发送一个数据包(报文)给服务端,需要注意的是数据包中的SYN标识位为1。我们前文已经介绍,如果SYN为1,则说明为建立连接的数据包。同时,在该数据包中包含一个请求序列号,该序列号也是建立连接的依据。
  2. 服务端回复连接确认: 服务端确认可以建立连接(服务端不一定可以建立连接,因为系统中套接字的数量是有限的)的情况下会向客户端发送一个应答数据包。在应答数据包中会将ACK标志位设置为1,表示为服务端应答数据包。同时,在应答数据包中会设置请求序列号和应答序列号的值,具体参考图3.
  3. 客户端回复连接确认: 最后,客户端再次发送一个连接确认数据包,告诉服务端连接建立成功。

从上面流程可以看出,连接的建立需要经过多次交互,这就是我们日常中所说的建立连接是高成本的操作。在实际生产环境中,为了应对这个问题,会减少连接建立的频度,通常的做法是建立连接池,传输数据时直接从连接池中获取连接,而不是新建连接。

有人可能觉得可以对建立连接的过程进行优化,比如将客户端最后一次的确认取消掉,觉得这个没有卵用。对于正常情况确实没有多大的作用,这里主要是应对异常情况。因为网络拓扑是非常复杂的,特别是在广域网中,有着数不清的网络节点,因此会出现各种异常情况。因此,TCP协议在设计的时候必须要保证异常情况下的可靠性。

我们这里举一个例子,就是连接请求超时的情况。假设客户端向服务端发送一个连接请求,由于各种原因,请求一直没有到达服务端,因此服务端也就没有回复连接确认消息。客户端连接超时,因此客户端重新发送一个连接请求到服务端,这次比较顺利,很快到达了,并且顺利建立了连接。之后,前一个数据包经过长途跋涉最终还是到了服务端,服务端也向客户端发送了回复数据包,服务端认为连接是建立成功的,并且会维持连接。但客户端层面认为连接是超时的,因此将永远不会关闭该连接。这样就会造成服务端有残留的资源,从而造成服务端资源浪费,久而久之可能会导致服务端无新连接资源可用。

另外一个需要说明的是客户端和服务端的套接字都有相应的状态,而且状态会随着连接的不同阶段变化。初始状态都是CLOSE,最终连接建立成功后都是ESTABLISHED,具体变化过程如图3所示。后面本文会详细介绍状态变化情况。

** 传输数据 **
完成连接建立之后,客户端和服务端就可以进行数据传输了。我们知道TCP是可靠的传输,那么传输的可靠性是通过什么来保证的呢?主要就是通过包头中的校验和、请求序列号和应答序列号(参考图2)。

TCP数据内容的可靠性是通过校验和保证的。TCP在发送数据时都会计算整个数据包的校验和,并存储在包头的校验和字段中。接收方会按照规则进行计算,从而确认接收到的数据是否是正确的。发送发计算校验和的流程大概如下:

  1. 把伪首部、TCP包头和TCP数据分为16为的字,并把TCP包头中的校验和字段置0
  2. 用反码加法累加所有16位数字
  3. 对计算结果去反,将其填充到TCP包头的校验和字段

接收方将所有原码相加,高位叠加,如果全为1则表示数据正确,否则说明数据有错误。

TCP数据包顺序的可靠性是通过请求序列号和应答序列号保证的。在数据传输中的每个请求都会有一个请求序列号,而在接收方接收到数据后会发送一个应答序列号,这样发送方就能知道数据是否被正确接收,而接收方也能知道数据是否出现乱序,从而保证数据包的顺序性。

** 断开连接 **
TCP关闭连接分为4步,称为4次挥手。连接的关闭不一定是在客户端发起,服务端也可以发起关闭连接。关闭连接的过程如下:

  1. 发起方发送一个FIN置位的数据包,用来请求关闭发送方到接收方的连接
  2. 接收方发送一个应答,ACK标志位为1,确认关闭。此时完成了发起方到接收方的连接,也即发送方无法再向接收方发送数据,但接收方还可以向发送方发送数据。
  3. 接收方数据传输完成后向发起方发送一个FIN为1的包,表示请求断开连接
  4. 发起方回复一个ACK包,确认关闭成功
图4 关闭连接流程示意图

TCP是全双工通信,因此关闭连接时需要双向关闭连接。首先是关闭发起方关闭本端的连接,然后是关闭接收方在收到发起方的关闭请求后,除了回复关闭应答外,还要确保数据传输完成后发起一个关闭连接的请求,保证双向同时关闭。

截止到这里,本文介绍了基于TCP协议进行网络编程的主要内容。当然这个只是入门级的,如果需要真正理解TCP协议和网络编程还需要学习很多内容。后续本号将陆续介绍给大家。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 计算机网络概述 网络编程的实质就是两个(或多个)设备(例如计算机)之间的数据传输。 按照计算机网络的定义,通过一定...
    蛋炒饭_By阅读 1,175评论 0 10
  • 目录 TCP协议的基本概念面向链接的服务可靠的服务序列号字节流传输 TCP协议数据段的格式TCP伪头部 TCP协议...
    kirito_song阅读 2,909评论 2 33
  • 当 app 和服务器进行通信的时候,大多数情况下,都是采用 HTTP 协议。HTTP 最初是为 web 浏览器而定...
    Flysss1219阅读 1,180评论 0 4
  • 个人认为,Goodboy1881先生的TCP /IP 协议详解学习博客系列博客是一部非常精彩的学习笔记,这虽然只是...
    贰零壹柒_fc10阅读 5,019评论 0 8
  • 今晚那个人成为了我的前任,而我并不难过。 我和她本来就是两个世界的人,分开对彼此都有好处。她很早就没把我放在心上了...
    绳子_阅读 449评论 1 0