六.linux网络编程

1.网络基础
1.网络的历史和协议的分成

Internet -"冷战"的产物

1957年十月和十一月,前苏联先后欧两颗“Spuinik”卫星上天

1958年美国总统艾森豪威尔向美国国会提出建立DARPA,即国防部高级研究计划署,简称ARPA.

1968年6月DARPA提出“资源共享计算机网络”,目的在于让DARPA的所有电脑互连起来,这个网络就叫做

ARPAnet,即“阿帕网”,是Internet的最早雏形。

2.网络互联促成了TCP/IP协议的产生

早期的ARPAnet使用网络控制协议,不能互联不同类型的计算机和不同类型的操作系统,没有纠错功能。

1973年有Rober Kahn 和 Vinton Cerf 两人合作为ARPAnet开发了新的互联网协议

1974年12月两人正式发表第一份TCP协议详细说明,但此协议在有数据包丢失时不能有效的纠正。

TCP协议分成了两个不同的协议:

  • 用来检测网络传输中差错的传输控制协议TCP
  • 专门负责对不同网络进行互联的互联网协议IP
  • 从此TCP/IP协议诞生应用层

1983年ARPAnet上停止使用NCP,互联网上的主机全部使用TCP/IP协议,TCP/IP协议成为Internet中的“世界语”。

3.网络的体系结构
  • 网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起
  • 每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务,同时使用下层提供的服务。
  • 网络体系结构即指网络的层次结构和每层所使用的协议的集合。
  • 两类非常重要的体系结构:OSI和 TCP/IP。

在这里插入图片描述

  • OSI模型相关的协议已经很少使用,但模型本身非常通用
  • OSI模型是一个理想化的模型,尚未有完整的实现
  • OSI模型共有七层
  • 二层交换机,三层交换机
4.TCP/IP协议族体系

TCP/IP协议族体系是Internet事实上的工业标准。

一共有四层

应用层Relnet,FTP,HTTP,DNS,SMTP
传输层TCP和UDP
网络层IP、ICM和IGMP,端到端传输
网络接口和物理层以太网,令牌环网,FDDI。WiFi、gps/2G/3G/4G,驱动(屏蔽硬件差异)
5.网络各层的协议解释
  • 1.网络接口和物理层

    • mac地址:48位全球唯一,网络设备的身份标识
      • ARP:地址解析协议,IP地址找到mac地址
      • RARP:mac地址–找到IP地址
      • PPP协议:拨号协议(GPRS/3G/4G)
  • 2.网络层

    • IP地址
    • IP协议:Internet protocol(分为IPv4和IPv6)
    • ICMP:Internet控制管理协议,Ping 命令属于ICMP
    • IGMP:Internet分组管理协议,广播和组播
  • 3.传输层

    • TCP:(Transfer Control protocol,传输控制协议)提供面向连接的,一对一的可靠传输的协议

      • 数据无误,数据不丢失,数据无失序
      • 适用场景
        • 适合对传输质量要求较高,以及传输大量数据的通信
        • 在需要可靠数据传输的场合,通常适用TCP协议
        • MSN/QQ等即时通讯软件的用户登录账号管理相关的功能通常采用TCP协议
    • UDP:(user Datagram Protocol,用户数据报协议):提供不可靠,无连接的传输协议

      • 适用场景
        • 发送小尺寸数据(如对DNS服务器进行IP地址查询时)
        • 在接收数据,给出应答较困难的网络中适用UDP(如无线网络)
        • 适合于广播/组播式通信中
        • MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通常采用UDP协议
        • 流媒体,VOD,VoIP,IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输
    • SCTP:(Stream Control Transmission Protocol:流控制传输协议),TCP的增强版,他能实现多主机,多链路的通信。

  • 4.应用层

    • 网页访问协议:HTTP/HTTPS
    • 邮件发送接收协议:POP3(收)/SMTP(发),IMAP(可接受邮件的一部分)
    • FTP:文件传输协议
    • Telnet/ssh:远程登录
    • 嵌入式相关
      • NTP:网络时钟协议
      • SNMP:简单网络管理协议(实现对网络涉及集中式管理)
      • RTP/RSTP:用于传输音视频的协议(安防监控)
