串口转以太客户端(增加uci、可连接多个服务器)
1、 进入barrier_breaker/package/utils文件夹,新建ttl_client
2、 该目录下的Makefile
#
# Copyright (C) OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
# include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk PKG_NAME:=ttl_client
PKG_RELEASE:=
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) include $(INCLUDE_DIR)/package.mk define Package/ttl_client
SECTION:=utils
CATEGORY:=Utilities
TITLE:=serial to tcp
DEPENDS:=+libuci +libpthread
endef define Package/ttl_client/description
A client of tcp to serial or serial to tcp
endef define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef define Build/Configure
endef define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) \
CC="$(TARGET_CC)" \
CFLAGS="$(TARGET_CFLAGS) -Wall -I$(LINUX_DIR)/user_headers/include" \
LDFLAGS="$(TARGET_LDFLAGS)"
endef define Package/ttl_client/install
$(INSTALL_DIR) $()/usr/sbin
$(INSTALL_DIR) $()/etc/config
$(CP) -rf ./files/ttl2tcp $()/etc/config/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ttl_client $()/usr/sbin/
endef $(eval $(call BuildPackage,ttl_client))
3、 新建files文件夹,在该文件夹中新建ttl2tcp配置文件,内容如下
package ttl2tcp config serial device
#option name ttyUSB0
#option name ttyS0
option name ttyATH0
option baudrate
option data
option parity None
option stop
option enabled config server
option ipaddr 172.16.1.165
option port config server
option ipaddr 172.16.1.139
option port config server
option ipaddr 172.16.1.235
option port
4、 barrier_breaker/package/utils/ttl_client/src中的Makefile文件,内容如下
CC = gcc
CFLAGS = -Wall
OBJS = ttl_client.o all: ttl_client %.o: %.c
$(CC) $(CFLAGS) -c -o $@ $< ttl_client: $(OBJS)
$(CC) -o $@ $(OBJS) -luci -lpthread clean:
rm -f rbcfg *.o
5、 barrier_breaker/package/utils/ttl_client/src中的ttl_client.c
/*
* ttl_client
*
* tingpan<dktingpan@sina.cn> 2015-05-31
*
* this is a client of serial translate to tcp or tcp translate to serial.
* serial read overtime is 1s
* every server read overtime is 0.5s,and the most server number is 3.
*/ #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <strings.h>
#include <time.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <uci.h>
#include <semaphore.h> #define SER_MAXLINE 128
#define SOCK_MAXLINE 136//为了应对缓存溢出,多分配一个字节
#define SERVER_MAXNUM 3 struct argument
{
int fd;
int sockfd[SERVER_MAXNUM];
};
unsigned char on_max;
struct _options {
char name[];
unsigned int baudrate;
//unsigned int data;
//unsigned int parity;
//unsigned int stop;
unsigned int enabled;
struct in_addr ipaddr[SERVER_MAXNUM];
unsigned int port[SERVER_MAXNUM];
};
struct _options opt;
//pthread_mutex_t socket_lock; //互斥锁 //为了保证用户输入的波特率是个正确的值,所以需要这两个数组验证,对于设置波特率时候,前面要加个B
int speed_arr[] = { B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300,
B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300, }; int name_arr[] = {, , , , , , , , ,
, , , , , , , , , }; /*-----------------------------------------------------------------------------
函数名: set_speed
参数: int fd ,int speed
返回值: void
描述: 设置fd表述符的串口波特率
*-----------------------------------------------------------------------------*/
void set_speed(int fd ,int speed)
{
struct termios opt;
int i;
int status; tcgetattr(fd,&opt);
for(i = ;i < sizeof(speed_arr)/sizeof(int);i++)
{
if(speed == name_arr[i]) //找到标准的波特率与用户一致
{
tcflush(fd,TCIOFLUSH); //清除IO输入和输出缓存
cfsetispeed(&opt,speed_arr[i]); //设置串口输入波特率
cfsetospeed(&opt,speed_arr[i]); //设置串口输出波特率 status = tcsetattr(fd,TCSANOW,&opt); //将属性设置到opt的数据结构中,并且立即生效
if(status != )
perror("tcsetattr fd:"); //设置失败
return ;
}
tcflush(fd,TCIOFLUSH); //每次清除IO缓存
}
}
/*-----------------------------------------------------------------------------
函数名: set_parity
参数: int fd
返回值: int
描述: 设置fd表述符的奇偶校验
*-----------------------------------------------------------------------------*/
int set_parity(int fd)
{
struct termios opt; if(tcgetattr(fd,&opt) != ) //或许原先的配置信息
{
perror("Get opt in parity error:");
return -;
} /*通过设置opt数据结构,来配置相关功能,以下为八个数据位,不使能奇偶校验*/
opt.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
opt.c_oflag &= ~OPOST;
opt.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
opt.c_cflag &= ~(CSIZE | PARENB);
opt.c_cflag |= CS8; tcflush(fd,TCIFLUSH); //清空输入缓存 if(tcsetattr(fd,TCSANOW,&opt) != )
{
perror("set attr parity error:");
return -;
} return ;
}
/*-----------------------------------------------------------------------------
函数名: serial_init
参数: char *dev_path,int speed,int is_block
返回值: 初始化成功返回打开的文件描述符
描述: 串口初始化,根据串口文件路径名,串口的速度,和串口是否阻塞,block为1表示阻塞
*-----------------------------------------------------------------------------*/
int serial_init(char *dev_path,int speed,int is_block)
{
int fd;
int flag; flag = ;
flag |= O_RDWR; //设置为可读写的串口属性文件
if(is_block == )
flag |=O_NONBLOCK; //若为0则表示以非阻塞方式打开 fd = open(dev_path,flag); //打开设备文件
if(fd < )
{
perror("Open device file err:");
close(fd);
return -;
} /*打开设备文件后,下面开始设置波特率*/
set_speed(fd,speed); //考虑到波特率可能被单独设置,所以独立成函数 /*设置奇偶校验*/
if(set_parity(fd) != )
{
perror("set parity error:");
close(fd); //一定要关闭文件,否则文件一直为打开状态
return -;
} return fd;
}
/*-----------------------------------------------------------------------------
函数名: serial_send
参数: int fd,char *str,unsigned int len
返回值: 发送成功返回发送长度,否则返回小于0的值
描述: 向fd描述符的串口发送数据,长度为len,内容为str
*-----------------------------------------------------------------------------*/
int serial_send(int fd,char *str,unsigned int len)
{
int ret; if(len > strlen(str)) //判断长度是否超过str的最大长度
len = strlen(str); ret = write(fd,str,len);
if(ret < )
{
perror("serial send err:");
return -;
} return ret;
} /*-----------------------------------------------------------------------------
函数名: serial_read
参数: int fd,char *str,unsigned int len,unsigned int timeout
返回值: 在规定的时间内读取数据,超时则退出,超时时间为ms级别
描述: 向fd描述符的串口接收数据,长度为len,存入str,timeout 为超时时间
*-----------------------------------------------------------------------------*/
int serial_read(int fd, char *str, unsigned int len, unsigned int timeout)
{
fd_set rfds;
struct timeval tv;
int ret; //每次读的结果
int sret; //select监控结果
int readlen = ; //实际读到的字节数
char * ptr; ptr = str; //读指针,每次移动,因为实际读出的长度和传入参数可能存在差异 FD_ZERO(&rfds); //清除文件描述符集合
FD_SET(fd,&rfds); //将fd加入fds文件描述符,以待下面用select方法监听 /*传入的timeout是ms级别的单位,这里需要转换为struct timeval 结构的*/
tv.tv_sec = timeout / ;
tv.tv_usec = (timeout%)*; /*防止读数据长度超过缓冲区*/
//if(sizeof(&str) < len)
// len = sizeof(str); /*开始读*/
while(readlen < len)
{
sret = select(fd+,&rfds,NULL,NULL,&tv); //检测串口是否可读 if(sret == -) //检测失败
{
perror("select:");
break;
}
else if(sret > ) //<SPAN style="WHITE-SPACE: pre"> </SPAN>//检测成功可读
{
ret = read(fd,ptr,); //第三个参数为请求读取的字节数
if(ret < )
{
perror("read err:");
break;
}
else if(ret == )
break; readlen += ret; //更新读的长度
ptr += ret; //更新读的位置
}
else //超时 sret == 0 ,没填满buf也退出循环
{
printf("timeout!\n");
break;
}
} return readlen;
} /**
* socket_read: 读取tcp数据
* @fd: socket文件描述符
* @str:将读到的数据存放在该地址
* @len:申请读取的字符长度
* @timeout:超时时间,单位ms
*/
int socket_read(int fd, char *str, unsigned int len, unsigned int timeout)
{
fd_set fdsr;
struct timeval tv;
int readlen = ;
char * ptr;
int ret;
ptr = str;
// initialize file descriptor set
FD_ZERO(&fdsr);//每次循环都要清空
FD_SET(fd, &fdsr);
tv.tv_sec = timeout / ;
tv.tv_usec = (timeout%)*;
while(readlen < len){
ret = select(fd + , &fdsr, NULL, NULL, &tv);
if (ret < ) {
perror("select");
//break;
exit(-);
} else if (ret == ) {
printf("timeout\n");
break;
}
//每次申请读取8个字节,但事实上是按发送端每次发送的字符串长度来确定的,如果长度小于8,则每次读取实际长度,如果大于8,则读取8字节。
//recv多少就从缓冲区中删除多少,剩下的数据可以在下次recv时得到
//即使子线程挂起,也一直有数据可以读,数据不丢失,真正的接收数据是协议来完成的,存放在s的接收缓冲中。
ret = recv(fd, ptr, , );//申请8个字节
if (ret <= ) {//如果连接已中止,返回0。如果发生错误,返回-1
printf("client close\n");
close(fd);
FD_CLR(fd, &fdsr);
fd = ;
} else {
readlen +=ret;
ptr += ret;
//printf("the ret length is:%d\n",readlen);
}
}
return readlen;
} /**
* read_config: 读取配置文件
* @popt: 配置信息保存的结构体
*/
static int read_config(struct _options *popt)
{
static struct uci_context *ctx;
struct uci_ptr ptr;
char a[];
char i;
unsigned char server_num = ; ctx = uci_alloc_context(); //读取设备名称
//if ((strcpy(a, "ttl2tcp.device.name") == NULL)
if ((strncpy(a, "ttl2tcp.device.name", sizeof(a)) == NULL)
//if (!snprintf(a,sizeof(a),"ttl2tcp.%s.baudrate",SERNAME)
|| (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
printf("Read ttl2tcp.device.name failed! exit.\n");
exit(-);
}
if (ptr.o) {
strncpy(popt->name, ptr.o->v.string, sizeof(popt->name));
} else {
printf("ttl2tcp.device.name Not found!\n");
} //读取串口波特率
if ((strncpy(a, "ttl2tcp.device.baudrate", sizeof(a)) == NULL)
//if (!snprintf(a,sizeof(a),"ttl2tcp.%s.baudrate",SERNAME)
|| (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
printf("Read tttl2tcp.device.baudrate failed! exit.\n");
exit(-);
}
if (ptr.o) {
popt->baudrate = strtol(ptr.o->v.string, NULL, );
} else {
printf("ttl2tcp.device.baudrate Not found!\n");
} //是否使能
//if (!snprintf(a,sizeof(a),"ttl2tcp.%s.enabled",SERNAME)
if ((strncpy(a, "ttl2tcp.device.enabled", sizeof(a)) == NULL)
|| (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
printf("Read ttl2tcp.device.enabled failed! exit.\n");
exit(-);
}
if (ptr.o) {
popt->enabled = strtol(ptr.o->v.string, NULL, );
} else {
printf("ttl2tcp.device.enabled Not found!\n");
} for(i=; i<SERVER_MAXNUM; i++){
//读取ip地址
//if ((strncpy(a, "ttl2tcp.@server[0].ipaddr",sizeof(a)) == NULL)
if (!snprintf(a,sizeof(a),"ttl2tcp.@server[%d].ipaddr",i)
|| (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
printf("Read ttl2tcp.@server[%d].ipaddr failed! exit.\n",i);
exit(-);
}
if (ptr.o) {
unsigned int laddr;
laddr = inet_addr(ptr.o->v.string);//因为ipaddr为网络字节顺序,大端字节序,而输入的为主机字节序
memcpy(&popt->ipaddr[i], &laddr, );
} else {
printf("ttl2tcp.@server[%d].ipaddr Not found!\n",i);
}
//读取port
//if ((strcpy(a, "ttl2tcp.@server[0].port") == NULL)
if (!snprintf(a,sizeof(a),"ttl2tcp.@server[%d].port",i)
|| (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
printf("Read ttl2tcp.@server[%d].port failed! exit.\n",i);
exit(-);
}
if (ptr.o) {
popt->port[i] = strtol(ptr.o->v.string, NULL, );
server_num++;
} else {
printf("ttl2tcp.@server[%d].port Not found!\n",i);
}
}
uci_free_context(ctx);
return server_num;
} /**
* conn_nonb: 非阻塞connect
* @sockfd: socket文件描述符
* @saptr:指向数据结构sockaddr的指针,其中包括目的端口和IP地址
* @salen:sockaddr的长度
* @nsec:超时时间,单位ms
*/
int conn_nonb(int sockfd, const struct sockaddr *saptr, socklen_t salen, int nsec)
{
int flags, n, error, code;
socklen_t len;
fd_set wset;
struct timeval tval; flags = fcntl(sockfd, F_GETFL, );
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); error = ;
if ((n = connect(sockfd, saptr, salen)) == ) { //马上连接成功,可能性小
printf("conn_nonb success!\n");
goto done;
}else if (n < && errno != EINPROGRESS){ //多次连接或服务端没开启,出错 ,第一次一般执行不到该处。
printf("conn_nonb error!\n");
return (-);
} /* Do whatever we want while the connect is taking place */
//连接建立已经启动,但是尚未完成
FD_ZERO(&wset);
FD_SET(sockfd, &wset);
tval.tv_sec = nsec;
tval.tv_usec = ;
//printf("conn_nonb select start!\n");
if ((n = select(sockfd+, NULL, &wset, //有连接要处理
NULL, nsec ? &tval : NULL)) == ) {
close(sockfd); /* timeout */
errno = ETIMEDOUT;
printf("conn_nonb select timeout!\n");
return (-);
} if (FD_ISSET(sockfd, &wset)) {
len = sizeof(error);
code = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
/* 如果发生错误,Solaris实现的getsockopt返回-1,
* 把pending error设置给errno. Berkeley实现的
* getsockopt返回0, pending error返回给error.
* 我们需要处理这两种情况 */
if (code < || error) {
close(sockfd);
if (error)
errno = error;
printf("conn_nonb getsockopt error!\n");
return (-);
}
} else {
fprintf(stderr, "select error: sockfd not set");
exit();
} done:
fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
return ();
} void *socket_thread(void *arg)
{
char sockbuf[SOCK_MAXLINE];
int sreadlen = ;
int freadlen = ;
unsigned char i;
struct argument thread_arg;
thread_arg = *(struct argument *)arg;
memset(sockbuf, , sizeof(sockbuf));
while()
{
for (i=; i<on_max;i++)
{
//pthread_mutex_lock(&socket_lock);//lock
sreadlen = socket_read(thread_arg.sockfd[i], sockbuf, SOCK_MAXLINE-, );//为了防止缓存溢出,少读取一个字节
printf("the sockbuf is:%s\n", sockbuf);//打印出数据
printf("the sockbuf length is:%d\n",sreadlen);
freadlen = serial_send(thread_arg.fd,sockbuf,sreadlen);
printf("send %d bytes!\n",freadlen);
memset(sockbuf, , sizeof(sockbuf));
//pthread_mutex_unlock(&socket_lock);//unlock
usleep();
}
}
} int main(int argc, char** argv)
{
//串口变量定义
int fd;
char str[]="hello linux serial!"; //字符串初始化
char serbuf[SER_MAXLINE];
int readlen;
char dev_path[]; // socket变量定义
int sockfd[SERVER_MAXNUM];//SERVER_MAXNUM
struct sockaddr_in servaddr[SERVER_MAXNUM];//SERVER_MAXNUM
unsigned char i;
unsigned char on_sockfd[SERVER_MAXNUM] = {};
on_max = ;//最大上线个数 //多线程
pthread_t thread;
int mret;
struct argument arg; //读取配置文件
unsigned char server_num;//服务器个数初始化
server_num = read_config(&opt);
if (opt.enabled != )
{
printf("do not enable ttl_client!\n");
exit(-);
} //串口初始化
if (!snprintf(dev_path,sizeof(dev_path),"/dev/%s",opt.name)
|| (fd = serial_init(dev_path,opt.baudrate,)) < )
{
perror("serial init err:");
return -;
}
memset(serbuf, , sizeof(serbuf)); //socket始化
for (i=; i<server_num; i++)
{
printf("socket init %d/%d\n",i,server_num);
if( (sockfd[i] = socket(AF_INET, SOCK_STREAM, )) < )
{
printf("create socket2 error: %s(errno: %d)\n", strerror(errno),errno);
exit();
}
memset(&servaddr[i], , sizeof(servaddr[i]));
servaddr[i].sin_family = AF_INET;
servaddr[i].sin_port = htons(opt.port[i]);
servaddr[i].sin_addr = opt.ipaddr[i];//与配置文件有关,注意配置文件要正确 //if( connect(sockfd[i], (struct sockaddr*)&servaddr[i], sizeof(servaddr[i])) < 0)
//非阻塞连接10s,如果前一个sockfd没有connect成功,则下次将建立一样的文件描述符号
if( conn_nonb(sockfd[i], (struct sockaddr*)&servaddr[i], sizeof(servaddr[i]),) < )
{
printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
continue;
//exit(0);
}
on_sockfd[on_max++] = i;
printf("send msg to server[%d]: %d\n",i,on_max-);//on_max-1为上线客户端的新编号
//socket发送
if( send(sockfd[i], str, strlen(str), ) < )
{
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit();
}
}
//如果没有一个服务器上线
if (on_max == )
{
exit();
} //锁
//pthread_mutex_init(&socket_lock, NULL); //创建多线程
arg.fd=fd;
for (i=; i<on_max;i++)
{
arg.sockfd[i]=sockfd[on_sockfd[i]];
}
mret = pthread_create(&thread, NULL, socket_thread, (void *)(long)&arg);
if (mret != )
{
printf("Create thread failed\n");
exit(mret);
}
printf("Create thread\n"); //串口转tcp
while()
{
readlen = serial_read(fd,serbuf,SER_MAXLINE,);//1s内如果数据没有装满buf,则读取完毕。 如果数据量大,则读取的速度也越快。
printf("the serbuf is :%s\n",serbuf);
printf("the serbuf length is :%d\n",readlen);
for (i=; i<on_max;i++)
{
printf("sockfd[%d]:%d\n",i,sockfd[on_sockfd[i]]);
if( send(sockfd[on_sockfd[i]], serbuf, readlen, ) < )//serbuf中有数据可以发送才会执行这条语句
{
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);//服务端断掉,则发送失败。
exit();
}
}
memset(serbuf, , sizeof(serbuf));
sleep();
}
//退出
close(fd);
for (i=; i<server_num;i++)
close(sockfd[i]);
exit();
}
串口转以太客户端(增加uci、可连接多个服务器)的更多相关文章
- Redis02 Redis客户端之Java、连接远程Redis服务器失败
1 查看支持Java的redis客户端 本博文采用 Jedis 作为redis客户端,采用 commons-pool2 作为连接redis服务器的连接池 2 下载相关依赖与实战 2.1 到 Repos ...
- HTTP实现长连接(TTP1.1和HTTP1.0相比较而言,最大的区别就是增加了持久连接支持Connection: keep-alive)
HTTP实现长连接 HTTP是无状态的 也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接.如果客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web ...
- ubuntu客户端使用RDP协议连接windows服务器
如果服务器是linux或unix,可以使用ssh远程连接服务器.也有服务器是windows的,通常使用RDP协议进行连接. 1 环境 客户端:ubuntu14.04 LST 服务器:windows ...
- 通过使用精简客户端,且不需要安装的客户端,配合PLSQL连接oracle数据库
通过使用精简客户端,且不需要安装的客户端,配合PLSQL连接oracle数据库. 首先下载安装包在Oralce官方网站上下载Oracle Instantclient Basic package.地址如 ...
- 3 Oracle 32位客户端安装及arcgis连接
关于Oracle服务器端安装及配置的过程详见第2篇文章,链接如下:http://www.cnblogs.com/gistrd/p/8494292.html,本篇介绍客户端安装配置及连接arcgis过程 ...
- 局域网内客户端无法使用机器名连接SQLServer服务器
在生产环境中有时会要求使用机器名连接SQLServer服务器,但有时捣好久都没法连上~ 针对这个问题做个简短记录,防止以后自己再遇到记不起原因,也方便一下其他同行! 废话不多说,作为工作多年的老家伙了 ...
- SQLServer 2005客户端远程连接sql2008 数据库服务器
SQL2005客户端远程连接sql2008 数据库服务器 by:授客 QQ:1033553122 准备工作: 客户端所在pc机配置: 配置数据源 控制面板-管理工具-ODBC数据源-系统DSN-添加- ...
- 通过数据库客户端界面工具DBeaver连接Hive
前言 本文讲解如何通过数据库客户端界面工具DBeaver连接hive,并解决驱动下载不下来的问题. 1.为什么使用客户端界面工具 为什么使用客户端界面工具而不用命令行使用hive 通过界面工具查看分析 ...
- 桌面远程连接阿里云服务器(windows)后丧失了双向文件复制粘贴功能的解决方案(第一条博客!)
近日应公司要求,需在windows服务器上架设一个交易中介软件. 过程之一:将软件压缩文件传到服务器上. 问题:在“运行”对话框通过输入'mstsc' 创建远程连接以后,出现本地桌面与服务器之间无法物 ...
随机推荐
- 2017中国大学生程序设计竞赛 - 网络选拔赛 HDU 6152 Friend-Graph(暴力搜索)
题目传送:http://acm.hdu.edu.cn/showproblem.php?pid=6152 Problem Description It is well known that small ...
- HDU 6049 17多校2 Sdjpx Is Happy(思维题difficult)
Problem Description Sdjpx is a powful man,he controls a big country.There are n soldiers numbered 1~ ...
- python 模块基础 和常用的模块
模块的定义 一个模块就是以.py结尾的python 文件,用来从逻辑上组织python代码.注意,模块名和变量名一样开头不能用数字,可以是双下划线和字母. 为什么要用模块? 将一些复杂的需要重复使用的 ...
- Spring Boot 揭秘与实战(五) 服务器篇 - 内嵌的服务器 Tomcat剖析
文章目录 1. 内嵌的 Tomcat,一个Jar包运行 2. 如何定制内嵌 Tomcat3. War 包部署的使用细节 2.1. 设置内嵌Tomcat的端口 2.2. 设置内嵌Tomcat的最大线程数 ...
- Spring Boot 揭秘与实战(二) 数据存储篇 - Redis
文章目录 1. 环境依赖 2. 数据源 2.1. 方案一 使用 Spring Boot 默认配置 2.2. 方案二 手动创建 3. 使用 redisTemplate 操作4. 总结 3.1. 工具类 ...
- lua 取table长度
http://blog.csdn.net/wangmanjie/article/details/52793902 static int unbound_search (Table *t, unsign ...
- Python学习笔记第十二周
目录: 数据库介绍 mysql 数据库安装使用 mysql管理 mysql 数据类型 常用mysql命令事务 索引 创建数据库 外键 增删改查表 权限 python 操作mysql ORM sqla ...
- 【转载】 PyTorch学习之六个学习率调整策略
原文地址: https://blog.csdn.net/shanglianlm/article/details/85143614 ----------------------------------- ...
- Twisted 安装
1,官网: https://www.twistedmatrix.com/trac/ 2,下载 https://twistedmatrix.com/Releases/Twisted/18.7/Twist ...
- Python之路,第二篇:Python入门与基础2
1,复合赋值运算符 += . -= . *= . /= . //= . %= , **= x += y 等同于 x = x + y x -= ...