C++后台开发知识总结(三)计算机网络

相关:
C++后台开发知识总结(一)C++基础
C++后台开发知识总结(二)数据库
C++后台开发知识总结(三)计算机网络
C++后台开发知识总结(四)操作系统/Linux 内核

OSI与TCP/IP各层的结构与功能,都有哪些协议

在这里插入图片描述
在这里插入图片描述
OSI七层模型及其包含的协议如下:
物理层:
在物理媒体上传输原始的数据比特流,传输单位为bit,主要包括的协议为:IEE802.3 CLOCK RJ45

数据链路层:
将数据分成一个个数据帧,以数据帧为单位传输。有应有答,遇错重发。传输单位为帧,主要包括的协议为MAC VLAN PPP
网络层:
负责数据包从源到宿的传递和网际互连,传输单位为包,主要包括的协议为IP ARP ICMP
传输层:
提供端到端的可靠报文传递和错误恢复,传输单位为报文,主要包括的协议为TCP UDP

会话层: 建立、管理和终止会话,传输单位为SPDU,主要包括的协议为RPC NFS
表示层: 对数据进行翻译、加密和压缩,传输单位为PPDU,主要包括的协议为JPEG ASII
应用层: 允许访问OSI环境的手段,传输单位为APDU,主要包括的协议为FTP HTTP DNS

TCP/IP 4层模型包括:
网络接口层:MAC VLAN
网络层:IP ARP ICMP
传输层:TCP UDP
应用层:HTTP DNS SMTP

TCP/IP/数据链路层的交互过程

网络层等到数据链层用mac地址作为通信目标,数据包到达网络等准备往数据链层发送的时候,首先会去自己的arp缓存表(存着ip-mac对应关系)去查找改目标ip的mac地址,如果查到了,就讲目标ip的mac地址封装到链路层数据包的包头。如果缓存中没有找到,会发起一个广播:who is ip XXX tell ip XXX,所有收到的广播的机器看这个ip是不是自己的,如果是自己的,则以单拨的形式将自己的mac地址回复给请求的机器

传递到IP层怎么知道报文该给哪个应用程序,它怎么区分UDP报文还是TCP报文

根据端口区分;
看ip头中的协议标识字段,17是udp,6是tcp

输入网址后发生了什么,用到哪些协议

在这里插入图片描述
http、dns、tcp、ip、arp地址解析协议、OSPF链路状态路由协议

1、首先,在浏览器地址栏中输入url
2、查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容。若没有,则跳到第三步操作。
3、在发送http请求前,需要域名解析(DNS解析)解析获取相应的IP地址。
4、浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手。
5、握手成功后,浏览器向服务器发送http请求,请求数据包。
6、服务器处理收到的请求,将数据返回至浏览器
7、浏览器收到HTTP响应
8、读取页面内容,浏览器渲染,解析html源码
9、生成Dom树、解析css样式、js交互
10、客户端和服务器交互
11、ajax查询

TCP三次握手和四次挥手

