热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:RabbitMQ学习总结

本文由编程笔记#小编为大家整理,主要介绍了RabbitMQ学习总结相关的知识,希望对你有一定的参考价值。 原文:RabbitMQ学习总结关于RabbitMQ是什么以及它的概念,不了解的可以先查看一下下
本文由编程笔记#小编为大家整理,主要介绍了RabbitMQ学习总结相关的知识,希望对你有一定的参考价值。



原文:RabbitMQ学习总结

关于RabbitMQ是什么以及它的概念,不了解的可以先查看一下下面推荐的几篇博客

https://blog.csdn.net/whoamiyang/article/details/54954780

https://www.cnblogs.com/frankyou/p/5283539.html

https://blog.csdn.net/mx472756841/article/details/50815895

官网介绍:http://www.rabbitmq.com/getstarted.html

本文github源码:http://www.cnblogs.com/bluesummer/p/8992225.html

因为之前不了解交换机及AMQP协议,上来就研究RabbitMQ,很多概念都有点蒙圈,所以建议大家在学习RabbitMQ之前先对一些概念有基本的了解


安装与配置:



  • 下载Erlang:http://www.erlang.org/downloads

  • 下载rabbitmq:http://www.rabbitmq.com/install-windows.html

  • 环境变量配置:

    新增:ERLANG_HOME= C:Program Fileserlx.x
    新增:RABBITMQ_SERVER=C:Program FilesRabbitMQ Server
    abbitmq_server-x.x.x
    Path中新增:%ERLANG_HOME%in;%RABBITMQ_SERVER%sbin;



服务相关命令



  • rabbitmq-plugins enable rabbitmq_management //开启管理插件

  • rabbitmq-service.bat start //开启服务

  • rabbitmq-service.bat stop //关闭服务

  • rabbitmqctl list_queues //查看任务

注意在执行命令rabbitmqctl list_queues时若报错unable to perform an operation on node。。。。,可将C:Users用户名.erlang.COOKIE.erlang.COOKIE文件拷贝到C:WindowsSystem32configsystemprofile.erlang.COOKIE中替换,然后重启服务

至此RabbitMQ服务我们已经安装好了


后台管理

开启管理插件后我们重启rabbitmq服务,打开http://localhost:15672/后台管理界面,
用户名和密码均为guest

guest账户在最新版本只能通过localhost登陆了,如果想要通过ip来登陆需要设置一下配置文件:


找到/rabbitmq_server-x.x.x/ebin下面的rabbit.app文件文件: 找到:loopback_users将里面的<<”guest”>>删除。

删除后的内容为:{loopback_users, []},然后重启服务


关于用户密码管理的操作我们都可以在管理页面中设置


默认端口:



  1. client端通信口5672

  2. 管理口15672

  3. server间内部通信口25672

  4. erlang发现口:4369

想要修改默认端口可修改 安装目录下 etc/rabbitmq.config文件,有个默认的example,改一改就可以了


发送消息

我们先构建一个应用程序,建议创建一个winform或wpf程序,控制台在这里并不太好用。
项目中引用nuget包:RabbitMQ.Client

接下来我们编写一个发送消息和接收消息的代码:

public void SendMsg(string message)
{
//这里的端口及用户名都是默认的,可以直接设置一个hostname=“localhost”其他的不用配置
var factory = new ConnectionFactory() { HostName = "192.168.1.15",Port=5672,UserName= "guest",Password= "guest" };
//创建一个连接,连接到服务器:
using (var cOnnection= factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
//创建一个名称为hello的消息队列
//durable:队列持久化,为了防止RabbitMQ在退出或者crash等异常情况下数据不会丢失,可以设置durable为true
//exclusive:排他队列,只对首次声明它的连接(Connection)可见,不允许其他连接访问,在连接断开的时候自动删除,无论是否设置了持久化
//autoDelete:自动删除,如果该队列已经没有消费者时,该队列会被自动删除。这种队列适用于临时队列。
channel.QueueDeclare(queue: "hello", durable: false, exclusive: false, autoDelete: true, arguments: null);
//channel.BasicConsume("hello", autoAck: true);

var props = channel.CreateBasicProperties();
//消息持久化,若启用durable则该属性启用
props.Persistent = true;
//封装消息主体
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "", routingKey: "hello", basicProperties: props, body: body);
Console.WriteLine(" 发送消息{0}", message);
}
}
}

