babyos2(42) network(8) -- udp, SOCK_DGRAM, gethostbyname_udp sock dgram-程序员宅基地

技术标签: udp  dns  echo  SOCK_DGRAM  babyos2  

前面为babyos2实现了发简单的UDP包,以及解析DNS,但通常DNS解析是用户态通过socket发DNS请求并解析结果,UNIX一般会使用gethostbyname。这次准备实现SOCK_DGRAM,以及用户态发dns请求及解析,并实现一个简单的DNS echo 服务器和客户端验证。

1. SOCK_DGRAM

这里实现一个简单的SOCK_DGRAM,类似于前面实现的socket_local_t和socket_raw_t,socket_dgram_t也继承自socket_t类:

class socket_dgram_t : public socket_t {
public:
    const uint32 c_max_buffer_num = 32;

    socket_dgram_t();
    void  init();

    int32 create(uint32 family, uint32 type, uint32 protocol);
    int32 get_name(sock_addr_t* addr);
    int32 release();
    int32 dup(socket_t* socket);

    int32 bind(sock_addr_t* myaddr);
    int32 listen(uint32 backlog);
    int32 accept(socket_t* server_socket);
    int32 connect(sock_addr_t* user_addr);
    int32 read(void* buf, uint32 size);
    int32 write(void* buf, uint32 size);
    int32 send_to(void *buf, uint32 size, sock_addr_t* addr_to);
    int32 recv_from(void *buf, uint32 size, sock_addr_t* addr_from);

    int32 net_receive(uint32 ip, uint16 port, net_buf_t* buf);


    static void             init_dgram_sockets();
    static socket_t*        alloc_dgram_socket();
    static void             release_dgram_socket(socket_t* socket);
    static socket_dgram_t*  lookup_dgram_socket(sock_addr_inet_t* addr);
    static int32            bind_dgram_socket(socket_dgram_t* socket, sock_addr_inet_t* addr);
    static int32            dgram_net_receive(net_buf_t* buf, uint32 src_ip, uint16 src_port, uint32 dst_ip, uint16 dst_port);
    static void             get_not_used_port(socket_dgram_t* socket);

public:
    uint32                  m_ref;
    sock_addr_inet_t        m_addr;
    list_t<dgram_buf_t>     m_buffers;
    semaphore_t             m_buffer_sem;

    static spinlock_t       s_lock;
    static socket_dgram_t   s_dgram_sockets[MAX_DGRAM_SOCKET];
};

几个static 成员函数:

void socket_dgram_t::init_dgram_sockets()
{
    socket_dgram_t tmp;
    tmp.init();

    s_lock.init();
    for (int i = 0; i < MAX_DGRAM_SOCKET; i++) {
        memcpy(&s_dgram_sockets[i], &tmp, sizeof(socket_dgram_t));
    }
}

socket_t* socket_dgram_t::alloc_dgram_socket()
{
    locker_t locker(s_lock);

    socket_dgram_t* socket = s_dgram_sockets;
    for (int i = 0; i < MAX_DGRAM_SOCKET; i++, socket++) {
        if (socket->m_ref == 0) {
            socket->m_ref = 1;
            return socket;
        }
    }

    return NULL;
}

int32 socket_dgram_t::bind_dgram_socket(socket_dgram_t* socket, sock_addr_inet_t* addr)
{
    locker_t locker(s_lock);

    socket_dgram_t* s = s_dgram_sockets;
    for (int i = 0; i < MAX_DGRAM_SOCKET; i++, s++) {
        if (s->m_ref != 0 && s->m_addr == *addr) {
            return -1;
        }
    }

    socket->m_addr.m_ip = net_t::ntohl(addr->m_ip);
    socket->m_addr.m_port = net_t::ntohs(addr->m_port);

    return 0;
}

int32 socket_dgram_t::dgram_net_receive(net_buf_t* buf, uint32 src_ip, uint16 src_port, uint32 dst_ip, uint16 dst_port)
{
    locker_t locker(s_lock);

    socket_dgram_t* socket = s_dgram_sockets;
    for (int i = 0; i < MAX_DGRAM_SOCKET; i++, socket++) {
        if (socket->m_ref == 0) {
            continue;
        }

        if (socket->m_addr.m_port == dst_port) {
            if (socket->net_receive(src_ip, src_port, buf)) {
                return 0;
            }
        }
    }

    return -1;
}