在这里插入图片描述
两个序号和三个标志位:
(1)序号:seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
(2)确认序号:ack序号,只有ACK标志位为1时,确认序号字段才有效,ack=seq+1
(3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:
ACK:确认序号有效; SYN:发起一个新连接; FIN:释放一个连接
在这里插入图片描述
客户端–发送带有SYN标志的数据包–一次握手–服务端
服务端–发送带有SYN/ACK标志的数据包–二次握手–客户端
客户端–发送带有带有ACK标志的数据包–三次握手–服务端
在这里插入图片描述
客户端-发送一个FIN,用来关闭客户端到服务器的数据传送
服务器-收到这个FIN,它发回一个ACK,确认序号为收到的序号加1 。
服务器-关闭与客户端的连接,发送一个FIN给客户端
客户端-发回ACK报文确认,并将确认序号设置为收到序号加1
为什么要四次挥手?
任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。

TIME_WAIT:作用以及如何避免

在这里插入图片描述
在这里插入图片描述
1:主机1发送FIN;此时,主机1进入FIN_WAIT_1状态;
2:主机2收到FIN,向主机1回一个ACK报文段;主机1进入FIN_WAIT_2状态;
3:主机2发送FIN,请求关闭连接,同时主机2进入LAST_ACK状态;
4:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。
MSL是指数据包在网络中的最大生存时间

为什么要有TIME_WAIT这个状态:
(1)TCP实现必须可靠地终止连接的两个方向(全双工关闭),主机1必须进入 TIME_WAIT 状态,因为主机1可能面临重发最终ACK的情形。假设最终的ACK丢失,主机2将重发FIN,主机1必须维护TCP状态信息以便可以重发最终的ACK,否则会发送RST,结果主机2认为发生错误。
(2)避免上一个连接延迟到达的数据包被下一个连接错误接收。如果在被推迟的报文未抵达前接收方断开了连接,随后又建立了一个与之前相同IP、Port的连接,而之前被推迟的报文在这时恰好到达,而此时此新连接非彼连接,从而会发生数据错乱。

出现太多TIME_WAIT可能导致的后果:
高并发短连接的TCP服务器上,当服务器处理完请求后立刻按照主动正常关闭连接。这个场景下,会出现大量socket处于TIMEWAIT状态。如果客户端的并发量持续很高,此时部分客户端就会显示连接不上。高并发可以让服务器在短时间范围内同时占用大量端口。短连接比如,取一个web页面,1秒钟的http短连接处理完业务,在关闭连接之后,这个业务用过的端口会停留在TIMEWAIT状态几分钟,而这几分钟,其他HTTP请求来临的时候是无法占用此端口的。服务器干正经事的时间和端口(资源)被挂着无法被使用的时间的比例是 1:几百,服务器资源严重浪费。

linux 大量的TIME_WAIT解决办法:
通过调整内核参数解决
vi /etc/sysctl.conf
编辑文件,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30

net.ipv4.tcp_syncookies = 1表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout修改系統默认的TIMEOUT时间

TCP、UDP协议的区别

在这里插入图片描述
UDP在传送数据之前不需要先建立连接,远地主机在收到UDP报文后,不需要给出任何确认。虽然UDP不提供可靠交付,但在某些情况下UDP确是一种最有效的工作方式(一般用于即时通信),比如: QQ语音 QQ视频 、直播等等
TCP提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。TCP不提供广播或多播服务。由于TCP要提供可靠的,面向连接的运输服务(TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP一般用于文件传输、发送和接收

TCP怎么保证可靠性

TCP保证可靠性:
(1)序列号、确认应答、超时重传
数据到达接收方,接收方需要发出一个确认应答,表示已经收到该数据段,并且确认序号会说明了它下一次需要接收的数据序列号。如果发送发迟迟未收到确认应答,那么可能是发送的数据丢失,也可能是确认应答丢失,这时发送方在等待一定时间后会进行重传。
(2)窗口控制与高速重发控制/快速重传(重复确认应答)
TCP会利用窗口控制来提高传输速度,意思是在一个窗口大小内,不用一定要等到应答才能发送下一段数据,窗口大小就是无需等待确认而可以继续发送数据的最大值。如果不使用窗口控制,每一个没收到确认应答的数据都要重发。
使用窗口控制,如果数据段1001-2000丢失,后面数据每次传输,确认应答都会不停地发送序号为1001的应答,表示我要接收1001开始的数据,发送端如果收到3次相同应答,就会立刻进行重发;但还有种情况有可能是数据都收到了,但是有的应答丢失了,这种情况不会进行重发,因为发送端知道,如果是数据段丢失,接收端不会放过它的,会疯狂向它提醒……
(3)拥塞控制
如果把窗口定的很大,发送端连续发送大量的数据,可能会造成网络的拥堵(大家都在用网,你在这狂发,吞吐量就那么大,当然会堵),甚至造成网络的瘫痪。所以TCP在为了防止这种情况而进行了拥塞控制。
慢启动:定义拥塞窗口,一开始将该窗口大小设为1,之后每次收到确认应答(经过一个rtt),将拥塞窗口大小2。
*
拥塞避免:设置慢启动阈值,一般开始都设为65536。拥塞避免是指当拥塞窗口大小达到这个阈值,拥塞窗口的值不再指数上升,而是加法增加(每次确认应答/每个rtt,拥塞窗口大小+1),以此来避免拥塞。
将报文段的超时重传看做拥塞,则一旦发生超时重传,我们需要先将阈值设为当前窗口大小的一半,并且将窗口大小设为初值1,然后重新进入慢启动过程。
**快速重传
:在遇到3次重复确认应答(高速重发控制)时,代表收到了3个报文段,但是这之前的1个段丢失了,便对它进行立即重传。
然后,先将阈值设为当前窗口大小的一半,然后将拥塞窗口大小设为慢启动阈值+3的大小。
这样可以达到:在TCP通信时,网络吞吐量呈现逐渐的上升,并且随着拥堵来降低吞吐量,再进入慢慢上升的过程,网络不会轻易的发生瘫痪。

GET 和 POST 的本质区别

GET POST
书签 可收藏为书签 不可收藏为书签
缓存 能被缓存 不能缓存
历史 参数保留在浏览器历史中 参数不会保存在浏览器历史中
对数据长度的限制 GET请求的参数是放在URL里的; URL 的最大长度是 2048 个字符 POST请求参数是放在请求body里的,无限制
对数据类型的限制 只允许 ASCII字符 没有限制。也允许二进制数据
安全性 GET的安全性较差,因为所发送的数据是URL的一部分 POST 比GET 更安全,因为参数不会被保存在浏览器历史或web服务器日志中
可见性 数据在URL中对所有人都是可见的 数据不会显示在 URL 中

本质区别: GET产生一个TCP数据包;POST产生两个TCP数据包。
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据); 而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

HTTP状态码

类别 原因短语
1XX Informational(信息性状态码) 接收的请求正在处理
2XX Success (成功状态码) 请求正常处理完毕
3XX Redirection (重定向状态码) 需要进行附加操作以完成请求
4XX Client Error (客户端错误状态码) 服务器无法处理请求
5XX Server Error (服务器错误状态码) 服务器处理请求出错
常见状态代码、状态描述的详细说明如下。
200 OK:客户端请求成功。
206 partial content服务器已经正确处理部分GET请求,实现断点续传或同时分片下载,该请求必须包含Range请求头来指示客户端期望得到的范围
300 multiple choices(可选重定向):被请求的资源有一系列可供选择的反馈信息,由浏览器/用户自行选择其中一个。
301 moved permanently(永久重定向):该资源已被永久移动到新位置,将来任何对该资源的访问都要使用本响应返回的若干个URI之一。
302 move temporarily(临时重定向):请求的资源现在临时从不同的URI中获得,
304 not modified :如果客户端发送一个待条件的GET请求并且该请求以经被允许,而文档内容未被改变,则返回304,该响应不包含包体(即可直接使用缓存)。
403 Forbidden:服务器收到请求,但是拒绝提供服务。
404 not Found:请求资源不存在,举个例子:输入了错误的URL。

HTTP长连接、短连接

在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。
而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:Connection:keep-alive
复制代码在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。

HTTP和HTTPS的区别

HTTPS即加密的HTTP,HTTPS并不是一个新协议,而是HTTP+SSL(TLS)。原本HTTP先和TCP(假定传输层是TCP协议)直接通信,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。
而加了SSL后,就变成HTTP先和SSL通信,再由SSL和TCP通信,相当于SSL被嵌在了HTTP和TCP之间,所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。此外客户端可以验证服务器端的身份,如果配置了客户端验证,服务器方也可以验证客户端的身份.

HTTPS协议需要服务端申请证书,浏览器端安装对应的根证书
HTTP协议端口是80,HTTPS协议端口是443

HTTPS优点:
HTTPS传输数据过程中使用密钥进行加密,所以安全性更高
HTTPS协议可以认证用户和服务器,确保数据发送到正确的用户和服务器
HTTPS缺点:
HTTPS握手阶段延时较高:由于在进行HTTP会话之前还需要进行SSL握手,因此HTTPS协议握手阶段延时增加
HTTPS部署成本高:一方面HTTPS协议需要使用证书来验证自身的安全性,所以需要购买CA证书;另一方面由于采用HTTPS协议需要进行加解密的计算,占用CPU资源较多,需要的服务器配置或数目高

数字证书是什么,里面都包含那些内容

数字证书是在一个身份和该身份的持有者所拥有的公/私钥对之间建立了一种联系,由认证中心(CA)或者认证中心的下级认证中心颁发的。
数字证书颁发过程如下:用户产生了自己的密钥对,并将公共密钥及部分个人身份信息传送给一家认证中心。认证中心在核实身份后,将执行一些必要的步骤,以确信请求确实由用户发送而来,然后,认证中心将发给用户一个数字证书,该证书内附了用户和他的密钥等信息,同时还附有对认证中心公共密钥加以确认的数字证书。当用户想证明其公开密钥的合法性时,就可以提供这一数字证书。

各种协议与HTTP协议之间的关系

在这里插入图片描述

IP地址作用,以及MAC地址作用

MAC地址是一个硬件地址,用来定义网络设备的位置,主要由数据链路层负责。而IP地址是IP协议提供的一种统一的地址格式,为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。

流量控制

流量控制:防止发送方发的太快,耗尽接收方的资源,从而使接收方来不及处理
依据:接收端缓冲区的大小
实现流量控制:
滑动窗口: 在确认应答策略中,对每一个发送的数据段,都要给一个ACK确认应答,收到ACK后再发送下一个数据段,这样做有一个比较大的缺点,就是性能比较差,尤其是数据往返的时间长的时候,使用滑动窗口,就可以一次发送多条数据,从而就提高了性能。
(1)接收端将自己可以接收的缓冲区大小放入TCP首部中的“窗口大小”字段,通过ACK来通知发送端
(2)窗口大小指的是不需要接收端的应答,可以一次连续的发送数据
(3)操作系统内核为了维护滑动窗口,需要开辟发送缓冲区,来记录当前还有那些数据没有应答,只有确认应答过的数据,才能从缓冲区删掉
(4)滑动窗口中的数据类型:发送了尚未确认的,没有发送,但是等待发送的。接收端窗口就是等待确认的数据序号组成。

拥塞控制

拥塞控制:防止发送方发的太快,使得网络来不及处理,从而导致网络拥塞
拥塞控制的表现:丢包、延时变长
拥塞控制是防止过多的数据注入网络,使得网络中的路由器或者链路过载。流量控制是点对点的通信量控制,而拥塞控制是全局的网络流量整体性的控制。发送双方都有一个拥塞窗口——cwnd。拥塞窗口的大小取决于网络的拥塞情况,并且是动态变化的,发送方一般让自己的发送窗口不大于拥塞窗口。
1、慢开始
TCP 连接开始,cwnd设置为1,字节未超过阈值ssthresh,接到确认呈指数型增长
2、拥塞避免
超过阈值变为线性增长。一旦发现网络拥塞,就把慢开始门限设为当前值的一半,并且重新设置cwnd为1,重新慢启动。
3、快重传
接收方每次收到一个失序的报文段后就立即发出重复确认,发送方只要连续收到三个重复确认就立即重传(尽早重传未被确认的报文段)。
4、快恢复
当发送方连续收到了三个重复确认(为什么是3个?因为1-2个重复ACK,很有可能是乱序,只有在3个及以上的时候才是有可能丢包了)时,把慢开始门限减半,由于发送方现在认为网络很可能没有发生拥塞,跳过指数增加,直接使拥塞窗口的线性增大。
在这里插入图片描述
采用快恢复算法时,慢开始只在建立连接和网络超时才使用。
采用慢开始和拥塞避免算法的时候
1.一旦cwnd>慢开始门限,就采用拥塞避免算法,减慢增长速度
2.一旦出现丢包的情况,就重新进行慢开始,减慢增长速度
采用快恢复和快重传算法的时候
1.一旦cwnd>慢开始门限,就采用拥塞避免算法,减慢增长速度
2.一旦发送方连续收到了三个重复确认,就采用拥塞避免算法,减慢增长速度
流量控制和拥塞控制的区别
相同点:
现象都是丢包;
实现机制都是让发送方发的慢一点,发的少一点
不同点:
(1)丢包位置:流量控制丢包位置是在接收端上、拥塞控制丢包位置是在路由器上
(2)作用的对象:流量控制的对象是接收方,怕发送方发的太快,使得接收方来不及处理
拥塞控制的对象是网络,怕发送发发的太快,造成网络拥塞,使得网络来不及处理

Ddos

Dos拒绝服务攻击攻击原理:DoS攻击就是利用合理的服务请求来占用过多的服务资源,从而使合法用户无法得到服务的响应。dos攻击与ddos攻击的区别就是,它是一对一的攻击,而ddos是分布式的攻击。
最常见的DoS攻击有对计算机网络的带宽攻击和连通性攻击。
带宽攻击: 指以极大的通信量冲击网络,使得所有可用网络资源都被消耗殆尽,最后导致合法的用户请求无法通过。
连通性攻击: 指用大量的连接请求冲击计算机,使得所有可用的操作系统资源都被消耗殆尽,最终计算机无法再处理合法用户的请求。
传统上,攻击者所面临的主要问题是网络带宽,由于较小的网络规模和较慢的网络速度的限制,攻击者无法发出过多的请求。大多数的DoS攻击还是需要相当大的带宽的,而以个人为单位的黑客们很难使用高带宽的资源。为了克服这个缺点,DoS攻击者开发了分布式的攻击。攻击者简单利用工具集合许多的网络带宽来同时对同一个目标发动大量的攻击请求,这就是DDoS攻击
SYN泛洪:
攻击主机C(地址伪装后为C)—–大量SYN包—->被攻击主机
C<——-SYN/ACK包—-被攻击主机
由于C地址不可达,被攻击主机等待超时。攻击主机通过发大量SYN包填满未连接队列,导致正常SYN包被拒绝服务。(防范:给每一个请求连接的IP地址分配一个Cookie)
常见的dos攻击:
死亡之ping:故意产生畸形的测试Ping包,声称自己的尺寸超过64KB上限,使未采取保护措施的网络系统出现内存分配错误,导致TCP/IP协议栈崩溃,最终接收方宕机。
泪滴:某些TCP/IP协议栈在收到含有重叠偏移的伪造分段时将崩溃。
UDP泛洪:伪造与某一主机的Chargen服务之间的一次的UDP连接,回复地址指向开着Echo服务的一台主机,通过将Chargen和Echo服务互指,来回传送毫无用处且占满带宽的垃圾数据
常见的Ddos攻击:
Smurf攻击:用一个伪造的源地址连续ping一个或多个计算机网络,这就导致所有计算机所响应的那个计算机并不是实际发送这个信息包的那个计算机。这个伪造的源地址,实际上就是攻击的目标,它将被极大数量的响应信息量所淹没。

XSS和 CSRF

XSS
跨站脚本攻击,这些可执行的脚本由攻击者提供,最终为用户浏览器加载,不同于大多数攻击,有存储型和反射型。
防御方式:编码,过滤,解码
CSRF
跨站请求伪造(攻击者盗用你的身份,以你的身份发送恶意请求),一次CSRF攻击的步骤:
登录受信任的网站A,并在本地生成cookie
在不登出A的情况下,访问危险网站B
防御的方法:总的思想在客户端页面增加伪随机值
Cookie Hashing 所有的表单都包含一个伪随机值
验证码
不同的表单包含一个不同的伪随机值

socket编程

什么是 socket
socket 的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。
UNIX/Linux 中的 socket
在 UNIX/Linux 系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。为了表示和区分已经打开的文件,UNIX/Linux 会给每个文件分配一个 ID,这个 ID 就是一个整数,被称为文件描述符(File Descriptor)。

通过 socket() 函数来创建一个网络连接,或者说打开一个网络文件,socket() 的返回值就是文件描述符。有了文件描述符,我们就可以使用普通的文件操作函数来传输数据了,例如:
用 read() 读取从远程计算机传来的数据;
用 write() 向远程计算机写入数据。

socket有哪些类型
流格式套接字(SOCK_STREAM)
SOCK_STREAM 是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。使用了 TCP 协议(The Transmission Control Protocol,传输控制协议)
应用场景:浏览器所使用的 http 协议就基于面向连接的套接字
数据报格式套接字(SOCK_DGRAM)
计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为数据报套接字所做的校验工作少,所以在传输效率方面比流格式套接字要高。使用 UDP 协议(User Datagram Protocol,用户数据报协议)。
应用场景:QQ 视频聊天和语音聊天

常用函数:

1
2
#include <sys/types.h>       
#include <sys/socket.h>

1、socket()描述字建立函数

1
2
//int socket(int domain, int type, int protocol);
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

domain 指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
AF_INET IPv4因特网域,AF_INET6 IPv6因特网域,AF_UNIX Unix域,AF_ROUTE 路由套接字,AF_KEY 密钥套接字,AF_UNSPEC 未指定
type 指定socket的类型
SOCK_STREAM,SOCK_DGRAM,SOCK_RAW protocol
protocol 通常赋值”0“。 0 选择type类型对应的默认协议,IPPROTO_TCP TCP传输协议,IPPROTO_UDP UDP传输协议,IPPROTO_SCTP SCTP传输协议,IPPROTO_TIPC TIPC传输协议

2、bind()函数:IP号端口号与相应描述字赋值函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//将套接字和IP、端口绑定
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
serv_addr.sin_port = htons(1234); //端口
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

//ipv4对应的是:
/*
struct sockaddr{
unisgned short as_family; // 协议族
char sa_data[14]; // IP+端口
};
*/
//同等替换:
/*
struct sockaddr_in {
sa_family_t sin_family; // 协议族
in_port_t sin_port; // 端口号
struct in_addr sin_addr; // IP地址结构体
unsigned char sin_zero[8]; // 填充
};
*/
//两个结构是等同的可以先互转换,第一个结构将地址和端口绑定了,第二个结构将两者分开表示*/
//IP地址结构如下:为32位字
/*
struct in_addr {
uint32_t s_addr;
};
*/

3、listen()函数:监听设置函数

1
2
//int listen(int sockfd, int backlog);
listen(serv_sock, 20); //请求队列中允许的最大请求数20

4、accept()函数:用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。

1
2
3
4
5
//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//接收客户端请求
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

5、connect()函数:客户机连接主机

1
2
//int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

基于TCP的socket:
服务器端程序:
1创建一个socket,用函数socket()
2绑定IP地址、端口等信息到socket上,用函数bind()
3设置允许的最大连接数,用函数listen()
4接收客户端上来的连接,用函数accept()
5收发数据,用函数send()和recv(),或者read()和write()
6关闭网络连接

客户端程序:
1创建一个socket,用函数socket()
2设置要连接的对方的IP地址和端口等属性
3连接服务器,用函数connect()
4收发数据,用函数send()和recv(),或read()和write()
5关闭网络连接

socket 编程1:读取一个字符串并打印出来。

服务器端代码 server.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(){
//创建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

//将套接字和IP、端口绑定
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
serv_addr.sin_port = htons(1234); //端口
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

//进入监听状态,等待用户发起请求
listen(serv_sock, 20); //请求队列中允许的最大请求数20

//接收客户端请求
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

//向客户端发送数据
char str[] = "http://c.biancheng.net/socket/";
write(clnt_sock, str, sizeof(str));

//关闭套接字
close(clnt_sock);
close(serv_sock);

return 0;
}

客户端代码 client.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main(){
//创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);

//向服务器(特定的IP和端口)发起请求
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
serv_addr.sin_port = htons(1234); //端口
connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

//读取服务器传回的数据
char buffer[40];
read(sock, buffer, sizeof(buffer)-1);

printf("Message form server: %s\n", buffer);

//关闭套接字
close(sock);

return 0;
}

启动一个终端(Shell),先编译 server.cpp 并运行:
[admin@localhost ~]$ g++ server.cpp -o server
[admin@localhost ~]$ ./server #等待请求的到来
正常情况下,程序运行到 accept() 函数就会被阻塞,等待客户端发起请求。
接下再启动一个终端,编译 client.cpp 并运行:
[admin@localhost ~]$ g++ client.cpp -o client
[admin@localhost ~]$ ./client
Message form server: http://c.biancheng.net/socket/
client 接收到从 server发送过来的字符串就运行结束了,同时,server 完成发送字符串的任务也运行结束了。

socket 编程2:文件传输

Server端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include<netinet/in.h>  // sockaddr_in
#include<sys/types.h> // socket
#include<sys/socket.h> // socket
#include<stdio.h> // printf
#include<stdlib.h> // exit
#include<string.h> // bzero

#define SERVER_PORT 8000
#define LENGTH_OF_LISTEN_QUEUE 20
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512

int main(void)
{
// 声明并初始化一个服务器端的socket地址结构
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htons(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);

// 创建socket,若成功,返回socket描述符
int server_socket_fd = socket(PF_INET, SOCK_STREAM, 0);
if(server_socket_fd < 0)
{
perror("Create Socket Failed:");
exit(1);
}
int opt = 1;
setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

// 绑定socket和socket地址结构
if(-1 == (bind(server_socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))))
{
perror("Server Bind Failed:");
exit(1);
}

// socket监听
if(-1 == (listen(server_socket_fd, LENGTH_OF_LISTEN_QUEUE)))
{
perror("Server Listen Failed:");
exit(1);
}

while(1)
{
// 定义客户端的socket地址结构
struct sockaddr_in client_addr;
socklen_t client_addr_length = sizeof(client_addr);

// 接受连接请求,返回一个新的socket(描述符),这个新socket用于同连接的客户端通信
// accept函数会把连接到的客户端信息写到client_addr中
int new_server_socket_fd = accept(server_socket_fd, (struct sockaddr*)&client_addr, &client_addr_length);
if(new_server_socket_fd < 0)
{
perror("Server Accept Failed:");
break;
}

// recv函数接收数据到缓冲区buffer中
char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);
if(recv(new_server_socket_fd, buffer, BUFFER_SIZE, 0) < 0)
{
perror("Server Recieve Data Failed:");
break;
}

// 然后从buffer(缓冲区)拷贝到file_name中
char file_name[FILE_NAME_MAX_SIZE+1];
bzero(file_name, FILE_NAME_MAX_SIZE+1);
strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer));
printf("%s\n", file_name);

