• 套接字+网络套接字函数+客户端大小写程序


    NAT映射

    一般来说:源主机和目的主机都属于局域网,也就是ip地址可能相同

    但是:路由器一般是公网ip,即整个网络环境可见

    每一个路由器会维护一个NAT映射表

    • 路由器的ip一般是固定的公网ip
    • 路由器与把与它相连的主机:私有ip+端口号映射成路由器ip+端口号

    此时再进行数据传输时:会用映射后的ip作为源地址,也就是保护了私有ip

    打洞机制

    首先进行通信的进程,需要经过路由器到达提供服务的服务器,然后服务器进行拆包后,帮助完成一次转发,再经过多跳路由器后,将信息传递给目的ip

    也就是传输的数据包必须途径服务器

    由于路由器具有保护机制-----第一次给路由器发送数据包的ip,会被路由器丢弃或屏蔽(防止恶意攻击)

    所以,相互通信的ip,要被路由器识别为熟悉的ip才可以直接通信

    • 主机和提供服务的服务器之间是可以直接通信的
    • 所以服务器在请求服务时候,会在两台想建立通信的主机之间,把其相连的路由器标记为熟悉
    • 这样服务器通过打洞机制就相当于在两台主机间建立了通路
    几种访问机制(注意不是局域网(内网)之间)
    1. 公网ip和公网ip之间进行通信,直接访问
    2. 公网ip和私网ip之间进行通信,路由器要将私网ip进行NAT映射后,进行通信
    3. 私网ip和私网ip之间进行通信,公有服务器对相连服路由器进行打洞机制,然后通信

    套接字

    • Socket本身有“插座”的意思
    • 在Linux环境下,用于表示进程间网络通信的特殊文件类型
    • 本质为内核借助缓冲区形成的伪文件

    注意socket一定有两端,成对出现:

    数据发送端和数据接收端

    就像插电,既要有插头有要求插排

    IP地址:在网络环境中,唯一标识一台主机

    端口号:主机中唯一标识一个进程

    IP+port:在网络环境中,唯一标识一个进程,把这个叫成socket

    管道

    管道一定有两端,一端只能读,一端只能写,每端分别用一个文件描述符指向

    数据只能从写段流入,从读端流出

    socket

    每次创建一个socket,都有一个文件描述符指向,但是这一个文件描述符由两个缓冲区指向

    一个缓冲区用于读数据,一个缓冲区用于写数据,也就是全双工

    两个socket是通过ip+port进行连接的

    网络字节序

    字节序:

    • 也就是字节序

    • 如果数据大于一个字节,在内存中存放是有顺序的

      一般有两种:

      • 小端字节序:将低位字节序存储在低位
      • 大端字节序:将高位字节序存储在低位

    其中:TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节

    但是,主机中采取的小端字节序

    小端存,大端传,所以需要字节序的转换

    假设内存:1000 1001 1002 1003 1004

    现在的变量:int a=0x12345678(注意位次,←依次升高)

    • 如果采用小端法存储(低位存低,高位存高)

      12是高位存1004,依次:34存1003.。。。。

    • 如果大端就是反过来(78存1004)

    网络字节序和主机字节序的转换
    #include 
    //host to network
    //long转IP地址;short转端口号
    uint32_t htonl(uint32_t hostlong);
    uint16_t htons(uint16_t hostshort);
    //network to host
    uint32_t ntohl(uint32_t netlong);
    uint16_t ntohs(uint16_t netshort);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用

    #include
    int main(){
        int a=0x01020304;
        short int b=0x0102;
        //long转IP地址;short转端口号
        //uint32_t htonl(uint32_t hostlong);
        //uint16_t htons(uint16_t hostshort);
        printf("htonl(0x%08x)=0x%08x\n",a,htonl(a));
        //08表示输出8位,x表示16进制
        printf("htons(0x%08x)=0x%08x\n",b,htonl(b));
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    guojiawei@ubantu-gjw:~/Desktop$ gcc mm.c
    guojiawei@ubantu-gjw:~/Desktop$ ./a.out 
    htonl(0x01020304)=0x04030201
    htons(0x00000102)=0x02010000
    
    • 1
    • 2
    • 3
    • 4

    ip地址转换

    #include 
    //ip(点分十进制) to net(网络字节序)
    int inet_pton(int af, const char *src, void *dst);
    /*
    af版本号---只有:AF_INET 和 AF_INET6 两种
    src---点分十进制的数值
    dst----转换后的整数的地址
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    #include 
    //net to ip(字符串)
    const char *inet_ntop(int af, const void *src,  char *dst, socklen_t size);
    //dst----点分十进制数串
    //size---缓存区的大小
    
    • 1
    • 2
    • 3
    • 4
    • 5

    实现:

    #include
    #include
    int main(){
        char ip_str[]="172.20.226.11";
        unsigned int ip_uint=0;
        inet_pton(AF_INET,ip_str,&ip_uint);//注意是地址
        printf("ip_uint=%d\n",ip_uint);
        unsigned char* ip_p=NULL;
        ip_p=(unsigned char*)&ip_uint;
        printf("ip_uint=%d.%d.%d.%d\n",*ip_p,*(ip_p+1),\
                              *(ip_p+2),*(ip_p+3) );
        return 0;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    #include
    #include
    int main(){
        char ip_str[16]={0};
        unsigned char ip[]={172,20,223,75};
        inet_ntop(AF_INET,(unsigned int*)ip,ip_str,16);
        printf("ip_str=%s\n",ip_str);
        return 0;
    } 
    //ip_str=172.20.223.75
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    套接字地址数据结构

    sockaddr结构体-----用于描述IPv4协议的地址结构

    • struct sockaddr由于不细致,现在已经不能用了
    • 可是基于sockaddr细分的sockaddr_in无法直接作为socket的网络API的参数,所以需要强制类型转换
    struct sockaddr {
                   sa_family_t  sa_family;
                          char  sa_data[14];}
    
    • 1
    • 2
    • 3
    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 */
    };
    //man 7 ip
    /* Internet address. */
    struct in_addr {
                   uint32_t       s_addr;     /* address in network byte order */};
    //由于历史遗留,只有一个成员,每次需要.使用
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    struct sockaddr_in {
    	__kernel_sa_family_t sin_family; 			/* Address family */  	地址结构类型
    	__be16 sin_port;					 		/* Port number */		端口号
    	struct in_addr sin_addr;					/* Internet address */	IP地址
    	/* Pad to size of `struct sockaddr'. */
    	unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
    	sizeof(unsigned short int) - sizeof(struct in_addr)];
    };//详细
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用:

    struct sockaddr_in addr;
    
    addr.sin_family=AF_INET//AF_INET6
    addr.sin_port=htonl//ntohs
    addr.sin_addr.s_addr=htol;ntosl;inet_ptonl;inrt_ntop;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    网络套接字函数

    socket()
     #include           /* See NOTES */
     #include 
    /*domain:
    	AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
    	AF_INET6 与上面类似,不过是来用IPv6的地址
    	AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
    */
    /*type:
           1:SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
           2:SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
           3:SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
    */
    /*protocol:
    	传0 表示使用默认协议,系统自动选择
    	IPPROTO_TCP;IPPROTO_UDP 指定传输tcp或者udp
    */
     int socket(int domain, int type, int protocol);
    //成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    使用

    #include          
    #include 
    #include 
    #include 
    int main(){
        int socketFd=0;
        //创建:使用IPV4地址协议的TCP流式套接字
        socketFd=socket(AF_INET,SOCK_STREAM,0);
        if(socketFd==-1){
            perror("socket()");
            exit(-1);
        }
        else{
            printf("socket has created successfully\n");
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    bind()
     #include          
     #include 
    /*
         sockfd:  socket文件描述符
         addr:     构造出IP地址加端口号
         addrlen:  sizeof(addr)长度
    */
      int bind(int sockfd, const struct sockaddr *addr,\
                            socklen_t addrlen);
    //返回值:0表示成功;-1表示失败
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    bind就是绑定,往套接字上绑定ip和端口号

    由于传指针的地址,还得加上大小

    #include          
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(){
        int socketFd=0;
        //创建:使用IPV4地址协议的TCP流式套接字
        socketFd=socket(AF_INET,SOCK_STREAM,0);
        /*struct sockaddr_in {
               sa_family_t    sin_family; 
               in_port_t      sin_port; 
               struct in_addr sin_addr;  
                     struct in_addr { uint32_t  s_addr;}
        };*/
        struct sockaddr_in my_addr;
        //bzero函数是c++ string.h中的函数。
        //描述:置字节字符串前n个字节为零且包括‘\0’
        bzero(&my_addr,sizeof(my_addr));
        my_addr.sin_family=AF_INET;
        unsigned short port=8000;
        my_addr.sin_port=htons(port);//hton:转换成网络字节序
        my_addr.sin_addr.s_addr=htons(INADDR_ANY);
        int res_bind=bind(socketFd,(struct sockaddr*)&my_addr,sizeof(my_addr));
        printf("res=%d\n",res_bind);
        return 0;
    }
    
    • 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
    listen()
     #include        
     #include 
    /*backlog:
    	排队建立3次握手队列和刚刚建立3次握手队列的链接数和
    	也就是内核为此套接字建立的最大连接数(同时允许多少个用户端建立连接)
    */
     int listen(int sockfd, int backlog);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    listen()仅被TCP通信服务器程序调用,实现监听服务

    • backlog—注意不是指定连接上限数,而是可以建立连接的
    accept()
     #include           /* See NOTES */
     #include 
    /*
       addr:
    	传出参数,返回链接客户端地址信息,含IP地址和端口号
       addrlen:
    	传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
    */
     int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    //成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    三方握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来

    socklen_t *addrlen:

    the caller must initialize it to contain the size (in bytes) of the structure pointed to by addr; on return it will contain the actual size of the peer address.

    connect()
     #include           /* See NOTES */
     #include 
    /*
       addr:
    	传入参数,指定服务器端地址信息,含IP地址和端口号
       addrlen:
    	传入参数,传入sizeof(addr)大小
    */
    int connect(int sockfd, const struct sockaddr *addr,\
                       socklen_t addrlen);
    //成功返回0,失败返回-1,设置errno
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。

    socket套接字流程图

    比较典型的流程图

    实现功能:客户端给服务器发送小写字母,服务器接收后转换成大写字母回传给客户端,然后客户端把内容显示到屏幕上

    服务器端:

    • socket创建一个网络套接字
    • bind函数绑定ip地址和端口号(struct sockaddr_in)
    • listen函数来限定同时发起服务器连接的数量
    • accept含有阻塞功能,直到收到了用户的消息才连接

    客户端:

    • socket必须绑定ip和端口号才能访问
    • 但是不用bind,会被自动分配,但是服务器端不可以自动分配,客户端无所谓
    • connect进行连接:连接服务器的ip和端口号

    客户端程序

    #include 
    #include 
    #include          
    #include 
    #include 
    #include 
    #include //大小写转换
    #define SERV_PORT 6667//端口号
    int main(void){
        int sfd;
        /*定义accept的客户端的参数*/
        int cfd; 
        char buf[100]={0};
        struct sockaddr_in cline_addr;
        socklen_t clin_addr_len;
        //sockaddr_in结构体定义在头文件:
        struct sockaddr_in serv_addr;
        sfd=socket(AF_INET,SOCK_STREAM,0);
        /*定义结构体*/
        serv_addr.sin_family=AF_INET;
        //大小端问题:需要hton l是ip;s是端口号
        serv_addr.sin_port=htons(SERV_PORT);
        /*简单方法--------htonl(INADDR_ANY)*/
        serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
        bind(sfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
        /*listen*/
        listen(sfd,32);//最大上限28个客户端
        /*accept---接收一个文件描述符作为参数,最后返回一个文件描述符*/
        clin_addr_len=sizeof(cline_addr);//注意第三个参数:&
        cfd=accept(sfd,(struct sockaddr*)&cline_addr,&clin_addr_len);
        /*连接后:开始读数据*/
        int n=read(cfd,buf,sizeof(buf));//从用户端读
        int i;
        for(i=0;i<n;i++){
            /*小写变大写*/
            buf[i]=toupper(buf[i]);
        }
        write(cfd,buf,n);//写回给数据,别用sizeof
        close(sfd);
        close(cfd);
        return 0;
    }
    
    • 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
    模拟客户端与服务端的通信
    //模拟客户端
    $ nc -l 端口号
    
    • 1
    • 2
    //模拟服务端
    $ nc ip地址  端口号
    
    • 1
    • 2

    程序运行结果

    hello world
    HELLO WORLD

    --------通信前提是等待,不能一下子就结束

  • 相关阅读:
    echarts-饼图和 模拟事件补充
    统计信号处理基础 习题解答6-11
    PyTorch搭建图卷积神经网络(GCN)完成对论文分类及预测实战(附源码和数据集)
    【Arduino ESP32教程入门】Note
    即刻报名,企业服务与新经济论坛亮点提前揭秘!
    c sharp基于随笔画的加解密系统
    只用二十行代码,用Python实现获取网抑云榜单文件保存本地,非常简单...
    vscode python run code 中文乱码
    leetcode top 100 (8)无重复字符的最长子串(滑动窗口
    阿里云邮件推送配置教程:有哪些关键步骤?
  • 原文地址:https://blog.csdn.net/weixin_47173597/article/details/127955508