从TCP到Socket,彻底理解网络编程是怎么回事

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

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

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

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

网络编程

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

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

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

#!/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:breaktctimeClient.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:breaktctimeClient.send(data.encode())data = tctimeClient.recv(BUFFSIZE).decode()if not data:breakprint(data)
tctimeClient.close()

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

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

TCP协议详解

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

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

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

 

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

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

连接的建立 

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

 

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包,确认关闭成功


d8bde4bf38db403ea3dfbcd46c9eaf52.png

 

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

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

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

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

相关文章

Java中对象的比较:复写基类的equals+Comparble接口类的比较+比较器比较+PriorityQueue的比较+源码分析

文章目录 Java中对象的比较一、元素的比较二、对象的比较1.复写基类的equals2.基于Comparble接口类的比较3.基于比较器比较三种方法的对比 4. 集合框架中PriorityQueue的比较方式 Java中对象的比较 一、元素的比较 在Java中,基本类型的对象可以直接比较大小。 二、…

【Python3】【力扣题】232. 用栈实现队列

【力扣题】题目描述: 栈:线性集合。后进先出。 队列:线性集合。先进先出。 【Python3】代码: 解题思路:两个栈,一个入队的栈,一个出队的栈。出栈时,若出队的栈为空,才将…

RabbitMQ 死信队列

在MQ中,当消息成为死信(Dead message)后,消息中间件可以将其从当前队列发送到另一个队列中,这个队列就是死信队列。而在RabbitMQ中,由于有交换机的概念,实际是将死信发送给了死信交换机&#xf…

c语言 结构体 简单实例

结构体 简单例子 要求&#xff1a; 结构体保存学生信息操作 代码 #include <stdio.h>//定义结构体 struct student{int ID;char name[20];char sex;char birthday[8];int grade; };int main(){int number;printf("请输入学生个数&#xff1a;");scanf(&quo…

lua中的循环 while、for、repeat until三种循环方式、pairs和ipairs区别

lua中的循环 while、for、repeat until三种循环方式、pairs和ipairs区别 介绍for循环参数ipairs和pairs whilerepeat until总结 介绍 这里我用while、for、repeat until分别输出1-20之间的奇数 &#xff0c;具体的语法可以看下面的代码 for循环 参数 定义一个初始值为start…

ARM64 linux并发与同步之原子操作

卷2&#xff1a;调试与案例分析 第一章 并发与同步 画了两张简图&#xff0c;方便理解&#xff0c;如下&#xff1a; 针对并发源的问题&#xff0c;我接触的项目中都是SMP系统&#xff0c;目前大部分也都是SMP系统&#xff1b; 对于SMP系统&#xff0c;情况会更复杂。 □ 同…

Servlet详解

一.Servlet生命周期 初始化提供服务销毁 1.测试生命周期 package com.demo.servlet;import javax.servlet.*; import java.io.IOException;public class LifeServlet implements Servlet {Overridepublic void init(ServletConfig servletConfig) throws ServletException {…

ros的安装和rosdep的初始化操作

ros的安装 安装ROS完整桌面版安装&#xff08;Desktop-Full&#xff09;环境配置rosdep初始化 安装ROS完整桌面版安装&#xff08;Desktop-Full&#xff09; 官网 选择思想者乌龟 选择ubuntu平台 操作 开始安装ROS 完整桌面版安装&#xff08;Desktop-Full&#xff0c;推荐…

小白学爬虫:手机app分享商品短连接获取淘宝商品链接接口|淘宝淘口令接口|淘宝真实商品链接接口|淘宝商品详情接口

通过手机APP分享的商品短链接&#xff0c;我们可以调用相应的接口来获取淘口令真实URL&#xff0c;进而获取到PC端的商品链接及商品ID。具体步骤如下&#xff1a; 1、通过手机APP分享至PC端的短链接&#xff0c;调用“item_password”接口。 2、该接口将返回淘口令真实URL。 3…

如何规划并新建大数据平台的独立生产域?5步走

一般来说&#xff0c;大数据平台包括以下4类数据生产域——生产生态环境&#xff08;正式生产环境&#xff09;、开发和测试环境、培训和演示环境、灾备环境。各生产域在由平台提供资源、安全、监控、故障恢复等保障的同时&#xff0c;不同的生产域之间还需要严格隔离&#xff…

基于生成对抗网络的照片上色动态算法设计与实现 - 深度学习 opencv python 计算机竞赛

文章目录 1 前言1 课题背景2 GAN(生成对抗网络)2.1 简介2.2 基本原理 3 DeOldify 框架4 First Order Motion Model5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于生成对抗网络的照片上色动态算法设计与实现 该项目较为新颖&am…

Qt OpenMP使用

1、概念 OpenMP是一种用于共享内存并行系统的多线程程序设计方案&#xff0c;支持的编程语言包括C、C和Fortran。OpenMP提供了对并行算法的高层抽象描述&#xff0c;特别适合在多核CPU机器上的并行程序设计。编译器根据程序中添加的pragma指令&#xff0c;自动将程序并行处理&…