// 打开文件并读取文件数据
FILE *fp = fopen(file_name, "r");
if(NULL == fp)
{
printf("File:%s Not Found\n", file_name);
}
else
{
bzero(buffer, BUFFER_SIZE);
int length = 0;
// 每读取一段数据,便将其发送给客户端,循环直到文件读完为止
while((length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0)
{
if(send(new_server_socket_fd, buffer, length, 0) < 0)
{
printf("Send File:%s Failed./n", file_name);
break;
}
bzero(buffer, BUFFER_SIZE);
}

// 关闭文件
fclose(fp);
printf("File:%s Transfer Successful!\n", file_name);
}
// 关闭与客户端的连接
close(new_server_socket_fd);
}
// 关闭监听用的socket
close(server_socket_fd);
return 0;
}

客户端代码 client.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include<netinet/in.h>   // sockaddr_in
#include<sys/types.h> // socket
#include<sys/socket.h> // socket
#include<stdio.h> // printf
#include<stdlib.h> // exit
#include<string.h> // bzero

#define SERVER_PORT 8000
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512

int main()
{
// 声明并初始化一个客户端的socket地址结构
struct sockaddr_in client_addr;
bzero(&client_addr, sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = htons(INADDR_ANY);
client_addr.sin_port = htons(0);

// 创建socket,若成功,返回socket描述符
int client_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(client_socket_fd < 0)
{
perror("Create Socket Failed:");
exit(1);
}

// 绑定客户端的socket和客户端的socket地址结构 非必需
if(-1 == (bind(client_socket_fd, (struct sockaddr*)&client_addr, sizeof(client_addr))))
{
perror("Client Bind Failed:");
exit(1);
}

// 声明一个服务器端的socket地址结构,并用服务器那边的IP地址及端口对其进行初始化,用于后面的连接
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
if(inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) == 0)
{
perror("Server IP Address Error:");
exit(1);
}
server_addr.sin_port = htons(SERVER_PORT);
socklen_t server_addr_length = sizeof(server_addr);

// 向服务器发起连接,连接成功后client_socket_fd代表了客户端和服务器的一个socket连接
if(connect(client_socket_fd, (struct sockaddr*)&server_addr, server_addr_length) < 0)
{
perror("Can Not Connect To Server IP:");
exit(0);
}

// 输入文件名 并放到缓冲区buffer中等待发送
char file_name[FILE_NAME_MAX_SIZE+1];
bzero(file_name, FILE_NAME_MAX_SIZE+1);
printf("Please Input File Name On Server:\t");
scanf("%s", file_name);

char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);
strncpy(buffer, file_name, strlen(file_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_name));

// 向服务器发送buffer中的数据
if(send(client_socket_fd, buffer, BUFFER_SIZE, 0) < 0)
{
perror("Send File Name Failed:");
exit(1);
}

// 打开文件,准备写入
FILE *fp = fopen(file_name, "w");
if(NULL == fp)
{
printf("File:\t%s Can Not Open To Write\n", file_name);
exit(1);
}

// 从服务器接收数据到buffer中
// 每接收一段数据,便将其写入文件中,循环直到文件接收完并写完为止
bzero(buffer, BUFFER_SIZE);
int length = 0;
while((length = recv(client_socket_fd, buffer, BUFFER_SIZE, 0)) > 0)
{
if(fwrite(buffer, sizeof(char), length, fp) < length)
{
printf("File:\t%s Write Failed\n", file_name);
break;
}
bzero(buffer, BUFFER_SIZE);
}

// 接收成功后,关闭文件,关闭socket
printf("Receive File:\t%s From Server IP Successful!\n", file_name);
close(fp);
close(client_socket_fd);
return 0;
}
0%