linux:网络编程 《网络基础1》《协议分层》《UDP通信》

1.网络基础:
网络的发展以及一些网络编程的知识点:
局域网:网络覆盖范围1000米以内的网络
城域网:网络覆盖一个城市的范围
广域网:网络覆盖范围通常在20千米以上
互联网,公网,因特网----国际化的超大型广域网
组网方式:以太网

在网络中如何识别一个主机
在网络中两台主机可以定向通信,那么在网络中每一台主机就必须具备唯一的标识符— IP地址

IP地址:是一个无符号4个字节的整数,作用是在网络中唯一标识一台主机。
因为数字没有规律难以记忆,因此才会将每个字节分开来表示:
分十进制的表示方式: 192.16.1.1
每一条网络中的数据都必须具备: 源端的IP地址和对端的IP地址网络中的转发设备必须明确这条数据从哪来,到哪去

ip地址是无符号四个字节的整数,那么IP地址总共也就43亿个。全世界够分吗?
IP地址版本的区分:
ipv4:无符号四个字节的整数,不够用,解决方案:DHCP技术—动态地址分配技术;NAT技术—网络地址转换技术
ipv6:无符号六个字节的数据,但是目前ipv6,推广难度高,到目前没有大规模普及—不向前兼容ipv4,改变成本高。

IP地址描述了哪两个主机之间在进行通信,但是问题是主机怎么知道那个数据应该是哪个进程处理 因此在一个主机上必须要有一个信息能够标识进程

在网络中的每亿条数据中,不但要有IP地址还要有一个能够标识这个数据是发送给那个主机上的哪个进程的

端口:无符号2个字节的整数,用于在一台主机上标识一个进程, 每一条数据都应该有端口信息, 并且每个进程都应该告诉操作系统接收到的发往哪个端口的数据应该交给自己处理。

一条网络中的数据有 了IP和端口PORT ,就描述符这条数据是从哪个主机的哪个进程发出,要到哪个主机的哪个进程上

端口的特性: 一个进程可以使用多个端口。但是一个端口只能被一个进程占用

协议:约定
网络通信协议:网络通信中数据格式的约定
网络互连的前提标准- – 订立网络通信协议标准,所有的设备采用统一网络通信协议,进行通信
网络通信中每条数据中都会包含五个要素:源端IP, 源端端口,对端IP ,对端端口,协议— 五元组

协议分层:在网络通信环境中,按照提供的服务,使用的接口,使用的协议,将网络通信环境进行层次划分,便于网络互联的实现
在这里插入图片描述
在网络通信环境中,协议分层比较典型的有两个:OSI七层模型,TCP/IP五层模型

协议分层:
OSI七层模型: 应用层 ,表示层, 会话层 ,传输层 , 网络层, 链路层, 物理层
TCP/IP(四层)五层模型:应用层, 传输层, 网络层, 链路层, (物理层)
应用层:负责应用程序之间的数据沟通(两个进程之间数据格式);HTTP/ FTP
传输层: 负责应用程序之间的数据传输(传输两端的描述—端口描述);TCP/ UDP
网络层:负责地址管理与路由选择(通过IP地址描述这条数据的两端主机);IP;路由器
链路层:负责相邻设备之间的数据传输(通过MAC地址描述两个相邻设备);ETH; 交换机
物理层:负责物理光电信号的传输:以太网协议; 集线器

MAC地址:网卡设备的物理硬件地址,每一个网卡出厂时设定的地址

聊QQ举例: 从键盘上获取原始数据,QQ这时候需要对数据进行处理,以及描述通信两端的QQ号—这都属于应用层的范畴,接下来QQ会把自己描述完毕的数据交给操作系统,操作系统首先描述,通过端口描述这个数据是哪两个进程之间的通信,保证QQ的数据肯定是QQ进行处理,接下来操作系统还要通知IP地址描述这是哪两个主机之间的通信(IP地址描述的是一条数据的起点和终点),接下来还要分多个阶段才能完成整个过程,先将数据从你的主机发送到你家的路由器,然后路由器再将数据发送给下一个路由器,通过MAC地址确定这个数据首先要发送给哪个与自己相连的设备,最后所有描述完毕后,最后通过物理介质将物理信号发送出去。

