Linux网络入门
Posted 一起去看日落吗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux网络入门相关的知识,希望对你有一定的参考价值。
🎇Linux:
- 博客主页:一起去看日落吗
- 分享博主的在Linux中学习到的知识和遇到的问题
博主的能力有限,出现错误希望大家不吝赐教
- 分享给大家一句我很喜欢的话: 看似不起波澜的日复一日,一定会在某一天让你看见坚持的意义,祝我们都能在鸡零狗碎里找到闪闪的快乐🌿🌞🐾。
💐 🌸 🌷 🍀
目录
💐 1. 计算机网络背景
🌷1.1 网络发展
- 独立模式:计算机之间相互独立
在早期的时候,计算机之间是相互独立的,此时如果多个计算机要协同完成某种业务,那么就只能等一台计算机处理完后再将数据传递给下一台计算机,然后下一台计算机再进行相应的业务处理,效率非常低下。
- 网络互联:多台计算机连接在一起,完成数据共享
这时就有人设法将这些计算机连接在一起,当某个业务需要多台计算机协同完成时,就可以将共享的数据放到服务器中进行集中管理,此时各个计算机就都能获取到这些共享的数据,所以各个业务在处理就能随时进行切换了。
- 局域网LAN:计算机数量更多了,通过交换机和路由器连接在一起
后来这样的网络雏形逐渐发展,连入这个网络中的机器变得越来越多,于是就出现了局域网的概念。
在局域网中有一种设备叫做交换机,交换机主要完成局域网内数据的转发工作,也就是在局域网内将数据从一台主机转发给另一台主机。各个局域网之间通过路由器连接起来,路由器主要完成数据的路由转发工作。
- 广域网WAN:将远隔千里的计算机都连在一起
各个局域网之间通过路由器相互连接在一起,便组成了一个更大的网络结构,我们将其称之为广域网。实际局域网和广域网是一种相对的概念,我们也可以将广域网看作一个比较大的局域网。
当然,你可能还听说过城域网和校园网这样的概念,城域网实际就是在一个城市范围内所建立的计算机通信网,而校园网对应的就是在一个校园范围内所建立的计算机通信网。城域网和校园网实际也是一种相对的概念,我们都可以将它们看作一个大的局域网。
🌷1.2 认识“协议”
“协议”本质就是一种约定,通信双方只要曾经做过某种约定,之后就可以使用这种约定来完成某种事情。而网络协议是通信计算机双方必须共同遵从的一组约定,因此我们一定要将这种约定用计算机语言表达出来,此时双方计算机才能识别约定的相关内容。
计算机之间的传输媒介是光信号和电信号. 通过 “频率” 和 “强弱” 来表示 0 和 1 这样的信息. 要想传递各种不同的信息, 就需要约定好双方的数据格式.
- 协议应该由谁来定制
在当前这个互联网时代,计算机生产商有很多,计算机操作系统也有很多,计算机网络硬件设备更是多种多样,那我们是如何让这些不同厂商之间生产的计算机能够相互顺畅的通信的呢?这时候就需要有人站出来,约定一个共同的标准,并且让大家都来遵守这个标准,这就是网络协议。
而那个站出来的人一定是该领域当中的佼佼者,因为网络协议的定制本质就是规则的定制,你要站出来制定规则那么一定要同行业的其他人都认可你。比如5G标准的定义,华为就是那个站出来的人,华为在通信领域已经具备很强的通信能力了,在这个行业里它就是所谓的佼佼者。
正所谓“一流的企业做标准,二流的企业做品牌,三流的企业做产品”。但是标准的定制有一部分是公益性、开源性的,它制定标准的目的是为了让我们的世界变得更好;而标准的定制还有一部分是具有一定盈利性质的,当你需要使用这项技术时就会收取部分专利费,此时就能将曾经做的技术投入进行变现。
💐 2. 网络协议初始
🌷2.1 协议分层
- 协议分层的好处
网络协议栈设计成层状结构,其目的就是为了将层与层之间进行解耦,保证代码的可维护性和可扩展性。
比如在打电话的时候,站在工程师的角度实际这两个人并不是直接进行沟通的,而是甲的电话将甲说的话记录下来,经过一系列编码转码后,通过通信网络将信息从甲的电话传递到了乙的电话,然后信息在乙的电话中再经过对应的编码转码,最后乙才通过话筒听到了甲所说的话。
其中,人与人之间通信使用的是汉语,我们可以将其称为语言层;而电话和电话之间通信使用的是电话系统相关的一些接口,我们可以将其称之为通信设备层。
后来随着科技的发展,我们都用上了智能手机,此时我们下层使用的通信设备变了,或是这部电话卖到了其他国家,此时上层使用的通信语言变了,但我们仍然可以正常沟通。
分层最大的好处在于“封装”,在分层情况下,将某层的协议进行替换后,通信双方之间是不会受到影响的。
- 理解各层之间直接通信
从上述例子我们还可以看出,虽然在打电话时我们并不是直接进行沟通的,但是我们可以认为我们是在直接进行沟通,并且这两台电话也不是直接在进行沟通的,数据经过电话后,还需要各种基站,各种电信网络来进行数据转发,但是这两台电话依旧可以认为是直接在和对方电话进行通信的。
因此对于网络协议我们需要有一个基本的认识:关于通信,同层协议可以认为自己在和对方层直接进行通信,从而达到简化对于网络协议栈的理解。
也就是说,在网络协议栈中我们可以认为通信双方的应用层之间直接在进行通信,也可以认为通信双方的传输层之间直接在进行通信,对于网络层和数据链路层也同样如此。
🌷2.2 OSI七层模型
上面我们说的是TCP/IP四层协议,而实际当初那个站出来的人定的协议叫做OSI七层协议:
- OSI(Open System Interconnection,开放系统互联)七层网络模型称为开方式系统互联参考模型,是一个逻辑上的定义和规范。
- OSI把网络从逻辑上分为了七层,每一层都有相关的、相对应的物理设备,比如路由器,交换机。
- OSI七层模型是一种框架性的设计方法,其最主要的功能就是帮助不同类型的主机实现数据传输,比如手机和电视之间数据的传输。
- OSI七层模型最大的优点是将服务、接口和协议这三个概念明确地区分开来,概念清楚,理论也比较完整,通过七个层次化的结构模型使不同的系统不同的网络之间实现可靠的通讯。
- 但是,OSI七层模型既复杂又不实用,所以后来在具体实现的时候就对其进行了调整,于是就有了我们现在看到的TCP/IP四层协议。
OSI七层模型如下:
🌷2.3 TCP/IP五层(或四层)模型
TCP/IP是一组协议的代名词,它还包括许多协议,共同组成了TCP/IP协议簇。TCP/IP通讯协议采用了五层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。
- 物理层: 负责光/电信号的传递方式。比如现在以太网通用的网线(双绞线)、早期以太网采用的同轴电缆(现在主要用于有线电视)、光纤,现在的WiFi无线网使用的电磁波等都属于物理层的概念。物理层的能力决定了最大传输速率、传输距离、抗干扰性等。集线器(Hub)就是工作在物理层的。
- 数据链路层: 负责设备之间的数据帧的传送和识别。例如网卡设备的驱动、帧同步、冲突检测(如果检测到冲突就自动重发)、数据差错校验等工作。数据链路层底层的网络通信标准有很多,如以太网、令牌环网、无线LAN等。交换机(Switch)就是工作在数据链路层的。
- 网络层: 负责地址管理和路由选择。例如在IP协议中,通过IP地址来标识一台主机,并通过路由表的方式规划出两台主机之间数据传输的线路(路由)。路由器(Router)就是工作在网络层的。
- 传输层: 负责两台主机之间的数据传输。例如传输控制协议(TCP),能够确保数据可靠的从源主机发送到目标主机。
- 应用层: 负责应用程序间沟通。比如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等。我们的网络编程主要就是针对应用层的。
与OSI七层模型各层的对应关系:
可以看到,它们的下面几层几乎是没有区别的,操作系统对应的是传输层和网络层,数据链路层和物理层都是对应在驱动层的,而TCP/IP协议当中的应用层就对应到OSI七层协议当中的应用层、表示层和会话层。
一般而言:
- 对于一台主机,它的操作系统内核实现了从传输层到物理层的内容。
- 对于一台路由器,它实现了从网络层到物理层的内容。
- 对于一台交换机,它实现了从数据链路层到物理层的内容。
- 对于集线器,它只实现了物理层的内容。
但这并不是绝对的,比如很多交换机也实现了网络层的转发,很多路由器也实现了部分传输层的内容(比如端口转发)。
- 集线器
电磁信号在长距离传输过程中信号是会衰减的,而集线器的主要功能就是对接收到的信号进行再生整形放大,以扩大网络的传输距离,同时把所有节点集中在以它为中心的节点上。
需要注意的是,集线器属于纯硬件网络底层设备,基本上不具有类似于交换机的“智能记忆”能力和“学习”能力,也不具备交换机所具有的MAC地址表,所以它发送数据时都是没有针对性的,而是采用广播方式发送,也就是说,当集线器要向某节点发送数据时,不是直接把数据发送到目的节点,而是把数据包发送到与集线器相连的所有节点。
- 以太网名字的由来(简述)
“以太网”这个名字实际来源于物理学,在早期的时候,很多科学家都认为任何信息的传播都是需要媒介的,因此物理学家们就认为光的传播也是需要媒介的。这时物理学家们就发现了一个问题:既然光能够从太阳传播到地球上,那么在这个传播的过程中一定需要某种媒介,但太阳和地球之间不是绝对真空的状态吗?
此时物理学家们就提出了一个“以太假说”,认为宇宙空间并不是真空的,而是填充满了一种叫做“以太”的物质的,此时“以太”就可以作为光传播的媒介。但后来经过实验后发现,“以太”这种物质实际上是不存在的。
其中在这个故事线当中有两个人是与计算机学科相关的,一个人叫做图灵,另一个叫做冯诺依曼,这两个人当时也是参与了这场讨论的,因为他们不仅是计算机科学家,同时也是数学家。
经过后来的发展,在局域网产生之后就需要对局域网进行命名,而在局域网命名的时候人们就将其命名为“以太网”。因为我们曾经在物理学上认为“以太”就是传送物质的介质,也就是说“以太”是具有某种通信能力的,虽然“以太”在物理学上验证是不存在的,但现在在计算机领域我们做出来了这样一种东西,它也是能够传播物质(信息)的,同时也为了纪念之前的“以太假说”,于是早期的局域网标准就被命名为了“以太网”。
💐 3. 网络传输基本流程
我们这里以两台主机进行文件传输为例,此时各层对应的协议如下:
🌷3.1 同局域网的两台主机通信
- 同局域网的两台主机通信
首先需要明确的是,同一个局域网内的主机是能够直接进行通信的,因为最初局域网设计的目的,就是为了让局域网内的主机能够进行通信。
当用户要将文件传输给另一台主机前,该文件数据需要先通过网络协议栈进行封装:
- 文件数据先交给应用层,应用层添加上对应应用层协议的报头信息后,将数据再交给传输层。
- 传输层收到数据后,再添加上对应传输层协议的报头信息,并将数据继续向下进行交付。
- 网络层收到数据后,再添加上对应网络层协议的报头信息,接着将数据再交给链路层。
- 链路层收到数据后,最后再添加上对应链路层协议的报头信息,至此数据封装完毕。
数据封装完毕后就可以通过局域网将其发送给对端主机了,而当对端主机收到数据后,对应也需要通过网络协议栈对该数据进行解包与分用:
- 链路层收到数据后,先将数据中对应链路层协议的报头信息提取出来,然后将剩下的数据交给网络层。
- 网络层收到该数据后,再将数据中对应网络层协议的报头信息提取出来,然后将剩下的数据继续向上进行交付。
- 传输层收到该数据后,再将数据中对应传输层协议的报头信息提取出来,然后将剩下的数据再交付给应用层。
- 应用层收到数据后,最后将数据中对应应用层协议的报头信息提取出来,至此便完成了数据的解包与分用。
也就是说,任何一台主机在发送数据之前,该数据都要先自顶向下贯穿协议栈来完成数据的封装,在这个过程中,每一层协议都会添加上对应的报头信息;而任何一台主机收到数据后,都要先自底向上贯穿协议栈来完成数据的解包和分用,在这个过程中,每一层协议都会将对应的报头信息提取出来。
-
数据包封装和分用
-
不同协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报(datagram),在链路层叫做帧(frame)。
-
应用层数据通过协议栈发到网络上,每层协议都要加上一个数据首部(header),称为封装(Encapsulation)。
-
首部信息中包含了一些类似于首部有多长,载荷(payload)有多长,上层协议是什么等信息。
-
数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,根据首部中的“上层协议字段”将数据交给对应的上层协议处理。
下图为数据封装的过程:
下图为数据分用的过程:
- 什么是报头?
报头本质也是一种数据,报头一般是通过位段实现的,因此协议栈的每一层都有一个对应的位段来表示当前层的报头。
数据的封装与解包:
- 数据封装,实际就是不断给数据加上各种对应的报头,这些报头里面填充的就是对应的各种协议细节。
- 数据解包,实际就是不断从数据中提取对应的报头,并对提取出来的报头进行数据分析。
- 报头与有效载荷
当对端主机收到数据后,需要自底向上贯穿协议栈,依次进行数据的解包与分用。在这个解包的过程中,每一层的协议只需要提取出数据中对应的报头,然后对该报头进行分析处理,而剩下的数据则直接交付给上层就行了。
因为每一层的协议实际上只关心数据中与当前层对应的报头信息,而剩下信息的具体内容根本不必关心,我们将数据中除当前层的报头以外的数据叫做“有效载荷”。
需要注意的是,上层协议在数据封装时添加的报头信息,在下层协议进行数据解包时看来就是有效载荷。比如数据封装时应用层添加的报头信息,在对端主机进行数据解包时,在对端主机的传输层、网络层以及链路层看来,该应用层曾经添加的报头信息就是有效载荷。
- 如何将报头与有效载荷进行分离?
协议栈的每一层都要从数据中提取对应的报头信息,而要将数据中的报头提取出来,首先就需要明确报头与有效载荷之间的界限,这样才能将它们进行分离。而每一层添加报头时都是将报头添加到数据的首部的,因此我们只要知道了报头的大小,就能够讲报头和有效载荷进行分离。
获取报头大小的方法通常有两种:
- 定长报头。顾名思义就是报头的大小是固定的。
- 自描述字段。报头当中提供了一个字段,用来表示报头的长度。
实际上每个协议都要提供一种方法,让我们获取到报头的大小,这样我们才能在解包时将报头与有效载荷进行分离。
- 当前层如何知道应该将有效载荷交付给上层的哪个协议?
网络协议栈的每一层都可能会对应多种协议,即便将报头与有效载荷分离了,那当前层应该将有效载荷交付给上层对应的哪个协议呢?
实际在每种协议的报头当中,几乎都会包含一个字段,表明我们应该把分离出来的有效载荷交付给上层的哪个协议,这就是分用的过程。
-
协议共性
-
提供一个将报头与有效载荷分离的方法。
-
协议当中必须包含一个字段,表明应该将有效载荷交付给上层的哪个协议。
实际当我们要学习一种协议的时候,首先就应该明确这两点,因为当我们在解包的时候必须将报头与有效载荷分离,而当我们在分用的时候必须知道应该将有效载荷交付给上层的哪个协议。
- 局域网内传输数据时,该局域网内的所有主机都能收到
在一个局域网当中,除了当前正在进行通信的A主机和B主机以外,还有其他的主机,那A主机是如何将数据成功发送给B主机的呢?
实际当主机A想要发数据给主机B的时候,该局域网内的其他主机也都收到了该数据,只不过除了主机B以外,其他主机识别到该数据并不是发给自己的,此时其他主机就把收到的数据丢弃了。
也就是说,在局域网(以太网)通信时,该局域网内所有的主机在底层其实都收到了任何数据,只不过经过筛选后只提交上来了发给自己的数据。
- 什么是碰撞?
当主机A在向主机B发送数据时,其他主机彼此之间可能也正在进行通信,甚至主机A在和主机B通信的同时也在和其他主机进行通信。
但同一局域网中的所有主机在通信时,使用的都是一个共同的通信信道,因此如果局域网内的多台主机同时进行通信,此时这些数据之间就可能会相互干扰。
每一个局域网都可以看作是一个碰撞域,如果某个主机发送出去的数据与其他主机发送的数据之间产生了干扰,我们就称这两台主机在该碰撞域中发生了碰撞。
- 如何判断发送出去的数据是否发生了碰撞?
因为发送到局域网当中的数据是所有主机都能够收到的,因此当一个主机将数据发送出去后,该主机本身也是能够收到这个数据的。当该主机收到该数据后就可以将其与之前发送出去的数据进行对比,如果发现收到的数据与之前发送出去的数据不相同,则说明在发送过程中发生了碰撞。
也就是说,主机实际是能够通过某种方式,知道曾经发送出去的数据是否发生了碰撞的。
- 发生碰撞后是如何处理的?
当一个主机发现自己发送出去的数据产生了碰撞,此时该主机就要执行“碰撞避免”算法。“碰撞避免”算法实际很简单:当一个主机发送出去的数据产生了碰撞,那么该主机可以选择等一段时间后,再重新发送该数据。这就像现实生活中的两个人同时想要说话,此时对方就都会说“你先说吧”,这实际上就是一种碰撞避免。
需要注意的是,实际在网络通信压力不大的时候发生碰撞的概率是不大的,我们不要太小瞧计算机的处理速度,也不要太小瞧网线传播数据的速度。
- 每个主机如何判断该数据是否是发送给自己的?
在局域网中发送的数据实际叫做MAC数据帧,在这个MAC数据帧的报头当中会包含两个字段,分别叫做源MAC地址和目的MAC地址。
每一台计算机都至少配有一张网卡,而每一张网卡在出厂时就已经内置了一个48位的序列号,我们将这个序列号称之为“MAC地址”,这个MAC地址是全球唯一的。
在局域网中进行通信的时候,每一个主机在收到一个MAC数据帧后,都会提取该MAC数据帧的报头,找到对应的目的MAC地址与自己的MAC地址进行比对。如果该MAC地址与自己的MAC地址不匹配,则直接将该MAC数据帧丢弃,只有MAC地址匹配时,该主机才会将该数据帧的有效载荷继续向上进行交付处理。
扩展一下:
-
单向数据发送: 主机发送数据帧时,将数据帧当中的目的MAC地址指定为某一台主机,此时每台主机对数据帧进行识别后,最终只有那台指定的主机会将该数据帧向上交付进行处理。
-
局域网内进行数据广播: 主机发送数据帧时,将数据帧当中的目的MAC地址设置为全1,此时所有主机收到该数据帧后都会对该数据帧进行处理。
-
与碰撞相关的一种局域网攻击机制
如果局域网内的某台主机一直向该局域网内发送一些无用的数据,那么其他主机一发数据就会产生碰撞,此时该局域网也就瘫痪了,这实际就是局域网本身的一个攻击原理。但需要注意的是,这台一直发送“垃圾数据”的主机,必须要通过某种方式绕过“碰撞避免”算法,否则当其发送的数据产生碰撞后,该主机自己也会执行“碰撞避免”算法。
🌷3.2 跨网络的两台主机通信
- 跨网络的两台主机通信
上面说的都是同一局域网内的主机之间的通信,那跨局域网的两台主机之间是如何进行通信的呢?
局域网之间都是通过路由器连接起来的,因此一个路由器至少能够横跨两个局域网。而这些被路由器级联局域网都认为,该路由器就是本局域网内的一台主机,因此路由器可以和这些局域网内的任意一台主机进行直接通信。
采用不同通信标准的两个局域网内的主机通信
如果路由器级联的两个局域网采用的是相同的通信标准,那么通信过程大致就是上述那样。但被路由器级联的局域网可能采用的是不同的通信标准,比如局域网1采用的是以太网,而局域网2采用的却是令牌环网。
由于以太网和令牌环网是不同的通信标准,它们给数据添加的报头也是不一样的,因此令牌环网当中的主机无法对以太网当中的数据帧进行解包。
这种情况实际是由路由器来处理的,路由器是工作在网络层的一个设备,我们可以认为路由器当中的协议栈是下面这样的。
此时当数据要从局域网1发送到局域网2时,路由器收到局域网1的数据后,会先将以太网对应的报头进行解包,然后将剩下的数据向上交付给网络层,在网络层进行一系列数据分析后,再将数据向下交付给链路层,此时在链路层当中就会给该数据添加上令牌环对应的报头信息,然后再将该数据发送到局域网2当中,此时该数据就能够在令牌环网当中传输了。
- 路由器为什么能够“认路”?
一个路由器可能会级联多个局域网,当路由器需要将一个局域网的数据转发到另一个局域网时,路由器如何知道该数据应该转发到哪一个局域网的呢?
路由器其实是通过IP地址来确定数据的转发方向的,因特网上的每台计算机都有一个唯一的IP地址,而在数据向下进行封装时,在网络层封装的报头当中就会包含两个字段,分别是源IP地址和目的IP地址。
当路由器需要将一个局域网的数据转发到另一个局域网时,在路由器的链路层会先将数据的在当前局域网对应的底层报头去掉,然后将剩下的数据向上交付给网络层,此时在网络层就可以获取到该数据对应的目的IP地址,然后路由器就可以根据该IP地址在路由表当中进行查找,最终就能够确认该数据应该发送到哪一个局域网。
- 屏蔽底层的差异
IP地址的存在除了帮助数据“路由”以外,还有一个很重要的作用,那就是屏蔽了底层网络的差异。对于通信主机双方的IP层及其往上的协议来说,它们并不需要关心底层采用的是以太网还是令牌环网,它们认为只要填写了源IP地址和目的IP地址就能够将数据发送出去,因此现在主流的网络也叫做“IP网络”。
这种类似的技术还有:
- 虚拟地址空间: 屏蔽了内存之间的差别,让所有的进程看到的都是同一块内存,并且这块内存的布局都是一样的。
- 一切皆文件: 通过文件结构体和函数指针的方案,让我们能够以对待文件的方式对待某些资源。
💐 4. 网络中的地址管理
🌷4.1 认识IP地址
- IP地址是在IP协议中,用来标识网络中不同主机的地址。
- 对于IPv4来说,IP地址是一个4字节,32位的整数。
- 我们通常也使用“点分十进制”的字符串表示IP地址,例如192.168.0.1,用点分割的每一个数字表示一个字节,范围是0-255。
需要注意的是,IP协议有两个版本,分别是IPv4和IPv6。IPv4用32个比特位来标识IP地址,而IPv6用128个比特位来标识IP地址。
🌷4.2 认识MAC地址
-
MAC地址用来识别数据链路层中相连的节点。
-
长度为48位,及6个字节,一般用16进制数字加上冒号的形式来表示(例如:08:00:27:03:fb:19)。
-
在网卡出厂时就确定了,不能修改。MAC地址通常是唯一的(虚拟机中的MAC地址不是真实的MAC地址,可能会冲突,也有些网卡支持用户配置MAC地址)。
-
如何在Linux中查看自己的MAC地址?
ifconfig命令可以查看当前主机所对应的网卡信息。
我们用到的大部分局域网都是以太网标准,其中ether对应就有”以太“的意思,而ether后面的这个地址就是当前云服务器所对应的MAC地址。但实际云服务器上的MAC地址可能不是真正的MAC地址,该MAC地址可能模拟出来的。
Linux入门培训教程 linux网络编程socket介绍
一.概念介绍
网络程序分为服务端程序和客户端程序。服务端即提供服务的一方,客户端为请求服务的一方。但实际情况是有些程序的客户端、服务器端角色不是这么明显,即互为Linux培训
客户端和服务端。
我们编写网络程序时,一般是基于TCP协议或者UDP协议进行网络通信的。兄弟连Linux培训小编整理:
TCP:(Transfer Control Protocol)传输控制协议是一种面向连接的协议,当我们的网络程序使用这个协议的时候,网络可以保证我们的客户端和服务端之间的传输是可靠的。
UDP:(User DatagramProtocol)用户数据报协议是一种非面向连接的协议, 这种协议并不能保证我们的网络程序的连接是可靠的。
我们编写的网络程序具体采用哪一类协议,要视具体情况而定。比如,如果是大数据量的通信,而且对数据的完整性要求不是特别高,则可以采用UDP协议,以得到更快的传输速率。如果我们是要实现一些诸如文件传输、社交通讯之类的功能,就需要采用TCP协议通信,以保证传输的可靠性。
二.初等网络函数介绍
nt socket(int domain, inttype,int protocol)
domain:说明我们网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等).
AF_UNIX只能够用于单一的Unix 系统进程间通信,
而AF_INET是针对Internet的,因而可以允许在远程
主机之间通信(当我们 man socket时发现 domain可选项是 PF_*而不是AF_*,因为glibc是posix的实现所以用PF代替了AF,
不过我们都可以使用的).
type:我们网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等)
SOCK_STREAM表明我们用的是TCP 协议,这样会提供按顺序的,可靠,双向,面向连接的比特流.
SOCK_DGRAM 表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信.
protocol:由于我们指定了type,所以这个地方我们一般只要用0来代替就可以了 socket为网络通讯做基本的准备.
成功时返回文件描述符,失败时返回-1,看errno可知道出错的详细情况.
int bind(int sockfd, structsockaddr *my_addr, int addrlen)
sockfd:是由socket调用返回的文件描述符.
addrlen:是sockaddr结构的长度.
my_addr:是一个指向sockaddr的指针. 在中有sockaddr的定义
struct sockaddr{
unisgned short as_family;
char sa_data[14];
};
不过由于系统的兼容性,我们一般不用这个头文件,而使用另外一个结构(struct sockaddr_in) 来代替.在中有sockaddr_in的定义
struct sockaddr_in{
unsigned short sin_family;
unsigned short intsin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
我们主要使用Internet所以
sin_family一般为AF_INET,
sin_addr设置为INADDR_ANY表示可以和任何的主机通信,
sin_port是我们要监听的端口号.sin_zero[8]是用来填充的.
bind将本地的端口同socket返回的文件描述符捆绑在一起.成功是返回0,失败的情况和socket一样
int listen(int sockfd,intbacklog)
sockfd:是bind后的文件描述符.
backlog:设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示可以介绍的排队长度.
listen函数将bind的文件描述符变为监听套接字.返回的情况和bind一样.
int accept(int sockfd,struct sockaddr *addr,int *addrlen)
sockfd:是listen后的文件描述符.
addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了. bind,listen和accept是服务器端用的函数,
accept调用时,服务器端的程序会一直阻塞到有一个 客户程序发出了连接. accept成功时返回最后的服务器端的文件描述符,
这个时候服务器端可以向该描述符写信息了. 失败时返回-1
int connect(int sockfd,struct sockaddr * serv_addr,int addrlen)
sockfd:socket返回的文件描述符.
serv_addr:储存了服务器端的连接信息.其中sin_add是服务端的地址
addrlen:serv_addr的长度
connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符 失败时返回-1.
更多函数请查看man …….
int getaddrinfo(const char*node, const char *service,
const struct addrinfo*hints,
struct addrinfo **res);
三.初等网络函数使用实例
一个教科书式的服务器端程序流程为:
建立套接字socket()--->将套接字绑定到ip地址bind()----->建立监听套接字listen()------>开始等待客户端请求accpet()
详细代码如下:
复制代码代码如下:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<sys/types.h>
#include <netdb.h>
int main(int argc, char*argv[])
{
int sockfd,connfd;
struct sockaddr_in srvaddr;
struct sockaddr_in cliaddr;
int len,port;
charhello[]="Hi,welcome to linux-code!\n";
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
fprintf(stderr,"Socketerror:%s\n\a",strerror(errno));
exit(1);
}
/* 服务器端填充 sockaddr结构 */
bzero(&srvaddr,sizeof(structsockaddr_in));
srvaddr.sin_family=AF_INET;
srvaddr.sin_addr.s_addr=htonl(INADDR_ANY);
srvaddr.sin_port=htons(1113);
/* 捆绑sockfd描述符 */
if(bind(sockfd,(structsockaddr *)(&srvaddr),sizeof(struct sockaddr))==-1){
fprintf(stderr,"Binderror:%s\n\a",strerror(errno));
exit(1);
}
/* 监听sockfd描述符 */
if(listen(sockfd,5)==-1){
fprintf(stderr,"Listenerror:%s\n\a",strerror(errno));
exit(1);
}
len=sizeof(structsockaddr_in);
while(1){ /* 服务器阻塞,直到客户程序建立连接 */
if((connfd=accept(sockfd,(structsockaddr *)(&cliaddr),&len))==-1){
fprintf(stderr,"Accepterror:%s\n\a",strerror(errno));
exit(1);
}
fprintf(stderr,"Serverget connection from %s\n",inet_ntoa(cliaddr.sin_addr));
if(write(connfd,hello,strlen(hello))==-1){
fprintf(stderr,"WriteError:%s\n",strerror(errno));
exit(1);
}
/* 这个通讯已经结束 */
close(connfd);
/* 循环下一个 */
}
close(sockfd);
exit(0);
}
一个教科书式的客户端程序流程为:
建立套接字socket()--->与服务器建立连接connect()
详细代码如下:
复制代码代码如下:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <sys/types.h>
#include <netdb.h>
int main(int argc, char*argv[]) {
int sockfd; char buf[1024];
struct sockaddr_in srvaddr;
struct hostent *phost; intnbytes;
if(argc!=3){
fprintf(stderr,"Usage:%s<IP> <portnumber>\a\n",argv[0]);
exit(1);
}
/* 客户程序开始建立 sockfd描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
fprintf(stderr,"socketError:%s\a\n",strerror(errno));
exit(1);
}
/* 客户程序填充服务端的资料 */
bzero(&srvaddr,sizeof(srvaddr));
srvaddr.sin_family=AF_INET;
srvaddr.sin_port=htons(atoi(argv[2]));
if (inet_pton(AF_INET,argv[1], &srvaddr.sin_addr) <= 0){
fprintf(stderr,"inet_ptonError:%s\a\n",strerror(errno));
exit(1);
}
/* 客户程序发起连接请求 */
if(connect(sockfd,(structsockaddr *)(&srvaddr),sizeof(struct sockaddr))==-1){
fprintf(stderr,"connectError:%s\a\n",strerror(errno));
exit(1);
}
/* 连接成功了 */
if((nbytes=read(sockfd,buf,1024))==-1){
fprintf(stderr,"readError:%s\n",strerror(errno));
exit(1);
}
buf[nbytes]=‘\0‘;
printf("received data:%s\n",buf);
/* 结束通讯 */
close(sockfd);
exit(0);
}
四.上述程序存在的问题
先运行上述程序的服务端程序,再运行客户端程序,可以得到如下结果:
服务器端结果:
[email protected]:~/code $./srv
Server get connection from192.168.1.153
Server get connection from127.0.0.1
Server get connection from192.168.1.153
客户器端结果:
[email protected]:~/code $./cli 192.168.1.153 1113
received data:Hi,welcome tolinux-code!
我们完成了一个简单的网络通信程序,该程序使用的io模型为同步(synchronous)阻塞(blocking)。服务器端调用accept(),write()等函数,如果没有客户端连接过来或者相应的文件描述符没有准备好写,程序就会在那里死死的等待,什么事情也不干。在实际应用中,这类程序是很少出现的。实际中使用的都是异步io模型。
以上是关于Linux网络入门的主要内容,如果未能解决你的问题,请参考以下文章