public class Consumer : IDisposable
{
public static int _number;
private static ConnectionFactory factory;
private static IConnection connection;
static Receive()
{
factory = new ConnectionFactory() { HostName = "localhost" };
}
public Receive()
{
_number++;
}
public void ReceiveMsg(Action callback)
{
if(cOnnection==null||!connection.IsOpen)
cOnnection= factory.CreateConnection();
IModel _channel = connection.CreateModel();
_channel.QueueDeclare(queue: "hello", durable: false, exclusive: false, autoDelete: true, arguments: null);
_channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
// 创建事件驱动的消费者
var cOnsumer= new EventingBasicConsumer(_channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
callback($"number:{_number}.message:{message}");
//模拟消息处理需要两秒
Thread.Sleep(2000);
//显示发送ack确认接收并处理完成消息,只有在前面进行启用显示发送ack机制后才奏效。
_channel.BasicAck(ea.DeliveryTag, false);
};
//指定消费队列,autoAct是否自动确认
string result = _channel.BasicConsume(queue: "hello", autoAck: false, consumer: consumer);
//设置后当所有的channel都关闭了连接会自动关闭
//connection.AutoClose = true;
}
public void Dispose()
{
if (connection != null && connection.IsOpen)
connection.Dispose();
}
}

上面一个很简单的消息队列的发送者和消费者,解释下基本的流程:


Publisher中调用send函数先创建一个连接到服务器,然后用该连接创建了一个channel,接着用该channel声明了一个hello的队列,最后向默认的交换机发送了一条消息。(exchange: "") 空字符串即为默认的交换机 ,消息的路由为hello ,默认的交换机是direct类型,根据路由名称完全匹配队列的名称。所有的队列都会绑定到默认的交换机上,路由名称就是队列的名称。所以默认的交换机将消息发送到名声为hello 的队列。紧接着Consumer中调用ReceiveMsg 函数从hello 队列获取消息,获取到消息后调用act函数通知broker该消息已经被成功地消费,broker将这条消息删除,如下图
技术分享图片


网上有部分示例是使用QueueingBasicConsumer来创建消费者的,我发现在新版本中已经过时了,原因是它容易造成内存溢出性能降低等一系列的问题,简单说一下QueueingBasicConsumer的处理流程,它接收到消息之后会把消息塞到一个Queue队列中,然后用户来循环这个队列处理消息,但是如果你一个消息处理的很慢,而消息又发送过来的很快很大,就会造成队列里面存的消息越来越多,最终造成内存溢出。所以现在推荐使用EventingBasicConsumer或者继承DefaultBasicConsumer来创建消费者,事件驱动就不会有这个问题了

上面的代码需要注意以下几点:




  1. 想要通过guest账户指定ip连接需要修改loopback_users配置

  2. 我们调用QueueDeclare函数声明一个队列,如果设置了队列持久化,即使重启服务队列仍然在。如果不是持久化,即使消息全都被消费了,只要服务没有重启,队列仍然存在。RabbitMQ不允许你使用不同的参数重新定义一个已经存在的队列,所以要么删除队列要么重新命名一个队列,删除队列可以通过管理界面来删除或者调用QueueDelete函数。

  3. 队列如果存在声明一次就够了,如果多次声明了一样的队列将不会有任何异常,但是如果消费者绑定了一个不存在的队列是会发生异常的:**_channel.BasicConsume**,所以习惯是在Woker中将需要监听的队列先声明一遍

  4. 排他队列:大概意思就是通过连接connectionA声明一个排他队列之后,以后也只能通过连接connectionA来访问该队列,其他连接一旦访问就会报队列被锁定的错误,这个实在想不到应用场景

  5. 队列持久化代表的是重启服务后队列仍然在,想要队列里的消息仍然存在需要同时设置消息持久化,但是如果只设置消息持久化不设置队列持久话也没有意义。但这也并不一定能保证消息一定不会丢失。首先必须要有消息确认机制来保证消息一定被正确消费了。最主要的问题是消息写入到磁盘需要一定的时间,如果服务接收到消息没有来得及写入磁盘就挂掉了,那么这个消息就丢失了,对于这一点可以查询一下RabbitMQ集群相关的文章

  6. 默认发送的消息都需要消费者确认,可以通过设置autoAct为true来自动确认消息,也可以调用BasicAck函数确认,总之如果消息需要确认,一定要在消息处理完成之后进行确认。如果当前消费者未确认的消息达到了perfetchCount的数量时,该消费者便无法再接收新的消息。 当消费者连接关闭之后未被确认的消息很快就会被退回。

  7. 可以通过BasicNack()函数将消息重新塞回队列,如果消息未确认消费者断开链接,消息也会退回。需要注意的是:如果不是因为程序异常而仅仅是因为业务逻辑上的错误,则不应该手动退回消息,否则退回的消息永远也无法被消费掉

  8. 我上面定义的消费者原本是想要多次实例化Receive来模拟多个消费者的,然而事实证明并不好用,想要模拟多个消费者还是需要打开多个程序

  9. EventingBasicConsumer的监听会创建一个前台线程一直在运行,所以在winform中如果关闭程序需要dispose掉connection占用的线程



