epoll单台设备支持百万并发连接
一些概念:
linux下一切接文件,文件描述符fd,文件I/O(包含socket,文本文件等),I/O多路复用,reactor模型,水平触发,边沿触发,多线程模型,阻塞和非阻塞,同步和异步
设备有限,目前实测能达到 11万并发连接。当客户端端口耗尽退出时,服务端异常退出待解决。
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<sys/epoll.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#define BUFFER_LEN 128
#define MAX_CON 1024
#define EVENTS_LEN 128
#define ITEM_LEN 4096
struct sock_item {
int fd;
char *rbuffer;
int rlength;
char *wbuffer;
int wlength;
int event;
void (*recv_cb)(int fd, char *buffer, int length);
void (*send_cb)(int fd, char *buffer, int length);
void (*accept_cb)(int fd, char *buffer, int length);
};
struct eventblock {
struct sock_item *items;
struct eventblock *next;
};
struct reactor {
int epfd;
int blkcnt;
struct eventblock *evblk;
};
int reactor_alloc(struct reactor *r){
if(!r) return -1;
struct eventblock *blk = r->evblk;
while(blk != NULL && blk->next != NULL) blk = blk->next;
struct sock_item *item = (struct sock_item *)calloc(1, ITEM_LEN * sizeof(struct sock_item));
if(!item){
printf("no memory ret:%d %s\n", errno, strerror(errno));
return -1;
}
struct eventblock *block = (struct eventblock *)calloc(1, sizeof(struct eventblock));
if(!block) {
free(item);
printf("no memory ret:%d %s\n", errno, strerror(errno));
return -1;
}
block->items = item;
block->next = NULL;
//blk == NULL 时表示首个节点
if(!blk)
r->evblk = block;
else
blk->next = block;
r->blkcnt++;
printf("create block:%p, cnt:%d\n", block, r->blkcnt);
return 0;
}
struct sock_item* reactor_lookup(struct reactor *r, int sockfd){
if(!r || !r->evblk || sockfd <= 0) return NULL;
int ret;
int blkidx = sockfd / ITEM_LEN;
while(blkidx >= r->blkcnt){
ret = reactor_alloc(r);
if(ret != 0) return NULL;
}
int i = 0;
struct eventblock *blk = r->evblk;
while(i++ < blkidx) blk = blk->next;
return &blk->items[sockfd % ITEM_LEN];
}
void *routine(void *arg){
int ret;
int clientfd = *(int *)arg;
while(1){
unsigned char buf[BUFFER_LEN] = {0};
ret = recv(clientfd, buf, BUFFER_LEN, 0);
if(ret == 0){ //客户端close,这里不做处理会产生 close_wait 状态
close(clientfd);
break;
}
printf("clientfd:%d buf:%s %d\n", clientfd, buf, ret);
ret = send(clientfd, buf, ret, 0);
printf("clientfd send %d\n", ret);
}
}
int main(){
//socket 插座,理解 socketfd 是插,bind sockaddr_in 是座
int ret;
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == -1) return -1;
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9999);
ret = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if(ret == -1) return -2;
//fd 默认阻塞
//设置非阻塞
#if 0
int flag = fcntl(listenfd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(listenfd, F_SETFL, flag);
#endif
listen(listenfd, 10); //类似酒店迎宾的人
#if 0
struct sockaddr_in client;
socklen_t len = sizeof(struct sockaddr_in);
int clientfd = accept(listenfd, (struct sockaddr *)&client, &len);
while(1){
unsigned char buf[BUFFER_LEN] = {0};
ret = recv(clientfd, buf, BUFFER_LEN, 0);
printf("clientfd:%d %s %d\n", clientfd, buf, ret);
ret = send(clientfd, buf, ret, 0);
printf("clientfd send %d\n", ret);
}
#elif 0
while(1){
struct sockaddr_in client;
socklen_t len = sizeof(struct sockaddr_in);
//类似酒店服务员
int clientfd = accept(listenfd, (struct sockaddr *)&client, &len);
pthread_t tid;
pthread_create(&tid, NULL, routine, &clientfd);
}
#elif 0 //<apue> pg404 select
fd_set rfds, wfds, rset, wset;
FD_ZERO(&rfds);
FD_SET(listenfd, &rfds);
FD_ZERO(&wfds);
int maxfd = listenfd; //对 maxfd 的理解?内核 bitmap 的下标上限?
unsigned char buf[MAX_CON][BUFFER_LEN + 1] = {{0}};
while(1){
rset = rfds;
wset = wfds;
//内核循环上限 可读集合 可写集合 出错 超时时间
select(maxfd + 1, &rset, &wset, NULL, NULL); //只监听listen的读事件
if(FD_ISSET(listenfd, &rset)){
printf("listenfd event:%d\n", listenfd);
//listen 已经有读事件,accpet该连接
struct sockaddr_in client;
socklen_t len = sizeof(struct sockaddr_in);
int clientfd = accept(listenfd, (struct sockaddr *)&client, &len); //accept会清空listen读事件
FD_SET(clientfd, &rfds); //设置客户端的监听读事件
if(clientfd > maxfd) maxfd = clientfd;
}
int i = 0;
for(i = listenfd + 1; i <= maxfd; ++i){
if(i >= MAX_CON) {
printf("over fd %d\n", i);
continue;
}
if(FD_ISSET(i, &rset)){
ret = recv(i, buf[i], BUFFER_LEN, 0);
if(ret == 0){
close(i);
FD_CLR(i, &rfds);
printf("close fd:%d\n", i);
continue;
}
printf("clientfd:%d %s %d\n", i, buf[i], ret);
FD_SET(i, &wfds); //监听是否可写
}
if(FD_ISSET(i, &wset)){
ret = send(i, buf[i], ret, 0);
printf("clientfd:%d send %d\n", i, ret);
FD_CLR(i, &wfds); //清空监听写
}
}
}
#elif 0 //搜索系统调用 SYSCALL_DEFINE(N) N表示该系统调用的参数个数
int epfd = epoll_create(1);
struct epoll_event ev, events[EVENTS_LEN];
ev.events = EPOLLIN; //这里选择水平还是边沿触发;如果是边沿触发,怎么做?(while accept listenfd 读事件)
ev.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); //将listenfd加入到红黑树中, 边沿触发监听listenfd的读事件
char buf[BUFFER_LEN] = {0};
while(1){
int nready = epoll_wait(epfd, events, EVENTS_LEN, -1); //-1阻塞,0立刻返回, 1000为1秒返回
printf("epoll_wait ret ready:%d\n", nready);
int i = 0;
for(i = 0; i < nready; ++i){
int clientfd = events[i].data.fd;
if(listenfd == clientfd){
//accept 建立连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
int connfd = accept(listenfd, (struct sockaddr *)&client, &len);
printf("accept connfd:%d\n", connfd);
ev.events = EPOLLET | EPOLLIN; //水平触发 和 边沿触发 的区别?
ev.data.fd = connfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
}else if(events[i].events & EPOLLIN){
ret = recv(clientfd, buf, BUFFER_LEN, 0);
if(ret == 0){
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &events[i]);
close(clientfd);
printf("clientfd:%d closed\n", clientfd);
//continue;
}
ev.events = EPOLLOUT; //改为监听可写事件
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);
}else if(events[i].events & EPOLLOUT){
ret = send(clientfd, buf, ret, 0);
if(ret <= 0){
char *err = strerror(errno);
printf("clientfd:%d closed ? ret %d errno %d:%s\n",
clientfd, ret, errno, err == NULL ? "unknow": err);
}
printf("clientfd:%d send:%s num:%d\n", clientfd, buf, ret);
ev.events = EPOLLIN; //改为监听可写事件
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);
}
}
}
#elif 1 //reactor 百万并发实现?
struct reactor *r = (struct reactor *)calloc(1, sizeof(struct reactor));
if(!r) {
printf("memory failed\n");
return -1;
}
ret = reactor_alloc(r);
if(ret != 0) return -1;
r->epfd = epoll_create(1);
struct epoll_event ev;
struct epoll_event events[EVENTS_LEN]; //准备好的事件缓冲区
ev.events = EPOLLIN; //默认水平触发
ev.data.fd = listenfd;
epoll_ctl(r->epfd, EPOLL_CTL_ADD, listenfd, &ev);
while(1){
int nready = epoll_wait(r->epfd, events, EVENTS_LEN, -1); //-1阻塞,0立刻返回, 1000为1秒返回
printf("epoll_wait ret ready:%d\n", nready);
int i = 0;
for(i = 0; i < nready; ++i){
int clientfd = events[i].data.fd;
if(listenfd == clientfd){
//accept 建立连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
int connfd = accept(listenfd, (struct sockaddr *)&client, &len);
printf("accept connfd:%d\n", connfd);
if(connfd <= 0) continue;
ev.events = EPOLLIN; //水平触发 和 边沿触发 的区别?
ev.data.fd = connfd;
epoll_ctl(r->epfd, EPOLL_CTL_ADD, connfd, &ev);
#if 0
//填充 sock_item
assert(r->items[connfd].rbuffer == NULL);
r->items[connfd].rbuffer = (char *)calloc(1, BUFFER_LEN);
r->items[connfd].rlength = 0;
assert(r->items[connfd].wbuffer == NULL);
r->items[connfd].wbuffer = (char *)calloc(1, BUFFER_LEN);
r->items[connfd].wlength = 0;
r->items[connfd].event = EPOLLIN;
assert(r->items[connfd].fd == 0);
r->items[connfd].fd = connfd;
#endif
struct sock_item *item = reactor_lookup(r, connfd);
if(item == NULL) {
printf("error lookup item\n");
continue;
}
item->fd = connfd;
assert(item->rbuffer == NULL);
item->rbuffer = calloc(1, BUFFER_LEN);
item->rlength = 0;
assert(item->wbuffer == NULL);
item->wbuffer = calloc(1, BUFFER_LEN);
item->wlength = 0;
printf("get item:%p, cfd:%d\n", item, item->fd);
}else if(events[i].events & EPOLLIN){
struct sock_item *item = reactor_lookup(r, clientfd);
printf("opr recv item:%p, item->fd:%d, connfd:%d\n", item, item->fd, clientfd);
assert(item != NULL && item->fd == clientfd);
char *rbuf = item->rbuffer;
char *wbuf = item->wbuffer;
assert(rbuf != NULL && wbuf != NULL);
ret = recv(item->fd, rbuf, BUFFER_LEN, 0);
if(ret == 0){
epoll_ctl(r->epfd, EPOLL_CTL_DEL, item->fd, &events[i]);
free(item->rbuffer); item->rbuffer = NULL;
free(item->wbuffer); item->wbuffer = NULL;
close(item->fd);
printf("clientfd:%d closed\n", item->fd);
item->fd = 0;
continue;
}
if(ret < 0) {
printf("clientfd:%d recv error ret:%d %s\n", item->fd, ret, strerror(errno));
continue;
}
item->rlength = ret;
memcpy(wbuf, rbuf, item->rlength);
item->wlength = ret;
printf("opr recv item:%p, clientfd:%d send:%s num:%d\n", item, item->fd, rbuf, ret);
ev.events = EPOLLOUT; //改为监听可写事件
ev.data.fd = item->fd;
epoll_ctl(r->epfd, EPOLL_CTL_MOD, item->fd, &ev);
}else if(events[i].events & EPOLLOUT){
struct sock_item *item = reactor_lookup(r, clientfd);
printf("opr send item:%p, item->fd:%d, connfd:%d\n", item, item->fd, clientfd);
assert(item != NULL && item->fd == clientfd);
char *wbuf = item->wbuffer;
assert(wbuf != NULL);
ret = send(item->fd, wbuf, item->wlength, 0);
if(ret <= 0){
char *err = strerror(errno);
printf("clientfd:%d closed ? ret %d errno %d:%s\n",
item->fd, ret, errno, err == NULL ? "unknow": err);
continue;
}
printf("opr send item:%p clientfd:%d send:%s num:%d\n", item, item->fd, wbuf, ret);
ev.events = EPOLLIN; //改为监听可写事件
ev.data.fd = item->fd;
epoll_ctl(r->epfd, EPOLL_CTL_MOD, item->fd, &ev);
}
}
}
#endif
return 0;
}
相关文章:
epoll单台设备支持百万并发连接
一些概念: linux下一切接文件,文件描述符fd,文件I/O(包含socket,文本文件等),I/O多路复用,reactor模型,水平触发,边沿触发,多线程模型,阻塞和非阻塞…...
网络字节序
文章目录网络字节序网络字节序 内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 网络数据流的地址统一按大端处理 发送主机通常将发送缓冲区中的数据按内存地址从低到高的…...
03- SVC 支持向量机做人脸识别 (项目三)
数据集描述: sklearn的lfw_people函数在线下载55个外国人图片文件夹数据集来精确实现人脸识别并提取人脸特征向量数据集地址: sklearn.datasets.fetch_lfw_people — scikit-learn 1.2.1 documentationPCA降维: pca PCA(n_components0.9) 数据拆分: X_train, X_test, y_tra…...
浅谈指向二维数组元素的指针变量
(1)指向数组元素的指针变量 例1.有一个3X4的二维数组,要求用指向元素的指针变量输出二维数组各元素的值. 编写程序 1 #include <stdio.h>2 int main()3 {4 int a[3][4] { 1,3,5,7,9,11,13,15,17,19,21,23 };5 int *p;6 for (p a[0]; p < a[0] 12; p) …...
左右值引用和移动语义
文章首发公众号:iDoitnow 1. 左右值和左右值引用 什么是左值、右值呢?一种极不严谨的理解为:在赋值的时候,能够被放到等号左边的值为左值,放在右边的值为右值。例如: int sum(int x, int y){return x y;…...
一起学习用Verilog在FPGA上实现CNN----(七)全连接层设计
1 全连接层设计 1.1 Layer 进行线性计算的单元layer,原理图如图所示: 1.2 processingElement Layer中的线性计算单元processingElement,原理图如图所示: processingElement模块展开原理图,如图所示,包含…...
tomcat打debug断点调试
windows debug调试 jdk版本:1.8.0_181 tomcat版本:apache-tomcat-9.0.68.0 idea版本:2020.1 方法一 修改catalina.bat 在%CATALINA_HOME%\bin\catalina.bat中找到 set “JAVA_OPTS%JAVA_OPTS% -Djava.protocol.handler.pkgsorg.apache…...
如果持有互斥锁的线程没有解锁退出了,该如何处理?
文章目录如果持有互斥锁的线程没有解锁退出了,该如何处理?问题引入PTHREAD_MUTEX_ROBUST 和 pthread_mutex_consistent登场了结论:如果持有互斥锁的线程没有解锁退出了,该如何处理? 问题引入 看下面一段代码…...
信息论绪论
本专栏针包含信息论与编码的核心知识,按知识点组织,可作为教学或学习的参考。markdown版本已归档至【Github仓库:information-theory】,需要的朋友们自取。或者关注公众号【AIShareLab】,回复 信息论 也可获取。 文章目…...
Buffer Status Reporting(BSR)
欢迎关注同名微信公众号“modem协议笔记”。 以一个实网中的异常场景开始,大概流程是有UL data要发送,UE触发BSR->no UL grant->SR->no UL grant->trigger RACH->RACH fail->RLF->RRC reestablishment:简单描述就是UE触…...
代码随想录LeetCode | 单调栈问题
前沿:撰写博客的目的是为了再刷时回顾和进一步完善,其次才是以教为学,所以如果有些博客写的较简陋,是为了保持进度不得已而为之,还请大家多多见谅。 预:看到题目后的思路和实现的代码。 见:参考…...
C++之可调用对象、bind绑定器和function包装器
可调用对象在C中,可以像函数一样调用的有:普通函数、类的静态成员函数、仿函数、lambda函数、类的非静态成员函数、可被转换为函数的类的对象,统称可调用对象或函数对象。可调用对象有类型,可以用指针存储它们的地址,可…...
MongoDB--》文档查询的详细具体操作
目录 统计查询 分页列表查询 排序查询 正则的复杂条件查询 比较查询 包含查询 条件连接查询 统计查询 统计查询使用count()方法,其语法格式如下: db.collection.count(query,options) ParameterTypeDescriptionquerydocument查询选择条件optio…...
网络协议(六):网络层
网络协议系列文章 网络协议(一):基本概念、计算机之间的连接方式 网络协议(二):MAC地址、IP地址、子网掩码、子网和超网 网络协议(三):路由器原理及数据包传输过程 网络协议(四):网络分类、ISP、上网方式、公网私网、NAT 网络…...
热启动预示生态起航的Smart Finance,与深度赋能的SMART通证
2023年初加密市场的回暖,意味着各个赛道都将在新的一年里走向新的叙事。最近,我们看到GameFi赛道也在市场回暖的背景下,逐渐走出阴霾。从融资数据上看,1月获得融资的GameFi项目共12个,融资突破8000万美元,1…...
提分必练,中创教育PMP全真模拟题分享
湖南中创教育每日五题分享来啦,“日日行,不怕千万里;常常做,不怕千万事。”,每日五题我们练起来! 1、在系统测试期间,按已识别原因的类型或类别记录了失败测试的数量。项目经理首先需要从最大故…...
PID控制算法基础介绍
PID控制的概念 生活中的一些小电器,比如恒温热水器、平衡车,无人机的飞行姿态和飞行速度控制,自动驾驶等等,都有应用到 PID——PID 控制在自动控制原理中是一套比较经典的算法。 为什么需要 PID 控制器呢? 你一定用…...
Ajax 学习笔记
一、Ajax1.1 什么是AjaxAJAX Asynchronous JavaScript and XML(异步的JavaScript和XML)。Ajax是一种在无需加载整个网页的情况下,能够更新部分网页的技术,它不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序的技术…...
力扣解法汇总1234. 替换子串得到平衡字符串
目录链接: 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目: https://github.com/September26/java-algorithms 原题链接:力扣 描述: 有一个只含有 Q, W, E, R 四种字符,且长度为 n 的字符串。 假如在该…...
C++关键字之const、inline、static
C 关键字总结 1.const const是 constant 的缩写,本意是不变的、不易改变的意思。在C中用来修饰内置类型变量,自定义对象,成员函数,返回值,函数参数使用如下: //修饰普通类型变量 const int a 7; int ba;…...
【成为架构师课程系列】怎样进行概念架构(Conceptual Architecture)?
目录 前言 什么是概念架构 概念架构阶段的3个步骤 初步设计 高层分割 分层式概念服务架构 Layer:逻辑层 Tier: 物理层 按通用性分层 技术堆叠 考虑非功能需求 【禅与计算机程序设计艺术:更多阅读】 前言 胜兵先胜而后求战,败兵先站而后求胜。…...
PostgreSQL的下载安装教程(macOS、Windows)
postgresql是GIS服务端几乎不可避免要打交道的数据库。因为mysql的空间扩展真是不尽人意。所以想要学会GIS服务端知识,postgresql(下文简称pg)你是必须要会的。 首先要知道,pg是一个空间数据库,和普通数据库不同的是pg支持空间数据的存储与操作。这里所谓的空间数据一般指…...
98年的确实卷,公司新来的卷王,我们这帮老油条真干不过.....
都说00后躺平了,但是有一说一,该卷的还是卷。这不,前段时间我们公司来了个00后,工作没两年,跳槽到我们公司起薪18K,都快接近我了。后来才知道人家是个卷王,从早干到晚就差搬张床到工位睡觉了。 …...
软件架构知识2-系统复杂度
架构设计的真正目的:是为了解决软件系统复杂度带来的问题,一个解决方案。 系统复杂度,如何入手: 1、通过熟悉和理解需求,识别系统复杂性所在的地方,然后针对这些复杂点进行架构设计。 2、架构设计并不是要…...
JavaSE学习day4_02 数组(超级重点)
3.数组 3.1什么是数组 数组就是存储数据长度固定的容器,存储多个数据的数据类型要一致。 3.2数组定义格式 3.2.1第一种(常用) 数据类型[] 数组名 示例: int[] arr; double[] arr; char[] arr; 3.2.2第二种(在…...
Theano教程:Python的内存管理
在写大型程序时候的一大挑战是如何保证最少的内存使用率。但是在Python中的内存管理是比较简单的。Python显示分配内存,使用引用计数系统管理对象,当指向某一个对象的引用数变为 0 的时候,该对象所占的内存就会被释放。理论上听起来很不错&am…...
Linux | Liunx安装Tomcat(Ubuntu版)
目录 一、下载并上传Tomcat压缩包到Ubuntu 1.1 下载并解压 1.2 执行 startup.sh 文件 二、验证Tomcat启动是否成功 2.1 查看启动日志 2.2 查看启动进程 三、Windows访问 Tomcat 服务 四、停止 Tomcat 服务 Tomcat是一款Web服务器,开发Web项目基本上都会用到…...
缓冲区浅析
缓冲区 程序运行输入数据时,从键盘的输入先存储到缓冲区,只有当缓冲区满或者输入回车时程序才会真正地从缓冲区读入数据 int main() {int a, b;cin >> a >> b;return 0; }in: 1 2\n 例如这里输入空格时程序没有输出,而是将空格…...
Day888.MySQL是怎么保证主备一致的 -MySQL实战
MySQL是怎么保证主备一致的 Hi,我是阿昌,今天学习记录的是关于MySQL是怎么保证主备一致的内容。 MySQL 能够成为现下最流行的开源数据库,binlog 功不可没。 在最开始,MySQL 是以容易学习和方便的高可用架构,被开发人…...
互联网舆情监测系统的发展阶段,TOOM互联网舆情监测系统有哪些?
互联网舆情监测系统是一种利用计算机技术对互联网上的大量信息进行实时监测、分析和评估的工具,旨在了解公众对某一事件、话题或品牌等的态度、情感倾向和影响力等。通过对社交媒体、论坛、新闻媒体等多个渠道的数据采集和处理,系统能够实现舆情事件的追…...
网站开发实用技术2.8.5/人民日报今日新闻
由于oracle 的高维护费用 ,公司决定 将Oracle 数据库迁移到mysql。下面是 对数据库迁移中 表和view视图迁移的 总结。 使用 navicat 工具 问题: MySQL 存在关键字和列名 或表名重复的现象 如:select from from 表名 解决方式: …...
网站手机端优化/网络营销推广策略
中国移动公布了一季度的业绩,业绩显示盈利有所增长,平均日赚约2.85亿元,看起来这个数据很惊人,不过与此前的赚钱能力相比,它的赚钱能力已有所下滑。中国移动2021年赚了1159亿元,日赚3.17亿,对比…...
做网站点击率赚钱吗/重庆旅游seo整站优化
上篇文章中介绍了MapXtreme Java Edition 4.8.2安装,这里介绍如何创建web gis应用。 (1)MyEclipse中tomcat配置 可以使用安装目录中已有的tomcat,也可以自己重新安装一个,这里使用新的tomcat。菜单-->Window-->Preferences-->MyEcli…...
wordpress怎么登录网站后台/建网站的公司
在集群环境中如何用LotusScript获取当前服务器 环境 产品: Lotus Domino Designer平台: 平台无关版本: 5.x, 6.x 问题 在集群环境中, 当其中一台服务器没有响应时, 因为失效转移的作用, 用户会被转移到集群中的另一台服务器上。如果我们在编写程序时在LotusScript中直接指定了…...
wordpress 讲解/广州seo公司排行
1.问题现场 目的:去掉字符串中的特殊符号,比如\t \r \n。 结果:同样的正则处理代码,得到的结果不同。 maxos: # -*- encoding:utf-8 -*- import torch, re s [可怜][可怜][可怜][可怜]那天下像张开的弓࿰…...
哪个网站做香烟回收/2022最近比较火的营销事件
CSS中背景图片的定位,困扰我很久了。今天总算搞懂了,一定要记下来。 在CSS中,背景图片的定位方法有3种: 1)关键字:background-position: top left; 2)像素:background-position: 0px…...