技术标签: 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.
希望能做出来,当然应该是只会做最关键的部分,只做基本功能,探究其基本原理。
文章浏览阅读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...._七年级下册计算机知识点
文章浏览阅读3k次。在平时开发中,可能会遇到UFUN函数没有的功能,比如创建PTP的加工程序(我目前没找到,哪位大神可以指点一下),可以使用Grip创建PTP,然后用UFUN函数UF_call_grip调用Grip程序。具体如下截图(左侧UFUN,右侧Grip程序):..._uf调用grip
文章浏览阅读156次。第一个:原生普通样式(随着主题不同,样式会变)第二个:原生普通样式-小icon第三个:自定义RatingBar 颜色第四个:自定义RatingBar DrawableRatingBar 各样式实现===============原生样式原生样式其实没什么好说的,使用系统提供的style 即可<RatingBarstyle="?android:attr/ratingBarStyleIndicator"android:layout_width=“wrap_cont.._ratingbar样式修改
文章浏览阅读4.6k次,点赞6次,收藏11次。安装vs2017:参考vs2017下载和安装。安装cmake3.12.3:cmake是一个工程文件生成工具。用户可以使用预定义好的cmake脚本,根据自己的选择(像是Visual Studio, Code::Blocks, Eclipse)生成不同IDE的工程文件。可以从它官方网站的下载页上获取。这里我选择的是Win32安装程序,如图所示:然后就是运行安装程序进行安装就行。配置glfw3...._vs2017的opengl环境搭建(完整篇)
文章浏览阅读976次。MLC NAND,UBIFS_ubifs warning
文章浏览阅读2.2k次。计算机系统的两种存储器形式介绍时间:2016-1-6计算机系统的存储器一般应包括两个部分;一个是包含在计算机主机中的主存储器,简称内存,它直接和运算器,控制器及输入输出设备联系,容量小,但存取速度快,一般只存放那些急需要处理的数据或正在运行的程序;另一个是包含在外设中的外存储器,简称外存,它间接和运算器,控制器联系,存取速度虽然慢,但存储容量大,是用来存放大量暂时还不用的数据和程序,一旦要用时,就..._计算机存储器系统采用的是主辅结构,主存速度快、容量相对较小,用于 1 分 程序,外
文章浏览阅读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编程软件
文章浏览阅读36次。【代码】HashMap扩容。_hashma扩容
文章浏览阅读2.9k次。1mvn dependency:copy-dependencies2 项目右键 -> Maven -> Disable Maven Nature3 项目右键 -> Configure -> Convert to Maven Project_maven资源加载不全,怎么重新加载
文章浏览阅读527次。DMLDML的全称是Database management Language,数据库管理语言。主要包括以下操作:insert、delete、update、optimize。本篇对其逐一介绍INSERT数据库表插入数据的方式:1、insert的完整语法:(做项目的过程中将字段名全写上,这样比较容易看懂)单条记录插入语法:insert into table_name (column_name1,......_dml的全称是
文章浏览阅读136次。可以参考: http://git.oschina.net/jrain-group/ 组织下的Java Modbus支持库Modbus-系列文章1、虚拟成对串口(1)下载虚拟串口软件VSPD(可在百度中搜索)image.png(2)打开软件,添加虚拟串口。在设备管理中,看到如下表示添加成功。..._最好用的 modebus调试工具