数据在应用层叫数据包,传输层叫数据段, 网络层叫数据报,链路层叫数据帧

网络字节序:网络通信中数据字节序的标准约定
字节序:CPU对数据在内存中以字节为单位的存取顺序
主机字节序:一台主机的字节序
大端字节序:低地址高位(位指的是二进制比特位)
b[0]=01 b[1]=02 b[2]=03 b[3]=04
小端字节序:低地址存低位
b[0]=04 b[1]=03 b[2]=02 b[3]=01

int a=0x01020304—这个数占据四个字节的内存空间,以字节为单位的顺序
uchar *b=&a; b[0]就是低地址,b[3]就是高地址;内存地址会随着下标而增加
0x01020304 00000001 00000010 00000011 00000100左边是高位,右边是低位
一个主机的主机字节序到底是大端还是小端取决于什么?-------CPU架构(x86小端/ MIPS架构–大端)
在网络通信中,若是两端主机的字节序不同,则会造成数据二义。
int a=0x00000001 1
小端存储:—低字节开始:0x01000000------------------>0x01000000
但是数据在发送的时候总是从低地址开始发送-------------->接收的时候,只管接受的数据就是低地址数据
解决方案:在网络通信中统一使用大端字节序作为网络通信的网络字节序标准
也就是说在网络通信中若一个数据存取单元大于一个字节,则需要将其转换为网络字节序进行传输
存取单元大于一个字节的数据类型:都是数字类型—short int long float double

套接字socket编程:网络通信程序的编写
网络通信程序的编写使用的都是套接字接口;
网络通信程序的编写:TCP/IP五层中应用层是面向程序的一层,应用层的协议都是程序员自己订立, 应用层处理完毕后都会讲数据交给操作系统 , 进入传输层开始往下封装

然而传输层提供了两个典型协议: UDP/TCP
UDP协议:用户数据协议----无连接的,不可靠的,面向数据报的一种传输方式(无法提供可靠传输,但是传输速度快)—视频传输
TCP协议:传输控制协议----基于连接的,可靠的,面向字节流的一种传输方式(提供可靠传输,但是传输速度慢)—普通文件传输

UDP通信程序的编写:
在网络通信中:都是端与端之间的通信,两台主机上的两个进程间的通信,并且通信两端有一种叫法:
客户端:通信两端中,首先主动发起请求的一端
服务端:通信两端中,首先被动接受请求的一端

通信流程:
客户端:
1.创建套接字,在内核中创建socket结构体
**2.为套接字绑定地址信息(不推荐)**描述远端地址信息
客户端不推荐程序员主动定绑定地址,因为一个端口只能被一个进程占用,一旦要绑定的端口已经被使用,就会绑定失败。
如果我们不绑定,等到发送数据的时候操作系统发现没有绑定地址,则会自动选择一个合适的地址进行绑定
尽可能避免地址冲突的概率
3.发送数据
4.接收数据
5.关闭套接字释放资源

