searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

make check自动识别空闲端口

2023-09-27 03:35:02
9
0

问题:

如果一台机器上同时进行make check会出现只有一个能成功,另一个会终止的情况。经过调研发现,make check的底层代码中,make check的端口被写死了,不会自动识别空闲端口号,导致在同时进行make check时端口被占用之后,无法完成。

通过底层代码可以发现,各端口号是固定的。

在make check的过程中,用gdb进行调试,并对statrt_node函数打断点,可以发现端口号一直为50852。

为了解决上述问题,需要进行自动识别空闲的端口,使端口被抢占后,能使用其他端口进行make check。

idea1:

  1. 编写一个函数去判断是否端口被占用(通过bind去判断?)

  2. 被占用就把端口号做变更,进行下一次判断(如何变更端口号?是用rand()在范围内随机,还是通过取余加减的计算方式寻找端口号?端口号得在1025~65535)

  3. 当端口号没被占用,就把其赋值给所需端口

(调研之后)idea2:

  1. windows下有API可以调用,来获取空闲端口

    可以使用GetTcpTable()、GetUdpTable()获得已经占用的端口列表

    IPHLPAPI_DLL_LINKAGE ULONG GetTcpTable(
    [out]     PMIB_TCPTABLE TcpTable,
    [in, out] PULONG        SizePointer,
    [in]      BOOL          Order
    );
    IPHLPAPI_DLL_LINKAGE ULONG GetUdpTable(
    [out]     PMIB_UDPTABLE UdpTable,
    [in, out] PULONG        SizePointer,
    [in]      BOOL          Order
    );
  2. 通过上面两个api封装成接口,

    uint16_t FindAvailableTcpPort(uint16_t begin = PORT_DOWN, uint16_t end = PORT_UP);

    uint16_t FindAvailableUdpPort(uint16_t begin = PORT_DOWN, uint16_t end = PORT_UP);

    uint16_t FindAvailablePort(uint16_t begin = PORT_DOWN, uint16_t end = PORT_UP);

    这三个接口分别的作用是:获得Tcp空闲端口,获得Udp空闲端口,获得Tcp和Udp都空闲的端口。

  3. 通过接口来获得空闲端口
#include <Windows.h>
#include <WinSock.h>
#include <tcpmib.h>
#include <IPHlpApi.h>

#include <vector>
#include <memory>
#include <algorithm>
#include <iostream>

#pragma comment(lib, "WS2_32.lib")
#pragma comment(lib, "IPHlpApi.lib")

#define PORT_DOWN 49152
#define PORT_UP 65535

static std::vector<uint16_t> GetAllTcpConnectionsPort()
{
    std::vector<uint16_t> ret;
    ULONG size = 0;
    GetTcpTable(NULL, &size, TRUE);
    std::unique_ptr<char[]> buffer(new char[size]);

    PMIB_TCPTABLE tcpTable = reinterpret_cast<PMIB_TCPTABLE>(buffer.get());
    if (GetTcpTable(tcpTable, &size, FALSE) == NO_ERROR)
        for (size_t i = 0; i < tcpTable->dwNumEntries; i++)
            ret.push_back(ntohs((uint16_t)tcpTable->table[i].dwLocalPort));
    std::sort(std::begin(ret), std::end(ret));
    return ret;
}

static std::vector<uint16_t> GetAllUdpConnectionsPort()
{
    std::vector<uint16_t> ret;
    ULONG size = 0;
    GetUdpTable(NULL, &size, TRUE);
    std::unique_ptr<char[]> buffer(new char[size]);

    PMIB_UDPTABLE udpTable = reinterpret_cast<PMIB_UDPTABLE>(buffer.get());
    if (GetUdpTable(udpTable, &size, FALSE) == NO_ERROR)
        for (size_t i = 0; i < udpTable->dwNumEntries; i++)
            ret.push_back(ntohs((uint16_t)udpTable->table[i].dwLocalPort));
    std::sort(std::begin(ret), std::end(ret));
    return ret;
}

uint16_t FindAvailableTcpPort(uint16_t begin = PORT_DOWN, uint16_t end = PORT_UP)
{
    auto vec = GetAllTcpConnectionsPort();
    for (uint16_t port = begin; port != end; ++port)
        if (!std::binary_search(std::begin(vec), std::end(vec), port))
            return port;
    return 0;
}

