首页
苏兮影视
随笔记
壁纸
更多
直播
时光轴
友联
关于
统计
Search
1
v2ray节点搭建
821 阅读
2
软件添加id功能按钮
794 阅读
3
typecho非常有特色的模块
585 阅读
4
QQ扫码无法登录的解决方案
570 阅读
5
QQxml消息卡片生成源码
544 阅读
谈天说地
建站源码
经验教程
资源分享
动漫美图
登录
Search
标签搜索
java
rust
flutter
esp32c3
springboot
安卓
linux
vue
docker
joe
快捷键
git
fish shell
maven
redis
netty
dart
groovy
js
设计模式
尽意
累计撰写
109
篇文章
累计收到
39
条评论
首页
栏目
谈天说地
建站源码
经验教程
资源分享
动漫美图
页面
苏兮影视
随笔记
壁纸
直播
时光轴
友联
关于
统计
搜索到
6
篇与
的结果
2025-05-01
ESP32C3使用BLE
初始化项目参考 项目创建 新建一个基于std的新项目在Cargo.toml添加必要的依赖[package] name = "esp32c3-ble-demo" version = "0.1.0" authors = ["suxii <m@suxii.cn>"] edition = "2021" resolver = "2" rust-version = "1.77" [[bin]] name = "esp32c3-ble-demo" harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors [features] default = [] experimental = ["esp-idf-svc/experimental"] [dependencies] log = "0.4" esp-idf-svc = { version = "0.51", features = ["critical-section", "embassy-time-driver", "embassy-sync","experimental","std"] } futures = "0.3.31" anyhow = "1.0.98" enumset = "1.1.5" heapless = "0.8.0" esp-idf-sys = "0.36.1" esp-idf-hal = "0.45.2" # 处理序列化,反序列化 serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" [build-dependencies] embuild = "0.33" 开启BLE功能将sdkconfig.defaults 文件替换成# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000 CONFIG_BT_BLUEDROID_ENABLED=y CONFIG_BT_ENABLED=y CONFIG_BT_BLE_ENABLED=y CONFIG_BT_GATTS_ENABLE=y CONFIG_BT_BLE_SMP_ENABLE=y CONFIG_BT_GATTS_SEND_SERVICE_CHANGE_MODE=y CONFIG_BT_BTC_TASK_STACK_SIZE=7000 CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y CONFIG_BT_CTRL_BLE_MAX_ACT=10 CONFIG_BT_CTRL_BLE_MAX_ACT_EFF=10 CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=0 CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 CONFIG_BT_CTRL_BLE_SCAN_DUPL=y # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). # This allows to use 1 ms granuality for thread sleeps (10 ms by default). #CONFIG_FREERTOS_HZ=1000 # Workaround for https://github.com/espressif/esp-idf/issues/7631 #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n并且在同级目录新建一个 sdkconfig.esp32c3文件# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000 CONFIG_BT_ENABLED=y CONFIG_BT_BLE_ENABLED=y CONFIG_BT_GATTS_ENABLE=y CONFIG_BT_GATTS_SEND_SERVICE_CHANGE_MODE=y CONFIG_BT_BTC_TASK_STACK_SIZE=7000 CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n CONFIG_BTDM_CTRL_MODE_BTDM=n # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). # This allows to use 1 ms granuality for thread sleeps (10 ms by default). #CONFIG_FREERTOS_HZ=1000 # Workaround for https://github.com/espressif/esp-idf/issues/7631 #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n代码编写新建一个ble_server.rs文件,用来管理BLE use std::sync::{mpsc, Arc, Condvar, Mutex}; use std::thread; use enumset::enum_set; use esp_idf_hal::ledc::{LedcDriver, LedcTimerDriver, Resolution}; use esp_idf_hal::modem; use esp_idf_svc::bt::ble::gap::{AdvConfiguration, BleGapEvent, EspBleGap}; use esp_idf_svc::bt::ble::gatt::server::{ConnectionId, EspGatts, GattsEvent, TransferId}; use esp_idf_svc::bt::ble::gatt::{ AutoResponse, GattCharacteristic, GattDescriptor, GattId, GattInterface, GattResponse, GattServiceId, GattStatus, Handle, Permission, Property, }; use esp_idf_svc::bt::{BdAddr, Ble, BtDriver, BtStatus, BtUuid}; use esp_idf_svc::hal::delay::FreeRtos; use esp_idf_svc::nvs::{ EspNvsPartition, NvsDefault}; use esp_idf_svc::sys::{EspError, ESP_FAIL}; use log::{error, info, warn}; use crate::ble_recv_message; use crate::message::Message; pub fn start( modem: modem::Modem, nvs_clone:EspNvsPartition<NvsDefault> ) -> anyhow::Result<()> { // nvs(Non-Volatile Storage,非易失性存储)是持久化存储,保证断电数据不丢失 // 创建ble蓝牙驱动 let bt = Arc::new(BtDriver::new(modem, Some(nvs_clone))?); // 自定义一个蓝牙集合服务,创建gap,跟gatts let server = ExampleServer::new( Arc::new(EspBleGap::new(bt.clone())?), Arc::new(EspGatts::new(bt.clone())?), ); info!("BLE Gap and Gatts 初始化完成"); let gap_server = server.clone(); // 处理gap事件回调吗,gap在连接阶段参与,不参与内容传输 server.gap.subscribe(move |event| { gap_server.check_esp_status(gap_server.on_gap_event(event)); })?; let gatts_server = server.clone(); // gap处理完成,说明连接已经建立了,现在开始处理gatt,也就是数据通信的部分 server.gatts.subscribe(move |(gatt_if, event)| { gatts_server.check_esp_status(gatts_server.on_gatts_event(gatt_if, event)) })?; info!("BLE Gap and Gatts 订阅事件初始化完成"); server.gatts.register_app(APP_ID)?; info!("Gatts BTP app 完成注册"); // 新线程处理发送消息 let _ = thread::Builder::new() .stack_size(2000) .spawn(move||{ let mut ind_data = 0_u16; loop { server.indicate(&ind_data.to_le_bytes()).unwrap(); info!("广播给客户端的信息为: {ind_data}"); ind_data = ind_data.wrapping_add(1); FreeRtos::delay_ms(10000); } }); anyhow::Ok(()) } const APP_ID: u16 = 0; const MAX_CONNECTIONS: usize = 2; // Our service UUID pub const SERVICE_UUID: u128 = 0xad91b201734740479e173bed82d75f9d; /// Our "recv" characteristic - i.e. where clients can send data. pub const RECV_CHARACTERISTIC_UUID: u128 = 0xb6fccb5087be44f3ae22f85485ea42c4; /// Our "indicate" characteristic - i.e. where clients can receive data if they subscribe to it pub const IND_CHARACTERISTIC_UUID: u128 = 0x503de214868246c4828fd59144da41be; type ExBtDriver = BtDriver<'static, Ble>; type ExEspBleGap = Arc<EspBleGap<'static, Ble, Arc<ExBtDriver>>>; type ExEspGatts = Arc<EspGatts<'static, Ble, Arc<ExBtDriver>>>; #[derive(Debug, Clone)] struct Connection { peer: BdAddr, conn_id: Handle, subscribed: bool, mtu: Option<u16>, } #[derive(Default)] struct State { gatt_if: Option<GattInterface>, service_handle: Option<Handle>, recv_handle: Option<Handle>, ind_handle: Option<Handle>, ind_cccd_handle: Option<Handle>, connections: heapless::Vec<Connection, MAX_CONNECTIONS>, response: GattResponse, ind_confirmed: Option<BdAddr>, } #[derive(Clone)] pub struct ExampleServer { gap: ExEspBleGap, gatts: ExEspGatts, state: Arc<Mutex<State>>, condvar: Arc<Condvar>, } impl ExampleServer { pub fn new(gap: ExEspBleGap, gatts: ExEspGatts) -> Self { Self { gap, gatts, state: Arc::new(Mutex::new(Default::default())), condvar: Arc::new(Condvar::new()), } } } impl ExampleServer { // 发送indicate消息 fn indicate(&self, data: &[u8]) -> Result<(), EspError> { for peer_index in 0..MAX_CONNECTIONS { // 发送数据给全部已经订阅的服务 let mut state = self.state.lock().unwrap(); loop { if state.connections.len() <= peer_index { // 表示已经向全部订阅的客户发送了消息 break; } let Some(gatt_if) = state.gatt_if else { // 丢失了gatt的接口连接 break; }; let Some(ind_handle) = state.ind_handle else { // 丢失了handle break; }; // confirmed翻译为 证实 // 检查状态:确保上次的指示数据已被客户端确认接收。 // 发送数据:如果状态允许,则向指定客户端发送新的指示数据。 // 更新状态:记录当前客户端已接收到指示数据。 // 等待条件:如果状态不允许发送数据,则等待其他线程的通知。 if state.ind_confirmed.is_none() { let conn = &state.connections[peer_index]; self.gatts .indicate(gatt_if, conn.conn_id, ind_handle, data)?; state.ind_confirmed = Some(conn.peer); let conn = &state.connections[peer_index]; info!("Indicated数据发送给客户端 {}", conn.peer); break; } else { state = self.condvar.wait(state).unwrap(); } } } Ok(()) } /// 处理客户端订阅 fn on_subscribed(&self, addr: BdAddr) { // todo 客户端订阅 warn!("客户端 {addr} 订阅了"); } /// 处理客户端取消订阅 fn on_unsubscribed(&self, addr: BdAddr) { // todo 客户肯定取消订阅 warn!("客户端 {addr} 取消订阅"); } /// 接收消息回调 fn on_recv(&self, addr: BdAddr, data: &[u8], offset: u16, mtu: Option<u16>) { // todo 这里写接收消息的逻辑 // 按大端解析 // let res = u16::from_be_bytes([data[0],data[1]]); let res = String::from_utf8_lossy(data); if let Err(e) = ble_recv_message::handle_message(&res){ error!("处理消息错误:{e}") } warn!("接收消息来自客户端 {addr}: {data:?}, offset: {offset}, mtu: {mtu:?} text:{res}"); } /// GAP 事件的主事件处理程序 fn on_gap_event(&self, event: BleGapEvent) -> Result<(), EspError> { info!("得到gap事件: {event:?}"); // ble广播配置 Advertising:广告,广播 if let BleGapEvent::AdvertisingConfigured(status) = event { // 从gap事件中,结构出BtStatus状态,然后检查bt的状态 // 如果bt连接状态检查不通过,就直接往上级抛异常 self.check_bt_status(status)?; // gap处理完成,开启广播 self.gap.start_advertising()?; } Ok(()) } /// GATTS 事件的主事件处理程序 fn on_gatts_event( &self, gatt_if: GattInterface, event: GattsEvent, ) -> Result<(), EspError> { info!("得到gatts事件消息: {event:?}"); match event { // 处理服务注册,注册成功后,创建服务 GattsEvent::ServiceRegistered { status, app_id } => { self.check_gatt_status(status)?; if APP_ID == app_id { self.create_service(gatt_if)?; } } // 处理服务创建完成后的事件,配置gatts的服务 GattsEvent::ServiceCreated { status, service_handle, .. } => { self.check_gatt_status(status)?; self.configure_and_start_service(service_handle)?; } // 处理特征添加 GattsEvent::CharacteristicAdded { status, attr_handle, service_handle, char_uuid, } => { self.check_gatt_status(status)?; self.register_characteristic(service_handle, attr_handle, char_uuid)?; } // 添加描述符 GattsEvent::DescriptorAdded { status, attr_handle, service_handle, descr_uuid, } => { self.check_gatt_status(status)?; self.register_cccd_descriptor(service_handle, attr_handle, descr_uuid)?; } // 删除服务 GattsEvent::ServiceDeleted { status, service_handle, } => { self.check_gatt_status(status)?; self.delete_service(service_handle)?; } // 注销服务 GattsEvent::ServiceUnregistered { status, service_handle, .. } => { self.check_gatt_status(status)?; self.unregister_service(service_handle)?; } // 注册或更新其 MTU(Maximum Transmission Unit 最大传输单元)值 GattsEvent::Mtu { conn_id, mtu } => { self.register_conn_mtu(conn_id, mtu)?; } // 处理连接的客户端 GattsEvent::PeerConnected { conn_id, addr, .. } => { self.create_conn(conn_id, addr)?; } // 处理客户端断开连接 GattsEvent::PeerDisconnected { addr, .. } => { self.delete_conn(addr)?; // todo 开启广播 self.gap.start_advertising()?; } GattsEvent::Write { conn_id, trans_id, addr, handle, offset, need_rsp, is_prep, value, } => { let handled = self.recv( gatt_if, conn_id, trans_id, addr, handle, offset, need_rsp, is_prep, value, )?; // 值为true,表示是客户端的操作,例如客户端注册,注销,发送消息 if handled { self.send_write_response( gatt_if, conn_id, trans_id, handle, offset, need_rsp, is_prep, value, )?; } } GattsEvent::Confirm { status, .. } => { self.check_gatt_status(status)?; self.confirm_indication()?; } _ => (), } Ok(()) } /// 创建服务并开始广播, /// 在通知我们 GATTS 应用程序已注册后,从事件回调中调 fn create_service(&self, gatt_if: GattInterface) -> Result<(), EspError> { // 初始化结构体的 GattInterface self.state.lock().unwrap().gatt_if = Some(gatt_if); // 设置esp32设备的名字 self.gap.set_device_name("ESP32")?; // flag 标志位是一个位掩码,具体含义可以参考 Bluetooth SIG 定义的标准。 // 常见的标志位组合: // 0x01:LE Limited Discoverable Mode(有限可发现模式)。 // 0x02:LE General Discoverable Mode(通用可发现模式)。 // 0x04:BR/EDR Not Supported(不支持经典蓝牙)。 // 0x18:LE and BR/EDR Controller(同时支持 LE 和经典蓝牙)。 // 在代码中,flag: 2 表示设备处于通用可发现模式(LE General Discoverable Mode)。 self.gap.set_adv_conf(&AdvConfiguration { include_name: true, // 指示是否在广播数据中包含设备名称 include_txpower: true, // 指示是否在广播数据中包含设备的发射功率 flag: 2, service_uuid: Some(BtUuid::uuid128(SERVICE_UUID)), // 指定设备提供的服务 UUID // service_data: todo!(), // 用于广播与特定服务相关的数据 // manufacturer_data: todo!(), // 用于广播厂商自定义的数据 ..Default::default() })?; self.gatts.create_service( gatt_if, &GattServiceId { id: GattId { uuid: BtUuid::uuid128(SERVICE_UUID), inst_id: 0, // 指定服务的实例Id 如果设备支持多个相同 UUID 的服务实例,inst_id 用于区分它们 }, is_primary: true, // 指示服务是否为主服务 }, 8, )?; Ok(()) } /// 删除服务 fn delete_service(&self, service_handle: Handle) -> Result<(), EspError> { let mut state = self.state.lock().unwrap(); if state.service_handle == Some(service_handle) { state.recv_handle = None; state.ind_handle = None; state.ind_cccd_handle = None; } Ok(()) } /// 取消注册服务 fn unregister_service(&self, service_handle: Handle) -> Result<(), EspError> { let mut state = self.state.lock().unwrap(); if state.service_handle == Some(service_handle) { state.gatt_if = None; state.service_handle = None; } Ok(()) } /// 配置并且启动服务 /// 在通知我们创建服务后从事件回调中调用 fn configure_and_start_service(&self, service_handle: Handle) -> Result<(), EspError> { // 初始化service_handle服务 self.state.lock().unwrap().service_handle = Some(service_handle); // 开启gatts服务 // pub type Handle = u16; 由于u16是基本类型,实现了copy,所以下面两个函数都能使用,本质是copy了一份数据 self.gatts.start_service(service_handle)?; // 添加特性 self.add_characteristics(service_handle)?; Ok(()) } /// 添加注册的两个uuid特性到服务中 /// 在通知我们创建服务后从事件回调中调用 fn add_characteristics(&self, service_handle: Handle) -> Result<(), EspError> { // permissions 定义了客户端对该特征的操作权限。 // 在这里,Permission::Write 表示客户端可以写入该特征的值。 // 其他常见的权限包括: // Permission::Read:允许读取特征值。 // Permission::Notify:允许服务器主动通知客户端特征值的变化。 // Permission::Indicate:类似于通知,但需要客户端确认。 // properties 定义了特征的属性(Properties),描述了该特征支持的操作类型。 // 在这里,Property::Write 表示客户端可以通过写操作更新该特征的值。 // 其他常见的属性包括: // Property::Read:允许客户端读取特征值。 // Property::Notify:允许服务器发送通知。 // Property::Indicate:允许服务器发送指示 self.gatts.add_characteristic( service_handle, // 服务的唯一标识 &GattCharacteristic { uuid: BtUuid::uuid128(RECV_CHARACTERISTIC_UUID), permissions: enum_set!(Permission::Write), properties: enum_set!(Property::Write), max_len: 200, // 定义了特征值的最大长度(以字节为单位) auto_rsp: AutoResponse::ByApp, // 表示应用程序需要手动响应客户端的请求 }, &[], // 表示该特征的描述符(Descriptors)列表,&[] 表示没有附加任何描述符 )?; self.gatts.add_characteristic( service_handle, &GattCharacteristic { uuid: BtUuid::uuid128(IND_CHARACTERISTIC_UUID), permissions: enum_set!(Permission::Write | Permission::Read), properties: enum_set!(Property::Indicate), max_len: 200, // Mac iondicate data auto_rsp: AutoResponse::ByApp, }, &[], )?; Ok(()) } /// 添加 CCCD 描述 /// 只添加特征indicate fn register_characteristic( &self, service_handle: Handle, attr_handle: Handle, char_uuid: BtUuid, ) -> Result<(), EspError> { // 在找esp主动推送消息给客户端的uuid服务 let indicate_char = { let mut state = self.state.lock().unwrap(); if state.service_handle != Some(service_handle) { false } else if char_uuid == BtUuid::uuid128(RECV_CHARACTERISTIC_UUID) { state.recv_handle = Some(attr_handle); false } else if char_uuid == BtUuid::uuid128(IND_CHARACTERISTIC_UUID) { state.ind_handle = Some(attr_handle); true } else { false } }; if indicate_char { self.gatts.add_descriptor( service_handle, &GattDescriptor { uuid: BtUuid::uuid16(0x2902), // CCCD 通常用于控制特征的通知(Notify)和指示(Indicate)功能,允许客户端订阅或取消订阅特征值的变化 permissions: enum_set!(Permission::Read | Permission::Write), }, )?; } Ok(()) } /// 注册 CCCD 描述符 /// 在通知我们添加了描述符后,从事件回调中调用 /// 这段代码的作用是注册一个 CCCD(Client Characteristic Configuration Descriptor)描述符,并将其相关信息存储到内部状态中。具体功能如下: /// 验证描述符的 UUID 是否为标准的 CCCD UUID(0x2902)。 /// 确认描述符所属的服务句柄是否与内部记录的服务句柄一致。 /// 如果条件满足,则将描述符的属性句柄(attr_handle)存储到内部状态中,以便后续用于处理通知或指示功能。 /// 这种设计通常用于 BLE(蓝牙低功耗)设备的 GATT 服务实现中,确保 CCCD 的句柄被正确记录,从而支持客户端订阅或取消订阅特征值的变化 fn register_cccd_descriptor( &self, service_handle: Handle, attr_handle: Handle, descr_uuid: BtUuid, ) -> Result<(), EspError> { let mut state = self.state.lock().unwrap(); if descr_uuid == BtUuid::uuid16(0x2902) // CCCD UUID && state.service_handle == Some(service_handle) { state.ind_cccd_handle = Some(attr_handle); } Ok(()) } /// 从客户端接收消息 fn register_conn_mtu(&self, conn_id: ConnectionId, mtu: u16) -> Result<(), EspError> { let mut state = self.state.lock().unwrap(); // 找到目标客户端 if let Some(conn) = state .connections .iter_mut() .find(|conn| conn.conn_id == conn_id) { // 并且设置最大传输单元的值,字节 conn.mtu = Some(mtu); } Ok(()) } /// 创建一个新的连接 fn create_conn(&self, conn_id: ConnectionId, addr: BdAddr) -> Result<(), EspError> { // 创建指定大小的连接,如果超过,新的连接将不再处理 let added = { let mut state = self.state.lock().unwrap(); if state.connections.len() < MAX_CONNECTIONS { state .connections .push(Connection { peer: addr, conn_id, subscribed: false, mtu: None, }) .map_err(|_| ()) .unwrap(); true } else { false } }; if added { // 如果已经添加,则配置gap参数 // addr:表示目标设备的蓝牙地址(BdAddr),用于标识要配置连接参数的远程设备。 // min_int_ms:表示连接间隔的最小值(单位为毫秒)。 // max_int_ms:表示连接间隔的最大值(单位为毫秒)。 // latency_ms:表示连接延迟(单位为毫秒)。 // timeout_ms:表示超时时间(单位为毫秒)。 self.gap.set_conn_params_conf(addr, 10, 20, 0, 400)?; } Ok(()) } /// 删除一个连接 fn delete_conn(&self, addr: BdAddr) -> Result<(), EspError> { let mut state = self.state.lock().unwrap(); // Connection { peer, .. }:这是结构体解构语法,用于从 Connection 结构体中提取字段 peer if let Some(index) = state .connections .iter() .position(|Connection { peer, .. }| *peer == addr) { // remove: // 从向量中移除指定索引位置的元素。 // 将该索引之后的所有元素向前移动一位,以保持顺序。 // 时间复杂度为 O(n),因为可能需要移动多个元素。 // swap_remove: // 从向量中移除指定索引位置的元素。 // 使用最后一个元素填补空缺,不保证顺序。 // 时间复杂度为 O(1),因为只涉及少量操作。 state.connections.swap_remove(index); } Ok(()) } /// 一个辅助方法,用于处理向我们发送数据到 “recv” 特征的客户端 #[allow(clippy::too_many_arguments)] fn recv( &self, _gatt_if: GattInterface, conn_id: ConnectionId, _trans_id: TransferId, addr: BdAddr, handle: Handle, offset: u16, _need_rsp: bool, _is_prep: bool, value: &[u8], ) -> Result<bool, EspError> { let mut state = self.state.lock().unwrap(); let recv_handle = state.recv_handle; let ind_cccd_handle = state.ind_cccd_handle; let Some(conn) = state .connections .iter_mut() .find(|conn| conn.conn_id == conn_id) else { return Ok(false); }; if Some(handle) == ind_cccd_handle { // 订阅或取消订阅我们的(indication characteristic)指示特性 if offset == 0 && value.len() == 2 { let value = u16::from_le_bytes([value[0], value[1]]); if value == 0x02 { if !conn.subscribed { conn.subscribed = true; self.on_subscribed(conn.peer); } } else if conn.subscribed { conn.subscribed = false; self.on_unsubscribed(conn.peer); } } } else if Some(handle) == recv_handle { // Receive data on the recv characteristic self.on_recv(addr, value, offset, conn.mtu); } else { return Ok(false); } Ok(true) } /// 辅助方法,向Peer客户端发送响应节点,该 Peer 节点刚刚向我们发送了 “recv” 上的一些数据 /// characteristic. (特征) /// 这只是必要的,因为我们支持写确认(与未确认的写入相比,这是更复杂的情况) #[allow(clippy::too_many_arguments)] fn send_write_response( &self, gatt_if: GattInterface, conn_id: ConnectionId, trans_id: TransferId, handle: Handle, offset: u16, need_rsp: bool, is_prep: bool, value: &[u8], ) -> Result<(), EspError> { // 是否需要处理 写确认 if !need_rsp { return Ok(()); } // 是否准备好了数据 if is_prep { let mut state = self.state.lock().unwrap(); state .response .attr_handle(handle) .auth_req(0) .offset(offset) .value(value) .map_err(|_| EspError::from_infallible::<ESP_FAIL>())?; self.gatts.send_response( gatt_if, conn_id, trans_id, GattStatus::Ok, Some(&state.response), )?; } else { self.gatts .send_response(gatt_if, conn_id, trans_id, GattStatus::Ok, None)?; } Ok(()) } /// 处理下一个 indication fn confirm_indication(&self) -> Result<(), EspError> { let mut state = self.state.lock().unwrap(); if state.ind_confirmed.is_none() { // Should not happen:表示我们已收到 // 我们没有发送一个指示(indication) // unreachable!() 是 Rust 提供的一个宏,用于标记代码中“不应该到达”的位置,到这里会恐慌 unreachable!(); } // 通知主循环可以发送下一个indication state.ind_confirmed = None; // So that the main loop can send the next indication self.condvar.notify_all(); Ok(()) } fn check_esp_status(&self, status: Result<(), EspError>) { if let Err(e) = status { warn!("检查esp的状态错误为: {:?}", e); } } fn check_bt_status(&self, status: BtStatus) -> Result<(), EspError> { // matches! 是匹配模式,!取反 // bt状态不匹配就抛连接失败异常 if !matches!(status, BtStatus::Success) { warn!("Got status: {:?}", status); Err(EspError::from_infallible::<ESP_FAIL>()) } else { Ok(()) } } fn check_gatt_status(&self, status: GattStatus) -> Result<(), EspError> { if !matches!(status, GattStatus::Ok) { warn!("gatt的状态错误: {:?}", status); Err(EspError::from_infallible::<ESP_FAIL>()) } else { Ok(()) } } } 新建一个ble_recv_message.rs文件,处理BLE的消息接收use log::{error, info}; use crate::{driver_manage::{GLOBAL_LED}, message::{LedMessage, Message}}; pub fn handle_message(src:&str) -> anyhow::Result<()>{ let message = Message::from_str(src); if let Ok(message) = message{ match message { Message::Led(led_message) => { handle_led_message(led_message)? }, Message::Unknown => { error!("收到错误的类型") }, } } anyhow::Ok(()) } fn handle_led_message(led_message:LedMessage)->anyhow::Result<()>{ info!("{:?}",led_message); if led_message.value == 1{ let mut guard = GLOBAL_LED.lock().unwrap(); guard.as_mut().unwrap().set_high()?; }else { let mut guard = GLOBAL_LED.lock().unwrap(); guard.as_mut().unwrap().set_low()?; } anyhow::Ok(()) } 新建message.rs,处理消息传输,序列化跟反序列化 use serde::{Deserialize, Serialize}; #[derive(Debug,Serialize,Deserialize)] #[serde(tag="type")] pub enum Message{ #[serde(rename = "led")] Led(LedMessage), #[serde(other)] Unknown } #[derive(Debug,Serialize,Deserialize)] #[serde(rename_all = "camelCase")] pub struct LedMessage{ // 0关,1开 pub value:u8 } impl Message{ pub fn to_json(&self) -> serde_json::Result<String>{ serde_json::to_string(self) } pub fn from_str(src:&str) -> serde_json::Result<Self>{ serde_json::from_str(src) } } 新建driver_manage.rs文件,用来管理注册的外设use std::sync::Mutex; use esp_idf_hal::{gpio::{AnyIOPin, AnyOutputPin, Gpio9, Output, OutputPin, PinDriver}, peripheral::Peripheral, prelude::Peripherals}; pub static GLOBAL_LED :Mutex<Option<PinDriver<'static,AnyOutputPin,Output>>> = Mutex::new(None); pub fn init_global(led_gpio:AnyOutputPin) -> anyhow::Result<()>{ { let led = PinDriver::output(led_gpio)?; let mut guard = GLOBAL_LED.lock().unwrap(); *guard = Some(led); } anyhow::Ok(()) } 编辑main.rs文件 use esp_idf_hal::{ gpio::OutputPin, prelude::Peripherals}; use esp_idf_svc::{log::EspLogger, nvs::EspDefaultNvsPartition}; mod ble_server; mod driver_manage; mod message; mod ble_recv_message; fn main() -> anyhow::Result<()> { esp_idf_svc::sys::link_patches(); EspLogger::initialize_default(); let peripherals = Peripherals::take()?; let nvs = EspDefaultNvsPartition::take()?; driver_manage::init_global( peripherals.pins.gpio12.downgrade_output())?; ble_server::start(peripherals.modem,nvs.clone())?; anyhow::Ok(()) } 效果展示成功接收到客户端消息
2025年05月01日
43 阅读
0 评论
0 点赞
配置和使用 CDN 加速网站
在现代网站建设中,为了提高访问速度和用户体验,使用内容分发网络(CDN)成为了一个普遍的选择。本文将带你了解 CDN 配置的基本流程,帮助你为网站实现高效的加速和稳定性。一、什么是 CDN?CDN,全称为内容分发网络(Content Delivery Network),是一种通过将网站内容分发到全球不同的节点来加速用户访问的技术。通过 CDN,当用户请求访问时,系统会将请求路由到离用户最近的服务器节点,减少响应时间并提高加载速度。二、CDN 配置的基本步骤以下是如何将 CDN 集成到你的网站的详细步骤:1. 注册并登录 CDN 平台首先,选择一个适合的 CDN 服务提供商,例如 腾讯云、阿里云、或本地云服务商。注册并登录 CDN 服务控制台。2. 添加加速域名在 CDN 管理平台上,添加你想加速的域名,例如 pan.example.com。CDN 平台会为你生成一个唯一的 CNAME 地址,例如 cdn.exampleprovider.com,这是用于 DNS 解析的。3. 配置源站源站域名:设置为网站服务器的实际域名或 IP 地址,如 pan.example.com 或服务器的公共 IP。回源 Host:通常设置为源站域名 pan.example.com。这确保了回源请求中的 Host 头与原始请求一致。如果有特殊需求,可根据情况调整。4. DNS 解析配置在你的域名服务商的 DNS 控制台中,将 pan.example.com 的 DNS 记录配置为 CNAME,并指向 CDN 提供的 CNAME 地址 cdn.exampleprovider.com。保存配置并等待 DNS 解析生效,这个过程可能需要几分钟到几小时。5. 调整回源设置回源方式:选择通过域名或 IP 进行回源。如果使用 IP,可以避免循环解析问题,但需要保证 IP 的稳定性。缓存策略:根据你的网站内容类型来设置缓存时间,例如静态资源可以长时间缓存,而动态内容应设置较短的缓存时间或不缓存。回源协议:设置为 HTTP 或 HTTPS,根据你网站的安全需求选择合适的协议。6. 检查配置和测试完成配置后,通过浏览器或网络工具访问 pan.example.com,确认访问流量已经通过 CDN 节点加速。同时,可以在 CDN 平台查看访问日志和缓存命中率,并根据需要优化缓存策略。三、常见问题及解决方案1. 加速域名与源站域名相同在某些情况下,你可能想将加速域名(如 pan.example.com)与源站域名设置为相同。这是可行的,但需注意以下几点:DNS 配置:确保加速域名正确解析为 CDN 提供的 CNAME 地址。回源避免循环:CDN 配置时,可以选择使用源站 IP 来避免请求回源至 CDN 本身。2. 回源 Host 的选择回源 Host 设置为源站域名(如 pan.example.com)即可。如果源站有特殊的 Host 验证需求,确保该 Host 设置能够被服务器接受和处理。3. 配置未生效或请求循环检查 DNS 解析是否正确指向 CDN 的 CNAME 地址。确认 CDN 配置中的源站地址无误,确保它不会导致回源请求指向 CDN 节点。四、优化 CDN 的使用分层缓存:根据网站的访问模式和流量来源设置多级缓存策略,以实现更高的缓存命中率。带宽和流量监控:定期检查 CDN 提供商的控制台,查看带宽使用和流量统计,避免超出预算。安全功能:使用 CDN 提供的额外安全功能,如 DDoS 防护和 Web 应用防火墙(WAF),来保护网站免受攻击。
2024年11月06日
117 阅读
0 评论
3 点赞
java使用日志
maven工程中引入日志门面框架 <!--日志门面--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.7</version> </dependency>引入日志实现框架<!--logback 日志实现--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.4.8</version> </dependency>在resource目录下配置 logback.xml 文件<?xml version="1.0" encoding="UTF-8" ?> <configuration> <!-- 配置集中管理属性 我们可以直接修改属性的value值 格式:${name} --> <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"></property> <!-- 日志输出格式: %-5level %d{yyyy-MM-dd HH:mm:ss.SSS}日期 %c类的完整名称 %M为方法 %L为行号 %thread为线程名称 %m或者$msg为信息 %n换行 --> <!--定义日志文件保存路径属性--> <!--win写/log会在盘符根目录,logs/在项目根目录--> <property name="log_dir" value="logs/"></property> <!--控制日志输出的appender--> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <!--控制输出流对象 默认System.out 改为System.err--> <target>System.err</target> <!--日志消息格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> </appender> <!--日志文件输出的appender--> <appender name="file" class="ch.qos.logback.core.FileAppender"> <!--日志文件保存路径--> <file>${log_dir}/logback.log</file> <!--日志消息格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> </appender> <!--root logger 配置--> <root level="ALL"> <appender-ref ref="console"/> <!-- <appender-ref ref="file"/>--> </root> </configuration>在java文件中引入使用public static final Logger logger = LoggerFactory.getLogger(Application.class);finish
2024年02月03日
143 阅读
0 评论
2 点赞
2021-01-24
php表单提交并发送邮件给某个邮箱(示例源码)
首先,建立一个index.html文件,代码如下:<html> <head> <title>Simple Send Mail </title> </head> <body> <h1>Mail Form</h1> <form name="form1" method="post" action="mail.php"> <table> <tr><td><b>To</b></td><td> <input type="text" name="mailto" size="35"> </td></tr> <tr><td><b>Subject</b></td> <td><input type="text" name="mailsubject" size="35"></td> </tr> <tr><td><b>Message</b></td> <td> <textarea name="mailbody" cols="50" rows="7"></textarea> </td> </tr> <tr><td colspan="2"> <input type="submit" name="Submit" value="Send"> </td> </tr> </table> </form> </body> </html>然后新建一个“mail.php”文档把传输的文档进行发送<?php $stm="邮件内容"; require("smtp.php"); ########################################## $smtpserver = "smtp.qq.com";//SMTP服务器 $smtpserverport = "465";//SMTP服务器端口 $smtpusermail = "XXX@qq.com";//SMTP服务器的用户邮箱 $smtpemailto = "AAA@qq.com";//发送给谁 $smtpuser = "XXX@qq.com";//SMTP服务器的用户帐号 $smtppass = "666";//SMTP服务器的用户密码 $mailsubject = "666 ";//邮件主题 $mailbody = $stm;//邮件内容 $mailtype = "HTML";//邮件格式(HTML/TXT),TXT为文本邮件 ########################################## $smtp = new smtp($smtpserver,$smtpserverport,true,$smtpuser,$smtppass);//这里面的一个true是表示使用身份验证,否则不使用身份验证. $smtp->debug = TRUE;//是否显示发送的调试信息 $smtp->sendmail($smtpemailto, $smtpusermail, $mailsubject, $mailbody, $mailtype); echo "<script>alert('邮件发送成功');parent.document.ADDUser.cheheh.click();</script>"; exit; } ?> 最后编写一个邮件类“smtp.php”<?php class smtp { /* Public Variables */ var $smtp_port; var $time_out; var $host_name; var $log_file; var $relay_host; var $debug; var $auth; var $user; var $pass; /* Private Variables */ var $sock; /* Constractor */ function smtp($relay_host = "", $smtp_port = 25,$auth = false,$user,$pass) { $this->debug = FALSE; $this->smtp_port = $smtp_port; $this->relay_host = $relay_host; $this->time_out = 30; //is used in fsockopen() $this->auth = $auth;//auth $this->user = $user; $this->pass = $pass; $this->host_name = "localhost"; //is used in HELO command $this->log_file = ""; $this->sock = FALSE; } /* Main Function */ function sendmail($to, $from, $subject = "", $body = "", $mailtype, $cc = "", $bcc = "", $additional_headers = "") { $mail_from = $this->get_address($this->strip_comment($from)); $body = ereg_replace("(^|(\r\n))(\.)", "\1.\3", $body); $header .= "MIME-Version:1.0\r\n"; if($mailtype=="HTML") { $header .= "Content-Type:text/html\r\n"; } $header .= "To: ".$to."\r\n"; if ($cc != "") { $header .= "Cc: ".$cc."\r\n"; } $header .= "From: $from<".$from.">\r\n"; $header .= "Subject: ".$subject."\r\n"; $header .= $additional_headers; $header .= "Date: ".date("r")."\r\n"; $header .= "X-Mailer:By Redhat (PHP/".phpversion().")\r\n"; list($msec, $sec) = explode(" ", microtime()); $header .= "Message-ID: <".date("YmdHis", $sec).".".($msec*1000000).".".$mail_from.">\r\n"; $TO = explode(",", $this->strip_comment($to)); if ($cc != "") { $TO = array_merge($TO, explode(",", $this->strip_comment($cc))); } if ($bcc != "") { $TO = array_merge($TO, explode(",", $this->strip_comment($bcc))); } $sent = TRUE; foreach ($TO as $rcpt_to) { $rcpt_to = $this->get_address($rcpt_to); if (!$this->smtp_sockopen($rcpt_to)) { $this->log_write("Error: Cannot send email to ".$rcpt_to."\n"); $sent = FALSE; continue; } if ($this->smtp_send($this->host_name, $mail_from, $rcpt_to, $header, $body)) { $this->log_write("E-mail has been sent to <".$rcpt_to.">\n"); } else { $this->log_write("Error: Cannot send email to <".$rcpt_to.">\n"); $sent = FALSE; } fclose($this->sock); $this->log_write("Disconnected from remote host\n"); } return $sent; } /* Private Functions */ function smtp_send($helo, $from, $to, $header, $body = "") { if (!$this->smtp_putcmd("HELO", $helo)) { return $this->smtp_error("sending HELO command"); } #auth if($this->auth) { if (!$this->smtp_putcmd("AUTH LOGIN", base64_encode($this->user))) { return $this->smtp_error("sending HELO command"); } if (!$this->smtp_putcmd("", base64_encode($this->pass))) { return $this->smtp_error("sending HELO command"); } } if (!$this->smtp_putcmd("MAIL", "FROM:<".$from.">")) { return $this->smtp_error("sending MAIL FROM command"); } if (!$this->smtp_putcmd("RCPT", "TO:<".$to.">")) { return $this->smtp_error("sending RCPT TO command"); } if (!$this->smtp_putcmd("DATA")) { return $this->smtp_error("sending DATA command"); } if (!$this->smtp_message($header, $body)) { return $this->smtp_error("sending message"); } if (!$this->smtp_eom()) { return $this->smtp_error("sending <CR><LF>.<CR><LF> [EOM]"); } if (!$this->smtp_putcmd("QUIT")) { return $this->smtp_error("sending QUIT command"); } return TRUE; } function smtp_sockopen($address) { if ($this->relay_host == "") { return $this->smtp_sockopen_mx($address); } else { return $this->smtp_sockopen_relay(); } } function smtp_sockopen_relay() { $this->log_write("Trying to ".$this->relay_host.":".$this->smtp_port."\n"); $this->sock = @fsockopen($this->relay_host, $this->smtp_port, $errno, $errstr, $this->time_out); if (!($this->sock && $this->smtp_ok())) { $this->log_write("Error: Cannot connenct to relay host ".$this->relay_host."\n"); $this->log_write("Error: ".$errstr." (".$errno.")\n"); return FALSE; } $this->log_write("Connected to relay host ".$this->relay_host."\n"); return TRUE;; } function smtp_sockopen_mx($address) { $domain = ereg_replace("^.+@([^@]+)$", "\1", $address); if (!@getmxrr($domain, $MXHOSTS)) { $this->log_write("Error: Cannot resolve MX \"".$domain."\"\n"); return FALSE; } foreach ($MXHOSTS as $host) { $this->log_write("Trying to ".$host.":".$this->smtp_port."\n"); $this->sock = @fsockopen($host, $this->smtp_port, $errno, $errstr, $this->time_out); if (!($this->sock && $this->smtp_ok())) { $this->log_write("Warning: Cannot connect to mx host ".$host."\n"); $this->log_write("Error: ".$errstr." (".$errno.")\n"); continue; } $this->log_write("Connected to mx host ".$host."\n"); return TRUE; } $this->log_write("Error: Cannot connect to any mx hosts (".implode(", ", $MXHOSTS).")\n"); return FALSE; } function smtp_message($header, $body) { fputs($this->sock, $header."\r\n".$body); $this->smtp_debug("> ".str_replace("\r\n", "\n"."> ", $header."\n> ".$body."\n> ")); return TRUE; } function smtp_eom() { fputs($this->sock, "\r\n.\r\n"); $this->smtp_debug(". [EOM]\n"); return $this->smtp_ok(); } function smtp_ok() { $response = str_replace("\r\n", "", fgets($this->sock, 512)); $this->smtp_debug($response."\n"); if (!ereg("^[23]", $response)) { fputs($this->sock, "QUIT\r\n"); fgets($this->sock, 512); $this->log_write("Error: Remote host returned \"".$response."\"\n"); return FALSE; } return TRUE; } function smtp_putcmd($cmd, $arg = "") { if ($arg != "") { if($cmd=="") { $cmd = $arg; } else { $cmd = $cmd." ".$arg; } } fputs($this->sock, $cmd."\r\n"); $this->smtp_debug("> ".$cmd."\n"); return $this->smtp_ok(); } function smtp_error($string) { $this->log_write("Error: Error occurred while ".$string.".\n"); return FALSE; } function log_write($message) { $this->smtp_debug($message); if ($this->log_file == "") { return TRUE; } $message = date("M d H:i:s ").get_current_user()."[".getmypid()."]: ".$message; if (!@file_exists($this->log_file) || !($fp = @fopen($this->log_file, "a"))) { $this->smtp_debug("Warning: Cannot open log file \"".$this->log_file."\"\n"); return FALSE;; } flock($fp, LOCK_EX); fputs($fp, $message); fclose($fp); return TRUE; } function strip_comment($address) { $comment = "\([^()]*\)"; while (ereg($comment, $address)) { $address = ereg_replace($comment, "", $address); } return $address; } function get_address($address) { $address = ereg_replace("([ \t\r\n])+", "", $address); $address = ereg_replace("^.*<(.+)>.*$", "\1", $address); return $address; } function smtp_debug($message) { if ($this->debug) { //echo $message; } } } ?><?php class smtp { /* Public Variables */ var $smtp_port; var $time_out; var $host_name; var $log_file; var $relay_host; var $debug; var $auth; var $user; var $pass; /* Private Variables */ var $sock; /* Constractor */ function smtp($relay_host = "", $smtp_port = 25,$auth = false,$user,$pass) { $this->debug = FALSE; $this->smtp_port = $smtp_port; $this->relay_host = $relay_host; $this->time_out = 30; //is used in fsockopen() $this->auth = $auth;//auth $this->user = $user; $this->pass = $pass; $this->host_name = "localhost"; //is used in HELO command $this->log_file = ""; $this->sock = FALSE; } /* Main Function */ function sendmail($to, $from, $subject = "", $body = "", $mailtype, $cc = "", $bcc = "", $additional_headers = "") { $mail_from = $this->get_address($this->strip_comment($from)); $body = ereg_replace("(^|(\r\n))(\.)", "\1.\3", $body); $header .= "MIME-Version:1.0\r\n"; if($mailtype=="HTML") { $header .= "Content-Type:text/html\r\n"; } $header .= "To: ".$to."\r\n"; if ($cc != "") { $header .= "Cc: ".$cc."\r\n"; } $header .= "From: $from<".$from.">\r\n"; $header .= "Subject: ".$subject."\r\n"; $header .= $additional_headers; $header .= "Date: ".date("r")."\r\n"; $header .= "X-Mailer:By Redhat (PHP/".phpversion().")\r\n"; list($msec, $sec) = explode(" ", microtime()); $header .= "Message-ID: <".date("YmdHis", $sec).".".($msec*1000000).".".$mail_from.">\r\n"; $TO = explode(",", $this->strip_comment($to)); if ($cc != "") { $TO = array_merge($TO, explode(",", $this->strip_comment($cc))); } if ($bcc != "") { $TO = array_merge($TO, explode(",", $this->strip_comment($bcc))); } $sent = TRUE; foreach ($TO as $rcpt_to) { $rcpt_to = $this->get_address($rcpt_to); if (!$this->smtp_sockopen($rcpt_to)) { $this->log_write("Error: Cannot send email to ".$rcpt_to."\n"); $sent = FALSE; continue; } if ($this->smtp_send($this->host_name, $mail_from, $rcpt_to, $header, $body)) { $this->log_write("E-mail has been sent to <".$rcpt_to.">\n"); } else { $this->log_write("Error: Cannot send email to <".$rcpt_to.">\n"); $sent = FALSE; } fclose($this->sock); $this->log_write("Disconnected from remote host\n"); } return $sent; } /* Private Functions */ function smtp_send($helo, $from, $to, $header, $body = "") { if (!$this->smtp_putcmd("HELO", $helo)) { return $this->smtp_error("sending HELO command"); } #auth if($this->auth) { if (!$this->smtp_putcmd("AUTH LOGIN", base64_encode($this->user))) { return $this->smtp_error("sending HELO command"); } if (!$this->smtp_putcmd("", base64_encode($this->pass))) { return $this->smtp_error("sending HELO command"); } } if (!$this->smtp_putcmd("MAIL", "FROM:<".$from.">")) { return $this->smtp_error("sending MAIL FROM command"); } if (!$this->smtp_putcmd("RCPT", "TO:<".$to.">")) { return $this->smtp_error("sending RCPT TO command"); } if (!$this->smtp_putcmd("DATA")) { return $this->smtp_error("sending DATA command"); } if (!$this->smtp_message($header, $body)) { return $this->smtp_error("sending message"); } if (!$this->smtp_eom()) { return $this->smtp_error("sending <CR><LF>.<CR><LF> [EOM]"); } if (!$this->smtp_putcmd("QUIT")) { return $this->smtp_error("sending QUIT command"); } return TRUE; } function smtp_sockopen($address) { if ($this->relay_host == "") { return $this->smtp_sockopen_mx($address); } else { return $this->smtp_sockopen_relay(); } } function smtp_sockopen_relay() { $this->log_write("Trying to ".$this->relay_host.":".$this->smtp_port."\n"); $this->sock = @fsockopen($this->relay_host, $this->smtp_port, $errno, $errstr, $this->time_out); if (!($this->sock && $this->smtp_ok())) { $this->log_write("Error: Cannot connenct to relay host ".$this->relay_host."\n"); $this->log_write("Error: ".$errstr." (".$errno.")\n"); return FALSE; } $this->log_write("Connected to relay host ".$this->relay_host."\n"); return TRUE;; } function smtp_sockopen_mx($address) { $domain = ereg_replace("^.+@([^@]+)$", "\1", $address); if (!@getmxrr($domain, $MXHOSTS)) { $this->log_write("Error: Cannot resolve MX \"".$domain."\"\n"); return FALSE; } foreach ($MXHOSTS as $host) { $this->log_write("Trying to ".$host.":".$this->smtp_port."\n"); $this->sock = @fsockopen($host, $this->smtp_port, $errno, $errstr, $this->time_out); if (!($this->sock && $this->smtp_ok())) { $this->log_write("Warning: Cannot connect to mx host ".$host."\n"); $this->log_write("Error: ".$errstr." (".$errno.")\n"); continue; } $this->log_write("Connected to mx host ".$host."\n"); return TRUE; } $this->log_write("Error: Cannot connect to any mx hosts (".implode(", ", $MXHOSTS).")\n"); return FALSE; } function smtp_message($header, $body) { fputs($this->sock, $header."\r\n".$body); $this->smtp_debug("> ".str_replace("\r\n", "\n"."> ", $header."\n> ".$body."\n> ")); return TRUE; } function smtp_eom() { fputs($this->sock, "\r\n.\r\n"); $this->smtp_debug(". [EOM]\n"); return $this->smtp_ok(); } function smtp_ok() { $response = str_replace("\r\n", "", fgets($this->sock, 512)); $this->smtp_debug($response."\n"); if (!ereg("^[23]", $response)) { fputs($this->sock, "QUIT\r\n"); fgets($this->sock, 512); $this->log_write("Error: Remote host returned \"".$response."\"\n"); return FALSE; } return TRUE; } function smtp_putcmd($cmd, $arg = "") { if ($arg != "") { if($cmd=="") { $cmd = $arg; } else { $cmd = $cmd." ".$arg; } } fputs($this->sock, $cmd."\r\n"); $this->smtp_debug("> ".$cmd."\n"); return $this->smtp_ok(); } function smtp_error($string) { $this->log_write("Error: Error occurred while ".$string.".\n"); return FALSE; } function log_write($message) { $this->smtp_debug($message); if ($this->log_file == "") { return TRUE; } $message = date("M d H:i:s ").get_current_user()."[".getmypid()."]: ".$message; if (!@file_exists($this->log_file) || !($fp = @fopen($this->log_file, "a"))) { $this->smtp_debug("Warning: Cannot open log file \"".$this->log_file."\"\n"); return FALSE;; } flock($fp, LOCK_EX); fputs($fp, $message); fclose($fp); return TRUE; } function strip_comment($address) { $comment = "\([^()]*\)"; while (ereg($comment, $address)) { $address = ereg_replace($comment, "", $address); } return $address; } function get_address($address) { $address = ereg_replace("([ \t\r\n])+", "", $address); $address = ereg_replace("^.*<(.+)>.*$", "\1", $address); return $address; } function smtp_debug($message) { if ($this->debug) { //echo $message; } } } ?>
2021年01月24日
288 阅读
3 评论
4 点赞
2020-12-20
QQxml消息卡片生成源码
QQ XML消息自定义生成源码 这款源码是一个非常实用的 可以用来引流 可以让分享出来的普通网址变为卡片消息 比如自定义内容 有些友友们 好奇心 这样就可以增加大家的点击了下载地址:https://suxii.lanzous.com/iHtMVjjqfmb
2020年12月20日
544 阅读
1 评论
2 点赞
1
2