6.网络的封包和拆包

在这里插入图片描述

TCP/IP协议下的数据包

在这里插入图片描述

MTU:最大传输单元;

以太网:1500

7.网络预备知识

  • 1.socket

    • 是一个编程接口,是一个特殊的文件描述符(对他执行IO的操作函数,比如read,write,close等),并不仅限于TCP/IP协议,面向连接TCP,无连接UDP。

    • socket代表网络编程的一种资源

    • 分类

      • 1.流式套接字(SOCK_STREAM)。唯一对应TCP

        提供了一个面向连接,可靠的数据传输服务,数据无差错,无重复的发送顺序接收。内射击流量控制,避免数据流淹没慢的接收方。数据被看作式字节流,无长度限制。

      • 2.数据包套接字(SOCK_DGRAM)。唯一对应UDP

        提供无连接服务器,数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。

      • 3.原始套接字(SOCK_RAW)。对应多个协议,发送穿透了传输层

        可以对较低层次协议如IP、ICMP直接访问。

  • 2.IP地址

    • IP地址是Internet中主机的标识,Internet中的主机要与别的机器通信必须具有一个IP地址,IP地址为32为(Ipv4)或者128位(Ipv6),每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由
    • 表示的形式:常用点分形式,如202.38.64.10,最后都会转化成一个32位的无符号整数
    • mobileIPV6:local IP(本地注册的IP),roma IP(漫游IP)
    • 特殊IP地址
      • 局域网IP: 192.XXX.XXX.XXX 10.XXX.XXX.XXX
      • 广播IP:xxx.xxx.xxx.255 255.255.255.255(全网广播) 网络风暴
      • 组播IP:224.xxx.xxx.xxx ~239.xxx.xxx.xxx
  • 3.端口号

    • 16位数字,1-65535,
    • 为了区分一台主机接收到的数据包应该转交给哪个任务进程处理,使用端口号来区别
    • 预留端口,1-1023(FTP:24, SSH:22, HTTP: 80 ,HTTPS :469)
    • 保留端口:1024-5000(不建议使用)
    • 可以使用的端口: 5000~65535
    • TCP端口号于UDP端口号独立
    • 网络里的通信是由 IP地址+端口号 来决定的
  • 4.字节序

    • 字节序是指不同的CPU访问内存中的多字节数据时候,存在大小端的问题

      低对低:小端。低端内存存放着低端数据

      在这里插入图片描述

      低对高:大端。低端内存存放着高端数据

    • 如果CPU访问的是字符串,则不存在大小端问题

    • 一般来说X86/ARM : 小端模式

    • power/miop:arm作为路由时,大端模式

    • 网络传输的时候采用大端模式

    • 字节转换函数

      • 把给定系统所采用的字节序称为主机字节序,为了避免不同类别主机之间在数据交换时由于对于字节序的不同而导致的差错,引入了网络字节序。
      • 主机字节序到网络字节序
        • u_long htonl(u_long hostlong); //4个字节
        • u_short htons(u_short short); //2个字节
      • 网络字节序到主机字节序
        • u_long ntohl(u_long hostlong);
        • u_short ntohs(u_short short);
    • IP地址的转换

      • inet_aton()
        • 将strptr所指的字符串转换成32位的网络字节序二进制值
      • inet_addr()
        • 功能同上,返回转换后的地址
        • 仅适用于IPV4,出错时返回-1。
        • 局限性:不能用于255.255.255.255的转换
      • inet_ntoa()
        • 将32位网络字节序二进制地址转换成点分十进制的字符串
      • inet_pton()
        • int inet_pton(int af, const char* src ,void* dst)
        • 将IPV4/IPV6 的地址转换成binary格式 //把人类的 IP 字符串翻译成内核能懂的二进制
        • 使用于IPV4/IPV6
        • 能正确处理255.255.255.255的转换问题
        • 参数:
          • 1.地址协议族(AF_INET或AF_INET6)
          • 2.src:是一个指针(填写分点形式的IP地址(主要指IPV4))
          • dst:转换的结果给到dst
      • inet_ntop(int af, const void *src, char *dst, socklen_t size)
        • 把ipv4和ipv6的网络字节序变成本地的字符串形式的IP地址
        • 参数
          • 1.af:地址协议族(AF_INET或AF_INET6)
          • 2.src:是一个指针(32)
          • 3.dst:输出结果为32位点分形式的IP地址
          • 4.size:长度