uint16_t FindAvailableUdpPort(uint16_t begin = PORT_DOWN, uint16_t end = PORT_UP)
{
    auto vec = GetAllUdpConnectionsPort();
    for (uint16_t port = begin; port != end; ++port)
        if (!std::binary_search(std::begin(vec), std::end(vec), port))
            return port;
    return 0;
}

uint16_t FindAvailablePort(uint16_t begin = PORT_DOWN, uint16_t end = PORT_UP)
{
    auto vecTcp = GetAllTcpConnectionsPort(),
        vecUdp = GetAllUdpConnectionsPort();
    for (uint16_t port = begin; port != end; ++port)
        if (!std::binary_search(std::begin(vecTcp), std::end(vecTcp), port) &&
            !std::binary_search(std::begin(vecUdp), std::end(vecUdp), port))
            return port;
    return 0;
}

int main()
{
    {
        WSADATA wsaData;
        WSAStartup(0x0201, &wsaData);
    }
    std::cout << "Tcp Available port : " << FindAvailableTcpPort() << "\n";
    std::cout << "Udp Available port : " << FindAvailableUdpPort() << "\n";
    std::cout << "Available port : " << FindAvailablePort() << "\n";
    {
        WSACleanup();
    }
    return 0;
}

该方法是在windows下实现的,GetTcpTable()、GetUdpTable()两个函数是windows特有的,在linux下没有,所以该方法无法使用。

idea3:

根据idea1,调研发现,可以借助socket实现,当我们使用socket去bind端口时,如果将端口号sin_port定为0,系统则会自动给我们一个当前可用的端口,关闭socket后,这个端口就是我们需要的。

具体步骤:

  1. winsock初始化

  2. 初始化一个AF_INET、SOCK_STREAM的套接字

  3. sockaddr_in的属性设置中将port定为0

  4. bind成功后,使用getsockname获取对应信息,拿到系统分配的sin_port

  5. 关闭该套接字,返回上一步中的sin_port

代码实现:

#include <Windows.h>

#pragma comment(lib, "ws2_32.lib")

bool getAvaliablePort(unsigned short& port) {
    WSADATA wsadata;
    if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0) {
        return false;
    }
    SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = 0;

    struct sockaddr_in connAddr;
    int len = sizeof connAddr;

    if (::bind(sock, (SOCKADDR*)&addr, sizeof addr) != 0) {
        return false;
    }

    if (getsockname(sock, (SOCKADDR*)&connAddr, &len) != 0) {
        return false;
    }

    port = ntohs(connAddr.sin_port);

    if (closesocket(sock) != 0) {
        return false;
    }

    return true;
}

尽管该方法,是在windows下进行实现的,但是socket的思路可以运用在linux环境中,所以借鉴该方法的思路,可以在linux下进行尝试。

idea4:

依据idea3的思路,使用socket方式,借助bind()函数来寻找空闲端口号。

代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define PORT 8080

int main() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port = htons(PORT),
        .sin_addr.s_addr = INADDR_ANY
    };

    int reuse = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
        perror("setsockopt failed");
        exit(EXIT_FAILURE);
    }

    while (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        fprintf(stderr, "Failed to bind to port %d: %s\n", PORT, strerror(errno));
        fprintf(stderr, "Trying to bind to a different port\n");
        addr.sin_port = htons(ntohs(addr.sin_port) + 1);
    }

    printf("Listening on port %d...\n", ntohs(addr.sin_port));
    if (listen(sock, 5) == -1) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    // 等待连接并处理...
    
    close(sock);
    return 0;
}

idea5:

idea4是在linux下进行识别空闲端口的一种方法,但是调研中还发现了一种很取巧的方式。

netstat -an | grep :xxxx 该shell命令是过滤出xxxx监听端口,如果xxxx端口没有被使用,则过滤出来的是空。C语言调用system函数执行该命令,如果端口被占用,则system的返回值为0,如果未被使用,则返回值为256,这样我们就可以根据system函数的返回值来确认端口有没有被使用。

代码实现:

#include <stdio.h>
#include <stdlib.h>
 
#include <sys/ioctl.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
 
