Linux C简单的web服务器

                               Linux C简单的web服务器

 

目录

Linux C简单的web服务器

一、基础类型重命名

二、包裹函数(wrap.h/wrap.c 主要是网络通讯和多线程的包裹函数)

三、服务端程序(web_server.h/web_server.c)—— 使用EPOLL高并发机制

四、HTTP解析(http.h/http.c写了一个基本的框架,很容易添加需要解析的文件类型)

五、项目目录组织(以项目的角度建立工程)

六、编译执行


HTTP基本协议

HTTP基本协议参照上面链接。我这里只介绍软件的设计过程和源码。

一、基础类型重命名

#ifndef _TYPE_H_
#define _TYPE_H_/* exact-width signed integer types */
typedef   signed           char int8_t;
typedef   signed short     int int16_t;
typedef   signed           int int32_t;/* exact-width unsigned integer types */
typedef unsigned           char uint8_t;
typedef unsigned short     int uint16_t;
typedef unsigned           int uint32_t;
//typedef unsigned           int size_t;#ifndef NULL
#ifdef __cplusplus              // EC++
#define NULL   0
#else
#define NULL   ((void *) 0)
#endif
#endif#ifndef boolean
typedef uint8_t boolean;
#endif#ifndef FALSE
#define FALSE 0
#endif#ifndef TRUE
#define TRUE  1
#endif#endif /* _TYPE_H_ */

二、包裹函数(wrap.h/wrap.c 主要是网络通讯和多线程的包裹函数)