2.TCP编程

1.TCP编程API

在这里插入图片描述

1.socket函数

int socket(int domain,int type,int protocol);

头文件:#include<sys/types.h>,include<sys/socket.h>

  • 参数

    • domain 域

      • AF_INET:IPV4 Internet protoco
      • AF_INET6:IPv6 Internet protoco
      • AF_UNIX,AF_LOCAL:local communication
        • AF_NETLINK:kernel user interface device //内核用户接口设备
        • AF_PACKET: low level packer interface //底层打包器接口
    • type

      • SOCK_STREAM: 流式套接字,唯一对应于TCP
      • SOCK_DGRAM:数据报套接字,唯一对应着UDP
      • SOCK_RAW:原始套接字,对应多个协议
    • protocol

      • 一般填0,原始套接字编程时需填充
  • 返回值

    • 成功返回文件描述符 //操作系统给这个新创建的网络连接(socket)分配的一个 “身份证号”。
    • 出错返回-1

如果是IPV6编程,要使用struct sockddr_in6结构体(man 7 IPV6),通常使用struct sockaddr_storage来编程。

2.bind绑定函数 //绑定IP和端口号

int bind(

​ int sockfd,

​ const struct sockaddr *addr,

​ socklen_t addrlen

​ );

  • 参数

    • sockfd:通过socket()函数拿到的fd

    • addr:采用struct sockaddr的结构体地址

      • struct sockaddr{ //通用结构体

        sa_family_t sa_family; //2个字

        char sa_data[4]; //14字节的协议地址

        };

      • struct sockaddr_in{ //基于Internel通信结构体

        sa_family_t sin_family; //两个字节 IPv4

        in_port_t sin_port; //两个字节 端口号

        struct in_addr sin_addr; //四个字节 IP地址

        sin_zero , //填充字节,使用之前需清零 bzero(&sin,sizeof(sin));

        };

      • struct in_addr{

        uint32_t s_addr; //32位网络字节序

        };

    • addrlen:地址长度

3.listen()函数 //(被动监听,等待客户及连接)

int listen(int sockfd,int backlog);

  • 参数:

    • sockfd: 通过socket()函数拿到的fd;
    • backLog:同时允许几路客户端和服务器进行正在连接的过程(正在三次握手),一般填5。
      • 内核中服务器的套接字fd会维护2个链表
        • 1.正在三次握手的客户端链表(数量=2*backlog+1)
        • 2.已经建立好连接的客户端链表(已经完成三次握手分配好了的newfd)
  • 返回值:

    • 成功返回0
    • 出错返回-1
  • listen(fd,5);//表示系统允许11(2*5+1)个客户端同时进行三次握手

4.accept()函数

阻塞等待客户端连接请求

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  • 参数
    • sockfd:经过前面socket()创建并通过bind(),listen()设置过的fd
    • addr:指向存放地址信息的结构体的首地址
      • 获取客户端IP地址和端口号
    • addrlen:存放地址信息的结构体的大小
  • 返回值
    • 成功,返回已经建立连接的新的newfd
    • 出错,返回-1

5.客户端连接函数connect()

int connect (int sockfd, struct sockaddr * serv_addr, int addrlen)

  • 参数:

    • sockfd:通过socket()函数拿到的fd
    • addr:struct sockaddr的结构体变量地址
    • addrlen:地址长度
  • 返回值:

    • 成功,返回0
    • 失败,返回-1
