• 套接字Socket——TCP、UDP通信


    套接字(socket:插座)
    是一种可以进行网络通信的内核对象,它有一个唯一的标识符,一般称它为socket描述符,跟文件描述符类似,也可以用read/write/close操作


    功能:创建socket对象,返回socket描述符
    domain:通信地址类型,AF_UNIX (本地进程间通信) 、AF_INET (使用ipv4地址通信) 、AF_INET6 (使用ipv6地址通信);
    type:
      SOCK_STREAM:数据流协议,TCP面向连接的通信协议,优点是安全可靠,数据不会丢失,但速度慢。一般常用于安全性较高的场景。
      SOCK_DGRAM:数据报协议,UDP面向无连接的通信协议,优点是速度快,数据可能丢失,安全性和可靠性与tcp相比不同。一般用于安全性要求不高但是对速度有要求的场景。 

    protocol:通常是0,表示为给定的域和套接字类型选择默认协议。

    准备通信地址:

    本地通信地址
    struct sockaddr_un
    {
      sun_family_t sun_family;   // 通信地址类型,AF_UNIX、AF_INET、AF_INET6等

      char sun_path[108];  // socket文件的路径,本地通信的socket文件
    };


    网络通信地址
    struct sockaddr_in
    {
      short int sin_family;  // 通信地址类型,AF_UNIX、AF_INET、AF_INET6等
      in_port_t sin_port;  // 端口号,以网络字节序表示的16位整数
      struct in_addr sin_addr;  // ip地址,32位的无符号整数
    }

    服务器绑定socket与通信地址:

    一个socket对象只能绑定一个地址。

    功能:把socket对象与通信地址建立联系

    sockfd:socket函数返回的socket描述符

    addr:通信地址

    addrlen:通信地址的长度

    客户端连接通信目标:

    个人计算机系统数据的存储方式可能是大端,也可能是小端,网络通信时需要的是大端数据,必须把数据转换成大端。
    uint32_t htonl(uint32_t hostlong);  功能:把32位的主机字节序转换成32位网络字节序
    uint16_t htons(uint16_t hostshort);  功能:把16位的主机字节序转换成16位网络字节序
    uint32_t ntohl(uint32_t netlong);  功能:把32位网络字节序转换成32位的主机字节序
    uint16_t ntohs(uint16_t netshort);  功能:把16位网络字节序转换成16位的主机字节序

    生成端口号:
    端口号就是一个16位的无符整数因此:uint16_t htons(uint16_t hostshort);

    生成ip地址:in_addr_t inet_addr(const char *cp);
    功能:把点分十进制的字符串ip地址转换成32位的无符号整数

    char *inet_ntoa(struct in_addr in);
    功能:把32的的网络字节序的ip地址转换成点分十进制的字符串ip地址。

    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
    功能:接收数据并获取发送端的地址

    如果addr非空,它将包含数据发送者的套接字端点地址。当调用recvfrom时,需要设置addrlen参数指向一个整数,该整数包含addr所指向的套接字缓冲区的字节 长度。返回时,该整数设为该地址的实际字节长度。因为可以获得发送者的地址,recvfrom通常用于无连接的套接字。否则,recvfrom等同于recv

    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
    功能:发送数据到指定的目标,面对无连接的套接字。

    当socket对象被全部关闭,会在内核中停留一段时间(会给重新连接的机会),如果再使用同样的ip地址和端口号时就会失败(延时关闭)。

    示例:

    udp_client

    #include <stdio.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <unistd.h>
    
    typedef struct sockaddr* sockaddrp;
    
    int main()
    {
        // 创建socket对象
        int sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if(0 > sockfd)
        {
            perror("socket");
            return -1;
        }
    
        // 准备通信地址
        struct sockaddr_in addr = {AF_INET};
        addr.sin_port = htons(6666);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        
        while(1)
        {
            char buf[255] = {};
            // 发送数据到目标
            printf("请输入要返回的数据:");
            gets(buf);
            sendto(sockfd,buf,strlen(buf),0,(sockaddrp)&addr,sizeof(addr));
            if(0 == strcmp(buf,"q")) break;
    
            // 接收数据与来时的路径
            socklen_t addr_len;
            recvfrom(sockfd,buf,sizeof(buf),0,(sockaddrp)&addr,&addr_len);
            printf("接收到数据:%s
    ",buf);
            if(0 == strcmp(buf,"q")) break;
    
        }
    
        // 关闭
        close(sockfd);
    }

    udp_server

    #include <stdio.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <unistd.h>
    
    typedef struct sockaddr* sockaddrp;
    
    int main()
    {
        // 创建socket对象
        int sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if(0 > sockfd)
        {
            perror("socket");
            return -1;
        }
    
        // 准备通信地址
        struct sockaddr_in addr = {AF_INET};
        addr.sin_port = htons(6666);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
        // 绑定对象与地址
        int ret = bind(sockfd,(sockaddrp)&addr,sizeof(addr));
        if(0 > ret)
        {
            perror("bind");
            return -1;
        }
        
        struct sockaddr_in src_addr = {};
        socklen_t addr_len = sizeof(addr);
    
        while(1)
        {
            char buf[255] = {};
            // 接收数据与来时的路径
            recvfrom(sockfd,buf,sizeof(buf),0,(sockaddrp)&src_addr,&addr_len);
            printf("接收到数据:%s
    ",buf);
            if(0 == strcmp(buf,"q")) break;
    
            // 发送数据到目标
            printf("请输入要返回的数据:");
            gets(buf);
            ret = sendto(sockfd,buf,strlen(buf),0,(sockaddrp)&src_addr,addr_len);
            if(0 == strcmp(buf,"q")) break;
        }
    
        // 关闭
        close(sockfd);
    }

    面向连接的通信TCP

    功能:声明sockfd处于监听状态,成功返回0,失败返回-1

    sockfd:socket函数返回的socket描述符

    backlog:提示系统该进程所要入队的未完成连接请求数量。一旦队列满,系统就会拒绝多余的连接请求。

    功能:当有客户端发起连接时,服务器就调用accept()返回并接收这个连接,如果有大量客户端发起请求,服务器来不及处理,还没有accept的客户端就处于连接等待状态。 
    addr:准备的通信地址  addrlen:通信地址长度

    类似于文件操作的read/write,只是多了一个flags标志位,通常填写0,表示为默认的阻塞操作,如果指定为MSG_DONTWAIT则允许非阻塞操作

    tcp_client

    #include <stdio.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include <string.h>
    #include <netinet/in.h>
    
    typedef struct sockaddr* sockaddrp;
    int main()
    {
        int sockfd = socket(AF_INET,SOCK_STREAM,0);
        
        struct sockaddr_in addr = {AF_INET};
        addr.sin_port = htons(6666);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
        connect(sockfd,(sockaddrp)&addr,sizeof(addr));
        while(1)
        {
            char buf[255] ={};
            printf("请输入要发送的数据:");
            gets(buf);
            send(sockfd,buf,strlen(buf),0);
            if(0 == strcmp("q",buf)) break;
    
            bzero(buf,sizeof(buf));
            int ret = recv(sockfd,buf,sizeof(buf),0);
            printf("读取到%d字节,内容:%s
    ",ret,buf);
            if(0 == strcmp("q",buf)) break;
            
    
        }
        close(sockfd);
    }

    tcp_server

    #include <stdio.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include <string.h>
    #include <netinet/in.h>
    
    typedef struct sockaddr* sockaddrp;
    int main()
    {
        //建立socket对象
        int sockfd = socket(AF_INET,SOCK_STREAM,0);
        if(sockfd < 0)
        {
            perror("socket");
            return -1;
        
        }
    
        //准备通讯地址
        struct sockaddr_in addr = {AF_INET};
        addr.sin_port = htons(6666);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
        //绑定对象与地址
        int ret = bind(sockfd,(sockaddrp)&addr,sizeof(addr));
        if(ret < 0)
        {
            perror("bind");
            return -1;
        }
    
        //设置排队数量
        listen(sockfd,1024);
    
        //等待连接
        struct sockaddr_in src_addr = {};
        socklen_t addr_len = sizeof(src_addr);
    
        bool flag = true;
        while(flag)
        {
            int clifd = accept(sockfd,(sockaddrp)&src_addr,&addr_len);
    
            //通信
            while(1)
            {
                char buf[255] = {};
                ret = recv(clifd,buf,sizeof(buf),0);
                printf("接收到%d字节数据,内容:%s
    ",ret,buf);
                if(0 == strcmp("q",buf))
                {
                    flag = false;
                    break;
                }
    
                printf("请输入返回内容:");
                gets(buf);
                send(clifd,buf,strlen(buf),0);
                if(0 == strcmp("q",buf))
                {
                    flag = false;
                    break;
                }
            }
            close(clifd);
        }
        //关闭socket
        close(sockfd);
    }
  • 相关阅读:
    Berry Jam(前缀后>差值,贪心)
    Privatization of Roads in Treeland (贪心+染色)
    主席树
    C 语言是一门抽象的、面向过程的语言,C 语言广泛应用于底层开发
    限流中间件AspNetCoreRateLimit
    远程调试 Visual Studio2022
    高并发下秒杀
    设计模式的分类和六大原则
    MySQL的锁机制
    代码是怎么运行
  • 原文地址:https://www.cnblogs.com/xiehuan-blog/p/9418913.html
Copyright © 2020-2023  润新知