轮询调度

轮询调度就是同时运行多个消费者,当任务数量很多的时候RabbitMQ会将消息分发给不同的消费者(Worker)来减轻压力,想要让RabbitMQ公平的分发任务,需要在worker中用以下代码来设置一个worker的最大未确认消息数量

channel.BasicQos(0, 1, false);

BasicQos方法接收三个参数:


prefetchSize:消费者接收消息的长度,如果长度在小于等于设定值,则接收,如果设置0,则不限消息长度

prefetchCount:消费者可同时缓存的最大消息数量,假设数值设为2,那么队列会向该woker推送两条消息,直到该Woker处理了该消息(处理指的是Act或者nack),队列才会再次向该woker推送新的消息。


上面的势力中,参数prefetchCount=1就代表此Worker同时只会处理一条消息,如果当前的消息没有处理完毕(没有act),rabbitmq就会把剩下的任务发送给其他的worker,如果所有的worker都很忙,消息久会在队列中排队等待


绑定

上面的一个示例中我们用的是默认的交换机发送消息,我们可以通过给exchange赋值来使用指定的交换机,通过QueueBind将交换机与队列进行绑定

_channel.QueueBind("log1", "logs", "info");

声明一个交换机的代码如下

_channel.ExchangeDeclare("logs", ExchangeType.Direct, false, false);

我们将队列log1绑定到了交换机:logs上,路由为info,交换机的类型为Direct,Direct代表的是路由完全匹配,现在我们向logs交换机发送一条消息,路由为info,队列log1就会接收到消息了

channel.BasicPublish(exchange: "logs", routingKey: "info", basicProperties: props, body: body);

队列和交换机的关系是多对多的,交换机的类型常用的有三个:Direct,Fanout,Topic,Headers


Direct:要求路由键完全匹配

Fanout:忽略路由键,给所有绑定到交换机上的队列都发送消息

Topic:模糊匹配,通过字母配合符号“*”和“#”来设置路由键

Headers:Headers类型用的比较少,它也忽略路由键,而是匹配交换机的headers,headers为键值对的hashtable,对publisher和consumer两边设置的header进行匹配,需要指定匹配的方式是 all还是any,具体代码可看github


下面展示了一个使用direct类型交换机的相关代码

public class LogDirectPub
{
public void SendMsg(string message)
{
var factory = new ConnectionFactory() { HostName = "192.168.1.15", Port = 5672, UserName = "guest", Password = "guest" };
//创建一个连接,连接到服务器:
using (var cOnnection= factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
var props = channel.CreateBasicProperties();
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "logs", routingKey: "info", basicProperties: props, body: body);
channel.BasicPublish(exchange: "logs", routingKey: "error", basicProperties: props, body: body);
Console.WriteLine("发送消息{0}", message);
}
}
}
}
public class LogDirectConsumer : IDisposable
{
private static ConnectionFactory factory;
private static IConnection connection;
static LogDirectConsumer()
{
factory = new ConnectionFactory() { HostName = "localhost" };
}
public void ReceiveMsg(Action callback)
{
if (cOnnection== null || !connection.IsOpen)
cOnnection= factory.CreateConnection();
IModel _channel = connection.CreateModel();
_channel.ExchangeDeclare("logs", ExchangeType.Direct, false, false);
_channel.QueueDeclare(queue: "log1", durable: false, exclusive: false, autoDelete: false, arguments: null);
_channel.QueueBind("log1", "logs", "info");
_channel.QueueBind("log1", "logs", "error");
_channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
var cOnsumer= new EventingBasicConsumer(_channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
callback($"log1Write.message:{ea.RoutingKey}:{message}");
//模拟消息处理需要两秒
Thread.Sleep(2000);
_channel.BasicAck(ea.DeliveryTag, false);
};
string result = _channel.BasicConsume(queue: "log1", autoAck: false, consumer: consumer);
}
public void Dispose()
{
if (connection != null && connection.IsOpen)
connection.Dispose();
}
}