服务端:
1.在程序中创建套接字
2.为套接字绑定地址信息
地址信息:IP地址和端口
每一条网络中的数据都要包含:源端IP,port;对端IP,port
接收端:告诉操作系统发往这个主机的那个端口的数据应该交给我 处理
发送端:告诉操作系统,发送的这条数据的源端地址是什么
就是在创建的套接字创建socket结构体中描述地址信息
3.服务端首先接收数据
创建套接字,实际上在内核中创建了一个结构体-socket,socket中会有两个缓冲区(发送/接收)
一旦网卡接收到数据,这时候操作系统会根据数据中的目的地址信息,去socket列表中进行匹配,这个数据应该放到谁的接收缓冲区中
接收数据只是通过接收接口直接从指定的socket接收缓冲区中取出数据
4.发送数据
将数据放到指定的socket的发送缓冲区中,操作系统会在合适的时候,从缓冲区中取出数据进行封装,最终传输出去
5.关闭套接字,释放资源
在这里插入图片描述
在这里插入图片描述
接口介绍:
1,创建套接字:在内核上创建socket结构体
int socket (int domain,int type, int protocol);
domain:地址域类型(地址有各种结构的—IPv4,ipv6, local)—表示这是一个什么样通信的套接字 AF_INET----ipv4协议版本的地址域
type:套接字类型(SOCK_STREAM—流式套接字—默认协议是tcp, SOCK_DGRAM----数据报套接字----默认协议是udp)
protocol: 协议类型–0表示套接字类型的默认协议:IPPROTO_TCP-6/
IPPROTO_UDP-17
返回值:返回套接字操作句柄—文件描述符;失败返回-1;
2.为套接字绑定地址信息:在创建socket结构体中描述源端地址信息,告诉操作系统那些数据放到自己的接收缓冲区
int bind(int sockfd, struct sockaddr *addr, socklen_t len)
sockfd: 创建套接字返回的操作句柄
len:地址信息长度
addr: 要绑定的地址信息-----struct sockaddr 通用地址结构–实际使用的时候不用这个
IPV4使用的是struct sockaddr_in(sin_family—地址域类型;sin_port—网络字节序端口;sin_addr.s_addr—网络字节序IP地址)
返回值:成功返回0;失败返回-1;
在这里插入图片描述
服务端: 1. 创建套接字-》2. 绑定地址信息-》3. 接收数据-》4. 发送数据-》5.关闭套接字
客户端:1.创建套接字-》 2.为套接字绑定地址信息(不推荐)-》3.发送数据-》4.接收数据-》5.关闭套接字
客户端没必要必须使用某个地址,因为用任意地址只要能够发送出去就可以—因此没必要必须绑定地址,而是可以让系统自动选择合适地址绑定

  1. int socket(地址域套接字类型,协议类型)-- 返回值-套接字描述符作为操作句柄找到内核的socket结构体
  2. int bind(套接字描述符,本地地址信息, 地址信息长度) —返回0
  3. size. t recrfrom(套接字描述符,数据缓冲区, 想要获取的数据长度, 标志位-0, 对端地址信息存放缓冲区, 地址信息长度缓冲区)
  4. sze. t sendto(套接字描述符,数据缓冲区, 想要发送数据长度, 标志位-0,对端的地址信息, 地址信息长度)
  5. int close(套接字描述符) — 关闭套接字

#include <arpa/neth>
uint32_ t hton(uint32_ t hostlong); 32位数据的主机字节序到网络字节序的转换
uint16. t htons(uint1t6 t hosthort); 16位数据的主机字节序到网络字节序的转换
uint32_ t ntohl(uint32_ t netlong); 网络字节序到主机字节序的转换接口
uint16 _t ntoh(uint16 t netshort);

in. addr t inet addrconst char *cp);将点分十进制的字符串ip地址转换为网络字节序数字地址

tcp通信协议的编写:面向连接,可靠传输,面向字节流
流程:
客户端:
1.创建套接字
2.为套接字绑定地址信息(不推荐)
3.向服务端发起连接请求
4.接收数据或者发送请求
连接建立成功之后,tcp套接字中既有源端,也有对端地址信息,因此通信的时候也不需要指定对端地址
5.关闭套接字

服务端:
1.创建套接字—内核中创建socket结构体
2.为套接字绑定地址信息
3.开始监听
将套接字状态置为监听状态,表示如果有客户端连接请求过来了,则去处理这个连接请求
并且,若有连接请求过来,处理过程,是为客户端连接请求创建一个新的套接字,用于与这个客户端进行通信
4.获取新建连接的套接字(因为新建的套接字在内核中,所以要先获取到新建连接套接字的描述符之后,才能通过这个描述符与指定客户端通信)
5.接收数据或者发送数据
tcp新建的套接字中既有源端地址,也有对端地址,因此收发数据都可以,并且不需要指定对端的地址信息
收发数据使用的是新建的套接字(原始创建的监听套接字只用于获取新连接)
6.关闭套接字

接口介绍:
1.int socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)
2.int bind(int sockfd, struct sockaddr *addr,socklen_t len);

