标签搜索

套接字编程中bind和reuse实践

anker
2021-06-26 / 0 评论 / 79 阅读 / 正在检测是否收录...

一般的开发中, 需要使用bind的情况是在服务端。今天遇到一个特殊的使用场景,客户端因为安全性要求,连接时不能使用内核分配的端口,需要使用指定的端口和服务器连接。自然就用到了bind接口。从bind又引入了reuse属性的使用。这里使用gethostbyname_r来做域名到IP地址的转换(curl也是如此),另外像云风skynet则使用getaddrinfo接口,后者用法更简单一些。当然他们都阻塞的。

以下是验证代码:

//anker<lichengman2006@gmail.com> @2021-06-17T20:18:16
//gcc client.c  -o client
//client www.google.com 80 9090 1
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    if (argc != 5) {
        fprintf(stderr,"usage: \n"
        "client $serverIp $serverPort $clientPort $reuse\n"
        "example: ./client www.google.com 80 9090 1\n");
        exit(1);
    }

    int h_errnop;
    int HOSTENT_SIZE = 9000;
    struct hostent *buf = NULL;
    struct hostent *he = NULL;

    char *hostname = argv[1];
    int port = atoi(argv[2]);

    buf = calloc(1, HOSTENT_SIZE);
    if(!buf)
        exit(1);
//    int gethostbyname_r(const char *name,
//            struct hostent *ret, char *buf, size_t buflen,
//            struct hostent **result, int *h_errnop);
//    name:Here name is either a hostname, or an IPv4 address in standard dot notation (as for inet_addr(3)), or an IPv6 address in colon (and possibly dot) notation
//    ret:which will be filled in on success, and a temporary work buffer buf of size buflen
//    result: will point to the result on success. In case of an error or if no entry is found result will be NULL. The functions return 0 on success and a nonzero error number on failure
    gethostbyname_r(hostname,
            (struct hostent *)buf,
            (char *)buf + sizeof(struct hostent),
            HOSTENT_SIZE - sizeof(struct hostent),
            &he, /* DIFFERENCE */
            &h_errnop);
    if(!he){ /* failure */
        he = NULL; /* set return code to NULL */
        free(buf);
        exit(1);
    }

    char *curr;
    struct sockaddr_in serveraddr;
    for(int i = 0; (curr = he->h_addr_list[i]) != NULL; i++) {
        //if(he->h_addrtype == AF_INET6){
        if(he->h_addrtype == AF_INET){   // only support ipv4
            serveraddr.sin_family = he->h_addrtype;
            memcpy(&serveraddr.sin_addr, curr, sizeof(struct in_addr));
            serveraddr.sin_port = htons((unsigned short)port);

            char s[64] = {'\0'};
            inet_ntop(AF_INET, &(serveraddr.sin_addr), s, 64);
            printf("get ip:%s\n", s);
        }else{
            // just print
            //struct sockaddr_in6 _addr;
            //memcpy(&_addr.sin6_addr, curr, sizeof(struct in6_addr));
            struct sockaddr_in6 *paddr = (struct sockaddr_in6*)curr;
            char s[64] = {'\0'};
            inet_ntop(AF_INET6, &(paddr->sin6_addr), s, 64);
            printf("get ipv6:%s\n", s);
        }
    }
    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    int enable_reuse = atoi(argv[4]);
    if(enable_reuse > 0){
        enable_reuse = 1;
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable_reuse, sizeof(int)) < 0){
            perror("set reuse failed\n");
            exit(1);
        }else{
            printf("set reuse succ!\n");
        }
    }

    int bindport = atoi(argv[3]);
    struct sockaddr_in clientaddr;
    clientaddr.sin_family = AF_INET;
    clientaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    clientaddr.sin_port = htons(bindport);

    if ((bind(sockfd, (struct sockaddr*)&clientaddr, sizeof(clientaddr))) != 0) {
        printf("socket bind failed...\n");
        exit(0);
    }
    else
        printf("Socket successfully binded.. port:%d\n", bindport);

    if (connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)) == -1) {
        perror("connect");
        exit(1);
    }else{
        printf("connect succ!\n");
        sleep(100);
        close(sockfd);
    }
}

通过代码可以验证客户端可以通过bind来使用指定端口连接服务器,以下均验证bind时,reuse属性导致情况:

  • 如不使用reuse模式,在第一个程序结束时,本地端口还处于TIME_WAIT状态。此时第2个客户端无论是否reuse都是无法再次使用的,提示绑定失败。
  • 在reuse模式下导致的TIME_WAIT状态,第2个客户端可以通过reuse模式使用,但是非reuse模式会绑定失败。
  • 在reuse模式下,可以同时启动多个客户端。但前提是连接不同的服务器地址。因为如果连接两个服务器地址相同>,此时网络五元组相同,协议栈根本无法区分,会报connect: Cannot assign requested address错误。
  • 理论上讲(因为没有亲自证实),reuse模式对listen是无效的。
0

评论 (0)

取消