RabbitMQ Management HTTP API

RabbitMQ有一套自己的http/api,地址为http://192.168.1.15:15672/api,可以查询你想查的所有信息配置,通过这些api,我们可以自己实现RabbitMQ的监控管理,英文看的头痛,这里有一篇中文的翻译文档:http://www.blogjava.net/qbna350816/archive/2016/08/13/431575.html

这是一个获取所有队列的简单示例:

string username = "guest";
string password = "guest";
string queuesUrl = "http://localhost:15672/api/queues";
///


/// 查询所有队列
///

///
public string GetAllQuenes()
{
string jsOnContent= GetApiResult(queuesUrl).Result;
List queues = JsonConvert.DeserializeObject>(jsonContent);
return JsonConvert.SerializeObject(queues);
}
private async Task GetApiResult(string Url)
{
var client = new HttpClient();
var passByte = Encoding.UTF8.GetBytes(string.Format("{0}:{1}", username, password));
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(passByte));
using (HttpResponseMessage respOnse= await client.GetAsync(Url).ConfigureAwait(false))
{
string result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return result;
}
}

自定义Consumer

之前说过用QueueingBasicConsumer会有性能问题,但是eventconsumer无法阻塞线程,对于某些需要阻塞线程的功能用起来不太方便,这时我们就可以自定义一个Consumer继承DefaultBasicConsumer,只需要实现其中的HandleBasicDeliver函数就可以了,下面是我定义的一个consumer,用来实现后面的Rpc客户端

public class QueueingConsumer : DefaultBasicConsumer
{
private IModel _channel;
private BasicDeliverEventArgs args = new BasicDeliverEventArgs();
private AutoResetEvent argResetEvent = new AutoResetEvent(false);
public QueueingConsumer(IModel channel)
{
_channel = channel;
}
public override void HandleBasicDeliver(string consumerTag,
ulong deliveryTag,
bool redelivered,
string exchange,
string routingKey,
IBasicProperties properties,
byte[] body)
{
args = new BasicDeliverEventArgs
{
COnsumerTag= consumerTag,
DeliveryTag = deliveryTag,
Redelivered = redelivered,
Exchange = exchange,
RoutingKey = routingKey,
BasicProperties = properties,
Body = body
};
argResetEvent.Set();
}
public void GetResult(Action callback)
{
argResetEvent.WaitOne();
callback(args);
}
}

RPC实现

Rpc是什么不用多说了,反正我也就知道他是远程过程调用嘛。用RabbitMQ来实现Rpc,官网有一篇简单的示例,但个人感觉RabbitMQ并不太适合做Rpc。不过用这个示例作为对RabbitMQ的一个学习成果实践还是蛮不错的,下面请看代码:

