• socket编程基础


    一、socket编程常用接口

    socket常用API如下,后文详细介绍

    // 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
    int socket(int domain, int type, int protocol);
    
    // 绑定端口号 (TCP/UDP, 服务器) 
    int bind(int socket, const struct sockaddr *address,
     socklen_t address_len);
     
    // 开始监听socket (TCP, 服务器)
    int listen(int socket, int backlog);
    
    // 接收请求 (TCP, 服务器)
    int accept(int socket, struct sockaddr* address,
     socklen_t* address_len);
     
    // 建立连接 (TCP, 客户端)
    int connect(int sockfd, const struct sockaddr *addr,
     socklen_t addrlen);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    二、sockaddr结构

    socket API适用于各种底层网络协议,如IPv4、IPv6;
    各种网络协议的地址格式并不相同,为了保证socket API的通用性,有了sockaddr结构。
    通过该结构告知socket API我们使用的网络协议是什么。
    在这里插入图片描述

    虽然socket API的参数是sockaddr类型,但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in。这个结构里主要有三部分信息: 地址类型,端口号,IP地址。
    在这里插入图片描述

    三、网络字节序

    我们知道内存存储数据有大小端之分,网络数据流也有大小端之分
    发送方和接收方都是按照从内存低地址到内存高地址操作,TCP/IP协议规定,网络数据流采用大端字节序。如果当前发送主机是小端, 就需要先将数据转成大端,否则可以直接发送

    为使socket程序具有可移植性,使同样的代码在大端机和小端机上都能运行,可以调用以下库函数做网络字节序和主机字节序的转换

    在这里插入图片描述

    h表示host,n表示network,l表示32位长整数,s表示16位短整数,
    例如htonl表示将32位的长整数从主机字节序转换为网络字节序。

    四、IP格式转换函数

    我们通常使用点分十进制的字符串表示IP地址
    但是sockaddr_in中的成员struct in_addr sin_addr是32位的格式
    socket编程使用如下函数进行两种格式互相转换

    字符串转in_addr的函数:

         int inet_aton(const char *cp, struct in_addr *inp);
    
         in_addr_t inet_addr(const char *cp);
    
    • 1
    • 2
    • 3

    inet_aton() 将 Internet 主机地址 cp 从 IPv4 点分十进制表示法转换为二进制形式(按网络字节顺序)并将其存储在inp 指向的结构。如果地址有效,inet_aton() 返回非零,否则返回零

    inet_addr() 函数将 Internet 主机地址 cp 从 IPv4 点分十进制法转换为网络字节顺序的二进制数据。 如果输入是无效,返回 INADDR_NONE(通常为 -1)

    in_addr转字符串的函数:

        char *inet_ntoa(struct in_addr in);
    
    • 1

    inet_ntoa() 函数将以网络字节顺序给出的 Internet 主机地址转换为 IPv4 点分十进制表示法的字符串。 字符串是在静态分配的缓冲区中,后续调用将覆盖该缓冲区。

    五、socket收发数据函数

    接收数据:

    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                      struct sockaddr *src_addr, socklen_t *addrlen);
    
    • 1
    • 2

    参数依次是:
    套接字、用户缓冲区位置、数据长度、读取方式(0为阻塞读取)、对端的sockaddr信息、对端的sockaddr信息长度。

    返回值:
    成功返回读取的信息长度,失败返回-1

    发送数据:

     ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                       const struct sockaddr *dest_addr, socklen_t addrlen);
    
    • 1
    • 2

    参数依次是套接字、用户缓冲区、数据大小、发送方式(0为阻塞式)、对端的sockaddr信息、对端的sockaddr信息长度

    六、基于UDP进行socket编程

    服务端

    1. 创建套接字
      int sock = socket(AF_INET, SOCK_DGRAM, 0);  
    
    • 1

    AF_INET代表IPV4,SOCK_DGRAM代表UDP,设置好前两个参数,第三个参数设为0即可。
    在这里插入图片描述
    在这里插入图片描述
    翻译:SOCK_DGRAM 支持数据报(固定最大长度的无连接、不可靠消息)

    1. 绑定

    目的:将套接字和服务端的IP、端口进行绑定

    在这里插入图片描述

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));   //保险起见,初始化一下
        local.sin_family = AF_INET;         //使用IPV4
        local.sin_port = htons(服务端端口);       //端口会在UDP报文中发送给客户端
                                            //所以需要主机字节序转成网络字节序
        
        //注意: 云服务器不能直接绑定明确的IP,因为那个IP是云服务器厂商虚拟出来的
        //推荐使用INADDR_ANY, 可以bind所有机器上面的ip
        local.sin_addr.s_addr = htonl(INADDR_ANY); 
    
        bind(sock, (struct sockaddr*)&local, sizeof(local));
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. 进行收发数据
    2. 程序退出前使用close关闭文件描述符

    客户端

    1. 创建套接字,方式同上
    2. 客户端不需要用户手动绑定,在发送数据的时候操作系统会随机分配、绑定端口
    3. 收发数据,服务端的端口和IP是用户告知客户端的
    4. 程序退出前使用close关闭文件描述符

    七、基于TCP进行socket编程

    服务端

    1. 创建套接字
     listen_sock = socket(AF_INET, SOCK_STREAM, 0);
     //SOCK_STREAM代表TCP
    
    • 1
    • 2
    1. 绑定,类似UDP服务端

    2. 监听

      TCP面向连接,收发数据前需要建立连接

      listen(listen_sock, backlog);  
    
    • 1

    listen第二个参数后文解释。

    1. 获取连接
      获知是谁来连接服务端,来连接的客户端的sockaddr信息保存在变量当中
         struct sockaddr_in peer;
         socklen_t len = sizeof(peer);
         //获取连接
         int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
    
    • 1
    • 2
    • 3
    • 4
    1. 收发数据
    2. 程序退出前使用close关闭文件描述符

    客户端

    1. 创建socket,类似服务端
    2. 发起连接,发起时操作系统自动绑定
       //填充服务器的socket信息
       struct sockaddr_in svr;
       bzero(&svr, sizeof(svr));
       svr.sin_family = AF_INET;
       svr.sin_port = htons(服务端的端口);
       svr.sin_addr.s_addr = inet_addr(服务端的IP);
    
       //2. 发起链接请求
       connect(sock, (struct sockaddr*)&svr, sizeof(svr));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 收发数据
    2. 程序退出前使用close关闭文件描述符

    八、三次握手和socket接口的关系

    在这里插入图片描述

    九、四次挥手和和socket的关系

    在这里插入图片描述
    Linux内核使用两个队列管理tcp连接:

    1. 半链接队列(用来保存处于SYN_SENT或者SYN_RECV状态的连接)
    2. 全连接队列(用来保存处于established状态,但是没有accept的连接)

    全连接队列的长度是listen()第二个参数backlog+1。
    全连接队列满时,无法继续让其他连接进入established状态。

  • 相关阅读:
    期刊查重会泄露论文吗?
    深度优先搜索详解
    BSA-maltose 牛血清白蛋白修饰麦芽糖 BSA-麦芽糖
    Oracle 中的索引
    源启容器平台KubeGien 打造云原生转型的破浪之舰
    STC单片机14——利用51单片机测量信号的频率,高低频及转速显示
    JavaWeb文件上传/下载(Servlet)
    Linux的防火墙: netfilter防火墙框架, iptables , CentOS的firewalld, Ubuntu的ufw
    大二Web课程设计:HTML+CSS学校静态网页设计——南京师范大学泰州学院(11页)
    shiro的会话管理器SessionManager
  • 原文地址:https://blog.csdn.net/sqjddb/article/details/126216533