#define PORT_START 5000
#define PORT_END 6000
int main()
{
    int i_random_port[2] = {0};
    time_t t;
    char psz_port_cmd[BUFF_SIZE];
    srand((unsigned)time(&t));
    
    int i=0;
    while(1)
    {
        i_random_port[i] = rand() % (PORT_END - PORT_START + 1) + PORT_START;
        sprintf(psz_port_cmd, "netstat -an | grep :%d > /dev/null", i_random_port[i]);
        printf("%d\n",system(psz_port_cmd));
        if(i ==1 && system(psz_port_cmd) && i_random_port[1] != i_random_port[0])
        {
            break;
        }
        else if(system(psz_port_cmd) && i==0)
        {
            i++;
        }
            
    }
 
 
    printf("port=%d\n", i_random_port[0]);
    printf("port=%d\n", i_random_port[1]);
}

打印结果如下:

256
256
port=5708
port=5745

对比选择合适的方法

通过结合实际生产情况,可以对比idea4和idea5的优缺点。

idea 优点 缺点
4 兼容性更好,适合大部分的机器 识别一个端口就需要创建一个socket去循环bind()端口,效率较低
5 使用system()函数调用netstat命令,更方便快捷 兼容性较低,不是所有机器都有netstat命令

经过筛选,最终采用idea4,使用socket来解决识别空闲端口号的问题。

实际解决方法

为了解决多个端口的写死问题,采用封装识别空闲端口号的api,在具体代码中调用该api,可以保证每个端口号都是可以动态变化的。

方法测试

为了测试编写的代码能在实际中正常使用,就得确保如果端口号被占用,就会自动识别其他端口号。

采用测试的方法是使用“netstat -nlp | grep 端口号”来启动一个端口号50849,加一条printf()函数,打印make check的测试结果端口号50852被占用之后的输出结果。

先占用端口号50849

进行make check测试

可以发现,当50849端口被占用后,会自动识别到50859,端口占用问题被解决。

0条评论
作者已关闭评论
j****n
4文章数
0粉丝数
j****n
4 文章 | 0 粉丝
j****n
4文章数
0粉丝数
j****n
4 文章 | 0 粉丝
原创

make check自动识别空闲端口

2023-09-27 03:35:02
9
0

问题:

如果一台机器上同时进行make check会出现只有一个能成功,另一个会终止的情况。经过调研发现,make check的底层代码中,make check的端口被写死了,不会自动识别空闲端口号,导致在同时进行make check时端口被占用之后,无法完成。

通过底层代码可以发现,各端口号是固定的。

在make check的过程中,用gdb进行调试,并对statrt_node函数打断点,可以发现端口号一直为50852。

为了解决上述问题,需要进行自动识别空闲的端口,使端口被抢占后,能使用其他端口进行make check。

idea1:

  1. 编写一个函数去判断是否端口被占用(通过bind去判断?)

  2. 被占用就把端口号做变更,进行下一次判断(如何变更端口号?是用rand()在范围内随机,还是通过取余加减的计算方式寻找端口号?端口号得在1025~65535)

  3. 当端口号没被占用,就把其赋值给所需端口

(调研之后)idea2:

  1. windows下有API可以调用,来获取空闲端口

    可以使用GetTcpTable()、GetUdpTable()获得已经占用的端口列表

    IPHLPAPI_DLL_LINKAGE ULONG GetTcpTable(
    [out]     PMIB_TCPTABLE TcpTable,
    [in, out] PULONG        SizePointer,
    [in]      BOOL          Order
    );
    IPHLPAPI_DLL_LINKAGE ULONG GetUdpTable(
    [out]     PMIB_UDPTABLE UdpTable,
    [in, out] PULONG        SizePointer,
    [in]      BOOL          Order
    );
  2. 通过上面两个api封装成接口,

    uint16_t FindAvailableTcpPort(uint16_t begin = PORT_DOWN, uint16_t end = PORT_UP);

    uint16_t FindAvailableUdpPort(uint16_t begin = PORT_DOWN, uint16_t end = PORT_UP);

    uint16_t FindAvailablePort(uint16_t begin = PORT_DOWN, uint16_t end = PORT_UP);

    这三个接口分别的作用是:获得Tcp空闲端口,获得Udp空闲端口,获得Tcp和Udp都空闲的端口。

  3. 通过接口来获得空闲端口
