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

智能推荐

在ubuntu 8.04下安装Oracle 11g二-程序员宅基地

文章浏览阅读408次。 在ubuntu 8.04下安装Oracle 11g2008年05月22日 星期四 11:02oracle 11g 数据库虽然提供了linux x86的版本,但是支持的linux版本只有Red Hat,Novell and Solaris 这几个,debian 和 ubuntu 不在支持之列,所以在ubuntu下安装就相对麻烦一些,请照着下文的方法一步一步的安装,不

初一计算机知识点下册,初一英语下册语法知识点全汇总-程序员宅基地

文章浏览阅读166次。新东方在线中考网整理了《初一英语下册语法知识点全汇总》,供同学们参考。一. 情态动词can的用法can+动词原形,它不随主语的人称和数而变化。1. 含有can的肯定句:主语+can+谓语动词的原形+其他。2. 含有can的否定句:主语+can't+动词的原形+其他。3. 变一般疑问句时,把can提前:Can+主语+动词原形+其他? 肯定回答:Yes,主语+can。否定回答:No,主语+can't...._七年级下册计算机知识点

NX/UG二次开发—其他—UFUN函数调用Grip程序_uf调用grip-程序员宅基地

文章浏览阅读3k次。在平时开发中,可能会遇到UFUN函数没有的功能,比如创建PTP的加工程序(我目前没找到,哪位大神可以指点一下),可以使用Grip创建PTP,然后用UFUN函数UF_call_grip调用Grip程序。具体如下截图(左侧UFUN,右侧Grip程序):..._uf调用grip

Android RatingBar的基本使用和自定义样式,kotlin中文教程_ratingbar样式修改-程序员宅基地

文章浏览阅读156次。第一个:原生普通样式(随着主题不同,样式会变)第二个:原生普通样式-小icon第三个:自定义RatingBar 颜色第四个:自定义RatingBar DrawableRatingBar 各样式实现===============原生样式原生样式其实没什么好说的,使用系统提供的style 即可<RatingBarstyle="?android:attr/ratingBarStyleIndicator"android:layout_width=“wrap_cont.._ratingbar样式修改

OpenGL环境搭建:vs2017+glfw3.2.1+glad4.5_vs2017的opengl环境搭建(完整篇)-程序员宅基地

文章浏览阅读4.6k次,点赞6次,收藏11次。安装vs2017:参考vs2017下载和安装。安装cmake3.12.3:cmake是一个工程文件生成工具。用户可以使用预定义好的cmake脚本,根据自己的选择(像是Visual Studio, Code::Blocks, Eclipse)生成不同IDE的工程文件。可以从它官方网站的下载页上获取。这里我选择的是Win32安装程序,如图所示:然后就是运行安装程序进行安装就行。配置glfw3...._vs2017的opengl环境搭建(完整篇)

在linux-4.19.78中使用UBIFS_ubifs warning-程序员宅基地

文章浏览阅读976次。MLC NAND,UBIFS_ubifs warning

随便推点

计算机系统内存储器介绍,计算机系统的两种存储器形式介绍-程序员宅基地

文章浏览阅读2.2k次。计算机系统的两种存储器形式介绍时间:2016-1-6计算机系统的存储器一般应包括两个部分;一个是包含在计算机主机中的主存储器,简称内存,它直接和运算器,控制器及输入输出设备联系,容量小,但存取速度快,一般只存放那些急需要处理的数据或正在运行的程序;另一个是包含在外设中的外存储器,简称外存,它间接和运算器,控制器联系,存取速度虽然慢,但存储容量大,是用来存放大量暂时还不用的数据和程序,一旦要用时,就..._计算机存储器系统采用的是主辅结构,主存速度快、容量相对较小,用于 1 分 程序,外

西门子PLC的编程工具是什么?_西门子plc编程软件-程序员宅基地

文章浏览阅读5.6k次。1. STEP 7(Simatic Manager):STEP 7或者Simatic Manager是西门子PLC编程最常用的软件开发环境。4. STEP 7 MicroWin:STEP 7 MicroWn是一款专门针对微型PLC(S7-200系列PLC)的编程软件,是Simatic Manager的简化版。如果需要与PLC系统配合使用,则需要与PLC编程工具进行配合使用。除了上述软件之外,西门子还提供了一些配套软件和工具,如PLC模拟器、硬件调试工具等,以帮助PLC编程人员快速地进行调试和测试。_西门子plc编程软件

HashMap扩容_hashma扩容-程序员宅基地

文章浏览阅读36次。【代码】HashMap扩容。_hashma扩容

Eclipse maven项目中依赖包不全,如何重新加载?_maven资源加载不全,怎么重新加载-程序员宅基地

文章浏览阅读2.9k次。1mvn dependency:copy-dependencies2 项目右键 -> Maven -> Disable Maven Nature3 项目右键 -> Configure -> Convert to Maven Project_maven资源加载不全,怎么重新加载

mysql dml全称中文_MySQL语言分类——DML-程序员宅基地

文章浏览阅读527次。DMLDML的全称是Database management Language,数据库管理语言。主要包括以下操作:insert、delete、update、optimize。本篇对其逐一介绍INSERT数据库表插入数据的方式:1、insert的完整语法:(做项目的过程中将字段名全写上,这样比较容易看懂)单条记录插入语法:insert into table_name (column_name1,......_dml的全称是

【小工匠聊Modbus】04-调试工具-程序员宅基地

文章浏览阅读136次。可以参考: http://git.oschina.net/jrain-group/ 组织下的Java Modbus支持库Modbus-系列文章1、虚拟成对串口(1)下载虚拟串口软件VSPD(可在百度中搜索)image.png(2)打开软件,添加虚拟串口。在设备管理中,看到如下表示添加成功。..._最好用的 modebus调试工具

推荐文章

热门文章

相关标签