void socket_dgram_t::get_not_used_port(socket_dgram_t* socket)
{
    locker_t locker(s_lock);

    socket_dgram_t* s = s_dgram_sockets;
    for (uint16 port = 4096; port < 65535; port++) {
        bool succ = true;
        for (int i = 0; i < MAX_DGRAM_SOCKET; i++, s++) {
            if (s->m_ref > 0 && s->m_addr.m_port == port) {
                succ = false;
                break;
            }
        }
        if (succ) {
            socket->m_addr.m_port = port;
            return;
        }
    }
}

init_dgram_sockets用于初始化预留的dgram sockets,tmp作用还是比较奇怪,用于拷贝虚函数表。

alloc_dgram_socket用于分配一个dgram socket.

bind_dgram_socket用于比较当前已分配的dgram socket,是否已绑定了当前端口号,若已绑定,则不允许再绑定;

get_not_used_port用于寻找一个未使用的端口号。

int32 socket_dgram_t::create(uint32 family, uint32 type, uint32 protocol)
{
    socket_t::create(family, type, protocol);
    if (protocol == 0) {
        protocol = socket_t::PROTO_UDP;
    }

    m_ref = 1;
    m_addr.m_ip = 0;
    m_addr.m_port = 0;
    m_buffers.init(os()->get_obj_pool_of_size());
    m_buffer_sem.init(0);

    return 0;
}

int32 socket_dgram_t::release()
{
    /* free all buffers */
    uint32 flag;
    spinlock_t* lock = m_buffers.get_lock();
    lock->lock_irqsave(flag);

    while (!m_buffers.empty()) {
        dgram_buf_t* dgram_buf = &(*m_buffers.begin());
        net_buf_t* buffer = dgram_buf->m_buf;
        os()->get_net()->free_net_buffer(buffer);
        m_buffers.pop_front();
    }

    lock->unlock_irqrestore(flag);

    /* set ref to 0 */
    m_ref = 0;
    return 0;
}

int32 socket_dgram_t::bind(sock_addr_t* myaddr)
{
    sock_addr_inet_t* addr = (sock_addr_inet_t *) myaddr;
    if (addr->m_ip == 0) {
        addr->m_ip = os()->get_net()->get_ipaddr();
    }

    return socket_dgram_t::bind_dgram_socket(this, addr);
}

create做创建初始化相关工作;

release用于使用完成,释放socket,主要是需要释放所有缓冲区,及引用计数清零。目前dgram socket不支持多引用计数,所以不是0就是1;

bind用于绑定端口号;

int32 socket_dgram_t::send_to(void *buf, uint32 size, sock_addr_t* addr_to)
{
    sock_addr_inet_t* addr = (sock_addr_inet_t *) addr_to;

    if (net_t::ntohs(m_addr.m_port) == 0) {
        m_addr.m_ip = os()->get_net()->get_ipaddr();
        socket_dgram_t::get_not_used_port(this);
    }

    os()->get_net()->get_udp()->transmit(net_t::ntohl(addr->m_ip), m_addr.m_port,
            net_t::ntohs(addr->m_port), (uint8 *) buf, size);

    return 0;
}

send_to用于发送UDP包到目的地址,若该socket尚未绑定端口号,需要寻找一个空闲的端口号给它;

int32 socket_dgram_t::recv_from(void *buf, uint32 size, sock_addr_t* addr_from)
{
    m_buffer_sem.down();

    uint32 flag;
    spinlock_t* lock = m_buffers.get_lock();
    lock->lock_irqsave(flag);

    if (!m_buffers.empty()) {
        dgram_buf_t* dgram_buf = &(*m_buffers.begin());

        sock_addr_inet_t* addr = (sock_addr_inet_t *) addr_from;
        addr->m_family = net_t::ntohs(dgram_buf->m_addr_from.m_family);
        addr->m_ip = net_t::ntohl(dgram_buf->m_addr_from.m_ip);
        addr->m_port = net_t::ntohs(dgram_buf->m_addr_from.m_port);

        net_buf_t* buffer = dgram_buf->m_buf;
        uint32 data_len = buffer->get_data_len();
        uint8* data = buffer->get_data();

        if (size > data_len) {
            size = data_len;
        }

        memcpy(buf, data, size);
        os()->get_net()->free_net_buffer(buffer);
        m_buffers.pop_front();
    }

    lock->unlock_irqrestore(flag);

    return 0;
}