3.int listen(int cockfd, int backlog)
sockfd: 套接字描述符,表示内核中哪个soctet开始监听;
backlog: 最大并发连接数—同一时间服务端能够接受多少个客户端的连接请求 backlog决定的是同一时间的连接数,而无法决定服务端最多接受到多少数据
在这里插入图片描述
恶意主机不断伪装IP向服务端发送syn请求----若服务端向每个syn创建socket,则有可能资源耗尽服务端崩溃
解决方案:就是同一时间。限制能处理的客户端连接请求数

4.int connect(int sockfd, struct sockaddr* addr,socklen_t len);
addr: 服务端地址信息—向服务端发送连接请求数
返回值: 返回0表示连接请求建立成功,返回-1表示连接请求失败
5. int accept(int sockfd,struct, struct sockaddr* addr,socklen_t *len);
获取新连接:sockfd—:获取那个监听套接字新建的连接
addr:新建连接—对应一个客户端的通信,客户端的地址信息
len:输入输出型参数,指定要获取的地址信息长度,以及返回的实际地址信息长度
返回值:返回的是新获取的连接的描述符用于与客户端进行通信,失败返回-1;
6.ssize_t recv(int sockfd, char buf,int len ,int flag);//服务端通过新建连接的描述符接收数据—不需要指定对端地址
7.ssize_t send(int sockfd, char
data, int len ,int flag);//发送数据

8.int close(int fd);

封装- TcpSocke类,实现面向对象编程,每一个实例化的对象都可以通过成员函教实现一个(p客户端或者服务端程子

class TcpSocket{
pivate:
int_sOcktdi;
public:
TcpSocket();
bool Socket();
bool Bind(const std::string &ip,uint16_t port);
bool Listen(int backlog=MAX_LISTEN);
bool Connect(const std:string &ip, uint16 t port);
bool Accept(TcpSocket sock, std::stringip= NUL uint16 *port=NLL);
bool Recvlstd:tring *buf;
bool Send(std:string *data);
bool Close();
};

在当前的tcp服务中,一个客户端只能与服务端通信一—为什么?
1.在第一个客户端到来之前,服务端获取连接,接收数据发送数据后,循环回去重新获取新连接,但是没有新连接就会阻塞,就算第一个连接继续发送数据也无法接受

2.有两个连接先后到来,若第一个连接被服务端获取,但是第一个连接一直不发送数据,则服务端就会阻塞在rev上,就算第二个连接建立成功发送了数据,我们也无法获取
在这里插入图片描述
tcp服务端程序流程阻塞的本质:
因为程序的流程是固定的,在固定情况下,我们没办法预测监听套接字有没有新连接,通信套接字有没有数据,若在没有新连接或者新数据的情况下,accept以及recv都有可能导致程序阻塞

当前这个服务端只有一个执行流, 但是它要完成两个有可能阻塞的功能:通过监听套接字获取新连接,以及通过新建套接字与客户端通信

若使用多执行流让每个执行流只负责一个功能就算一 个执行流阻塞也不会影响其他
一个执行流专门负责通过监听套接字获取新连接。
一旦获取到新连接,则创建一 个新的执行流,专门与一个客户端进行通信
这种情况下,就算没有新连接, 也不会影响其他执行流与客户端进行通信;依旧就算一个客户端不发送数据也只会阻塞一 个通信执行流, 而我们依然可以在其他的执行流中获取新连接,以及与其他的客户端进行通信。

多执行流实现:多进程、多线程
多进程注意事项:
父子进程数据独有,父进程最好将新建的套接字关闭调,不会对子进程造成影响,但是若不关闭,则会占用父进程资源
父进程采用信号回调函数中进行进程等待,当子进程退出的时候再去处理僵尸进程,避免僵尸进程,并且避免父进程阻塞

多线程注意事项:
因为线程之间数据共享,因此千万主线程不能关闭套接字,因为一旦关闭,则普通线程也会无法访问

在编程网络通信程序的时候,端口不要随意使用—1024以前的端口都不要使用–因为这些端口在系统中默认是给某些特殊服务使用的,比如HTTP服务器-80, 远程登录ssh服务-22, ,如果非要使用,则需要使用root权限运行程序才可以使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值