public class RpcPub
{
public async Task SendMsg(string message)
{
ConnectionFactory factory = RabbitMQHelper.ConFactory;
//创建一个连接,连接到服务器:
using (var cOnnection= factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
//定义一个临时的队列,用来接收返回的消息
string replyQueueName = channel.QueueDeclare().QueueName;
var cOnsumer= new QueueingConsumer(channel);
//监听该临时队列,自动act消息
channel.BasicConsume(queue: replyQueueName, autoAck: true, consumer: consumer);
string corrId = Guid.NewGuid().ToString();
var props = channel.CreateBasicProperties();
//定义ReplyTo让服务端知道返回消息给哪个路由
props.ReplyTo = replyQueueName;
//定义CorrelationId作为消息的唯一关联ID
props.CorrelatiOnId= corrId;
var messageBytes = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "", routingKey: "rpc_queue", basicProperties: props, body: messageBytes);
Task result = new Task(() =>
{
while (true)
{
string replystr = string.Empty;
consumer.GetResult((args) =>
{
if (args.BasicProperties.CorrelatiOnId== corrId)
{
replystr = Encoding.UTF8.GetString(args.Body);
}
});
if (replystr != string.Empty)
return replystr;
}
});
result.Start();
return await result;
}
}
}
}
public class RpcConsumer : IDisposable
{
private ConnectionFactory factory = RabbitMQHelper.ConFactory;
private IConnection connection;
public void ReceiveMsg(Action callback)
{
if (cOnnection== null || !connection.IsOpen)
cOnnection= factory.CreateConnection();
IModel channel = connection.CreateModel();
channel.QueueDeclare(queue: "rpc_queue", durable: false, exclusive: false, autoDelete: false, arguments: null);
//channel.BasicQos(0, 1, false);
var cOnsumer= new EventingBasicConsumer(channel);
consumer.Received += (model, arg) =>
{
var props = arg.BasicProperties;
var replyProps = channel.CreateBasicProperties();
replyProps.CorrelatiOnId= props.CorrelationId;
callback($"接收到消息:{Encoding.UTF8.GetString(arg.Body)}");
var respOnseBytes= Encoding.UTF8.GetBytes($"成功接收你的消息:{ Encoding.UTF8.GetString(arg.Body)}");
channel.BasicPublish(exchange: "", routingKey: props.ReplyTo, basicProperties: replyProps, body: responseBytes);
channel.BasicAck(deliveryTag: arg.DeliveryTag, multiple: false);
};
channel.BasicConsume(queue: "rpc_queue", autoAck: false, consumer: consumer);
}
public void Dispose()
{
if (connection != null && connection.IsOpen)
connection.Dispose();
}
}

基本流程:




  1. 当客户端发送消息之前,创建一个匿名的回调队列channel.QueueDeclare(),并监听该队列。

  2. 客户端获取匿名队列的名称,在请求中设置2个属性:replyTo=回调队列名称;CorrelatiOnId=请求关联的唯一id

  3. 客户端发送请求到rpc_queue队列中。

  4. RPC服务器端监听rpc_queue队列中的请求,当请求到来时,服务器端会处理消息,返回结果发送到replyTo指定的队列,在请求中设置1个属性:CorrelatiOnId=请求过来的CorrelationId

  5. 客户端监听的队列收到消息,检查correlationId是否与之前生成的匹配,匹配成功返回结果。

  6. 对于为什么要验证correlationId这一项,有两个原因,1.消息可能并不是rpc服务器发送的 2.rpc服务如果在某个阶段突然挂掉,可能会发送一个不包含correlationId的消息


技术分享图片


publish confirm

在消费端可通过消息确认机制来保证队列的正常消费 ,在服务端可通过数据持久化到磁盘保证数据的不丢失 ,发送端同样可以使用publish confrim机制来保证数据的正确发送

confirm有普通模式WaitForConfirms和批量模式WaitForConfirmsOrDie

具体流程为:标记该消息需要confirm,发送消息,等待confirm结果。一个保证消息可靠性的相关代码体现为:

var props = channel.CreateBasicProperties();
props.Persistent = true;
var body = Encoding.UTF8.GetBytes("hi");
channel.ConfirmSelect();
channel.BasicPublish(exchange: "ali", routingKey: "ali.point", mandatory: true, basicProperties: props, body: body);
//获取rabbitmq服务返回的消息
channel.BasicReturn += Channel_BasicReturn;
try
{
bool pubAct = channel.WaitForConfirms();
if (!pubAct)
Console.WriteLine("消息发送失败");
}
catch (Exception)
{
Console.WriteLine("消息发送失败");
}

用了confirm机制之后,发送一条消息会遇到以下几种情况:

1.消息成功发送到交换机,成功匹配到队列,pubAct=true
2.消息成功发送到交换机,没有队列绑定该路由,pubAct=true。 此时如果设置mandatory=true,则会触发BasicReturn事件,通知路由未匹配到任何队列,如果mandatory=false,消息直接抛弃
3.交换机名称未定义,或消息发送失败,抛出异常
4.消息成功发送到交换机之后,尚未持久化到磁盘,pubAct=false (尚未验证,消息确认失败的情况不太容易模拟,所以这条结论不一定准确)