3并发服务器

1.TCP多线程服务器

在这里插入图片描述

2.TCP多进程并发服务器

父子进程:子进程进行读值,父进程一直接受客户机,并且使用信号处理来避免僵尸进程。

4.UDP编程

TCP的API补充:

1.send()函数 //相当于write函数

  • ssize_t send(int sockfd,

    const void *buf,

    size_t len,

    int flags

    );

    • 参数:
      • sockfd: socket函数返回的fd
      • buffer: 发送缓冲区首地址(写到哪里去)
      • length: 发送的字节
      • flags: 发送方式(通常为0),作用和write一样
        • MSG_DONTWAIT,非阻塞
        • MSG_OOB:用于TCP类型的带外数据(out of band)
  • 返回值:

    • 成功:实际发送的字节数
    • 失败:-1,并设置errno

网络中接收数据:

2.recv() /read()

  • int recv( socket s, char FAR *buf, int len, int flags);
    • flag:一般填0,和read作用一样
    • 特殊的标志:
      • MSG_DONTWAIT
      • MSG_OOB:读取带外数据
      • MSG_PEEK:流 (第二次读取的数据和第一次一样,不会被读走数值)

3.UDP编程

在这里插入图片描述

bind:绑定服务器:TCP地址和端口号

客户机不需要bind绑定ip和端口号,只需要输入服务器的ip和端口号,操作系统会自动给客户端分配临时端口和默认 IP

receivefrom():阻塞等待客户端数据

例子:
recvfrom(fd,buf,bufsize-1,0,(struct sockaddr*)&cin,&addrlen)

sendto():指定服务器的IP地址和端口号,要发送的数据

无连接尽力传输,UDP:是不可靠传输

应用:实时的音视频传输,DNS域名解析包

5.IO多路复用

1.IO模型

在unix/linu下主要有四种I/O模式:

  • 阻塞I/O: 最常用

    • 大部分程序使用的都是阻塞模式的I/O

    • 缺省情况下,套接字建立后所处于的模式就是阻塞I/O模式

    • 读操作:read,recv,recvfrom

    • 写操作:write,send

    • 其他操作:accept,connect

      • 以read函数为例

        • 进程调用read函数从套接字上读取数据,当套接字的接收缓冲区中还没有数据可读,函数read将发送阻塞
        • 它会一直阻塞下去,等待套接字的接收缓冲区中有数据可读
        • 经过一段时间后,缓冲区内接收到数据,于是内核便去唤醒该进程,通过read访问这些数据
        • 如果在进程阻塞过程中,对方发生故障,那这个进程将永远阻塞下去。
        • UDP不用等待,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,在UDP套接字上执行的写操作永远都不会阻塞。
      • 写阻塞

        • 在写操作时发送阻塞的情况要比读操作少。主要发生在要写入的缓冲区的大小小于要写入的数据量的情况下。
        • 这时,写操作不进行任何拷贝工作,将发送阻塞。
        • 一但发送缓冲区内有足够多的空间,内核将唤醒进程,将数据从用户缓冲区中拷贝到相应的发送数据缓冲区。
        • UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,在UDP套接字上执行的写操作永远都不会阻塞,sendto。
  • 非阻塞I/O:可防止进程阻塞在I/O操作上,需要轮询

    • 当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核"当我请求的I/O操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。"
    • 当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停的测试是否一个文件描述符有数据可读(polling)。
    • 应用程序不停的polling内核来检查是否I/O操作已经就绪,这将是一个极浪费CPU资源的操作。
    • 这种模式使用中不普遍。
      • 1.fcntl()函数
      • 当你一开始建立一个套接字描述符的时候,系统内核 将其设置为阻塞IO模式。可以使用函数fcntl()设置一个套接字的标志位为O_NOBLOCK来实现非阻塞。
int fcntl(int fd, int cmd, long arg)
int flag;
flag = fcntl(sockfd,F_GETFL,0)
flag |= O_NOBLOCK;
fcntl(sockfd,F_SETFL,flag);