#ifndef WRAP_H
#define WRAP_H#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <pthread.h>//===================================Socket/File Wrapper Function==========================================
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
void Bind(int fd, const struct sockaddr* sa, socklen_t salen);
void Connect(int fd, const struct sockaddr* sa, socklen_t salen);
void Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
void Setsockopt(int fd, int level, int optname, void *optval, socklen_t optlen);
int Ioctl(int d, int request, ...);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void*vptr, size_t n);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
int Open(const char *pathname, int flags, mode_t mode);
void Close(int fd);//========================================Epoll Wrapper function============================================
int Epollcreate(int size);
int Epollctl(int epfd, int op, int fd, struct epoll_event *event);
int Epollwait(int epfd, struct epoll_event * events, int maxevents, int timeout);//======================================pthread Wrapper function============================================
void Pthread_create(pthread_t *tid, const pthread_attr_t *attr, void * (*func)(void *), void *arg);
void Pthread_detach(pthread_t tid);
void Pthread_join(pthread_t tid, void **status);
void Pthread_mutex_lock(pthread_mutex_t *mptr);
void Pthread_mutex_unlock(pthread_mutex_t *mptr);
void Pthread_cond_signal(pthread_cond_t *cptr);
void Pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);#endif
#include "wrap.h"static void perr_exit(const char *s)
{perror(s);exit(1);
}int Accept(int fd, struct sockaddr *sa, socklen_t* salenptr)
{int newfd;
again:if((newfd = accept(fd, sa, salenptr)) < 0){if((errno == ECONNABORTED) || (errno == EINTR)){goto again;}else{perr_exit("accept error");}}return newfd;
}void Bind(int fd, const struct sockaddr* sa, socklen_t salen)
{if(bind(fd, sa, salen) < 0){perr_exit("bind error");}
}void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{if(connect(fd, sa, salen) < 0){perr_exit("connect error");}
}void Listen(int fd, int backlog)
{if(listen(fd, backlog) < 0){perr_exit("listen error");}
}int Socket(int family, int type, int protocol)
{int socketfd;if((socketfd = socket(family, type, protocol)) < 0){perr_exit("socket error");}return socketfd;
}void Setsockopt(int fd, int level, int optname, void *optval, socklen_t optlen)
{if(-1 == setsockopt(fd, level, optname, optval, optlen)){perr_exit("setsockopt error");}
}ssize_t Read(int fd, void *ptr, size_t nbytes)
{ssize_t n;
again:if((n = read(fd, ptr, nbytes)) == -1){if(errno == EINTR){goto again;}else{return -1;}}return n;
}ssize_t Write(int fd, const void *ptr, size_t nbytes)
{ssize_t n;
again:if((n = write(fd, ptr, nbytes)) == -1){if(errno == EINTR){goto again;}else{return -1;}}return n;
}ssize_t Readn(int fd, void *vptr, size_t n)
{size_t nleft;ssize_t nread;char *ptr;ptr = vptr;nleft = n;while(nleft > 0){if((nread = read(fd, ptr, nleft)) < 0){if(errno == EINTR){nread = 0;}else{return -1;}}else if(nread == 0){break;}nleft -= nread;ptr += nread;}return (n - nleft);
}ssize_t Writen(int fd, const void *vptr, size_t n)
{size_t nleft;ssize_t nwritten;const char* ptr;ptr = vptr;nleft = n;while(nleft > 0){if((nwritten = write(fd, ptr, nleft)) <= 0){if(nwritten < 0 && errno == EINTR){nwritten = 0;}else{return -1;}}nleft -= nwritten;ptr += nwritten;}return n;
}static ssize_t my_read(int fd, char *ptr)
{static int read_cnt;static char *read_ptr;static char read_buf[100];if(read_cnt <= 0){
again:if((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0){if(errno == EINTR){goto again;}else{return -1;}}else if(read_cnt == 0){return 0;}read_ptr = read_buf;}read_cnt--;*ptr = *read_ptr++;return 1;
}ssize_t Readline(int fd, void *vptr, size_t maxlen)
{ssize_t n, rc;char c, *ptr;ptr = vptr;for(n = 1; n < maxlen; n++){if((rc = my_read(fd, &c)) == 1){*ptr++ = c;if(c == '\n'){break;}}else if(rc == 0){*ptr = 0;return n - 1;}else{return (n - 1);}}*ptr = 0;return n;
}int Open(const char *pathname, int flags, mode_t mode)
{int fd = open(pathname, flags, mode);if( -1 == fd ){perr_exit("open file error...");}return fd;
}void Close(int fd)
{if(close(fd) == -1){perr_exit("close error...");}
}int Epollcreate(int size)
{int sockfd;if((sockfd = epoll_create(size)) == -1){perr_exit("epoll create error...");}return sockfd;
}int Epollctl(int epfd, int op, int fd, struct epoll_event *event)
{int status;if((status = epoll_ctl(epfd, op, fd, event)) == -1){perr_exit("epoll ctl error...");}return status;
}int Epollwait(int epfd, struct epoll_event * events, int maxevents, int timeout)
{int ret;if((ret = epoll_wait(epfd, events, maxevents, timeout)) == -1){perr_exit("epoll wait error...");}return ret;
}void Pthread_create(pthread_t *tid, const pthread_attr_t *attr,void * (*func)(void *), void *arg)
{int n = pthread_create(tid, attr, func, arg);if ( n == 0){return;}errno = n;perr_exit("pthread_create error");
}void Pthread_detach(pthread_t tid)
{int n = pthread_detach(tid);if ( n == 0){return;}errno = n;perr_exit("pthread_detach error");
}void Pthread_join(pthread_t tid, void **status)
{int n = pthread_join(tid, status);if ( n == 0 ){return;}errno = n;perr_exit("pthread_join error");
}void Pthread_mutex_lock(pthread_mutex_t *mptr)
{int n = pthread_mutex_lock(mptr);if ( n == 0 ){return;}errno = n;perr_exit("pthread_mutex_lock error...");
}void Pthread_mutex_unlock(pthread_mutex_t *mptr)
{int n = pthread_mutex_unlock(mptr);if ( n == 0 ){return;}errno = n;perr_exit("pthread_mutex_unlock error...");
}void Pthread_cond_signal(pthread_cond_t *cptr)
{int n = pthread_cond_signal(cptr);if ( n == 0 ){return;}errno = n;perr_exit("pthread_cond_signal error...");
}void Pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr)
{int n = pthread_cond_wait(cptr, mptr);if ( n == 0 ){return;}errno = n;perr_exit("pthread_cond_wait error...");
}