#include <Windows.h>
#include <WinSock.h>
#include <tcpmib.h>
#include <IPHlpApi.h>

#include <vector>
#include <memory>
#include <algorithm>
#include <iostream>

#pragma comment(lib, "WS2_32.lib")
#pragma comment(lib, "IPHlpApi.lib")

#define PORT_DOWN 49152
#define PORT_UP 65535

static std::vector<uint16_t> GetAllTcpConnectionsPort()
{
    std::vector<uint16_t> ret;
    ULONG size = 0;
    GetTcpTable(NULL, &size, TRUE);
    std::unique_ptr<char[]> buffer(new char[size]);

    PMIB_TCPTABLE tcpTable = reinterpret_cast<PMIB_TCPTABLE>(buffer.get());
    if (GetTcpTable(tcpTable, &size, FALSE) == NO_ERROR)
        for (size_t i = 0; i < tcpTable->dwNumEntries; i++)
            ret.push_back(ntohs((uint16_t)tcpTable->table[i].dwLocalPort));
    std::sort(std::begin(ret), std::end(ret));
    return ret;
}

static std::vector<uint16_t> GetAllUdpConnectionsPort()
{
    std::vector<uint16_t> ret;
    ULONG size = 0;
    GetUdpTable(NULL, &size, TRUE);
    std::unique_ptr<char[]> buffer(new char[size]);

    PMIB_UDPTABLE udpTable = reinterpret_cast<PMIB_UDPTABLE>(buffer.get());
    if (GetUdpTable(udpTable, &size, FALSE) == NO_ERROR)
        for (size_t i = 0; i < udpTable->dwNumEntries; i++)
            ret.push_back(ntohs((uint16_t)udpTable->table[i].dwLocalPort));
    std::sort(std::begin(ret), std::end(ret));
    return ret;
}

uint16_t FindAvailableTcpPort(uint16_t begin = PORT_DOWN, uint16_t end = PORT_UP)
{
    auto vec = GetAllTcpConnectionsPort();
    for (uint16_t port = begin; port != end; ++port)
        if (!std::binary_search(std::begin(vec), std::end(vec), port))
            return port;
    return 0;
}

uint16_t FindAvailableUdpPort(uint16_t begin = PORT_DOWN, uint16_t end = PORT_UP)
{
    auto vec = GetAllUdpConnectionsPort();
    for (uint16_t port = begin; port != end; ++port)
        if (!std::binary_search(std::begin(vec), std::end(vec), port))
            return port;
    return 0;
}

uint16_t FindAvailablePort(uint16_t begin = PORT_DOWN, uint16_t end = PORT_UP)
{
    auto vecTcp = GetAllTcpConnectionsPort(),
        vecUdp = GetAllUdpConnectionsPort();
    for (uint16_t port = begin; port != end; ++port)
        if (!std::binary_search(std::begin(vecTcp), std::end(vecTcp), port) &&
            !std::binary_search(std::begin(vecUdp), std::end(vecUdp), port))
            return port;
    return 0;
}

int main()
{
    {
        WSADATA wsaData;
        WSAStartup(0x0201, &wsaData);
    }
    std::cout << "Tcp Available port : " << FindAvailableTcpPort() << "\n";
    std::cout << "Udp Available port : " << FindAvailableUdpPort() << "\n";
    std::cout << "Available port : " << FindAvailablePort() << "\n";
    {
        WSACleanup();
    }
    return 0;
}

该方法是在windows下实现的,GetTcpTable()、GetUdpTable()两个函数是windows特有的,在linux下没有,所以该方法无法使用。

idea3:

根据idea1,调研发现,可以借助socket实现,当我们使用socket去bind端口时,如果将端口号sin_port定为0,系统则会自动给我们一个当前可用的端口,关闭socket后,这个端口就是我们需要的。

具体步骤:

  1. winsock初始化

  2. 初始化一个AF_INET、SOCK_STREAM的套接字

  3. sockaddr_in的属性设置中将port定为0

  4. bind成功后,使用getsockname获取对应信息,拿到系统分配的sin_port

  5. 关闭该套接字,返回上一步中的sin_port

