您的位置:首页 > 理论基础 > 计算机网络

alin的学习之路(Linux网络编程:一)(网络模型、帧格式、socket套接字、服务器端实现)

2020-07-29 19:26 369 查看

alin的学习之路(Linux网络编程:一)(网络模型、帧格式、socket套接字、服务器端实现)

1. 协议

协议是一组规则,规定了如何发送数据。通信的双发都需要遵守该规则

2. 网络分层结构模型

1. OSI七层模型

物:物理层

数:数据链路层

网:网络层

传:传输层

会:会话层

表:表示曾

应:应用层

2. TCP/IP模型

链:数据链路层(网络接口层):以太网帧协议,ARP协议

网:网络层:IP协议,ICMP协议,IGMP协议

传:传输层:TCP协议,UDP协议

应:应用层:HTTP,ftp,nfs,telnet,ssh

3. 网络通信过程

数据在没有被封装之前,是不能传输到网络上去进行通信的。

数据产生后,经由数据链路层、网络层、传输层、应用层,四层每一层都会对数据进行不同的封装,封装后才能传送到网络中进行通信。

到达目的端时,经由应用层、传输层、网络层、数据链路层,四层每一层都会解封装,从而最终得到通信数据。

4. 网络协议格式

1. 以太网帧格式

  • 以太网规定,数据包必须从一块网卡,传送到另一块网卡。

  • 目的地址、源地址 —— mac地址(ifconfig命令可查看)。网卡编号。全球唯一。

  • ARP协议:
    根据 IP地址, 获取 mac 地址。

    ARP 包括请求和应答,请求携带发送端的 mac 地址,通过应答的信息来获取目的端的 mac 地址

