• linux网络编程


    数据存储优先顺序的转换

    计算机数据存储有两种字节优先顺序:高位字节优先(称为大端模式)和低位字节优先(称为小端模式)。内存的低地址存储数据的低字节,高地址存储数据的高字节的方式叫小端模式。内存的高地址存储数据的低字节,低地址存储数据高字节的方式称为大端模式。

    eg:对于内存中存放的数0x12345678来说(注意,对于数据而言,此处12是高字节,78是低字节;对于地址而言,左边是低地址,右边是高地址)

    如果是采用大端模式存放的,则其真实的数是:0x12345678

    如果是采用小端模式存放的,则其真实的数是:0x78563412

    综上:小端模式,先存低字节;大端模式先存高字节。

    如果称某个系统所采用的字节序为主机字节序,则它可能是小端模式的,也可能是大端模式的。通常我们的电脑存放数据,都是以小端模式存放。而端口号和IP地址都是以网络字节序存储的,不是主机字节序,网络字节序都是大端模式。要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。

    Pra1

    原题

    实现将字符串形式存放的ip地址(点分十进制数)转换为主机字节序的32位二进制数值。IP为:“180.97.33.107”。十六进制为b4.61.21.6b

    思路

    一般我们的主机字节序均为小端字节序,也就是选存放数据的低字节。

    1. 将字符串中的每个数取出来

    2. 分别左移(注意,左移时一定会补0)

             180 : 00 00 00 b4    左移0  位   00 00 00 b4
             97   : 00 00 00 61    左移8  位   00 00 61 00
             33   : 00 00 00 21    左移16位   00 21 00 00
             107 : 00 00 00 6b    左移24位   6b 00 00 00

    3. 或操作

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #define IP "180.97.33.107"
    
    /* 将ip地址转换为主机字节序的二进制数值,输出用16进制表示 */
    static int my_atoh(char *ip)
    {
        int arr[4]; /* 用于存放从ip地址中扣出的4个整数 */
        int my_ip;
        sscanf(IP,"%d.%d.%d.%d",arr,arr+1,arr+2,arr+3);
        my_ip = (arr[3] << 24) | (arr[2] << 16) | (arr[1] << 8) | arr[0];
        return my_ip;
    }
    
    int main(int argc, char *argv[])
    {
        int my_host = my_atoh(IP);
        printf("ip  : %s 
    ",IP);
        printf("host: %x 
    ",my_host);
        return 0;
    }


    Pra2

    原题

    实现将主机字节序(小端模式存放)转换为网络字节序(大端模式存放)。即主机字节序为6b2161b4 –> b461216b

    思路1

    将6b与b4交换,21与61交换。

    #include <stdio.h>
    
    int my_hton(int ip)
    {
        /* &ip指向用32个bit表示的整型数ip,将int*类型的指针转换为char*类型的指针,
         * 则ptr指向用8个bit表示(第0-7位)的整型数,
         * ptr+1指向用8个bit表示(第8-15位)的整型数,
         * ptr+2指向用8个bit表示(第16-23位)的整型数,
         * ptr+3指向用8个bit表示(第24-31位)的整型数。*/
        
        char *ptr = (char*)&ip;
        
        char tmp;
        tmp = ptr[0];
        ptr[0] = ptr[3];
        ptr[3] = tmp;
    
        tmp = ptr[1];
        ptr[1] = ptr[2];
        ptr[2] = tmp;
    
        return ip;
    }
    
    int main(int argc, char *argv[])
    {
        int my_host = 0x6b2161b4;
        int my_net = my_hton(my_host);
        printf("my_host: %x 
    ",my_host);
        printf("my_net : %x 
    ",my_net);
        return 0;
    }

    实际上,ip地址为点分十进制,每个字节的表示范围都是0-255。而我们的char类型通常默认为有符号数,也就是说其表示范围是-128-127。那么我在代码中标记为黄色的部分是不是出错了呢?实际上并没有错,因为在此处我们只是关心ip每一位的存储情况,并不要求用到具体每个字节的实际十进制表示数值。在用printf格式化输出时,是根据数据在内存中的二进制形式来格式化的,而16进制形式是没有负数的。

    思路2

    1. 对于0x6b2161b4,先右移并进行与操作,取出各字节

    例如:要取出6b,则0x6b2161b4右移24位,之后与0xff进行与操作即可

    注意:千万不能先进行与操作,再右移。如,要取出6b,先使0x6b2161b4与0xff000000进行与操作,之后右移24位。这是错误的,因为右移会补符号位。(tips:左移补0,并舍弃符号位)

    2. 分别再左移,调整到适当位置。 (以下两步类似于Pra1)

    3. 或操作

    #include <stdio.h>
    
    int my_hton2(int ip)
    {
        int my_net;
        my_net = ((ip&0xff)<<24) | (((ip>>8)&0xff) << 16) | (((ip>>16)&0xff) << 8) | ((ip>>24)&0xff);
        return my_net;
    }
    
    int main(int argc, char *argv[])
    {
        int my_host = 0x6b2161b4;
        int my_net = my_hton2(my_host);
        printf("my_host: %x 
    ", my_host);
        printf("my_net : %x 
    ", my_net);
        return 0;
    }

    实现将网络字节序转换为点分十进制

    #include <stdio.h>
    
    static char* my_ntoa(int ip)
    {
        static char buf[1024] = "";
        sprintf(buf,"%d.%d.%d.%d",(ip >> 24) & 0xff,(ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);
        return buf;
    }
    
    int main(int argc,char *argv[])
    {
        int my_net = 0xb461216b;
        printf("my_net: %x 
    ", my_net);
        printf("IP    : %s 
    ", my_ntoa(my_net));
        return 0;
    }

    我们的my_atoh,my_hton合并为系统函数inet_aton,而my_ntoa即为系统函数inet_ntoa。
    #include <stdio.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    int main(int argc, char *argv[])
    {
        char ip_buf[] = "180.97.33.107";
        struct in_addr my_addr;
        inet_aton(ip_buf,&my_addr);
        printf("ip : %s 
    ", ip_buf);
        printf("net: %x 
    ", my_addr.s_addr);
        return 0;
    }
    ip : 180.97.33.107
    net: 6b2161b4
    照理,网络字节序是大端存储,应该返回0xb461216b。实际上调用系统函数inet_aton后,就直接在变量my_addr.s_addr的实际内存空间中以二进制形式写入了0xb461216b(其实用位运算,就可以直接操作二进制位,上篇博文有具体实现)。之所以运行结果是0x6b2161b4,是因为我们的主机是小端存储,用printf显示结果是先取低字节。

    #include <stdio.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    int main(int argc, char *argv[])
    {
        struct in_addr my_addr;
        my_addr.s_addr = 0xb461216b;
        printf("net: %x 
    ", my_addr.s_addr);
        printf("ip : %s 
    ", inet_ntoa(my_addr));
        return 0;
    }
    net: b461216b
    ip : 107.33.97.180
    照理,ip应该输出的是180.97.33.107。其实道理很简单,我们的主机是小端模式存储,而网络字节序是大端模式,当我们把0xb461216b赋值给my_addr.s_addr 时,实际上在内存中存储形式是0x6b2161b4,而inet_ntoa的具体实现时通过位运算直接操纵二进制位的,因此结果就自然输出107.33.97.180。


    // 测试大端机还是小端机
    #include <stdio.h> #include <stdlib.h> int main() { int a = 0x61;//97 printf("%x ",(char*)(&a)[0]); }
    结果输出61,说明是小端机,先存低字节。
    系统函数无论inet_aton还是inet_ntoa,都是直接以位运算形式实现的,因此其关注的是数据在内存中实际的二进制存储形式。

    使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起。

    TCP通信的基本步骤如下:

    服务端:socket---bind---listen---while(1){---accept---recv---send---close---}------close

    客户端:socket------------------------------connect---send---recv-----------------close

    UDP通信的基本步骤如下:

    服务端:socket---bind---recvfrom---sendto----close

    客户端:socket----------sendto----recvfrom---close

    TCP
    
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    server:
    
    int socket(int domain, int type, int protocol);
    
    int bind(int fd_server, struct sockaddr *addr_server, int addrlen); /* 绑定服务器联系方式,让客户端有明确的联系方式可以连接 */
    
    int listen(int fd_server, int backlog); /* 将fd_server转换为被动套接字,同时内核将listen到的请求的联系方式放入队列 */
    
    int accept(int fd_server, struct sockaddr *addr_client, int *addrlen); /* 返回客户端socket的另一端,以此建立连线 *//* addr_client为传出参数,存放请求连接方的联系方式 ,如不需要刻意置为NULL */
    
    client:
    
    int connect (int fd_client,struct sockaddr *addr_server, int addrlen); /* 通过服务器的联系方式addr_server去连接服务器 */
    
    /* 作为服务器,一定要绑定联系方式,不然请求方没有明确的联系方式可以连接 ,
       作为客户端,可以不用绑定联系方式,其端口号会由系统自动分配。          
       建立连线的关键在于accept函数会返回客户端socket描述符所对应的另一端socket的描述符,
       并且客户端的联系方式也可以在accept中使用传出函数获取。                              */
       
    after connect:
    
    /* 建立连线后,就可以利用socket描述符发送接收信息了 */
    int recv(int sockfd,void *buf,int len,unsigned int flags);
    
    int send(int s,const void * msg,int len,unsigned int flags);
    
    /* 会话结束,关闭 */
    int close(int fd);

    UDP
    
    server:
    
    int socket(int domain, int type, int protocol);
    
    /* 绑定服务器联系方式,让客户端有明确的联系方式可以连接 */
    int bind(int fd_server, struct sockaddr *addr_server, int addrlen); 
    
    /* 由于fd_server已经绑定联系方式(addr_server),请求方发送信息时,会通过addr_server发送消息至服务器fd_server,
       因此,服务器可以通过fd_server收到消息,并且请求方的联系方式可以通过传出参数addr_client获取                */
    int recvfrom(int fd_server, void *buf, int len, unsigned int flags, struct sockaddr *addr_client, int *addrlen); 
    
    /* 请求方的联系方式(addr_client)会由系统自己绑定,服务器是通过recvfrom的传出参数获取addr_client的。
       因此服务器发送消息时,可以通过客户端联系方式addr_client发送至客户端fd_client。                  */
    int sendto(int fd_server, const void *msg, int len, unsigned int flags, const struct sockaddr *addr_client, int addrlen);
    
    client:
    
    /* 客户端的联系方式,服务器可以在recvfrom时获取,因此没有必要在一开始就绑定 */
    int sendto(int fd_server, const void *msg, int len, unsigned int flags, const struct sockaddr *addr_client, int addrlen);
    
    int recvfrom(int fd_server, void *buf, int len, unsigned int flags, struct sockaddr *addr_client, int *addrlen); 
    
    /* 实际上,UTP通信方式并没有建立socket连线,双方均是通过联系方式(IP和端口号)进行通信的。
       服务器需要在一开始绑定联系方式,不然请求方没有明确的联系方式可以连接。
       当服务器recvfrom,不但可以收到客户端的消息,同时可以获取客户端的联系方式。            */


    1. TCP通信会建立连线。服务器的socket端口会bind联系方式,给客户端一个明确的联系方式去connect。服务器会listen到客户端的connect,并将其联系方式放入内核维护的任务请求队列中。之后服务器accept,获取与客户端通信的对应socket端口,从而建立连线!之后双方通过socket端口进行通信。

    2. UDP通信不会建立连线。服务器的socket端口同样会bind联系方式。客户端通过此联系方式向服务器发送(sendto)消息,由于此联系方式已绑定到服务器的socket端口,因此服务器一定可以收到(recvfrom)消息,与此同时通过recvfrom的传出参数获取客户端的联系方式,从而可以通过客户端的联系方式向客户端发送消息。即,UTP的通信是基于联系方式(IP和端口号)的。是不可靠的通信方式。

    3. 为什么TCP中的accept与UDP中的recvfrom都可以通过传出参数获取对方联系方式?不理解的话,可以类比寄信,别人给你寄一封信时,总会写上自己的联系方式吧。

  • 相关阅读:
    linux文件权限查看及修改(实用)
    将JSON对象带有格式的写出到文件中
    mySQL数据库Sql语句执行效率检查--Explain命令
    mysql优化
    Linux-设置固定IP
    logback 配置详解(二)——appender
    logback 配置详解(一)——logger、root
    Thread
    Singleton
    多线程编程总结
  • 原文地址:https://www.cnblogs.com/hxjbc/p/3963970.html
Copyright © 2020-2023  润新知