代码实现:

#include <Windows.h>

#pragma comment(lib, "ws2_32.lib")

bool getAvaliablePort(unsigned short& port) {
    WSADATA wsadata;
    if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0) {
        return false;
    }
    SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = 0;

    struct sockaddr_in connAddr;
    int len = sizeof connAddr;

    if (::bind(sock, (SOCKADDR*)&addr, sizeof addr) != 0) {
        return false;
    }

    if (getsockname(sock, (SOCKADDR*)&connAddr, &len) != 0) {
        return false;
    }

    port = ntohs(connAddr.sin_port);

    if (closesocket(sock) != 0) {
        return false;
    }

    return true;
}

尽管该方法,是在windows下进行实现的,但是socket的思路可以运用在linux环境中,所以借鉴该方法的思路,可以在linux下进行尝试。

idea4:

依据idea3的思路,使用socket方式,借助bind()函数来寻找空闲端口号。

代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define PORT 8080

int main() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port = htons(PORT),
        .sin_addr.s_addr = INADDR_ANY
    };

    int reuse = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
        perror("setsockopt failed");
        exit(EXIT_FAILURE);
    }

    while (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        fprintf(stderr, "Failed to bind to port %d: %s\n", PORT, strerror(errno));
        fprintf(stderr, "Trying to bind to a different port\n");
        addr.sin_port = htons(ntohs(addr.sin_port) + 1);
    }

    printf("Listening on port %d...\n", ntohs(addr.sin_port));
    if (listen(sock, 5) == -1) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    // 等待连接并处理...
    
    close(sock);
    return 0;
}

idea5:

idea4是在linux下进行识别空闲端口的一种方法,但是调研中还发现了一种很取巧的方式。

netstat -an | grep :xxxx 该shell命令是过滤出xxxx监听端口,如果xxxx端口没有被使用,则过滤出来的是空。C语言调用system函数执行该命令,如果端口被占用,则system的返回值为0,如果未被使用,则返回值为256,这样我们就可以根据system函数的返回值来确认端口有没有被使用。

代码实现:

#include <stdio.h>
#include <stdlib.h>
 
#include <sys/ioctl.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
 
#define PORT_START 5000
#define PORT_END 6000
int main()
{
    int i_random_port[2] = {0};
    time_t t;
    char psz_port_cmd[BUFF_SIZE];
    srand((unsigned)time(&t));
    
    int i=0;
    while(1)
    {
        i_random_port[i] = rand() % (PORT_END - PORT_START + 1) + PORT_START;
        sprintf(psz_port_cmd, "netstat -an | grep :%d > /dev/null", i_random_port[i]);
        printf("%d\n",system(psz_port_cmd));
        if(i ==1 && system(psz_port_cmd) && i_random_port[1] != i_random_port[0])
        {
            break;
        }
        else if(system(psz_port_cmd) && i==0)
        {
            i++;
        }
            
    }
 
 
    printf("port=%d\n", i_random_port[0]);
    printf("port=%d\n", i_random_port[1]);
}

打印结果如下:

256
256
port=5708
port=5745

对比选择合适的方法

通过结合实际生产情况,可以对比idea4和idea5的优缺点。

idea 优点 缺点
4 兼容性更好,适合大部分的机器 识别一个端口就需要创建一个socket去循环bind()端口,效率较低
5 使用system()函数调用netstat命令,更方便快捷 兼容性较低,不是所有机器都有netstat命令

经过筛选,最终采用idea4,使用socket来解决识别空闲端口号的问题。

实际解决方法

为了解决多个端口的写死问题,采用封装识别空闲端口号的api,在具体代码中调用该api,可以保证每个端口号都是可以动态变化的。

方法测试

为了测试编写的代码能在实际中正常使用,就得确保如果端口号被占用,就会自动识别其他端口号。

采用测试的方法是使用“netstat -nlp | grep 端口号”来启动一个端口号50849,加一条printf()函数,打印make check的测试结果端口号50852被占用之后的输出结果。

先占用端口号50849

进行make check测试

可以发现,当50849端口被占用后,会自动识别到50859,端口占用问题被解决。

文章来自个人专栏
postgresql学习
4 文章 | 1 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0