recv_from用于从缓冲区中摘下一个buffer并拷贝数据到用户缓冲区,并给addr_from赋值,指明包的来源;然后释放相关的buffer等。

int32 socket_dgram_t::net_receive(uint32 ip, uint16 port, net_buf_t* buf)
{
    if (m_buffers.size() >= c_max_buffer_num) {
        return -ENOMEM;
    }

    uint32 flag;
    spinlock_t* lock = m_buffers.get_lock();
    lock->lock_irqsave(flag);
    dgram_buf_t dgram_buf;
    dgram_buf.init(ip, port, buf);
    m_buffers.push_back(dgram_buf);
    lock->unlock_irqrestore(flag);

    m_buffer_sem.up();

    return 0;
}

该函数用于从协议栈接收一个udp包到socket的缓冲区,该类的static函数遍历所有dgram socket匹配端口号并用找到的socket调用该函数。缓冲区中需要记录包的来源。

int32 udp_t::receive(net_buf_t* buf, uint32 ip)
{
    udp_hdr_t* hdr = (udp_hdr_t *) buf->get_data();
    udp_pseudo_hdr_t pseudo_hdr;
    uint16 total = net_t::ntohs(hdr->m_length);
    pseudo_hdr.init(net_t::htonl(ip),
                    net_t::htonl(os()->get_net()->get_ipaddr()),
                    ip_t::PROTO_UDP,
                    net_t::htons(total));


    net_buf_t* buffer = os()->get_net()->alloc_net_buffer(sizeof(pseudo_hdr) + buf->get_data_len());
    if (buffer == NULL) {
        return -1;
    }

    buffer->append(&pseudo_hdr, sizeof(udp_pseudo_hdr_t));
    buffer->append(buf->get_data(), total);
    uint16 check_sum = net_t::check_sum(buffer->get_data(), buffer->get_data_len());

    os()->get_net()->free_net_buffer(buffer);
    if (check_sum != 0) {
        //console()->kprintf(RED, "receive a UDP packet, but check sum is wrong\n");
        return -1;
    }

    buf->pop_front(sizeof(udp_hdr_t));
    return socket_dgram_t::dgram_net_receive(buf, ip, net_t::ntohs(hdr->m_src_port), 
            os()->get_net()->get_ipaddr(), net_t::ntohs(hdr->m_dst_port));
}

该函数为UDP接到一个包后做checksum校验,并交给适当的dgram socket处理。

2.gethostbyname

这里准备为babyos2实现一个简化版的gethostbyname,只通过name获取IP地址。

uint32 userlib_t::get_ip_by_name(const char* name)
{
    int sock_fd = userlib_t::socket(socket_t::AF_INET, socket_t::SOCK_DGRAM, 0);
    if (sock_fd < 0) {
        userlib_t::printf("ERROR, get_ip_by_name create socket failed, error %u\n", sock_fd);
        return -1;
    }


    sock_addr_inet_t addr;
    addr.m_family = socket_t::AF_INET;
    addr.m_ip = userlib_t::htonl(userlib_t::make_ipaddr(192, 168, 100, 1));
    addr.m_port = userlib_t::htons(53);


    uint8 buffer[512] = {0};
    for (int i = 0; i < 5; i++) {
        userlib_t::memset(buffer, 0, 512);
        dns_hdr_t hdr;
        prepare_dns_hdr(&hdr, i);
        memcpy(buffer, &hdr, sizeof(dns_hdr_t));


        uint32 count = sizeof(dns_hdr_t);
        int ret = prepare_dns_query(name, buffer + count);
        if (ret < 0) {
            userlib_t::printf("ERROR, get_ip_by_name invalid name\n");
            return -1;
        }
        count += ret;


        ret = userlib_t::send_to(sock_fd, buffer, count, &addr);
        if (ret < 0) {
            userlib_t::printf("ERROR, get_ip_by_name failed to send_to, error %u\n", ret);
            break;
        }


        userlib_t::memset(buffer, 0, 512);
        sock_addr_inet_t addr_recv;
        ret = userlib_t::recv_from(sock_fd, buffer, 512, &addr_recv);
        if (ret < 0) {
            userlib_t::printf("ERROR, get_ip_by_name failed to recv_from, error %u\n", ret);
            break;
        }


        uint32 ip = dns_resolve(name, buffer);
        if (ip != 0) {
            return ip;
        }
    }


    return 0;
}

