当前位置: 首页 > 科技观察

老曹眼中的网络编程基础

时间:2023-03-20 19:43:50 科技观察

我们很幸运,因为我们有网络。互联网是一个神奇的东西,它改变了你我的生活方式,也改变了整个世界。然而,网络的无标度和小世界特性使其变得复杂、无处不在、无所不能,以至于我们无法区分甚至无法描述它。对于一个码农来说,了解网络的基础可能要从理解定义开始,理解OSI的七层协议模型,深入到Socket的内部,然后才能熟练的进行网络编程。关于网络关于网络,字典中的定义是这样的:在电气系统中,按照一定的要求,由若干元件组成的用于传输电信号的电路或电路的一部分称为网络。作为一名一直从事TMN开发的通信专业毕业生,我坚信网络是从通信系统中诞生的。沟通是人与人之间通过一定的媒介进行信息的交换和传递。传统的通信网(即电话网)由传输、交换和终端三部分组成。通信网络是指将各种孤立的设备进行物理连接,实现信息交换的链接,从而达到资源共享和通信的目的。通信网络可以从覆盖范围、拓扑结构、交换方式等多个角度进行分类……让我们把满满的回忆留在书架上。网络的概念外延不断扩大,抽象思维能力是人们创新乃至创造的根源。网络用来表示许多对象及其相互关系,数学图、物理模型、交通网络、人际网络、城市网络等。简而言之,网络被概括为从类似问题中抽象出来的用数学图表示和研究的模型科学。很多小伙伴认为了解了这些东西,就没有用了。我们关心的只是计算机网络。计算机网络是将分布在不同地点的多个计算机系统用通信线路和设备连接起来,按照网络协议共享软硬件功能,最终实现资源共享的系统。特别是,我们说的网络只是互联网——互联网,或者移动互联网,需要的是编写互联网应用程序。但是,一位工作了五六年的编程高手曾经告诉我,现在终于明白基础知识是多么重要了,技术是在不断发展的,那些原则和编程模型是相对不变的。老码农想当然地认为,编程实践是一个从具体到抽象,再到具体、循环往复、螺旋上升的过程。了解前世今生,只是触及“势”而已。地基越牢固,建筑的想象空间就越大。网络编程的基础,大概要从OSI的七层协议模型说起。七层模型七层模型(OSI,OpenSystemInterconnectionReferenceModel)是国际标准化组织制定的用于计算机或通信系统之间互连的参考系统。它是一个七层的抽象模型,不仅包括一系列抽象的术语和概念,还包括具体的协议。经典的描述如下:简述各层的含义:物理层:建立、维护和断开物理连接。数据链路层(Link):逻辑连接、硬件地址寻址、错误校验等。网络层(Network):进行逻辑寻址,实现不同网络间的路径选择。传输层(Transport):定义传输数据的协议端口号,以及流量控制和错误检查。会话层(SessionLayer):建立、管理和终止会话。表示层:数据表示、安全和压缩。应用层(Application):网络服务和最终用户之间的接口。每一层都使用下一层提供的服务与对等层进行通信。每一层都使用自己的协议。明白了这一点,就不是鸡蛋了。但是,这个模型确实是大多数网络编程的基础,作为一个抽象类存在,而TCP/IP协议栈只是这个模型的具体实现。TCP/IP协议模型TCP/IP是Internet的基础,是一组协议的代名词,包括许多协议,它们组成了TCP/IP协议栈。TCP/IP有四层模型和五层模型。区别在于数据链路层是否作为一个独立的层存在。个人比较喜欢5层模型,这样二层和三层交换设备更容易理解。当您谈论网络中的第2层或第3层交换机时,您知道您指的是哪些协议。数据是如何传输的?这就需要了解网络层和传输层的协议。我们熟悉的IP包的结构是这样的:IP协议和IP地址是两个不同的概念,这里不涉及IPV6。如果你不关注网络安全,你不需要熟悉这些结构。传输层就是利用这样的数据包进行传输,传输层又分为面向连接的可靠传输TCP和数据报UDP。TCP包结构:建立TCP连接的三次握手必须知道。在调优系统时,内核中的相关网络参数都与这个图密切相关。UDP是一种无连接传输层协议,提供简单且不可靠的信息传输。协议结构比较简单,包括源和目的端口号、长度和校验和。基于TCP和UDP的数据封装和分析示例如下:还是一样?如果您了解数据包的大小,您会发现什么?PayLoad是多少?在设计协议通信时,这些为我们提供了定义的粒度依据。此外,让我们通过一个例子来看一下。模型解释示例FTP就是一个很好的例子。为方便起见,假设两台电脑是A和B,A上的一个文件X将使用FTP传输到B。首先,计算机A和B之间必须有物理层连接,可以是通过RJ-45电路接口的同轴电缆或双绞线等有线连接,也可以是WIFI等无线连接。先简单点,考虑局域网,路由器交换机和WIFI热点暂时不讨论。这些物理层连接建立了比特流的原始传输路径。接下来,数据链路层登场,在两台计算机之间建立数据链路。如果计算机C、D、E等同时连接到A和B所在的网络,A和B之间如何建立数据链路?这个过程就是物理寻址,需要在很多物理连接B中找到A。这取决于计算机的物理地址,也就是MAC地址,也就是网卡上的MAC地址。以太网采用CSMA/CD方式传输数据。数据在以太局域网中以广播方式传输。整个局域网的所有节点都会收到这个帧,只有目标MAC地址和自己的MAC地址相同。将被接受。A通过差错控制和访问控制找到B的网卡,建立可靠的数据通路。IP地址呢?数据链路建立后,还需要IP地址吗?我们FTP命令中指定的是IP地址而不是MAC地址?IP地址是逻辑地址,包括网络地址和主机地址。如果A和B在不同的局域网中,中间有多个路由器,那么A需要对B进行逻辑寻址。物理地址用于底层硬件的通信,逻辑地址用于上层协议之间的通信。在以太网中:逻辑地址是IP地址,物理地址是MAC地址。在使用中,这两个地址通过某种算法链接起来。因此,IP用于在网络上选择路由。在FTP命令中,IP中的原始地址是A的IP地址,目的地址是B的IP地址。这里应该是网络层,负责将分组数据从源地址传输到目的地址。当A向B传输文件时,如果文件中的部分数据丢失,可能会导致B无法正常读取或使用,因此需要可靠的连接来保证传输过程的完整性。这就是传输层的TCP协议,FTP是建立在TCP之上的。TCP的三次握手决定了双方数据包的序号、最大接收数据(窗口)的大小和MSS(MaximumSegmentSize)。TCP使用IP完成寻址,TCP提供端口号,FTP中目的端口号一般为21。远程主机。会话层用于建立、维护和管理应用程序之间的会话。它的主要功能是对话控制和同步。编程中涉及的session是session层的具体体现。表示层完成数据的解码、加解密、压缩和解压等。例如FTP中的bin命令表示二进制传输,即传输层数据的格式。HTTP协议体中的Json、XML等都可以认为是表现层。应用层就是具体的应用本身。FTP中的PUT、GET等命令是应用程序特有的功能特性。简单来说,物理层到电缆连接,数据链路层到网卡,网络层到主机,传输层到端口,会话层维护会话,表示层表达数据格式,以及应用层是具体FTP中的各种命令功能。.了解7层模型就可以编程socket了,拿起一门编程语言就可以玩了吗?一开始尝试一下是可以的。如果你想更进一步,老码农认为还是看底层实现比较好,因为一切归根结底都是系统调用。如何在操作系统层面查看网络?套接字在这里。在Linux世界里,“一切皆文件”。操作系统把网络读写当作IO操作,就像读写文件一样,对外提供的编程接口是Socket。因此,套接字(socket)是通信的基石,是支持TCP/IP协议网络通信的基本操作单元。Socket本质上为进程通信提供了一个端点。在进行进程通信之前,双方必须先创建一个端点,否则无法建立联系并相互通信。一个完整的套接字有一个本地唯一的套接字号,由操作系统分配。从设计模式的角度看,Socket其实是一种外观模式,将复杂的TCP/IP协议栈隐藏在Socket接口的背后。对于用户来说,一套简单的Socket接口就可以了。当应用程序创建套接字时,操作系统返回一个整数作为描述符来标识套接字。然后,应用程序以该描述符作为传递参数,通过调用函数来完成某些操作(如通过网络传输数据或接收输入数据)。以TCP为例,一个典型的Socket用法如下:在很多操作系统中,Socket描述符和其他I/O描述符是集成在一起的,操作系统将socket描述符实现为一个指针数组,指向内部数据结构.进一步看,操作系统为每个正在运行的进程维护了一个单独的文件描述符表。当一个进程打开一个文件时,系统将一个指向文件内部数据结构的指针写入文件描述符表中,并将该表的索引值返回给调用者。由于Socket与操作系统的IO操作相关,各个操作系统IO实现的不同会导致Socket编程上的一些差异。看看我Mac上的Socket.so,你会发现它和CentOS上的有些不同。进程在进行Socket操作时,也有多种处理方式,如阻塞IO、非阻塞IO、多路复用(select/poll/epoll)、AIO等。多路复用通常在提高性能方面起着重要作用。select系统调用的作用是监视多个文件描述符。当带有文件描述符的文件读写操作完成并发生异常或超时时,调用将返回这些文件描述符。select需要遍历所有的文件描述符。就遍历操作而言,复杂度为O(N)。epoll相关的系统调用是在linux2.5之后的一个版本引入的。该系统调用针对传统select/poll的缺点在设计上做了很大的改动。select/poll的缺点是每次调用都需要从用户态反复读入参数,反复扫描文件描述符。在每次调用开始时,当前进程被放入每个文件描述符的等待队列中。调用结束后,进程从每个等待队列中删除。epoll将select/poll单个操作拆分为一个epollcreate,多个epollctrl和一个等待。另外,操作系统内核增加了文件系统,用于epoll操作。每个或多个需要监控的文件描述符都有对应的inode节点,主要信息存放在eventpoll结构体中。监控文件的重要信息存储在epitem结构中,是一对多的关系。由于在执行epollcreate和epollctrl时用户态信息已经保存到内核中,即使之后再重复调用epoll_wait,也不会重复复制参数,不会重复扫描文件描述符,也不会再执行当前进程反复保存。放入/取出等待队列。所以目前主流的Server端Socket实现大多使用epoll,比如Nginx,可以在配置文件中明确看到使用epoll。网络编程了解操作系统层面的7层协议模型和Socket实现,可以方便我们对网络编程的理解。在系统架构中,一个重要的部分就是拓扑架构,它涉及到网络等基础设施,所以7层协议的下四层将帮助我们观察和判断业务系统的网络结构。在设计系统时,往往采用面向接口的设计,接口往往是基于HTTP协议的RestfulAPI。接口的粒度可以以数据段为约束,同时可以关注移动互联网中的弱网环境。不同的编程语言有不同的框架和库。实际编写网络程序代码并不复杂。例如在Erlang中使用gen_tcp写一个简单的Echo服务器:Start_echo_server()->{ok,Listen}=gen_tcp:listen(1234,[binary,{packet,4},{reuseaddr,true},{active,true}]),{ok,socket}=get_tcp:accept(Listen),gen_tcp:close(Listen),loop(Socket).loop(Socket)->receive{tcp,Socket,Bin}->io:format("serverreceivedbinary=~p~n",[Bin])Str=binary_to_term(Bin),io:format("server(unpacked)~p~n",[Str]),Reply=lib_misc:string2value(Str),io:format(“serverreplying=~p~n”,[Reply]),gen_tcp:send(Socket,term_to_binary(Reply)),loop(Socket);{tcp_closed,Socket}->Io:format(“ServerSocketclosed~n”)结尾。但是,写一个漂亮的服务端程序还是一件很辛苦的事情,比如python,我很喜欢Tornado的代码,ioloop.py里面有复用的选项:@classmethoddefconfigurable_default(cls):ifhasattr(select,"epoll"):fromtornado.platform.epollimportEPollIOLoopreturnEPollIOLoopifhasattr(select,"kqueue"):#Python2.6+onBSDorMacfromtornado.platform.kqueueimportKQueueIOLoopreturnKQueueIOLoopfromtornado.platform.selectimportSelectIOLoopreturnSelectIOLoop在HTTPServer.py中同样继承了TCPServer,进入并实现了HTTP协议,代码片段如下:classHTTPServer(TCPServer,Configurable,httputil.HTTPServerConnectionDelegate):...definitial_questloopalize(no,requestself,re=None,xheaders=False,ssl_options=None,protocol=None,decompress_request=False,chunk_size=None,max_header_size=None,idle_connection_timeout=None,body_timeout=None,max_body_size=None,max_buffer_size=None):self.request_callback=request_callbackself。no_keep_alive=no_keep_aliveself.xheaders=xheadersself.protocol=protocolself.conn_params=HTTP1ConnectionParameters(decompress=decompress_request,chunk_sizechunk_size=chunk_size,max_header_sizemax_header_size=max_header_size,header_timeout=idle_connection_timeoutor3600,max_body_sizemax_body_size=max_body_size,body_timeoutbody_timeout=body_timeout)TCPServer.__init__(self,io_loopio_loop=io_loop,ssl_optionsssl_options=ssl_options,max_buffer_sizemax_buffer_size=max_buffer_size,read_chunk_size=chunk_size)self._connections=set()...也许,老码友说的全错了。了解了所谓的网络基础,不一定能写出漂亮的代码,不了解所谓的网络基础也不一定能写出漂亮的代码,让他自己说吧。点此阅读作者更多好文