推荐阅读
  • 本文提供了 RabbitMQ 3.7 的快速上手指南,详细介绍了环境搭建、生产者和消费者的配置与使用。通过官方教程的指引,读者可以轻松完成初步测试和实践,快速掌握 RabbitMQ 的核心功能和基本操作。 ... [详细]
  • AppFog 是一个基于 CloudFoundry 的多语言 PaaS(平台即服务)提供商,允许用户在其平台上轻松构建和部署 Web 应用程序。本文将通过详细的图文步骤,指导读者如何在 AppFog 免费云平台上成功部署 WordPress,帮助用户快速搭建个人博客或网站。 ... [详细]
  • 浅析python实现布隆过滤器及Redis中的缓存穿透原理_python
    本文带你了解了位图的实现,布隆过滤器的原理及Python中的使用,以及布隆过滤器如何应对Redis中的缓存穿透,相信你对布隆过滤 ... [详细]
  • 单片微机原理P3:80C51外部拓展系统
      外部拓展其实是个相对来说很好玩的章节,可以真正开始用单片机写程序了,比较重要的是外部存储器拓展,81C55拓展,矩阵键盘,动态显示,DAC和ADC。0.IO接口电路概念与存 ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • 在开发过程中,我最初也依赖于功能全面但操作繁琐的集成开发环境(IDE),如Borland Delphi 和 Microsoft Visual Studio。然而,随着对高效开发的追求,我逐渐转向了更加轻量级和灵活的工具组合。通过 CLIfe,我构建了一个高度定制化的开发环境,不仅提高了代码编写效率,还简化了项目管理流程。这一配置结合了多种强大的命令行工具和插件,使我在日常开发中能够更加得心应手。 ... [详细]
  • Windows环境下RabbitMQ安装详尽指南
    Windows环境下RabbitMQ安装详尽指南 ... [详细]
  • 技术日志:Ansible的安装及模块管理详解 ... [详细]
  • 优化后的标题:PHP分布式高并发秒杀系统设计与实现
    PHPSeckill是一个基于PHP、Lua和Redis构建的高效分布式秒杀系统。该项目利用php_apcu扩展优化性能,实现了高并发环境下的秒杀功能。系统设计充分考虑了分布式架构的可扩展性和稳定性,适用于大规模用户同时访问的场景。项目代码已开源,可在Gitee平台上获取。 ... [详细]
  • 三角测量计算三维坐标的代码_双目三维重建——层次化重建思考
    双目三维重建——层次化重建思考FesianXu2020.7.22atANTFINANCIALintern前言本文是笔者阅读[1]第10章内容的笔记,本文从宏观的角度阐 ... [详细]
  • 本文回顾了作者初次接触Unicode编码时的经历,并详细探讨了ASCII、ANSI、GB2312、UNICODE以及UTF-8和UTF-16编码的区别和应用场景。通过实例分析,帮助读者更好地理解和使用这些编码。 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 本文探讨了如何通过编程手段在Linux系统中禁用硬件预取功能。基于Intel® Core™微架构的应用性能优化需求,文章详细介绍了相关配置方法和代码实现,旨在帮助开发人员有效控制硬件预取行为,提升应用程序的运行效率。 ... [详细]
  • 掌握PHP框架开发与应用的核心知识点:构建高效PHP框架所需的技术与能力综述
    掌握PHP框架开发与应用的核心知识点对于构建高效PHP框架至关重要。本文综述了开发PHP框架所需的关键技术和能力,包括但不限于对PHP语言的深入理解、设计模式的应用、数据库操作、安全性措施以及性能优化等方面。对于初学者而言,熟悉主流框架如Laravel、Symfony等的实际应用场景,有助于更好地理解和掌握自定义框架开发的精髓。 ... [详细]
  • SQLmap自动化注入工具命令详解(第28-29天 实战演练)
    SQL注入工具如SQLMap等在网络安全测试中广泛应用。SQLMap是一款开源的自动化SQL注入工具,支持12种不同的数据库,具体支持的数据库类型可在其插件目录中查看。作为当前最强大的注入工具之一,SQLMap在实际应用中具有极高的效率和准确性。 ... [详细]
author-avatar
时尚女郎之女性购物天地
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有