该函数创建一个socket,准备好dns相关的数据,并send to DNS服务器;然后recv_from DNS服务器,接收到数据后解析结果。

准备数据,解析结果跟前面在内核中请求、解析基本相同,不再赘述。代码如下:

static void prepare_dns_hdr(dns_hdr_t* hdr, uint16 id)
{
    hdr->m_transaction_id         = userlib_t::htons(id); 
    hdr->m_flags.m_flags_qr       = 0;
    hdr->m_flags.m_flags_opcode   = 0;
    hdr->m_flags.m_flags_aa       = 0;
    hdr->m_flags.m_flags_tc       = 0;
    hdr->m_flags.m_flags_rd       = 1;
    hdr->m_flags.m_flags_ra       = 0;
    hdr->m_flags.m_flags_z        = 0;
    hdr->m_flags.m_flags_rcode    = 0;
    hdr->m_flags_val              = userlib_t::htons(hdr->m_flags_val);
    hdr->m_qd_count               = userlib_t::htons(1);
    hdr->m_an_count               = userlib_t::htons(0);
    hdr->m_ns_count               = userlib_t::htons(0);
    hdr->m_ar_count               = userlib_t::htons(0);
}

static uint32 resolve_name(const uint8* dns_data, const uint8* data, char* name)
{
    const uint8* start = data;
    uint8 len = (uint8) *data++;
    uint32 total = 0;
    while (len != 0) {
        if (len <= MAX_LABEL_LEN) {
            userlib_t::memcpy(name, data, len);
            name += len;
            data += len;
        }
        else {
            uint16 offset = ((len & 0x3f) << 8 | *data++);
            const uint8* p = dns_data + offset;
            uint32 count = resolve_name(dns_data, p, name);
            name += userlib_t::strlen(name);
            break;
        }

        len = *data++;
        if (len != 0) {
            *name++ = '.';
        }
    }

    *name++ = '\0';
    return data - start;
}

static uint32 prepare_dns_query(const char* name, uint8* buffer)
{
    const char* p = name;
    uint8* tmp = buffer;

    while (*p != '\0') {
        const char* begin = p;
        while (*p != '\0' && *p != '.') {
            p++;
        }
        if (p - begin > MAX_LABEL_LEN) {
            return -1;
        }
        
        uint8 count = p - begin;
        *buffer++ = count;

        userlib_t::memcpy(buffer, begin, count);
        buffer += count;

        if (*p == '\0') {
            *buffer++ = 0;

            *((uint16 *) buffer) = userlib_t::htons(0x0001);  /* type */
            buffer += 2;

            *((uint16 *) buffer) = userlib_t::htons(0x0001);  /* class */
            buffer += 2;

            return buffer - tmp; 
        }
        p++;
    }

    return -1;
}