2. IP段格式

  • 版本:IPv4、IPv6
  • TTL: time to live:防止路由包拥塞,表示最大路由跳数 用来设置数据包,在路由节点中的跳转上限。每经过一个路由节点,该值 -1。
  • 减为0的路由节点, 有义务将该数据包丢弃。 防止拥塞网络。
  • 源IP地址:32位 —— 4字节。 192.168.1.4 —— 点分十进制 IP地址。本质 是一个 string。 为人类看。
  • 目的IP地址:32位 —— 4字节
  • 3. UDP协议格式

    • 源端口号:16位。2^16 — 取值范围:0 ~ 65535
    • 目的端口号:16位。2^16 — 取值范围:0 ~ 65535
    • IP地址:在 网络环境中, 唯一标识一台主机。
    • port:在 网络主机上,唯一标识一个进程。
    • IP+port :在 网络环境中, 唯一标识一个 进程。 —— socket 套接字。

    4. TCP协议格式

    • 源端口号:16位。2^16 — 取值范围:0 ~ 65535
    • 目的端口号:16位。2^16 — 取值范围:0 ~ 65535
    • 32位序号。
    • 32位确认序号。
    • 6个标志位。
    • 16位窗口大小。---- 取值范围:0 ~ 65535

    TCP 优先建立好连接,后续消息全部在该路径上发送,UDP则是每次传输的路由路径不同

    5. 网络应用程序设计模式

    C/S 模式(client/server):

    • 优点:提前缓存数据,提高通信效率。协议选择灵活。开发调试较方便。
    • 缺点:对用户安全性构成威胁。开发工作量大。不能跨平台。
    • 应用场景:对数据安全、稳定要求较高场合。需要提前缓冲数据。

    B/S 模式(browser/server):

    • 优点:不需要额外向用户主机安装应用。开发工作量相对小。跨平台使用。
    • 缺点:不能缓存大量数据。协议选择不灵活。调试不方便。
    • 应用场景:需要跨平台。

    6. 网络套接字

    在一次通信中,套接字socket必须成对出现

    一个文件描述符指向一个套接字 ( 该套接字内部由内核借助两个缓冲区实现。)

    1. 网络字节序

    小端法:(pc本地存储,IA架构)高位存高地址,低位存低地址。
    大端法:(网络存储)高位存低地址,低位存高地址。

    需要在做网络传输时,将 “网络字节序” —— “本机字节序” 相互转换。

    #include <arpa/inet.h>
    
    h表示host,n表示network,l表示32位长整数,s表示16位短整数。
    
    // 本地字节序(IP) ---> 网络字节序(IP)
    uint32_t htonl(uint32_t hostlong);
    参数:要求是一个 int, 但 “192.168.6.108” 是 string。——> atoi() --> int --> htonl() --> 网络字节序。
    
    // 本地字节序(port) ---> 网络字节序(port)
    uint16_t htons(uint16_t hostshort);
    
    // 网络字节序(IP) --->本地字节序(IP)
    uint32_t ntohl(uint32_t netlong);
    
    // 网络字节序(port) --->本地字节序(port)
    uint16_t ntohs(uint16_t netshort);

    2. IP地址转换函数

    #include <arpa/inet.h>
    // 将 本机字节序 string 类型的 IP地址, 转换为 网络字节序 数值类型
    int inet_pton(int af, const char *src, void *dst);
    af:AF_INET(IPv4)、AF_INET6(IPv6)
    src:传入。IP地址(string类型,点分十进制)
    dst:传出。转换后的数值类型的 IP地址。
    返回值:
    1:成功。
    0:语法正确, IP无效。
    -1:失败。
    // 将 网络字节序 数值类型, 转换回程 本机字节序 string。
    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
    af:AF_INET(IPv4)、AF_INET6(IPv6)
    src:传入。网络字节序 IP地址。
    dst:传出。string 类型 IP
    size:dst 的大小。
    返回值:
    成功:dst 传出值。string 类型的 IP地址。
    失败:NULL

    3. sockaddr 数据结构

    使用 man 7 ip 查看 struct sockaddr_in 结构体类型

    struct sockaddr_in {
    sa_family_t   sin_family; /* address family: AF_INET */
    in_port_t    sin_port;  /* port in network byte order */
    struct in_addr sin_addr;  /* internet address */
    };
    
    /* Internet address. */
    struct in_addr {
    uint32_t    s_addr;   /* address in network byte order */
    };
    
    // 举例:
    struct sockaddr_in addr;  // 定义地址结构(IP+port)变量
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8000);
    // #define INADDR_ANY 0 --- 取系统中任意一个 有效的 IP地址。
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    // 模拟,使用 bind 绑定地址结构。
    bind(lfd, (struct sockaddr *)&addr, sizeof(addr));

    7. socket 通信流程图

    8. socket 通信函数

    1. socket 函数

    #include <sys/types.h>  该头文件包含在 <unistd.h>
    #include <sys/socket.h>
    
    // 创建一个新的套接字。
    int socket(int domain, int type, int protocol);
    domain: AF_INET、AF_INET6、AF_UNIX
    type:SOCK_STREAM、SOCK_DGRAM
    protocol:通常传 0
    返回值:
    成功:返回新套接字对应的 fd
    失败:-1,errno

    2. bind 函数

    #include <sys/types.h>  该头文件包含在 <unistd.h>
    #include <sys/socket.h>
    
    // 给 socket 绑定IP+port
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    sockfd:socket函数的返回值。———— 监听套接字。
    addr: 地址结构体。 ———— 类型应该是:  struct sockaddr_in ,函数调用时需要强制类型转换
    addrlen: sizeof(addr) 地址结构大小
    返回值:
    成功:0
    失败:-1, errno

    3. listen 函数

    #include <sys/types.h>
    #include <sys/socket.h>
    
    // 设置同时与服务器建立连接的 上限数。———— 该函数不会阻塞程序。
    int listen(int sockfd, int backlog);
    sockfd: socket 函数的返回值
    backlog: 设置的监听最大数。 128
    返回值:
    成功:0
    失败:-1, errno

    4. accpet 函数

    // 阻塞等待客户端连接请求。 成功的话,返回一个与客户端成功建立连接的,用于通信的 socket文件描述符
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    sockfd: socket 函数的返回值
    addr: 传出参数。 成功与服务器建立连接的那个客户端地址结构。
    addrlen:传入传出参数。
    入:addr的大小。出:客户端 addr 的实际大小。
    举例:socklen_t clt_addr_len = sizeof(addr);  &clt_addr_len
    返回值:
    成功:能与客户端进行数据通信的 socket 对应的 文件描述符。
    失败:-1, errno

    5. connect 函数

    // 使用现有的 socket 与服务器建立连接
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    sockfd: socket 函数的返回值
    addr:传入参数。 服务器的地址结构。
    addrlen: 服务器地址结构的大小。
    返回值:
    成功:0
    失败:-1, errno
    ———— 客户端自己的地址结构,没有指定,由系统自动分配 --- "隐式绑定"

    9. TCP-CS 模型通信

    1. server端

    通信流程:

    1. socket函数创建监听套接字
    2. bind函数绑定IP地址和端口号
    3. listen函数设置最大监听上限
    4. accept函数阻塞等待客户端接入服务器,返回值传出客户端的IP地址和端口号
    5. read函数从accept的返回值文件描述符中读数据
    6. 数据处理:小写转大写,使用toupper函数
    7. write函数写回到accept的返回值,即写回到客户端
    8. close函数关闭文件描述符

    编码实现:

    #include <stdio.h>
    #include <sys/types.h>
    #include <arpa/inet.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <ctype.h>
    
    #define SRV_PORT 9527
    #define SIZE 4096
    
    int main()
    {
    int lfd = 0;
    
    struct sockaddr_in srv_addr,clt_addr;
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons(SRV_PORT);
    srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    //1.创建套接字
    lfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == lfd)
    {
    perror("socket");
    return 1;
    }
    
    //绑定IP和port
    int ret = bind(lfd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
    if(-1 == ret)
    {
    perror("bind");
    return 1;
    }
    
    //listen 设置最大监听数
    ret = listen(lfd,128);
    if(-1 == ret)
    {
    perror("listen");
    return 1;
    }
    
    printf("服务器阻塞等待客户端接入\n");
    
    //accept 阻塞等待客户端接入
    socklen_t clt_addr_len = sizeof(clt_addr_len);
    int cfd = accept(lfd,(struct sockaddr*)&clt_addr,&clt_addr_len);
    if(-1 == cfd)
    {
    perror("accept");
    return 1;
    }
    
    char ip[SIZE] = {0};
    printf("客户端接入:ip:%s,port:%d\n",
    inet_ntop(AF_INET,&clt_addr.sin_addr.s_addr,ip,SIZE),
    ntohs(clt_addr.sin_port));
    
    char buf[SIZE] = {0};
    while((ret = read(cfd,buf,SIZE)) != 0)
    {
    if('\0' != buf[strlen(buf)-1])
    buf[strlen(buf)-1] = '\0';
    for(int i=0 ;i<ret ;++i)
    {
    buf[i] = toupper(buf[i]);
    }
    write(cfd,buf,ret);
    printf("client:%s\n",buf);
    memset(buf,0,SIZE);
    }
    
    close(cfd);
    close(lfd);
    
    return 0;
    }

    2. client端

    通信流程:

    1. socket函数创建通信套接字文件描述符
    2. connect函数与服务器建立连接,传入的参数中addr表示服务器的IP地址和端口号
    3. 从标准输入中读字符串,将该字符串通过write写到通信套接字中
    4. read函数从服务器读处理后的数据
    5. 结束后关闭文件描述符

    编码实现:

    #include <stdio.h>
    #include <sys/types.h>
    #include <arpa/inet.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <ctype.h>
    
    #define SRV_PORT 9527
    #define SIZE 128
    
    int main()
    {
    int cfd;
    
    cfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == cfd)
    {
    perror("socket");
    return 1;
    }
    
    struct sockaddr_in srv_addr;
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons(SRV_PORT);
    inet_pton(AF_INET,"127.0.0.1",&srv_addr.sin_addr.s_addr);
    
    int ret = connect(cfd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
    if(-1 == ret)
    {
    perror("connect");
    return 1;
    }
    
    printf("客户端连接成功\n");
    char buf[SIZE];
    while(1)
    {
    fgets(buf,SIZE,stdin);
    ret = write(cfd,buf,SIZE);
    ret = read(cfd,buf,SIZE);
    if(0 == ret)
    {
    printf("客户端退出\n");
    break;
    }
    }
    close(cfd);
    return 0;
    }
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: 
    相关文章推荐