三、服务端程序(web_server.h/web_server.c)—— 使用EPOLL高并发机制

#ifndef SERVER_H_
#define SERVER_H_#include <pthread.h>
#include <sys/epoll.h>
#include "wrap.h"
#include "klist.h"#define WEB_SERVER_PORT     80
#define WEB_SOCKET_EVENTS   65535typedef struct
{int client_fd;struct sockaddr_in client_addr;struct list_head list;
} web_client_t;typedef struct
{int sockfd;                // server socketint port;                  // server portstruct sockaddr_in addr;   // server addrint epollfd;               // epoll handlestruct epoll_event event_list[WEB_SOCKET_EVENTS]; // epoll event listpthread_t recv_thread;web_client_t client;       // client list -- save all client info
} web_server_t;/* recv and send queue frame */
#define TCP_FRAME_SIZE 1200
typedef struct
{int sockfd;  // client socketuint16_t length;char data[TCP_FRAME_SIZE];
} web_frame_t;void *WebServertInit(void);#endif /* SERVER_H_ */
#include "web_server.h"
#include "http.h"static web_server_t *web_socket_init(void)
{int opt = 1;web_server_t *current;struct epoll_event event;current = (web_server_t *)malloc(sizeof(web_server_t));current->port = WEB_SERVER_PORT;current->sockfd = Socket(AF_INET, SOCK_STREAM, 0);// SOL_SOCKET: port can same, ip notSetsockopt(current->sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));current->addr.sin_family = AF_INET;current->addr.sin_port = htons(current->port);current->addr.sin_addr.s_addr = INADDR_ANY;Bind(current->sockfd, (struct sockaddr *)&current->addr, sizeof(current->addr));Listen(current->sockfd, 10);current->epollfd = Epollcreate(WEB_SOCKET_EVENTS);event.events = EPOLLIN | EPOLLET;event.data.fd = current->sockfd;// epoll_ctl set addEpollctl(current->epollfd, EPOLL_CTL_ADD, current->sockfd, &event);INIT_LIST_HEAD(&current->client.list);return current;
}static void web_socket_accept(web_server_t *arg)
{web_server_t *current = arg;struct sockaddr_in addr;int len = sizeof(struct sockaddr_in);int new_fd = Accept(current->sockfd, (struct sockaddr *)&addr, (socklen_t *)&len);printf("new connection client_fd ( %d ) %s: %d\n", new_fd, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));/* register epoll */struct epoll_event event;event.data.fd = new_fd;event.events = EPOLLIN | EPOLLET;Epollctl(current->epollfd, EPOLL_CTL_ADD, new_fd, &event);/* add client node */web_client_t *node = (web_client_t *)malloc(sizeof(web_client_t));node->client_fd = new_fd;memcpy(&node->client_addr, &addr, sizeof(struct sockaddr_in));list_add(&node->list, &current->client.list);
}static void web_socket_recv(web_server_t *arg, int sockfd)
{web_server_t *current = arg;int length = 0;web_frame_t frame = {0};web_client_t *point = NULL;struct list_head *pos, *cur = NULL;length = read(sockfd, frame.data, TCP_FRAME_SIZE);if(0 == length){list_for_each_safe(pos, cur, &current->client.list){/* delete client node, close connect socket */point = list_entry(pos, web_client_t, list);if(point->client_fd == sockfd){printf("client[%d] close\n", sockfd);list_del(pos);free(point);Close(sockfd);}}}else if(length > 0){frame.sockfd = sockfd;frame.length = length;printf("***************************************\r\n");printf("%s\r\n", frame.data);printf("***************************************\r\n");HTTPSend(sockfd, frame.data, frame.length);#if 0list_for_each_safe(pos, cur, &current->client.list){/* delete client node, close connect socket */point = list_entry(pos, web_client_t, list);if(point->client_fd == sockfd){printf("client[%d] close\n", sockfd);list_del(pos);free(point);Close(sockfd);return;}}
#endif}else{exit(-1);}}static void *server_recv_thread(void *arg)
{web_server_t *current = (web_server_t *)arg;while(1){int timeout = 300, i;int ret = Epollwait(current->epollfd, current->event_list, WEB_SOCKET_EVENTS, timeout);if(ret == 0){// timeoutcontinue;}for(i = 0; i < ret; ++i){if((current->event_list[i].events & EPOLLERR) ||(current->event_list[i].events & EPOLLHUP) ||!(current->event_list[i].events & EPOLLIN)){printf("epoll error\n");Close(current->event_list[i].data.fd);exit(-1);}if(current->event_list[i].data.fd == current->sockfd){web_socket_accept(current);}else{web_socket_recv(current, current->event_list[i].data.fd);}}}Close(current->epollfd);Close(current->sockfd);return NULL;
}void * WebServertInit(void)
{web_server_t *current = web_socket_init();Pthread_create(&current->recv_thread, NULL, server_recv_thread, current);return (void *)current;
}

四、HTTP解析(http.h/http.c写了一个基本的框架,很容易添加需要解析的文件类型)

#ifndef _HTTP_H_
#define _HTTP_H_#include "type.h"void HTTPSend(int socketfd, char *content, uint16_t length);#endif /* _HTTP_H_ */
#include "http.h"
#include <stdio.h>
#include <string.h>
#include "wrap.h"static void http_html_cb(int sockfd, const char *filename);
static void http_jpg_cb(int sockfd, const char *filename);
static void http_gif_cb(int sockfd, const char *filename);typedef struct
{char *type;void (* http_cb)(int sockfd, const char *data);
} http_type_t;static http_type_t http_items[] =
{{".html", http_html_cb},{".jpg",  http_jpg_cb},{".jpeg", http_jpg_cb},{".gif" , http_gif_cb},
};static const char *http_ack = "HTTP/1.1 200 OK\r\n""Content-Type: %s\r\n""Server: LiBang's Server V1.0\r\n""Accept-Ranges: bytes\r\n""Content-Length: %d\r\n\r\n";static void http_url(char *http_head, char *file_type, char *filename)
{uint16_t i = 0, j = 0, k = 0;while(http_head[i] != '/'){++i;}while(http_head[i] != '.'){++i;filename[k++] = http_head[i];}while(http_head[i] != ' '){file_type[j++] = http_head[i++];filename[k++] = http_head[i];}filename[k - 1] = '\0'; // delete ' '
}static void http_pakedge_send(int sockfd, const char *filename, char *type)
{FILE *filefd;char http_head[2048] = {0}, buffer[1024000] = {0};uint32_t len;filefd = fopen(filename, "rb");if(filefd == NULL){printf("\r\n error...\r\n");return;}fseek(filefd, 0, SEEK_END);len = ftell(filefd);rewind(filefd);fread(buffer, len, 1, filefd);sprintf(http_head, http_ack, type, len);send(sockfd, http_head, strlen(http_head), 0);send(sockfd, buffer, len, 0);
}static void http_html_cb(int sockfd, const char *filename)
{http_pakedge_send(sockfd, filename, "text/html");
}static void http_jpg_cb(int sockfd, const char *filename)
{http_pakedge_send(sockfd, filename, "image/jpeg");
}static void http_gif_cb(int sockfd, const char *filename)
{http_pakedge_send(sockfd, filename, "image/gif");
}void HTTPSend(int sockfd, char *content, uint16_t length)
{char i, type[36] = {0}, filename[100] = {0};http_url(content, type, filename);printf("URL: %s,  filename: %s\r\n", type, filename);for(i = 0; i < sizeof(http_items) / sizeof(http_items[0]); ++i){if(memcmp(http_items[i].type, type, strlen(http_items[i].type)) == 0){http_items[i].http_cb(sockfd, filename);break;}}
}

要添加的文件类型就在http_items这个结构体数组里面,按照格式添加就OK了。

接下来创建一个html文件(index.html)

<html>
<head><title> Test Page </title></head>
<body><p> Test OK </p><img src = "mypic.jpg">
</body>\
</html>

在这个html里面,网页会显示“Test OK”和一张图片(这个图片自己想办法弄吧,哈哈)。

到这里,项目的架构基本上就完成了,再建一个main.c文件,调用一下接口就完了。

#include <stdio.h>
#include "web_server.h"int main(void)
{WebServertInit();printf("------------test-----------\n");while(1){sleep(1);}return 0;
}

五、项目目录组织(以项目的角度建立工程)

common/

               klist.h  type.h  wrap.c  wrap.h  Makefile

webserver/

               http.h  http.c  web_server.h  web_server.c  Makefile

index.html

mypic.jpj

main.c

Makefile

Makefile.build

顶层目录有一个Makefile,各子层目录下都有一个Makefile。

先写顶层目录下的Makefile和Makefile.build

依次是Makefile和Mkefile.build文件,最终会在顶层生成一个可执行文件http(目录可以自行指定):


CROSS_COMPILE =AS		= $(CROSS_COMPILE)as
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nmSTRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdumpexport AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMPCFLAGS := -Wall -O2 -g 
CFLAGS += -I $(shell pwd)/common
CFLAGS += -I $(shell pwd)/webserver
CFLAGS += -I $(shell pwd)/LDFLAGS := -lm -lpthreadexport CFLAGS LDFLAGSTOPDIR := $(shell pwd)
export TOPDIRTARGET := httpobj-y += common/
obj-y += webserver/
obj-y += main.oall : make -C ./ -f $(TOPDIR)/Makefile.build$(CC) -o $(TARGET) built-in.o $(LDFLAGS)clean:rm -f $(shell find -name "*.o")rm -f $(shell find -name "*.d")rm -f $(TARGET)
PHONY := __build
__build:obj-y :=
subdir-y :=include Makefile__subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y	+= $(__subdir-y)subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))ifneq ($(dep_files),)include $(dep_files)
endifPHONY += $(subdir-y)__build : $(subdir-y) built-in.o$(subdir-y):make -C $@ -f $(TOPDIR)/Makefile.buildbuilt-in.o : $(cur_objs) $(subdir_objs)$(LD) -r -o $@ $^dep_file = .$@.d%.o : %.c$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<.PHONY : $(PHONY)

好了,说完了顶层目录(我就不给各位详细分析Makefile了,我以前也写过Makefile的博客,可以参考),再说说各子层Makefile的编写。上面把模板写好了,各子层就只需要包含它自己的.o文件就好。

common目录下的Makefile:

obj-y += wrap.o

webserver目录下的Makefile:

obj-y += http.o
obj-y += web_server.o

六、编译执行

切换到顶层目录,执行以下make即可生成可执行文件http。

有四个警告,你们去消除吧,哈哈,执行以下看看效果呗,在web网页上面输入 http://自己的IP/index.html,这个index.html是自己编写的。好了看看效果吧,正常解析出了这个html。

我们在log里面也打印出了接收到的消息,我们来看看。

OK,到这里一个最简单的一个web服务器就完成了,服务器采用了epoll机制,打开多个网页不成问题,http解析的部分写的比较简单,但是结构是比较清晰的,根据自己的需要可以添加各种类型的文件,一种类型一个处理函数,框架是不用动的。不过有一点可惜的是,目前大多数都是用https协议了(http+ssl),就是不再是透明传输了,加了一层加密层,让数据更加安全,这个简单的https的实现后面再说。

 

 

 

 

 

 

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/465573.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大学城美好生活组图

离开学校一段时间了&#xff0c;但这里还有很好的回忆................... 以下是广州大学城分享的点点滴滴&#xff0c;享受一下生活吧......................... 广州大学城&#xff0c;位于广州番禺区珠江出海口的江心岛上&#xff0c;与享誉中西方国家的、培育了大批国、共…

什么是音色?

要问最近最火的节目是什么&#xff1f;《浪姐》绝对可以冲击C位。要问最近最火的剧是哪部&#xff1f;有全中国小学生最近都怕的张东升老师那部前三甲无疑。要问最近最火的歌是哪首&#xff1f;《Mojito》或许是唯一的答案。这首极具拉丁风格的歌让周董再一次回答了“谁是周杰伦…

爱了雷布斯

本来今天想写个NTP的文章&#xff0c;但是因为小米昨天的这个操作一直想夸一下&#xff0c;所以写写雷布斯&#xff0c;吹吹雷布斯。我是米粉&#xff0c;肯定是米粉&#xff0c;前段时间还有读者来问我&#xff0c;现在能不能入手小米的股票&#xff0c;这个我不敢给建议&…

运行地址与加载地址估计大部分人没弄明白~

本文为【单片机步入嵌入式Linux】系列文章的第二篇&#xff0c;主要是跟大家讲解一下链接过程中几个地址的区分与理解~1 单片机存储分配在玩单片机(以stm32为例)的时候会有RAM空间和ROM空间&#xff0c;RAM空间主要是用于数据的访问&#xff0c;而ROM空间用于存放烧录的固件&am…

我妈在深圳的这些日子

今天送了我丈母娘回家&#xff0c;平时在家里&#xff0c;我会叫妈。下面文章中写到的我爸、我妈、指的是我老丈人和丈母娘。上个周末&#xff0c;我跟我妈说&#xff0c;谢谢你过来帮忙我们照顾楠哥&#xff0c;辛苦你了。我说了两遍&#xff0c;可能她听的不是很清楚&#xf…

【腾讯面试题】兔子试毒

大家好&#xff0c;我是牛牛&#xff0c;经过了忙碌的一周&#xff0c;终于盼来了周五。今天给大家分享一道有趣有料的算法题&#xff0c;希望能让大家开启周末的好心情。01故事起源有1000瓶药水&#xff0c;其中有一瓶是毒药&#xff0c;只要喝上一滴&#xff0c;一天之后就必…

分享一个剪切板的小软件CopyQ

我是最近在工作的时候经常需要复制一些命令&#xff0c;而且这些命令如果用手敲的话会超级麻烦&#xff0c;所以体验了几个剪切板的小软件&#xff0c;这个是我体验之后觉得最不错的一个&#xff0c;分享给大家。软件链接地址https://github.com/hluk/CopyQ/releases软件图标我…

GridView自定义分页

有时候的只是需要一些简单的但却是自定义的分页功能&#xff0c;但是又舍不得objectdatasource的排序功能&#xff0c;那就只有把pageddatasoure和objectdatasour结合起来&#xff0c; 由于pageddatasource实现的是IEnumberable&#xff0c;直接把objectdatasource赋给它是不行…

在朋友圈求助的NTP问题~

之前朋友圈求助的问题最后是我一个朋友尝试了一天的配置「这个配置尝试的过程需要技术基础&#xff0c;但是不管如何的技术基础都是需要去不断的尝试的」&#xff0c;终于找到了方法&#xff0c;所以~我给他们送了秋天的第一杯奶茶~「是他们是因为他们都是一群我很喜欢的同事」…

聊聊身边的嵌入式,英语学习利器点读笔

家里有小孩的朋友&#xff0c;可能对下面的这款产品不陌生。点读笔&#xff0c;一个会发声的电子产品&#xff0c;我当时为了给孩子做英语启蒙&#xff0c;买了小达人点读笔(上图最下方那个&#xff0c;另外两个分别是宝玩英语和巧虎配套的点读笔)&#xff0c;用了好几年了&…

EasyUI_datagrid

案例一丶jquery.easyui.min.js:10631 Uncaught TypeError: this.renderEmptyRow is not a function 解决方法&#xff1a;datagrid记录为空就会报错。是easyui里没处理return 空的方法。需要进行添加一些东西。具体我也不清楚&#xff0c; 案例二丶清除datagrid右侧空白区域 查…

三枚硬币自制收音机

大家好&#xff0c;我是记得诚。我们称这个收音机为三个硬币收音机是因为我们使用了三个硬币作为锚点&#xff0c;连接收音机各个部件&#xff0c;这将使我们的制作过程变得极为简单。在这个收音机中我们使用了一个特殊的10晶体管集成电路&#xff0c;使最后做出的收音机效果非…

双重检查锁实现单例模式的线程安全问题

一、结论 双重校验锁的单例模式代码如下&#xff1a; public class Singleton {   private static Singleton singleton; private Singleton() {} public static Singleton getSingleton() {     if (singleton null) { // 1       synchronized (Singleton.clas…

摇杆控制方向原理_为工业安全守好”门”!各种方向的控制阀原理图大集合

单向的、换向的....你想了解的方向控制阀都在这里了&#xff01;方向控制阀门液压阀是用来控制液压系统中油液的流动方向或调节其流量和压力的。方向控制阀作为液压阀的一种&#xff0c;利用流道的更换控制着油液的流动方向。单向型方向控制阀是只允许气流沿一个方向流动的方向…

对于新生代农民工,你有什么想说的?

昨晚上这个新闻很多人转&#xff0c;但是可能很少有人知道他的链接出处&#xff0c;链接来自于http://www.mohrss.gov.cn/SYrlzyhshbzb/jiuye/gzdt/202108/t20210816_420736.html我记得我还在上小学的时候&#xff0c;我们家有干不完的农活&#xff0c;暑假每天都要下田干活&am…

谁是经营之神

—北京维富友携手北京服装学院开展服装ERP沙盘大赛时间4月21日&#xff0c;地点北京服装学院&#xff0c;参加人数35人&#xff0c;参加人员北京维富友软件公司高级讲师、服装学院教师和同学共35人。目的&#xff1a;为了让学生更好的掌握服装企业管理和盈利管控&#xff0c;北…

在Android初次的前期学习中的二个小例子(2)

Hello13:SQLite数据库 一、简述SQLite的概念和主要特性SQLite是一个轻量级的关系型数据库&#xff0c;运算速度快&#xff0c;占用资源少&#xff0c;使用非常方便&#xff0c;支持SQL语法标准和数据库事务原则。相对于SharedPreferences使用文件保存数据&#xff0c;SQLite具有…

access 增加字段 工具_Java效率工具之Lombok

作者&#xff1a;LiWenD正在掘金来源&#xff1a;https://juejin.im/post/5b00517cf265da0ba0636d4b上一篇&#xff1a;数据库查询速度优化之解决技巧还在编写无聊枯燥又难以维护的POJO吗&#xff1f;洁癖者的春天在哪里&#xff1f;请看Lombok&#xff01;在过往的Java项目中&…

一文读懂 | 进程并发与同步

并发 是指在某一时间段内能够处理多个任务的能力&#xff0c;而 并行 是指同一时间能够处理多个任务的能力。并发和并行看起来很像&#xff0c;但实际上是有区别的&#xff0c;如下图&#xff08;图片来源于网络&#xff09;&#xff1a;concurrency-parallelism上图的意思是&a…

rust卡领地柜权限_RFID智能医疗耗材柜,上海智能高值耗材柜,国药智能医用耗材柜...

近几年因为我们的医疗改革一直在进步并改革&#xff0c;国家对我们的医疗方面的补助也有了明显的加大投入&#xff0c;与此同时让各种公立私立医院如雨后春笋般层出不穷&#xff0c;各大医院为了在医疗市场占有一席之地&#xff0c;都在各个方面开始想办法提升自己医院的水准。…