static uint32 dns_resolve(const char* dest, const uint8* buffer)
{
    dns_hdr_t* hdr = (dns_hdr_t *) buffer;

    uint16 query_count = userlib_t::ntohs(hdr->m_qd_count);
    uint16 answer_count = userlib_t::ntohs(hdr->m_an_count);
    userlib_t::printf("ID: 0x%x, flags: 0x%x, questions num: %u, answer num: %u\n", 
            userlib_t::ntohs(hdr->m_transaction_id), userlib_t::ntohs(hdr->m_flags_val), 
            query_count, answer_count);

    const uint8* dns_data = buffer;
    const uint8* p = dns_data + sizeof(dns_hdr_t);
    char name[512] = {0};
    char dest_name[512] = {0};
    userlib_t::strcpy(dest_name, dest);

    userlib_t::printf("queries:\n");
    for (int i = 0; i < query_count; i++) {
        userlib_t::memset(name, 0, 512);
        p += resolve_name(dns_data, p, name);
        uint16* query_type = (uint16 *) p;
        uint16* query_class = (uint16 *) (query_type + 1);
        userlib_t::printf("%s, type 0x%4x, class 0x%4x\n", name, 
                userlib_t::ntohs(*query_type), userlib_t::ntohs(*query_class));
        p = (uint8 *) (query_class + 1);
    }

    userlib_t::printf("answers:\n");
    for (int i = 0; i < answer_count; i++) {
        userlib_t::memset(name, 0, 512);
        p += resolve_name(dns_data, p, name);

        uint16* ans_type = (uint16 *) p;
        uint16* ans_class = (uint16 *) (ans_type + 1);
        uint32* ttl = (uint32 *) (ans_class + 1);
        uint16* data_len = (uint16 *) (ttl + 1);
        userlib_t::printf("%s, type 0x%4x, class 0x%4x, ttl: 0x%8x, data len: 0x%4x -> ",  name, 
                userlib_t::ntohs(*ans_type), userlib_t::ntohs(*ans_class), 
                userlib_t::ntohl(*ttl), userlib_t::ntohs(*data_len));

        p = (uint8 *) (data_len + 1);
        if (userlib_t::ntohs(*ans_type) == RR_TYPE_A) {
            uint32* ip = (uint32 *) p;
            userlib_t::printf("0x%x\n", userlib_t::ntohl(*ip));
            if (userlib_t::strcmp(dest_name, name) == 0) {
                return *ip;
            }
        }
        else if (userlib_t::ntohs(*ans_type) == RR_TYPE_CNAME) {
            userlib_t::memset(name, 0, 512);
            resolve_name(dns_data, p, name);
            userlib_t::printf("%s\n", name);
            userlib_t::strcpy(dest_name, name);
        }
        p += userlib_t::ntohs(*data_len);
    }

    return 0;
}
void ns_lookup(const char* name)
{
    uint32 ip = userlib_t::get_ip_by_name(name);
    uint8* p = (uint8 *) &ip;
    userlib_t::printf("IP: %u.%u.%u.%u\n", p[0], p[1], p[2], p[3]);
}

3.nslookup结果:


可以发现能够正确解析www.baidu.com和www.qq.com

4.UDP echo server

const int TEST_UDP_PORT = 12345;
static void udp_server()
{
    int sock_fd = userlib_t::socket(socket_t::AF_INET, socket_t::SOCK_DGRAM, 0);
    if (sock_fd < 0) {
        userlib_t::printf("ERROR, server create socket failed, error %u\n", sock_fd);
        return;
    }
    userlib_t::printf("server create socket success, fd: %u\n", sock_fd);

    sock_addr_inet_t addr;
    addr.m_family = socket_t::AF_INET;
    addr.m_ip = userlib_t::htonl(sock_addr_inet_t::INADDR_ANY);
    addr.m_port = userlib_t::htons(TEST_UDP_PORT);
    if (userlib_t::bind(sock_fd, &addr) < 0) {
        userlib_t::printf("ERROR, server bind failed\n");
        return;
    }
    userlib_t::printf("server bind success\n");

    char buffer[512] = {0};
    for (; ; ) {
        userlib_t::memset(buffer, 0, 512);
        sock_addr_inet_t addr_client;
        int ret = userlib_t::recv_from(sock_fd, buffer, 512, &addr_client);
        if (ret < 0) {
            userlib_t::printf("ERROR, failed to recv_from, error %u\n", ret);
            break;
        }
        userlib_t::printf("server receive from %x, %u: %s\n", addr_client.m_ip, addr_client.m_port, buffer);

        ret = userlib_t::send_to(sock_fd, buffer, userlib_t::strlen(buffer), &addr_client);
        if (ret < 0) {
            userlib_t::printf("ERROR, failed to send_to, error %u\n", ret);
            break;
        }
    }
}

static void test_udp_server()
{
    int32 pid = userlib_t::fork();
    if (pid == 0) {
        /* server */
        udp_server();
        userlib_t::exit(0);
    }

    /* shell */
    userlib_t::wait(pid);
}

在shell中响应testudpserver命令,该命令执行时fork一个新进程来执行udp_server,

udup_server中创建一个SOCK_DGRAM类型的socket并绑定到12345端口等待客户端,客户端发来信息后在server端打印该信息,并将信息原样发回客户端。

5.UDP echo client