2.ioctl()函数

int b_on = 1;

ioctl(sock_fd, FIONBIO,&b_on);
  • I/O多路复用:允许同时对多个I/O进行控制

    • 基本常识:linux中每个进程最多可以打开1024个文件,最多有1024个文件描述符,文件描述符的特点:

      1.非负整数

      2.从最小可用的数字来分配

      3.每个进程启动时默认打开0、1、2三个文件描述符

      多路复用针对不止套接字fd,也针对普通的文件描述符fd。

    • 应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;

    • 若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;

    • 若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;

    • 比较好的方法是使用IO多路复用,其基本思想是:

      • 先构造一张又关描述符的表,然后调用一个函数,当这些文件描述符中的一个或多个已准备好进行IO时函数才返回。
      • 函数返回时告诉进程那个描述符已就绪,可以进行IO操作

步骤:

1.fd_set

  • void FD_zero(fd_set* fdset) //对集合清零
  • void FD_set(int fd, fd_set * fdset) //把fd加入集合
  • void FD_CLR(int fd,fd_set *fdset) //从集合中清除fd
  • void FD_ISSET(int fd, fd_set *fdset) //判断fd是否在fd_set中

2.select()

  • int select(int maxfdp, //maxfd+1

    ​ fd_set *readfds, //读集合

    ​ fd_set *writefds, //写集合

    ​ fd_set *errorfds, //异常集合

    ​ struct timeval *timeout //超时

​ );

一般:填写集合,写集合填空,异常集合(带位数据),

超时:

struct timeval{

long tv_sec; //秒

long tv_usec; //微妙

};

select退出后:集合表示有数据的集合

if(FD_ISSET(fd,set))
{
	//1.如果监听套接字有数据,新的客户端进行连接,则accept
	//2.若建立连接的套接字有数据,则去读read
}
例子:多路复用
int main(void)
{
    struct timeval tout;
    fd_set rset;
    int maxfd = ‐1;
    fd = socket(...);     //创建socket
    bind(fd,...);           //绑定
    listen(fd,...);        //监听
    while(1)
	{
        maxfd = fd;
        FD_ZERO(&rset);      //对集合清零
        FD_SET(&rset);  	 //依次把已经建立好连接fd加入到集合中,记录最大的文件描述符
        
        tout.tv_sec = 5;
        tout.tv_user = 0;
       
        select(maxfd+1,&rset,NULL,NULL,&tout); //阻塞
        
        if(FD_ISSET(fd,&rset)) //有1个或者多个文件描述符有数据
        {
            newfd = accept(fd,...);
            //依次判断已建立连接的客户端是否有数据
            //xxx
        }
	}

}

select()里面的各个文件描述符fd_set集合的参数在select()前后发生了变化

  • 前:表示关心的文件描述符集合 (所有的)
  • 后:有数据的集合 (如不是在超时还回情况下)
    • kernel对集合进行了改变

多路复用的步骤:

  1. 列清单:把要监控的 “资源”(如多个套接字、文件)放进一个集合(比如用 fd_set)。
    (相当于写下所有要盯的快递柜编号)
  2. 等通知:调用多路复用函数(如 select),程序会暂停,等待集合中任一资源有 “动静”(比如有数据到来、可以写入)。
    (相当于站在中间等,哪个快递柜响了就有动作)
  3. 查是谁:当有动静时,检查集合中哪些资源触发了事件(用 FD_ISSET 等)。
    (看看是哪个编号的快递柜响了)
  4. 处理事:逐个处理有事件的资源(比如读数据、发数据)。
    (去对应的快递柜取 / 放东西)
  5. 重复等:处理完后,回到步骤 2 继续等待下一轮事件。
  • 信号驱动I/O:一种异步通信模型
    • SIGIO
6.TCPIP协议原理

1.三次握手、四次握手

  • 一定要标注客户端和服务器
  • 三次握手的连接必须是由客户端发起(四次握手客户端和服务器都可以发起)
  • SYN,ACK,FIN等标志符号应该写上。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