10多个socket编程实例源码
一、网络构成
社交网络,世界上任意两个人之间最多不超过六个中间人就可以建立联系?
1个人认识100个人,而这100个人又认识100个不认识的人,经过6次连接,1个人可以与其他人100亿人连接,而地球上只有60亿人。
世界上物理网络有很多种类型,比如:令牌网,以太网,专用网络,固网,移动网络,互联网,物联网等等,这里我们主要探讨互联网也叫Intnet。因特网是一个世界范围的计算机网络,即它是一个互联了遍及全世界数十亿计算设备的网络,基于以太网实现。
计算机网络是由 核心网-边缘网-接入网,交换机,路由器,网关,终端主机(提供服务)等主要元素构成的。
Intnet构成
端系统也叫主机,通过通信链路(communication link)和分组交换机(packet switch)连接到一起。市面上流行着各种类型、各具特色的分组交换机,两种最有名的类型是路由器(router和链路层交换机(link-layer switch)。
端系统通过因特网服务提供商(Internet Service Provider, ISP)接入因特网,包括如 本地电缆或电话公司那样的住宅区ISP、公司ISP、大学ISP,在机场、旅馆、咖啡店和其 他公共场所提供WiFi接入的ISP,以及为智能手机和其他设备提供移动接入的蜂窝数据 ISP。
端系统交互
定义了在两个或多个通信实体之间交换的报文的格式和顺序,以及报文发送和/或接收一条报文或其他事件所采取的动作规范。
IOS ,国际标准化组织
IETF(Internet Engineering Task Force),即Internet工程任务组
ITU,国际电联
IEEE:电气与电子工程师协会
IEEE 802:又称为LMSC(LAN /MAN Standards Committee, 局域网/城域网标准委员会),IEEE 802委员会成立于1980年2月,它的任务是制定局域网和城域网标准。IEEE 802中定义的服务和协议限定在OSI模型[OSI网络参考模型]的最低两层(即物理层和数据链路层)。
IEEE802协议族:IEEE 802也指IEEE标准中关于局域网和城域网的一系列标准。
IEEE 802.1 :局域网体系结构、寻址、网络互联和网络
IEEE 802.11:无线局域网(WLAN)的介质访问控制协议及物理层技术规范。
IEEE 802.11i,2004年,无线网络的安全方面的补充。.
IEEE 802.15:采用蓝牙技术的无线个人网(Wireless Personal Area Networks,WPAN)技术规范。
Request For Comments,缩写为RFC,征求意见稿,是由互联网工程任务组(IETF)发布的一系列备忘录。文件收集了有关互联网相关信息,以及UNIX和互联网社群的软件文件,以编号排定。RFC文件是由互联网协会(ISOC)赞助发行。
前沿的技术研发往往需要去查看RFC文档。
OSI七层模型和 TCP/IP模型。
应用层:TFTP,HTTP,SNMP,SMTP,DNS,Telnet,MQTT ,RTMP 等等
传输层:TCP,UDP
网络层:IP,ICMP,OSPF,EIGRP,IGMP,ARP等
数据链路层:PPP,PP2P等 (Frame,帧)
1、计算机网络的数据交换方式基本可以分为两大类:线路交换与存储转发交换。
线路交换方式与电话交换的工作方式类似。两台计算机通过通信子网进行数据交换之前,首先要在通信子网中建立一个实际的物理线路连接。
线路交换方式的优点:
当线路连接过程完成后,在两台主机之间建立的物理线路连接为此次通信专用,通信实时性强。适用于交互式会话类通信。
线路交换方式的缺点:
不适用于计算机与计算机之间的突发性通信。不具备数据存储能力,不能平滑通信量。不具备差错控制能力,无法发现与纠正传输差错。
存储转发交换又可以分为两类:报文存储转发交换(简称为报文交换)与报文分组存储转发交换(简称为分组交换);
分组交换又可以进一步分为数据报交换与虚电路交换。
存储转发交换的特点:
发送的数据与目的地址、源地址、控制信息一起,按照一定的格式组成一个数据单元(报文或报文分组)再发送出去。路由器可以动态选择传输路径,可以平滑通信量,线路的利用率高,可以对不同通信速率的线路进行速率转换。数据单元在通过路由器时需要进行差错处理,可以提高数据传输可靠性。
报文交换与报文分组的比较:
利用存储转发交换原理传送数据时,被传送的数据单元相应可以分为两类:报文(message)与报文分组(packet)。
在计算机网络中,如果人们不对传输的数据块长度做任何限制,直接封装成一个包进行传输,那么封闭后的包称为报文。
将报文作为一个数据传输单元的方法称为报文存储转发交换或报文交换。
分组交换方法的优点:
将报文划分成有固定格式和最大长度限制的分组进行传输,有利于提高路由器检测接收分组是否出错、出错重传处理过程的效率,有利于提高路由器存储空间的利用率。路由选择算法可以根据链路通信状态、网络拓扑变化,动态地为不同的分组选择不同的传输路径,有利于减小分组传输延迟,提高数据传输的可靠性。
在实际应用中,分组交换技术可以分为两类:数据报(data gram,DG)与虚电路(virtual circuit,VC)。
在数据报方式中,分组传输前不需要预先在源主机与目的主机之间建立“线路连接”。
数据报传输方式的特点:
同一报文的不同分组可以经过不同的传输路径通过通信子网。同一报文的不同分组到达目的主机时可能出现乱序、重复与丢失现象。每个分组在传输过程中都必须带有目的地址与源地址。数据报方式的传输延迟较大,适用于突发性通信,不适用长报文、会话式通信。
虚电路方式,试图将数据报与线路交换相结合起来,发挥这两种方法各自的优点,以达到最佳的数据交换效果。虚电路需要依靠信令。
虚电路方式的特点:
在每次分组传输之前,需要在源主机与目的主机之间建立一条虚电路。所有分组都通过虚电路顺序传送,分组不必携带目的地址、源地址等信息。分组通过虚电路上的每个路由器时,路由器只需要进行差错检测,而不进行路由选择。路由器可以与多个主机之间的通信建立多条虚电路。
通信服务类型:面向连接服务与无连接服务
采用的通信服务类型不同,通信的可靠性与协议的复杂程度也都不相同,需要耗费资源也不同。
面向连接服务,数据传输过程必须经过三个阶段:连接建立、连接维护与释放连接。
在数据传输过程中,各个分组不需要携带目的结点的地址。传输的数据收发顺序不变,因此传输的可靠性好,但是协议复杂,通信效率不高。
在无连接服务中,每个分组都携带完整的目的结点地址,各个分组在系统中是独立传送的,无连接服务数据传输过程不需要建立连接。由于无连接服务发送的分组可能经历不同路径发送到目的主机,目的主机接收的分组可能出现乱序、重复与丢失现象。无连接服务的可靠性不是很好,但是由于省去了很多保证机制,它的通信协议相对简单,通信效率比较高,节约资源。
2、 套接字(Socket)
Socket是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象interface。
Socket是对TCP/IP协议的封装,它是一组接口。
套接字有三个属性:域(domain,又被称为协议族,protocol family),类型(type),和协议(protocol)。
地址的格式随域的不同而不同,每个协议族又可以使用一个或多个地址族定义地址格式。
AF_INET和PF_INET效果相同。
SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等
常见的2个类型的套接字: 流套接字和数据包套接字
流套接字(在某些方面类似域标准的输入/输出流)提供的是一个有序,可靠,双向字节流的连接。 提供面向连接的服务。
流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP连接实现的。他们也是AF_UNIX域中常见的套接字类型。
数据包套接字:与流套接字相反,由类型SOCK_DGRAM指定的,数据包套接字不建立和维持连接。它对可以发送的数据包的长度有限制。数据报作为一个单独的网络消息被传输,它可能会丢失,复制或乱序到达。
数据报套接字实在AF_INET域中通过UDP/IP连接实现,它提供的是一种无序的不可靠服务。
只要底层的传输机制允许不止一个协议来提供要求的套接字类型,我们就可以为套接字选择一个特定的协议。
常用的协议有,IPPROTO_TCP、IPPROTO_UDP、IPPROTO_RAW、IPPROTO_STCP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、IP原始socket协议,STCP传输协议、TIPC传输协议。
定义在Linux系统 /usr/include/netinet.in.h
IPPROTO_IP 创建一个套接字,用于为基于 IPv4 的协议(TCP、UDP 等)发送/接收原始数据。它将为您处理 IP 标头,但您负责在 IP 有效负载中处理/创建其他协议数据。
IPPROTO_RAW 创建一个套接字,用于为任何类型的协议发送/接收原始数据。它不会为您处理任何标头,您负责处理/创建所有有效负载数据,包括 IP 和其他标头。
比如,要调试ARP协议,生成的套接字 ,就需要调用 IPPROTO_RAW协议:
int s = socket (AF_INET, SOCK_RAW, IPPROTO_RAW));
大家熟悉的taceroute 程序 的实现是基于IPPROTO_RAW协议。
Ping 是基于IPPROTO_ICMP协议。
Socket编程需要的五元组: 源IP, 源端口, 目的IP, 目的端口, 类型 。
1、TCP即传输控制协议(Transmission Control Protocol)
工作在传输层的协议,是一种面向有连接的传输层协议,能够对自己提供的连接实施控制。可靠,有序,不重复。适用于要求可靠传输的应用,例如文件传输。面向字节流,相比UDP 传输慢,开销大。
TCP通讯就像是打电话,在这一系列流程都能得到及时反馈,并能确保对方及时接收到。
UDP协议就相当于是写信给对方,寄出去信件之后不能知道对方是否收到信件,信件内容是否完整,也不能得到及时反馈。
TCP是为了在不可靠的互联网上提供可靠的端到端字节流而专门设计的一个传输协议。
互联网与单个网络有很大的不同,因为互联网的不同部分可能有截然不同的拓扑结构、带宽、延迟、数据包大小和其他参数。TCP的设计目标是能够动态地适应互联网的这些特性,而且具备面向各种故障的健壮性。
不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,IP层提供尽力而为的包交换服务,而是提供不可靠的包交换。
应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的报文段(通常受计算连接的网络的数据链路层的最大传输单元(MTU)的限制)。之后TCP把结果包传输给IP层,有它来通过网络将包传送给接收端实体的TCP层。
TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。
然后接收端实体对已成功接收到的包回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未接收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。TCP用一个校验和函数来校验数据是否有误;在发送和接收时都要计算校验。
每台支持TCP的机器都有一个TCP传输实体。TCP 实体可以是一个库过程、一个用户进程、或者内核的一部分。
在所有这些情形下,它管理TCP流,以及与IP层之间的接口。TCP传输实体接收本地进程的用户数据流,将他们分割成不超过64KB(实际上去掉IP和TCP头,通常不超过1460数据字节)的分段,每个分段以单独的IP数据报形式发送。当包含TCP数据的数据报到达一台机器时,它们被递交给TCP传输实体,TCP传输实体重构出原始的字节流。
2、TCP 的几个特点总结:
(1)基于流的方式;
(2)面向连接;
(3)可靠通信方式;
(4)在网络情况不佳的时候尽量降低系统由于重传带来的带宽开销;
(5)通信连接维护是面向的两个端点的,而不考虑中间网段和节点。
为满足TCP协议的这些特点,TCP协议做了以下规定:
①数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组;
②到达确认ACK:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认;
③超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有接收到对应的确认,重发分片;
④滑动窗口:TCP连接每一方的接收缓冲空间大小都固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出;
⑤失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对接收的数据进行重新排序,将接收到的数据以正确的顺序交给应用层;
⑥重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据;
⑦数据校验:TCP将保持它首部和数据的校验和,这是一个端到端的校验和,目的是检测数据在传输过程中的任何变化。如果收到分片的校验和由差错,TCP将丢弃这个分片,并确认接收到此报文段导致对端超时并重发。
当客户端向服务端发起连接时,会先发一包连接请求数据,过去询问一下,能否与你建立连接?这包数据称之为SYN包,如果对端同意连接,则回复一包SYN+ACK包,客户端收到之后,发送一包ACK包,连接建立,因为这个过程中互相发送了三包数据,所以称之为三次握手。
用 netstat 命令可以查看连接状态,用wiresharks 可以跟踪通讯状态。
一包数据可能会被拆成多包发送,如何处理丢包问题,这些数据包到达的先后顺序不同,如何处理乱序问题?
针对这些问题,tcp协议为每一个连接建立了发送缓冲区,从建立链接后的第一个字节的序列号为0,后面每个字节的序列号就会增加1,发送数据时,从数据缓冲区取一部分数据组成发送报文,在tcp协议头中会附带序列号和长度,接收端在收到数据后需要回复确认报文,确认报文中的ack等于接受序列号加长度,也就是下包数据发送的起始序列号,这样一问一答的发送方式,能够使发送端确认发送的数据已经被对方收到,发送端也可以发送一次的连续的多包数据,接受端只需要回复一次ack就可以了。如下图
处于连接状态的客户端和服务端,都可以发起关闭连接请求,此时需要四次挥手来进行连接关闭。假设客户端主动发起连接关闭请求,他给服务端发起一包FIN包,标识要关闭连接,自己进入终止等待1装填,服务端收到FIN包,发送一包ACK包,标识自己进入了关闭等待状态,客户端进入终止等待2状态,这是第二次挥手,服务端此时还可以发送未发送的数据,而客户端还可以接受数据,待服务端发送完数据之后,发送一包FIN包,最后进入确认状态,这是第3次挥手,客户端收到之后恢复ACK包,进入超时等待状态,经过超时时间后关闭连接,而服务端收到ACK包后,立即关闭连接,这是第四次挥手。
为什么客户端要等待超时时间?这是为了保证对方已经收到ACK包,因为假设客户端发送完最后一包ACK包后释放了连接,一旦ACK包在网络中丢失,服务端将一直停留在 最后确认状态,如果等待一段时间,这时服务端会因为没有收到ack包重发FIN包,客户端会响应 这个FIN包进行重发ack包,并刷新超时时间,这个机制跟第三次握手一样。也是为了保证在不可靠的网络链路中进行可靠的连接断开确认。
在发送方发送一个数据报文段后,会启动一个超时计时器。
超时计时器的作用是在一定时间内等待接收方对该数据报文段进行确认,如果超过了设定的时间还未收到确认,则认为该数据报文段丢失,并触发重传。
选择合适的超时时间是超时与重传策略的重要因素。超时时间应该既不太长导致不必要的等待,也不太短导致过早的重传。
TCP协议通常使用自适应算法来动态调整超时时间。例如,使用加权移动平均值(RTT加权平均值)来估计往返时间,并根据历史的RTT变化情况进行调整
如果超时计时器触发,即超过了设定的超时时间仍然没有收到确认,发送方会重新发送该数据报文段。
超时重传会继续进行,直到收到确认或达到最大重传次数(通常为限制的值)为止。
为了提高重传的效率,TCP协议引入了快速重传和冗余ACK的机制。
当接收方收到一个“冗余”的ACK(即确认号同一个序列号的报文段已经收到过多次),它会立即发送一个重复的ACK来通知发送方。
当发送方连续收到3个重复的ACK时,就会认为该数据报文段已经丢失,并立即进行重传,而不是等待超时计时器触发。
滑动窗口协议是用来改善吞吐量的一种技术,即容许发送方在接收任何应答之前传送附加的包。接收方告诉发送方在某一时刻能送多少包(称窗口尺寸)。 TCP中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为0时,发送方一般不能再发送数据包,但有两种情况除外,一种情况是可以发送紧急数据,例如,允许用户终止在远端机上的运行进程。另一种情况是发送方可以发送一个1字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口。
TCP是双工的协议,会话的双方都可以同时接收、发送数据。TCP会话的双方都各自维护一个“发送窗口”和一个“接收窗口”。其中各自的“接收窗口”大小取决于应用、系统、硬件的限制(TCP传输速率不能大于应用的数据处理速率)。各自的“发送窗口”则要求取决于对端通告的“接收窗口”,要求相同。
发送窗口只有收到对端对于本段发送窗口内字节的ACK确认,才会移动发送窗口的左边界
TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。 Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。 所谓“小段”,指的是小于MSS尺寸的数据块,所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。
Nagle算法的规则(可参考tcp_output.c文件里tcp_nagle_check函数注释):
(1)如果包长度达到MSS,则允许发送;
(2)如果该包含有FIN,则允许发送;
(3)设置了TCP_NODELAY选项,则允许发送;
(4)未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送;
(5)上述条件都未满足,但发生了超时(一般为200ms),则立即发送。
Nagle算法只允许一个未被ACK的包存在于网络,它并不管包的大小,因此它事实上就是一个扩展的停-等协议,只不过它是基于包停-等的,而不是基于字节停-等的。Nagle算法完全由TCP协议的ACK机制决定,这会带来一些问题,比如如果对端ACK回复很快的话,Nagle事实上不会拼接太多的数据包,虽然避免了网络拥塞,网络总体的利用率依然很低。
有时候为了实时性,在代码种会禁止使用Nagle算法。
版本:IP协议的版本,目前的IP协议版本号为4,下一代IP协议版本号为6。
首部长度:IP报头的长度。固定部分的长度(20字节)和可变部分的长度之和。共占4位。最大为1111,即10进制的15,代表IP报头的最大长度可以为15个32bits(4字节),也就是最长可为15*4=60字节,除去固定部分的长度20字节,可变部分的长度最大为40字节。
服务类型:Type Of Service。
总长度:IP报文的总长度。报头的长度和数据部分的长度之和。
标识:唯一的标识主机发送的每一分数据报。通常每发送一个报文,它的值加一。当IP报文长度超过传输网络的MTU(最大传输单元,一般是1500字节)时必须分片,这个标识字段的值被复制到所有数据分片的标识字段中,使得这些分片在达到最终目的地时可以依照标识字段的内容重新组成原先的数据。
标志:共3位。R、DF、MF三位。目前只有后两位有效,DF位:为1表示不分片,为0表示分片。MF:为1表示“更多的片”,为0表示这是最后一片。
片位移:本分片在原先数据报文中相对首位的偏移位。(需要再乘以8)
生存时间:IP报文所允许通过的路由器的最大数量。每经过一个路由器,TTL减1,当为0时,路由器将该数据报丢弃。TTL 字段是由发送端初始设置一个 8 bit字段.推荐的初始值由分配数字 RFC 指定,当前值为 64。发送 ICMP 回显应答时经常把 TTL 设为最大值 255。
协议:指出IP报文携带的数据使用的是那种协议,以便目的主机的IP层能知道要将数据报上交到哪个进程(不同的协议有专门不同的进程处理)。和端口号类似,此处采用协议号,TCP的协议号为6,UDP的协议号为17。ICMP的协议号为1,IGMP的协议号为2.
首部校验和:计算IP头部的校验和,检查IP报头的完整性。
源IP地址:标识IP数据报的源端设备。
目的IP地址:标识IP数据报的目的地址。
标志位:6bits,URG、ACK、PSH、RSH、SYN、FIN,具体含义如下:
URG:紧急指针位(URGent),当URG=1时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据)。
ACK:确认位(ACKnowledge),只有当ACK=1时确认号字段才有效。当ACK=0时,确认号无效。
PSH:推送位(PuSH),接收TCP收到推送位比特置1的报文段,就尽快地交付给接收应用进程,而不再等到整个缓存都填满了后再向上交付。
RST:重置位(ReSeT),当RST=1时,表明TCP连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。一般在三种情况下出现:拒绝连接请求、异常终止连接、终止空闲连接。
SYN:同步位(SYNchronous),同步比特SYN置为1,就表示这是一个连接请求或连接接报文。
FIN:终止位(FINal),用来释放一个连接。当FIN=1时,表明此报文段的发送端的数据已发送完毕,并要求释放运输连接。
TCP支持使用TCP头中的三个标记(flag)来支持ECN。第一个标记是随机和(Nonce Sum,简称NS),用于防止TCP发送者的数据包标记被意外或恶意改动。另两位用于回传拥塞指示(即指示发送者应减少信息发送量)和确认接收到了拥塞指示回应。这即是ECN-Echo(ECE)和Congestion Window Reduced(CWR)位。在TCP连接上使用ECN是可选的;当ECN被使用时,它必须在连接建立时通过SYN和SYN-ACK段中包含适当选项来协商。当在一个TCP连接上协商ECN后,发送方指示连接上的TCP段携带IP分组传输流量,将支持ECN的传输用ECT码点标记。这使支持ECN的中间路由器可以标记具有CE码点的IP分组而不是丢弃它们,以指示即将发生的阻塞。当接收到具*遇到阻塞码点时,TCP接收者使用TCP头中的ECE标记回传这个阻塞指示。当一个端点收到TCP带有ECE位的段时,它减少其拥塞窗口来代替丢包。然后,它设置段的CWR位来确认阻塞指示。节点保持传输设置有ECE位的TCP段,直到它接收到设置有CWR的段。
TCP 通讯业务流程图
双方创建socket(每个 socket 被创建后,都会分配两个缓冲区,即输入缓冲区和输出缓冲区),其中服务端的socket需要绑定一个端口,一个进程最多可以创建1024个socket绑定这同一个端口(linux系统默认限制一个用户进程最多只能同时维持1024个文件描述符)。
在这两个socket之间建立连接对,socket使用send函数和recv函数来传输数据(send函数先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器,recv函数则是TCP协议先接受数据到缓冲区,然后再从缓冲区读取数据)
/*
TCP server
echo_server.c
*/
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFSIZE 1024
#define PORT 2022
int main(){
int servsock, clntsock;
struct sockaddr_in laddr, raddr;
socklen_t raddr_len;
int str_len = 0;
char str[BUFSIZE];
servsock = socket(AF_INET, SOCK_STREAM, 0);
//第三个参数协议,调用者不想指定,可用0,所以经常看到大家这个参数设置为0。
// IPPROTO_IP
if(servsock == -1){
perror("socket()");
exit(1);
}
memset(&laddr, 0, sizeof(laddr));
laddr.sin_family = AF_INET;
laddr.sin_addr.s_addr = htonl(INADDR_ANY);
laddr.sin_port = htons(PORT);
if(bind(servsock, (struct sockaddr*)&laddr, sizeof (laddr)) == -1){
perror("bind()");
exit(1);
}
if(listen(servsock, 5) == -1){
perror("listen()");
exit(1);
}
while(1){
raddr_len = sizeof raddr;
if((clntsock = accept(servsock, (struct sockaddr*)&raddr, &raddr_len)) == -1){
perror("accept()");
exit(1);
}
if((str_len = read(clntsock, str, BUFSIZE)) == -1){ //recv
perror("read()");
exit(1);
}
printf("Receive from %s[%d] -> %s\n", inet_ntoa(raddr.sin_addr), ntohs(raddr.sin_port), str);
if(write(clntsock, str, BUFSIZE) == -1){
perror("write()");
exit(1);
}
close(clntsock);
}
close(servsock);
exit(0);
}
#####tcp 客户端 #### echo_client.cpp##########
/*
*tcp 客户端
*echo_client.cpp
echo 127.0.0.1 2023 hello
*/
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFSIZE 1024
int main(int argc, char** argv){
int clntsock;
struct sockaddr_in laddr;
char msg[BUFSIZE];
int str_len;
if(argc != 4){
fprintf(stderr, "Usage: echo
exit(1);
}
clntsock = socket(AF_INET, SOCK_STREAM, 0);
if(clntsock == -1){
perror("socket()");
exit(1);
}
memset(&laddr, 0, sizeof laddr);
laddr.sin_family = AF_INET;
laddr.sin_addr.s_addr = inet_addr(argv[1]);
laddr.sin_port = htons(atoi(argv[2]));
if(connect(clntsock, (struct sockaddr*)&laddr, sizeof laddr) == -1){
perror("connect()");
exit(1);
}
strcpy(msg, argv[3]);
if(write(clntsock, msg, BUFSIZE) == -1){ //send
perror("write");
exit(1);
}
if((str_len = read(clntsock, msg, BUFSIZE)) == -1){
perror("read()");
exit(1);
}
msg[BUFSIZE] = 0;
printf("Message: %s\n", msg);
close(clntsock);
exit(0);
}
右边是测试工具client运行界面
Wiresharks 过滤条件 ip.src == 192.168.0.107 && tcp.dstport == 2023
UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。UDP在IP报文的协议号是17。
UDP 包格式
UDP数据包在ip数据包中的封装
不可靠,无连接,面向数据报文的(字符流),传输成本低
分组首部开销小,TCP首部20字节,UDP首部8字节。
UDP无连接,时间上不存在建立连接需要的时延。空间上,TCP需要在端系统中维护连接状态,需要一定的开销。
UDP没有拥塞控制,应用层能够更好的控制要发送的数据和发送时间,网络中的拥塞控制也不会影响主机的发送速率。某些实时应用要求以稳定的速度发送,能容 忍一些数据的丢失,但是不能允许有较大的时延(比如实时视频,直播等)
UDP提供尽最大努力的交付,不保证可靠交付。所有维护传输可靠性的工作需要用户在应用层来完成。没有TCP的确认机制、重传机制。如果因为网络原因没有传送到对端,UDP也不会给应用层返回错误信息。
UDP是面向报文的,对应用层交下来的报文,添加首部后直接向下交付给IP层,既不合并,也不拆分,保留这些报文的边界。
UDP常用一次性传输比较少量数据的网络应用,如DNS,SNMP等,因为对于这些应用,若是采用TCP,为连接的创建,维护和拆除带来不小的开销。
UDP协议的应用场景:
UDP常用于多媒体应用(如IP电话,实时视频会议,流媒体等)数据的可靠传输对他们而言并不重要, 使用TCP的拥塞控制会使他们有较大的延迟,也是不可容忍的。
2、UDP 编程实例:
2.1、Server端
/*
Server端
socket-program/ 14.udp/ udpServer.c
运行 执行命令 ./udperServer
使用端口 8888
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//UDP server
#define IP "127.0.0.1"
#define SPORT 8888
#define SIZE 100
int socketInit(const char * ip, unsigned short port);
int main()
{
int socketID = 0;
ssize_t ret = 0;
char buffer[SIZE] = {0};
//int addrLength = 0;
socklen_t addrLength = sizeof(struct sockaddr_in);
struct sockaddr_in addr;
socketID = socketInit("127.0.0.1", 8888);
//等待数据(接收消息)
addrLength = sizeof(addr);
ret = recvfrom(socketID, buffer, SIZE - 1, 0, (struct sockaddr *)&addr, &addrLength);
if ( 0 > ret )
{
perror("recvfrom error");
}
else
{
//处理消息
printf("server get data:%s, from port:%d\r\n", buffer, ntohs(addr.sin_port));
}
//作出回应
fgets(buffer, SIZE - 1, stdin);
ret = sendto(socketID, buffer, strlen(buffer), 0, (struct sockaddr *)&addr, addrLength);
if ( 0 > ret )
{
perror("server send message error");
}
else
{
printf("服务器回复客户端:%s\r\n", buffer);
}
//关闭socket
close(socketID);
return 0;
}
int socketInit(const char * ip, unsigned short port)
{
int socketID = 0;
int on = 1;
//创建socket
socketID = socket( PF_INET, SOCK_DGRAM, 0 );
if ( 0 > socketID )
{
perror("socket error");
exit(0);
}
printf("socket ok\r\n");
if (0 > setsockopt(socketID, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
{
perror("set sock opt reUseAddr error");
}
//绑定自己的ip+port
struct sockaddr_in addr = {
.sin_family = PF_INET,
.sin_port = htons(port),
.sin_addr = {
.s_addr = INADDR_ANY,
},
};
if ( 0 > bind(socketID, (struct sockaddr *)&addr, sizeof(addr)))
{
perror("bind error");
exit(0);
}
printf("bind ok\r\n");
return socketID;
}
2.1 Client客户端
/*
Client客户端
socket-program\ 14.udp\ udpClient.c
运行 执行 /.udpClient
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//UDP client
#define IP "127.0.0.1"
#define SPORT 8888
#define SIZE 100
int main()
{
int socketID = 0;
char buf[SIZE] = {0};
//int addrLength = 0;
socklen_t addrLength = sizeof(struct sockaddr_in); //lgl
struct sockaddr_in addr;
ssize_t ret = 0;
//创建socket
socketID = socket( PF_INET, SOCK_DGRAM, 0 );
if ( 0 > socketID )
{
perror("socket error");
return -1;
}
printf("client socket ok\r\n");
//准备要发送的消息
fgets(buf, SIZE - 1, stdin);
//设置对方的地址
addrLength = sizeof(addr);
memset(&addr, 0, addrLength);
addr.sin_family = PF_INET;
addr.sin_port = htons(SPORT);
addr.sin_addr.s_addr = inet_addr(IP);
//发送消息
ret = sendto( socketID, buf, strlen(buf), 0, (struct sockaddr *)&addr, addrLength);
if ( 0 > ret )
{
perror("send to error");
}
memset(buf, SIZE, 0);
//等待对方的回应
ret = recvfrom(socketID, buf, SIZE - 1, 0, (struct sockaddr *)&addr, &addrLength);
if ( 0 > ret )
{
perror("recvfrom error");
}
else
{
//处理消息
printf("client get data:%s, from port:%d\r\n", buf, ntohs(addr.sin_port));
}
//关闭socket
close(socketID);
return 0;
}
接下来 ,我将讲一些socket的高级的编程,讲解中涉及的源码,可以到我提供的下载链接处下载。
在Linux 下面的 网络模型,
异步,非阻塞,多线程,多进程 , fork子进程,
异步多路复用,
基于事件/消息select ,
epoll 已经内置到linux内核的api
第三方 通讯库libevent
Boost 通讯库
目前 ,linux下面主流的c++ 通讯编程使用就是基于 epoll和libevent。