static void udp_client()
{
    int sock_fd = userlib_t::socket(socket_t::AF_INET, socket_t::SOCK_DGRAM, 0);
    if (sock_fd < 0) {
        userlib_t::printf("ERROR, client create socket failed, error %u\n", sock_fd);
        return;
    }
    userlib_t::printf("client create socket success, fd: %u\n", sock_fd);

    sock_addr_inet_t addr;
    addr.m_family = socket_t::AF_INET;
    addr.m_ip = userlib_t::htonl(userlib_t::make_ipaddr(192, 168, 1, 105));
    addr.m_port = userlib_t::htons(TEST_UDP_PORT);

    char buffer[512] = {0};
    for (int i = 0; i < 5; i++) {
        userlib_t::memset(buffer, 0, 512);
        userlib_t::gets(buffer, 512);
        int ret = userlib_t::send_to(sock_fd, buffer, userlib_t::strlen(buffer), &addr);
        if (ret < 0) {
            userlib_t::printf("ERROR, failed to send_to, error %u\n", ret);
            break;
        }

        userlib_t::memset(buffer, 0, 512);
        sock_addr_inet_t addr_recv;
        ret = userlib_t::recv_from(sock_fd, buffer, 512, &addr_recv);
        if (ret < 0) {
            userlib_t::printf("ERROR, failed to recv_from, error %u\n", ret);
            break;
        }

        userlib_t::printf("receive: %s\n", buffer);
    }
}

static void test_udp_client()
{
    int32 pid = userlib_t::fork();
    if (pid == 0) {
        /* server */
        udp_client();
        userlib_t::exit(0);
    }

    /* shell */
    userlib_t::wait(pid);
}

客户端功能类似,等待用户输入,并发送到服务端,然后等待服务端信息,接收到信息后打印信息。

6.echo 结果


可以发现能够在两个虚拟机之间分别运行UDP echo server和client,完成echo功能。


7.ping域名


ping使用get_ip_by_name获取IP。


后续计划:

终于到了TCP/IP最重要也是最困难的地方了:TCP, SOCK_STREAM.

希望能做出来,当然应该是只会做最关键的部分,只做基本功能,探究其基本原理。


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/guzhou_diaoke/article/details/79953916

智能推荐

基于Kepler.gl 和 Google Earth Engine 卫星数据创建拉伸多边形地图-程序员宅基地

文章浏览阅读965次,点赞18次,收藏21次。现在我们有了 2021 年和 2023 年的 NDVI 数据帧,我们需要从 2021 年的值中减去 2023 年的值以捕获 NDVI 的差异。该数据集包括像素级别的植被值,我们将编写一个自定义函数来根据红色和绿色波段的表面反射率计算 NDVI。在我的上一篇文章中,我演示了如何将单个多边形分割/镶嵌为一组大小均匀的六边形。现在我们有了植被损失数据,让我们使用 Kepler.gl 可视化每个六边形的植被损失。将地图保存为 HTML 文件,在浏览器中打开 HTML 以获得更好的视图。现在我们将调用该函数并使用、

Echarts绘制任意数据的正态分布图_echarts正态分布图-程序员宅基地

文章浏览阅读3.3k次,点赞6次,收藏5次。正态分布,又称高斯分布或钟形曲线,是统计学中最为重要和常用的分布之一。_echarts正态分布图

Android中发送短信等普通方法_android bundle.get("pdus");-程序员宅基地

文章浏览阅读217次。首先要在Mainfest.xml中加入所需要的权限:[html] view plain copyprint?uses-permission android:name="android.permission.SEND_SMS"/> uses-permission android:name="android.permission.READ_SMS"/> _android bundle.get("pdus");

2021-07-26 WSL2 的安装和联网_wsl2 联网-程序员宅基地

文章浏览阅读2.6k次。0、说明最近在学习 Data Assimilation Research Testbed (DART) 相关内容,其软件是在 Unix/Linux 操作系统下编译和运行的 ,由于我的电脑是 Windows 10 的,DART 推荐可以使用 Windows Subsystem For Linux (WSL) 来创建一个 Windows 下的 Linux 子系统。以下的内容主要介绍如何安装 WSL2,以及 WSL2 的联网。1、如何在 Windows 10 下安装WSL具体的安装流程可以在 microso_wsl2 联网

DATABASE_LINK 数据库连接_添加 database link重复的数据库链接命-程序员宅基地

文章浏览阅读1k次。DB_LINK 介绍在本机数据库orcl上创建了一个prod_link的publicdblink(使用远程主机的scott用户连接),则用sqlplus连接到本机数据库,执行select * from scott.emp@prod_link即可以将远程数据库上的scott用户下的emp表中的数据获取到。也可以在本地建一个同义词来指向scott.emp@prod_link,这样取值就方便多了..._添加 database link重复的数据库链接命

云-腾讯云-实时音视频:实时音视频(TRTC)-程序员宅基地

文章浏览阅读3.1k次。ylbtech-云-腾讯云-实时音视频:实时音视频(TRTC)支持跨终端、全平台之间互通,从零开始快速搭建实时音视频通信平台1.返回顶部 1、腾讯实时音视频(Tencent Real-Time Communication,TRTC)拥有QQ十几年来在音视频技术上的积累,致力于帮助企业快速搭建低成本、高品质音视频通讯能力的完整解决方案。..._腾讯实时音视频 分享链接

随便推点

用c语言写个日历表_农历库c语言-程序员宅基地

文章浏览阅读534次,点赞10次,收藏8次。编写一个完整的日历表需要处理许多细节,包括公历和农历之间的转换、节气、闰年等。运行程序后,会输出指定年份的日历表。注意,这个程序只是一个简单的示例,还有很多可以改进和扩展的地方,例如添加节气、节日等。_农历库c语言

FL Studio21.1.1.3750中文破解百度网盘下载地址含Crack补丁_fl studio 21 注册机-程序员宅基地

文章浏览阅读1w次,点赞28次,收藏27次。FL Studio21.1.1.3750中文破解版是最优秀、最繁荣的数字音频工作站 (DAW) 之一,日新月异。它是一款录音机和编辑器,可让您不惜一切代价制作精美的音乐作品并保存精彩的活动画廊。为方便用户,FL Studio 21提供三种不同的版本——Fruity 版、Producer 版和签名版。所有这些版本都是独一无二的,同样具有竞争力。用户可以根据自己的需要选择其中任何一种。FL Studio21.1.1.3750中文版可以说是一站式综合音乐制作单位,可以让您录制、作曲、混音和编辑音乐。_fl studio 21 注册机

冯.诺伊曼体系结构的计算机工作原理是,冯 诺依曼型计算机的工作原理是什么...-程序员宅基地

文章浏览阅读1.3k次。冯诺依曼计算机工作原理冯 诺依曼计算机工作原理的核心是 和 程序控制世界上不同型号的计算机,就其工作原理而言,一般都是认为冯 诺依曼提出了什么原理冯 诺依曼原理中,计算机硬件系统由那五大部分组成的 急急急急急急急急急急急急急急急急急急急急急急冯诺依曼结构计算机工作原理的核心冯诺依曼结构和现代计算机结构模型 转载重学计算机组成原理 一 冯 诺依曼体系结构从冯.诺依曼的存储程序工作原理及计算机的组成来..._简述冯诺依曼计算机结构及工作原理

四国军棋引擎开发(2)简单的事件驱动模型下棋-程序员宅基地

文章浏览阅读559次。这次在随机乱下的基础上加上了一些简单的处理,如进营、炸棋、吃子等功能,在和敌方棋子产生碰撞之后会获取敌方棋子大小的一些信息,目前采用的是事件驱动模型,当下完一步棋界面返回结果后会判断是否触发了相关事件,有事件发生则处理相关事件,没有事件发生则仍然是随机下棋。1.事件驱动模型首先定义一个各种事件的枚举变量,目前的事件有工兵吃子,摸暗棋,进营,明确吃子,炸棋。定义如下:enum MoveE..._军棋引擎

STL与泛型编程-第一周笔记-Geekband-程序员宅基地

文章浏览阅读85次。1, 模板观念与函数模板简单模板: template< typename T > T Function( T a, T b) {… }类模板: template struct Object{……….}; 函数模板 template< class T> inline T Function( T a, T b){……} 不可以使用不同型别的..._geekband 讲义

vb.net正则表达式html,VB.Net常用的正则表达式(实例)-程序员宅基地

文章浏览阅读158次。"^\d+$"  //非负整数(正整数 + 0)"^[0-9]*[1-9][0-9]*$"  //正整数"^((-\d+)|(0+))$"  //非正整数(负整数 + 0)"^-[0-9]*[1-9][0-9]*$"  //负整数"^-?\d+$"    //整数"^\d+(\.\d+)?$"  //非负浮点数(正浮点数 + 0)"^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0..._vb.net 正则表达式 取html中的herf