利他才能利己


  • 首页

  • 标签

  • 归档

  • 搜索

微服务: 步步为营

发表于 2019-05-28 | 分类于 Server |

上图是我从 spring.io 首页介绍截图过来的,简洁而不失霸气!

最近两年,Spring Boot 伴随着微服务的发展而越发火热,很多公司也纷纷开始使用 Spring Boot 和 Spring Cloud 来开发自己的服务,Java开发的浪潮愈发汹涌。这年头不知道微服务不知道 Spring Boot 就感觉生活缺点什么似的。我们还是抱着学习的心态来学习一下这个时代宠儿吧,不要盲目吹捧就可以了。

由 Pivotal 提供的 Spring Boot 是一套基于Java语言的开发框架,之前的 Spring 框架配置比较复杂,设计 Spring Boot 目的是用来简化 Spring 的开发过程,一定程度的去尽力解放程序员,换句话说 Spring Boot 是建立在 Spring 基础之上的。Spring Boot 更像是一个框架仓库,它拥有你基本想要的东西,简单到只需要你添加配置就可以,并且 Spring Boot 可以帮你处理各个库之间的依赖兼容问题,不用我们去关心这些问题。“约定优于配置” 的思想贯穿 Spring Boot 的始终,约定优于配置(Convention Over Configuration),也被称之为按约定编程,它是一种软件设计范式、思想,约定优于配置旨在减少开发人员需做决定的数量、获得简单的好处,而又不失灵活性,对于我们来说是一种编程的新体验。

为了自己学习,也为了方便想学习 Spring Boot 的小伙伴,准备搞一套 Spring Boot 系列文章来滋润一下无聊的生活。

Spring Boot 系列文章我准备采用以终为始的方式来分享,所谓的以终为始就是我们先给自己顶个目标,然后再想方案来完成目标,细化每个实施步骤。

至于什么是 微服务,什么是 Spring Boot 大家可以去网上搜索了解,后续分享的文章中我也会渗透式的分享一下相关的知识。这里强调一点,微服务不等于 Spring Boot 或者 Spring Cloud,他们只是实现微服务的一种手段而已。

学习要求

1、你得会Java基础知识,至少会用JDK的基本类。如果不会,赶紧去补补,学习没那么难,只要你别太懒!

2、你要使用过IDEA这个无与伦比的集成开发工具,放弃 Eclipse 吧!不是它不好,只是IDEA太优秀。

3、英语要凑合,有没有四级证书真的无所谓,官方文档大多都是英文的却都是精华。

4、坚强的毅力,乐观的心态,很多困难都是纸老虎,当你搞定它了之后你就知道自己有多厉害了。

5、多运动多思考,身体是革命的本钱,其他的都是扯淡!

以终为始

大目标:基于 Spring Cloud 构建微服务开发一个APP。

实施计划:

1、了解和学习 Spring Boot

目标:使用 Spring Boot 构建一个简单的服务,客户端或者WEB端可访问该服务。

如果你不会移动开发,可以使用C/C++、C#或者JS来开发一个客户端,哪怕终端程序也是可以的。

刚开始学习的时候,如果遇到不理解的知识点先记下来,不要因为这些东西阻挡你的学习,等学到一定的时候再回过头来研究,这就好比你刚上球场学习打篮球一样,能先稳稳的拿住球再传给队友就很了不起了,得不得分那是后面的事情。

在这一部分,我们做好登录和注册即可。

2、深入学习 Spring Boot

继续学习 Spring Boot 的知识,用户完成了注册,然后登录进入我们的APP需要给他们展示更加丰富的场景。这个时候需要配合后台服务进行开发工作,借助这次机会可以更加深入的学习和认识 Spring Boot了。

3、 了解和学习 Spring Cloud

在这个阶段,需要使用 Spring Cloud 改造我们已经开发的单体应用服务了。

微服务的关键不是如何将你的服务拆的如何 “微”,而是如何做好服务治理。于是在 Spring Boot 基础之上便有了 Spring Cloud,它是完全基于 Spring Boot 开发的,对外提供了在微服务架构中如何做好服务治理的一套解决方案。

学习完 Spring Boot 之后需要了解一些关于 Spring Cloud 的知识,进入 Spring Cloud 的世界一窥究竟,这部分我们一起学习如何做服务治理,服务部署等知识。

4、做一次架构师

综合 Spring Boot 和 Spring Cloud 相关的知识,我们重新梳理一下之前做的东西。把整个系统串起来,画出流程图和架构图。

设计包括服务本身的设计、数据库和缓存等设计。

5、完善和优化服务

借鉴业界好的案例对我们的服务进行优化,包括数据库、缓存、API和其他设计做一次重构,并对我们这个微项目进行一次复盘。

最重要的是要奖励一下自己。


Spring Boot 系列文章我会努力坚持写下去,希望能帮到大家。

干就完了!

改变,从你我相识开始~

微服务项目系列文章

发表于 2019-05-28 | 分类于 Server |

狂风暴雨的那个周末,突发奇想,想把微服务相关的内容分享给大家,思前想后还是决定用项目的形式来推进否则很难进行下去,于是就有了项目的这些文章(持续更新中…)。

他们都是有生命的,对我来说。


项目开篇

  • 微服务: 步步为营

    学习微服务的计划。

Spring Boot

微服务: 想办法让项目运行起来

使用IDEA搭建第一个SpringBoot项目。

微服务: Git入门

写过的代码就是自己的财富,需要把他管理起来,代码管理我用Git管理工具。

微服务: 本地热部署

如何热部署项目,不需要每次都手动重启。

微服务: 立志做个伟大的项目

确定学习微服务的项目。

微服务: 学习几个容易混淆的URL注解

学习跟URL相关的几个注解,如 @RequestParam、@QueryParam、@PathVariable、@PathParam。

微服务: 简单的用户名注册和登录

写注册和登录的接口,并完成接口的单元测试。

微服务-Swagger让你可以多抽一支烟

接口编码完成之后,结合 Swagger 可以同时完成文档的编写。

微服务-Swagger生成Markdown文档

如何生成离线文档。

微服务: 部署服务

如何部署已经写好的服务。

MySQL8-0-15在Win10上的折腾记

安装MySQL。

微服务: MySQL基本操作

使用MySQL数据库。

微服务: 结合MySQL实现登录注册

前端登录注册界面访问 API,读写数据库实现数据的存储和读取实战。

微服务: Actuator实现服务监测

如何安全的使用 Actuator。

微服务: Admin与服务监测和管理

如何使用 spring-admin(client&server)实现服务管理和监测。

微服务-配置Nginx反向代理

Nginx 作为反向代理的实践知识。

ngrok让服务近在咫尺

ngrok 实现内网穿透。

微服务-ConfigurationProperties配置

properties 到 Bean 的转换。

微服务-多-Module-管理工程

管理工程,规范工程。

Win10-安装-Redis

微服务-SpringBoot-集成-Redis

Redis 的简单使用。

Spring Boot 番外篇

  • 跨域和OPTIONS这对欢喜冤家

    解决跨域问题。

  • 减少跨域中的OPTIONS请

    如何减少跨域请求,使用 SpringBoot 完成实例。

  • 短地址原理

    实例(SpringBoot)讲解短地址实现原理。

Spring Cloud

待更新😜

客户端

Android 相关

  • 微服务-Android客户端-实现注册和登录

    使用Android实现MSBlog项目,本篇实现了工程框架的搭建、注册和登录功能。

iOS 相关

待更新😜

不经历风雨怎么见彩虹,没有人能够随随便便成功~

程序员这个职业

发表于 2019-05-25 | 分类于 随笔 |

今天想跟大家聊聊关于程序员职业的话题~


谣言可畏

如果你是做编程这个行业的,一定会听到很多人抱怨这个行业太辛苦了,网上也流行着这么一句话:“程序员挣得多,死得早”,简直扎心!

关于 “程序员挣得多,死得早” 这句话其实是不完全合理的。

我记得几年前,一个加班的周末我一个人坐电梯上去正好碰到一位送水的大爷。大爷瞄了我一眼说:”小伙子这个点再这里还上班的一定是搞编程的吧?“。我点了点头:”大爷,行家呀,没想到被你一眼识破“。大爷接着说道:”这个行业好呀,挣得多,你们一个月最少也有好几万吧?“,我当时被大爷说出来的数字吓了一跳,那可是我不吃不喝一年的工资呀!我尴尬的笑着说:”大爷,你真逗!“,然后简单聊了几句就下了电梯,大爷的话久久不能让我释怀,感觉自己做了一份假工作。

“挣得多” 完全是谣言,“死得早” 倒是有可能,想想都疼。关于程序员,加班到是家常便饭,在你职业生涯的早期加班是因为自己能力不足,随着你 “键”步如飞能力的提升,自以为不用再这么拼命了,谁曾想…新的编程语言横穿出世,新的技术层出不穷,如果不学习不努力只能被这个行业给淘汰掉。

如果你有足够好的运气能遇到好的项目,遇到好的团队,肯定能从中获益,比如你在支付宝、或者在王者荣耀团队,或者你在微信,又或者你在今日头条。

好的运气并不是每个人都有,因为好的运气更需要好的自己,你要做好充分的准备去迎接 ”好运气“ 的到来。

建立人脉

和你相处的同事当中,肯定会有诙谐幽默的型的,理智理性型的,还有感情用事说话很直接的类型的人,也许还有小肚鸡肠卑鄙小人型的。直到我读到明朝历史,我才知道皇帝还可以几十年不上朝的,称其为 ”无为而治“。所以,世界那么大,遇到形形色色的人是一件再正常不过的事情,没什么大惊小怪的。

不过我接触的程序员的同事中,幽默诙谐的人不多,和这类人一起工作你会觉得很开心。这类人更容易缓解团队紧张而又沉闷的氛围,更容易被人选为组长。我建议大家该放松的时候就放松,别整天摆着脸给人一种 ”欠你钱“ 的错觉。

和同事搞好关系,可以给你日后的工作带来很大的帮助。比如,你在工作中遇到阻碍或者困难,同事们都会帮你出谋划策,哪怕一个小小的提示可能就能让你灵光一闪,轻松解决问题。你在公司的时间久了,除了和同部门的同事处理好关系,还有跟其他部门的同事多接触。开发和质量管理部、运维中心、设计部以及业务部门多少都会有一些工作上的接触,不妨跟他们多咨询一些非你本专业的知识,这样既可以学到知识又可以结交朋友,职场的交际能力是门大学问。

不知道你有没有发现,公司很多的业务主管都是公司有交际能力的老员工来担当?为什么呢,因为这些人在公司待的时间久,沟通能力有强,所以很多跨部门的协调工作他都能轻而易举的搞定。

除了自己要加强交际能力,还有加强自己的技术能力以及技术影响力,比如你带头在短时间内解决了一个遗留很久的问题,这个会很快让你成为大家心目中的技术佼佼者。

另外,自己要多去帮助别人,当你有困难的时候有人帮助你,那么当别人有困难的时候你也要能及时的帮助别人,这样大家彼此才能建立信任感,送人玫瑰手留余香,很简单的道理。

自我定位

也许有一天,你被公司高层盯上了。高层觉得你很不错,各方面都比较优秀,决定选你为部门负责人。

作为部门负责人,你新官上任可千万别乱放火,你要知道水能载舟亦能覆舟!很多人当上部门领导后,喜欢动用人事部门开始进行裁员行动,有些很优秀的员工都被他给赶走了。大哥,这不是封建社会了,你以为自己是秦始皇吗?!切!

就跟大家上学选班长一样,大家为什么选你做班长?一般是因为你学习努力成绩优秀并且能带动大家一起学习和进步,在工作当中也一样,选你做负责人,是让你能带领团队一起作战拿下公司定下的目标,让你帮助大家解决问题而不是让你仗势欺人,摆出一副高高在上的姿态让人难以 ”望其项背“。

特别是技术出身的部门领导,一定要做好自我定位。除了帮大家帮团队解决问题,你自己的技术能力不能落下也就是说不能丧失编程的能力,否则你连最后保住饭碗的能力都没有了。

万一,那天你做了管理者,要想清楚你能做什么以及你不能做什么,最重要的是找好自我定位,别忘了你以前也是一个程序员。


扫码关注,期待与你的交流~

JavaIO中神奇的flush

发表于 2019-05-19 | 分类于 Java |

这篇文章在之前已经写过,只是没有在公众号发布过。该文阅读量已经达到了 36553 次了,有必要分享给大家。

1

后续我会陆续的把一些大家评论比较多并且很好的知识点在公众号分享给大家,希望和大家在编程的道路上一起进步,把苦涩的日子变得有趣一点。

无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点 这里 可以跳转到教程。

内容概要

Java IO 流的设计不得不让人拍案叫绝,佩服设计者鬼斧天工的手法。

如果你是 Java 初学者,我敢保证第一次接触 Java 的 IO 类,一定会 ”狂晕!”,倒不是因为它有多么难学而是太多让人难以琢磨,让人有种 “不识庐山真面目” 的感觉,当你用心学完之后也会收获 “只缘身在此山中” 的顿悟。

在 Java 编程的日子中尤其是在网络编程中,几乎离不开 Java 的 IO 流,所以学好和用好它是很有必要的。

关于 Java 的 IO 流的分类,可以去找找相关的资料,今天跟大家分享一下其中比较神奇的 flush 方法。

flush 与 OutputStream

该类实现了 Flushable 接口,所以重写了 flush 方法,看看 flush() 源码,会更加的让你明白:

1
public void flush() throws IOException { } 

Sorry,该实现为空。这里的 flush() 居然是一个空方法,什么也没做。看清楚啊,该方法不是抽象方法,是一个实实在在的方法。除了方法体中一无所有,其它还好!看JDK的API如何解释吧!

1
2
3
4
5
6
7
8
flush 
public void flush() throws IOException
刷新此输出流并强制写出所有缓冲的输出字节。
flush 的常规协定是:如果此输出流的实现已经缓冲了以前写入的任何字节,则调用此方法指示应将这些字节立即写入它们预期的目标。
如果此流的预期目标是由基础操作系统提供的一个抽象(如一个文件),则刷新此流只能保证将以前写入到流的字节传递给操作系统进行写入,但不保证能将这些字节实际写入到物理设备(如磁盘驱动器)。
OutputStream 的 flush 方法不执行任何操作。
指定者: 接口 Flushable 中的 flush
抛出: IOException - 如果发生 I/O 错误。

开始,我安慰自己,该类是一个抽象类,它的子类肯定重写了该方法。

好吧,OutputStream 的直接子类有:ByteArrayOutputStream 、FileOutputStream、FilterOutputStream、ObjectOutputStream 、OutputStream、PipedOutputStream 等几个类。

注意:这里的子类 OutputStream 是包 org.omg.CORBA.portable 的。

对于 FileOutputStream、ByteArrayOutputStream、org.omg.CORBA.portable.OutputStream 类它们的 flush() 方法均是从父类继承的 flush 方法。

FilterOutputStream 类重写了 flush() 方法,但是实质还是调用父类的 flush() 方法。ObjectOutputStream、PipedOutputStream 类重写了 flush() 方法。

JDK 中,有两大类IO流即字节流和字符流(包括输入和输出),这些流类中有些是带有缓冲区功能的有些是没有的。BufferedInputStream 和 BufferedOutputStream 是字节缓冲区类;BufferedReader 和 BufferedWriter 是字符缓冲区类。

来举两个小例子,第一个例子主要是使用非缓冲区的 IO 流向文本中写入指定的字符串,第二个例子是使用带有缓冲区的 IO 流向文本中写入一定的字节数据。

1、例子1:向文本中写入字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.BufferedOutputStream; 
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;

public class Test {
public static void main(String[] args) throws Exception {
File file = new File("text.txt");
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
DataOutputStream dos = new DataOutputStream(fos);
dos.writeBytes("java io");
}
}

这段代执行后,会在当前目录下产生 1KB 的文件(内容为 java io)。

特别注意,这里没有调用 flush 方法,只是调用了 writeBytes 方法。调用流程图如下:

1

2、例子2:向文本中写入一定字节的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.BufferedOutputStream; 
import java.io.File;
import java.io.FileOutputStream;

public class Test {
public static void main(String[] args) throws Exception {
File file = new File("text.txt");
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] b = new byte[1024*8];
bos.write(b);
bos.flush();
}
}

这段代执行后,会在当前目录下产生 8KB 字节的文件。调用关系如下:

1

现在我们修改第二个例子的代码,主要修改两个地方

(1)、注释掉调用 flush() 方法;

(2)、将 byte 大小改为 1024 大小。

修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;

public class Test {
public static void main(String[] args) throws Exception {
File file = new File("text.txt");
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] b = new byte[1024];
bos.write(b);
//bos.flush();
}
}

OK,再次运行代码,额的神啊???文件大小居然是0字节。

why?

仔细的你会发现,第一个例子中的代码中并没有调用 flush() 方法,居然可以正常的写入。为什么第二个就不可以呢?还是从源码入手找答案吧!

DataOutputStream 继承 FilterOutputStream ,实现了 DataOutput 接口。我们知道 FilterOutputStream 类重写了 flush() 方法,但是实质还是调用父类的 flush() 方法。DataOutputStream 类的 flush() 方法效仿其父类 FilterOutputStream 的做法,如下:

1
2
3
public void flush() throws IOException { 	
out.flush();
}

那么,即使你在第一个例子的代码后面加上 dos.flush() 结果也是正常的,与不加是一样的效果,因为它们的父类 flush() 方法均为空,这就是为什么第一个代码的神奇所在。

再看看第二个代码的 “病因” 在哪里?先看看 BufferedOutputStream 类的结构:

1
public class BufferedOutputStream extends FilterOutputStream

再看看,它的 flush() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
public synchronized void flush() throws IOException {         
flushBuffer();
out.flush();
}

/** Flush the internal buffer */
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
}

不错,该类重写了 flush() 方法,不像前面几个类那样不是继承就是山寨父类的 flush() 方法。BufferedOutputStream 类是一个使用了缓冲技术的类,这种类一把都会自己实现 flush() 方法。

那么,有人会问使用这种类的时候,难道必须使用 flush() 方法吗,当然不是喽??!!不过有个前提,你的字节数据必须不能小于 8KB。实例代码,注意没有 flush()方法。

(1)、写入的数据不小于8KB

如下示例代码,byte 大小改为 8KB:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.BufferedOutputStream; 
import java.io.File;
import java.io.FileOutputStream;

public class Test {
public static void main(String[] args) throws Exception {
File file = new File("text.txt");
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] b = new byte[1024*8];
bos.write(b);
//bos.flush();
}
}

执行代码,会产生大小为 8KB 的文本文件。

(2)、修改默认缓冲区大小

如下示例代码,修改一下构造 BufferedOutputStream 的方法,设置默认缓冲区大小为 1024。

1
2
3
4
5
6
7
8
9
File file = new File("text4.txt");
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos, 1024);
byte[] b = new byte[1024];
bos.write(b);
//bos.flush();

执行代码,会产生 1KB 大小的文本文件。

这里提醒一下,如果你的文件读写没有达到预期目的,十之八九是因为你没有调用 flush() 或者 close() 方法。

另外,字符流类大多数都实现了 flush() 或者 close() 方法,只不过,它们调用的是 StreamEncoder 类的该方法。该类位于 sun.nio.cs 包下面,其源码在我们JDK中是没有的。

可以点击 StreamEncoder.java 查看源码。

flush 与 Writer

该类 Writer 是一个抽象类,声明如下:

1
public abstract class Writer implements Appendable, Closeable, Flushable

Writer 类的 flush() 方法是一个抽象方法,其子类一般都实现了该方法。

所以,一般使用字符流之后需要调用一下 flush() 或者 close() 方法。

1
abstract public void flush() throws IOException;

细节请看JDK的API,或者Java的源码以及上面的 StreamEncoder 类源码。

今天就说到这里吧,本文主要借助Java IO中字节流与字符流的 flush() 方法,来说明学编程语言看源码和思考是很重要的。

总之,不管你使用哪种流(字符、字节、具有缓冲的流)技术,不妨调用一下 flush() 或者 close() 方法,防止数据无法写到输出流中。


学习没有一蹴而就的方法,坚持思考、练习才是王道~

macOS 生成 RSA 公钥和私钥

发表于 2019-05-11 | 分类于 MacOS |

如果你没有安装 openssl,请先使用 brew 安装它。

生成 RSA 私钥

在 macOS 终端输入下面命令,会生成1024位的私钥,如下:

1
openssl genrsa -out rsa_private.pem 1024

执行成功后,在当前目录会生成 rsa_private.pem 文件了。

注意:这里生成的是 PKCS1 格式的文件,也称之为传统的私钥格式。

生成 RSA 公钥

得到私钥后,执行如下命令可以得到公钥,命令如下:

1
openssl rsa -in rsa_private.pem -out rsa_public.pem -pubout 

执行成功后,在当前目录会生成 rsa_public.pem 的文件,这个就是公钥。

格式转换

有些后端服务需要 PKCS8 格式的密钥,可以使用 openssl 工具把 PKCS1 和 PKCS8 进行互转。

PKCS 全称是 Public-Key Cryptography Standards,是由 RSA 实验室与其它安全系统开发商为促进公钥密码的发展而制订的一系列标准。

把 RSA 私钥转 PKCS1 转换为 PKCS8 格式,执行如下:

1
openssl pkcs8 -topk8 -inform PEM -in rsa_private.pem -outform PEM -nocrypt -out rsa_private_pkcs8.pem

把 RSA 私钥 PKCS8 格式转换为 PKCS1 格式,执行如下:

1
openssl rsa -in rsa_private_pkcs8.pem -out pkcs1.pem

以上生成的 RSA(pem) 文件,可以使用 vim、vscode 等文本编辑器打开查看。

无论是 PKCS1 还是 PKCS8,它们都是 PEM 编码存储的格式。

PEM 格式

PEM 格式既是对 DER 编码转码为 Base64 字符格式,通过解码,将会还原为 DER 格式。即 PEM 是建立在 DER 编码之上的一种格式。

总之,PEM 是明文格式,可以包含证书或者是密钥,其内容通常是以类似 —–BEGIN …—– 开头,以 —–END …—– 为结尾的这样的格式,如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDX+VXh+9zZBC3jRLcLyGGygb/vuiQj13x9ElH9pQmUg29UJ0q1
hcSFuoKmt4id8jyj82fI2PMISzoGucqQFm9HXIV65UcgeUx7REDhgZOBmSCWZ2lW
e2Fnf/htyiy9YVEN4jL7H+b67cIcEDHgYZLtPvSZgyr0UyOMC+E40kTcnQIDAQAB
AoGBAKKsYD7l6VPgrPuY31LVrJi62ComB2Cjx1WhffZR3+RoF2vImNtkB0E+2Xff
l9x3/6ry1IMove3KTzadrHBZJZi1Y6lmHE95uv0O4p16ETnqvKzO8MkcH3qVQe1Z
EyNd/wXr4A8DW3ZlNP/QIPOgzK949yTiP53wapiRwExEf7nhAkEA7CbbdoslRzhQ
9YY1Sdbmc7NHpZdKhkgZwOVJZBoRwfHtSnFcpQUlOzLGCOxP+tGn5mpzhmpDFeUR
bv7mgaQvtQJBAOogUtiJbffk0p3uu7r5nCBmC6BqbG8yVqBVk+r/gS5iHBeBQVyp
FGYYLPQ9daHWluNku07MhqarPcXytEk7ekkCQDBjofK/fmsCkixNTNwNiflJUiVO
W/n6CjTphslZQhIGNb5cTkSOMlvYApZPffACR+pCA9wWKBrzbkgVOTx1XUUCQQDE
KJLg47wkLy6brhLss6ewqW7iV3hEfhuAOFM3WB1mvEKnGt+lf/AcXfMEK54sGzvO
h2475jaG3ABkoiWVGeUpAkAgckc/Pf7KVRF/mDVruMJBbJLYL4H4s78nssFQ4d9V
yXKsIM/AHUxZ5ZKiC7vtEtaGrTLAiuIuQZzBgGpSHX/e
-----END RSA PRIVATE KEY-----

以下是证书文件名称,来自维基百科 X.509 的截图。


要始终保持学习的激情~

C函数中返回字符数组

发表于 2019-05-10 | 分类于 C/C++ |

内容简介

在阅读本篇文章之前,建议大家看一下下面2篇文章:

  • 不见得你会计算C字符串长度
  • 双宿双飞的 malloc 和 free

这篇文章主要分享三个点:

1、为什么作为局部变量的字符数组不能直接返回,而字符指针却可以?

2、当字符数组是局部变量的时候,函数如何返回它?

3、字符数组(char [])和字符指针(char *)如何互转?

局部变量的字符数组

在C中如果我们直接返回字符数组,编译会直接报警告。如下示例:

1
2
3
4
5
char * fork_user_name()
{
char name[] = "veryitman";
return name;
}

在Xcode中编译警告信息是这样的:

1
Address of stack memory associated with local variable 'name' returned

在Linux上面GCC编译显示警告是这样的:

1
warning: function returns address of local variable [-Wreturn-local-addr]

无论哪种警告信息,基本意思都是告诉我们不应该返回一个局部变量 name 的地址(函数内部的变量在栈内存上)。

如果我们修改一下代码,将 char 改为指针变量 char * ,示例如下:

1
2
3
4
5
char * fork_user_name2()
{
char *name = "veryitman";
return name;
}

无论是Linux的GCC还是Xcode的Clang编译器都不会报出警告。

首先我们要知道,常量是放在数据段里面的。

这里比较特殊,局部变量 name 保存在栈中,但是字符串 veryitman 的值是一个常量,保存在常量区。即便函数返回了,数据段里面的常量数据也还不会消亡,它会直到程序结束才会消失,其内存空间直到程序运行结束才会被释放。 所以,返回的地址是一个实际存在的有效地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
char * fork_user_name()
{
char name[] = "veryitman";
return name;
}

char * fork_user_name2()
{
char *name = "veryitman";
return name;
}

int main()
{
printf("fork_user_name: %s\n", fork_user_name());
printf("fork_user_name2: %s\n", fork_user_name2());

return 0;
}

用GCC编译、运行后的打印结果,如下:

1
2
fork_user_name: (null)
fork_user_name2: veryitman

总之,在函数中的局部变量只要是返回类似 int[]、char[]、long[] 地址的,都是不正确的做法。

一切皆有可能

下面例子是不正确的,如下:

1
2
3
4
5
char * v_string()
{
char rest[10] = {'\0'};
return rest;
}

1、使用 static

在C语言中,用 static 限定外部变量与函数,该外部变量或者函数除了对该所在的文件可见外,其他文件都无法访问。 而用 static 声明内部变量,则该变量是某个特定函数的局部变量,只能在该函数中使用。但它与自动变量不同的是,不管其所在函数是否被调用,它一直存在,而不像自动变量那样,随着所在函数的被调用和退出而存在和消失。换句话说,static 类型的内部变量是一种只能在某个特定函数中使用但一直占据存储空间的变量。

所以使用static修饰一下,就没有问题了。示例如下:

1
2
3
4
5
char * v_string()
{
static char rest[10] = {'\0'};
return rest;
}

2、使用 malloc

这种方式可以解决这个问题,是因为使用 malloc 分配的内存是在堆上而不是在栈内存上面。但是要记得将其在调用方使用 free 释放申请的内存空间,否则容易造成内存泄漏问题。

具体可以看看 双宿双飞的 malloc 和 free 这篇文章。

1
2
3
4
5
6
char * v_string()
{
char *p = (char *)malloc(10 * sizeof(char));
p = "\0";
return p;
}

3、全局变量

这个很好理解。全局变量在程序真个生命周期中都是有效的,所以使用全局变量也可以解决类似问题。

但是这种方案就会让这个封装的方法不够内聚,因为它依赖了全局变量。

1
2
3
4
5
6
char g_rest[100];
char * v_string()
{
strcpy(g_rest, "verytiamn");
return g_rest;
}

4、返回形参指针变量

在Linux Kernel(内核源码版本5.0.7)中,函数 strcpy 的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef __HAVE_ARCH_STRCPY
/**
* strcpy - Copy a %NUL terminated string
* @dest: Where to copy the string to
* @src: Where to copy the string from
*/
#undef strcpy
char *strcpy(char *dest, const char *src)
{
char *tmp = dest;

while ((*dest++ = *src++) != '\0')
/* nothing */;
return tmp;
}
EXPORT_SYMBOL(strcpy);
#endif

参考内核实现,我们可以修改一下自己的代码,示例如下:

1
2
3
4
5
6
char * v_string(char *s1, char *s2)
{
char *tmp = s1;
// 省略...
return tmp;
}

这里补充另外一个知识点,函数 strcpy 在glibc和Linux Kernel中实现不一样。

在glibc的新版中(2.29版本),本质是调用了函数 memcpy, 实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stddef.h>
#include <string.h>

#undef strcpy

#ifndef STRCPY
# define STRCPY strcpy
#endif

/* Copy SRC to DEST. */
char * STRCPY (char *dest, const char *src)
{
return memcpy (dest, src, strlen (src) + 1);
}
libc_hidden_builtin_def (strcpy)

包括 strncpy 在glibc和Linux Kernel中实现也不一样,有兴趣的可以去看看源码。

字符数组和字符指针的互转

char [] 转 char *

这种情况下,可以直接进行赋值,示例如下:

1
2
3
4
5
6
7
8
int main()
{
char c_str_array[] = "veryitman.com";
char *p_str;
p_str = c_str_array;
printf("p_str: %s\n", p_str);
return 0;
}

char * 转 char []

是不是也可以直接进行赋值呢?撸段代码看看,如下:

1
2
3
4
5
6
7
8
int main()
{
char c_str_array[] = "veryitman.com";
char *p_str = "veryitman.com";
c_str_array = p_str;
printf("c_str_array: %s\n", c_str_array);
return 0;
}

很遗憾,编译报错,GCC编译错误截图如下:

1557417012236

Clang编译错误如下:

1
Array type 'char [14]' is not assignable

可以考虑使用 strncpy 来实现,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
char c_str_array[] = "veryitman.com";
char *p_str = "veryitman.com";
strncpy(c_str_array, p_str, strlen(p_str));
printf("c_str_array: %s\n", c_str_array);
return 0
}

时间可以改变一切,但你得做点什么!

双宿双飞的 malloc 和 free

发表于 2019-05-02 | 分类于 C/C++ |

最近在看人工智能相关的知识,无意中发现了一个巨牛的 人工智能教程,分享一下给大家。

教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点 这里 可以直接看教程。


简介

函数 malloc 和 free 是一对好朋友,几乎是形影不离。有 malloc 的地方就应该有 free 的存在。

今天跟大家聊聊 malloc 和 free 这对好基友,这两个函数都是对堆内存进行管理的函数,另外还有 calloc、realloc、reallocf、valloc 等堆内存管理函数。

void *

在进行下面话题之前,我们先回忆一下 void * 是什么?

void * 表示未确定类型的指针。C/C++规定,void * 类型可以强制转换为任何其它类型的指针。

void * 也被称之为无类型指针,void * 可以指向任意类型的数据,就是说可以用任意类型的指针对 void * 赋值,如下示例:

1
2
3
void *p1;
int *p2;
p1 = p2;

但一般不会反过来使用,如下示例在有些编译器上面可以编译通过,有些就不行:

1
2
3
void *p1;
int *p2;
p2 = p1;

可以修改一下代码,将 void * 转换为对应的指针类型再进行赋值,如下示例:

1
2
3
void *p1;
int *p2;
p2 = (char *)p1;

由于 GNU 和 ANSI 对 void * 类型指针参与运算的规定不一样,所以为了兼容二者并且让程序有更好的兼容性,最好还是将 void * 转换为有明确类型的指针再参与运算,如下示例。

1
2
3
4
void *pd;
char *pc = (char *)pd;
pc ++;
pc += 1;

malloc

函数原型:

1
void * malloc(size_t size);

malloc 向系统申请分配指定 size 个字节的内存空间,即 malloc 函数用来从堆空间中申请指定的 size 个字节的内存大小,返回类型是 void * 类型,如果成功,就会返回指向申请分配的内存,否则返回空指针,所以 malloc 不保证一定成功。

查看函数手册或者直接在Linux、macOS等Unix系统上面直接 man malloc 会显示对应的函数信息:

1
2
3
4
The malloc() function allocates size bytes of memory and returns a pointer to the allocated memory.

If successful, malloc() function return a pointer to allocated memory.
If there is an error, they return a NULL pointer and set errno to ENOMEM.

另外需要注意一个问题,使用 malloc 函数分配内存空间成功后,malloc 不会对数据进行初始化,里边数据是随机的垃圾数据,所以一般结合 memset 函数和 malloc 函数 一起使用。

1
2
3
4
5
6
int *arr;
arr = (int *)malloc(10 * sizeof(int));
if (NULL != arr) {
memset(arr, 0, 10 * sizeof(int));
printf("arr: %p\n", arr);
}
1
2
3
4
5
6
char *arr;
arr = (char *)malloc(10 * sizeof(char));
if (NULL != arr) {
memset(arr, '\0', 10 * sizeof(char));
printf("arr string: %s\n", arr);
}

为了安全起见,建议可以考虑使用 calloc() 函数,后面会提到它。

函数 free、malloc、calloc() 都被包含在 stdlib.h 头文件中,当然了 malloc.h 头文件也有对应的函数声明,实现在 malloc.c 文件中。

关于 malloc 函数中的 size 参数务必要大于指针本身的数值,在32位操作系统上,指针占用4字节的大小;在64位操作系统上,指针占用8字节的大小。

free

函数原型:

1
void free(void *ptr);

我们知道在 C 语言中, 堆上的内存空间不会自动释放(Java 有自动回收机制,而 C 语言没有),直到调用 free 函数,才会释放堆上的存储空间,即 free 函数会释放指针指向的内存分配空间。

下面是函数手册查到关于 free 函数的资料:

1
2
The free() function deallocates the memory allocation pointed to by ptr.
If ptr is a NULL pointer, no operation is performed.

对于 free 函数我们要走出一个误区,不要以为调用了 free 函数,变量就变为 NULL 值了。本质是 free 函数只是割断了指针所指的申请的那块内存之间的关系,并没有改变所指的地址(本身保存的地址并没有改变)。如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
char *pchar = (char *)malloc(10 * sizeof(char));

if (NULL != pchar) {
strcpy(pchar, "blog");
/* pchar所指的内存被释放,但是pchar所指的地址仍然不变 */
free(pchar);

/* 该判断没有起到防错作用,此时 pchar 并不为 NULL */
if (NULL != pchar) {
strcpy(pchar, "it");
printf("pchar: %s", pchar);
}
}

正确且安全的做法是对指针变量先进行 free 然后再将其值置为 NULL,如下下面示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
char *pchar = (char *)malloc(10 * sizeof(char));

if (NULL != pchar) {
strcpy(pchar, "blog");
/* pchar所指的内存被释放,但是pchar所指的地址仍然不变 */
free(pchar);
/* 将其置为 NULL 值 */
pchar = NULL;

/* 该判断没有起到防错作用,此时 pchar 并不为 NULL */
if (NULL != pchar) {
strcpy(pchar, "it");
printf("pchar: %s", pchar);
}
}

函数 free 只能释放alloc的内存空间,对于其他非alloc的指针变量使用free会出现错误,如下面的例子:

1
2
3
4
int a = 0x2010;
int *p;
p = &a;
free(p);

运行后直接跪了,报类似下面的错误信息:

1
pointer being freed was not allocated.

另外,告诉大家一个秘密,free 一个空指针没问题。如下例子:

1
2
3
int *p;
p = NULL;
free(p);

代码可以正常运行,在 free 函数源码为证:

1
2
3
4
/* free(0) has no effect */
if (mem == 0){
return;
}

而 NULL 在源码(在 morecore.c 文件中)中定义就是0,如下:

1
2
3
#ifndef NULL
# define NULL 0
#endif

malloc、free 小结

1、连续内存块

malloc 函数申请的是连续的一块内存,如果所申请的内存块大于目前堆上剩余内存块,则内存分配会失败,函数返回 NULL 值。

注意:上面说的 堆上剩余内存块 不是所有剩余内存块之和,而是连续的内存。

2、双宿双飞才好

调用 malloc 函数多余 free 函数会发生内存泄漏,这个很好理解,因为申请过的内存没有被释放完。调用 malloc 函数少于free 函数,肯定会出错。换句话说,在程序中 malloc 的使用次数务必要和 free 相等,否则必有隐患或者发生错误。

如下面的例子 free 两次指针变量就会在运行时报错: malloc: *** error for object 0x10071be90: pointer being freed was not allocated,也称之为 double free。

1
2
3
char *pchar = (char *)malloc(10 * sizeof(char));
free(pchar);
free(pchar);

对指针变量进行 free 之后,一定要记得对其赋值为 NULL,否则该指针就是一个野指针,这个在上面已经说明。

3、0字节的内存有毒

使用 malloc 函数也可以申请0字节的内存,该函数的返回值并不是 NULL,而是返回一个正常的内存地址,所以如果使用这种方式申请的内存很危险,如下面的例子,指针 pchar 是一个使用 malloc 函数创建的占用0字节的内存空间的一个指针变量,if (NULL == pchar) 并没有生效,而是执行了 else 语句中的代码。

1
2
3
4
5
6
7
char *pchar = (char *)malloc(0);
if (NULL == pchar) {
printf("malloc 0 byte memory failed.\n");
} else {
printf("malloc 0 byte successfully and pchar: %s.\n", pchar);
printf("pchar: %s.\n", pchar);
}

这中方式的指针类似于一个野指针,可以使用但有风险,不知道这个指针后面的内存空间被谁使用着,要是被核心进程使用,会造成相应程序的崩溃。

4、调皮的free

使用非alloc方式创建的内存,使用 free 函数会报错,这个要注意。另外,free 空指针没有影响。

calloc、realloc、reallocf、valloc

1、calloc 函数

1
void * calloc(size_t count, size_t size);

在堆上,分配 n*size 个字节,并初始化为0,返回 void *类型,返回值情况跟 malloc 一致。

函数 malloc() 和函数 calloc() 的主要区别是前者不能初始化所分配的内存空间,而后者能。如果由 malloc() 函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之,如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据。也就是说,使用 malloc() 函数的程序开始时(内存空间还没有被重新分配)能正常进行,但经过一段时间(内存空间还已经被重新分配)可能会出现问题。

函数 calloc() 会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为0;如果你是为指针类型的元素分配内存,那么这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零。

1
2
3
The calloc() function contiguously allocates enough space for count objects that are size bytes of memory each and returns a pointer to the allocated memory.

The allocated memory is filled with bytes of value zero.

2、realloc() 函数

1
void * realloc(void *ptr, size_t size);

realloc() 会将 ptr 所指向的内存块的大小修改为 size,并将新的内存指针返回。假设之前内存块的大小为 n,如果 size <= n,那么截取的内容不会发生变化,如果 size > n,那么新分配的内存不会被初始化。

对于上面说的新的内存指针地址可能变也可能不变,假如原来alloc的内存后面还有足够多剩余内存的话,realloc后的内存=原来的内存+剩余内存,realloc还是返回原来内存的地址即不会创建新的内存。假如原来alloc的内存后面没有足够多剩余内存的话,realloc将申请新的内存,然后把原来的内存数据拷贝到新内存里,原来的内存将被free掉,realloc返回新内存的地址。

另外要注意,如果 ptr = NULL,那么相当于调用 malloc(size);如果 ptr != NULL且size = 0,那么相当于调用 free(ptr)。

当调用 realloc 失败的时候,返回NULL,并且原来的内存不改变,不会释放也不会移动。

1
2
3
4
5
6
7
8
9
The realloc() function tries to change the size of the allocation pointed to by ptr to size, and returns ptr. 

If there is not enough room to enlarge the memory allocation pointed to by ptr, realloc() creates a new allocation, copies as much of the old data pointed to by ptr as will fit to the new allocation, frees the old allocation, and returns a pointer to the allocated memory.

If ptr is NULL, realloc() is identical to a call to malloc() for size bytes.

If size is zero and ptr is not NULL, a new, minimum sized object is allocated and the original object is freed.

When extending a region allocated with calloc(3), realloc(3) does not guarantee that the additional memory is also zero-filled.

看一下源码的关键部分,如下:

1
2
3
4
5
6
7
8
9
10
11
12
void * __libc_realloc (void *oldmem, size_t bytes)
{
// 省略...

#if REALLOC_ZERO_BYTES_FREES
if (bytes == 0 && oldmem != NULL) {
__libc_free (oldmem); return 0;
}
#endif

// 省略...
}

这里可以看出,如果 oldmem 不为NULL且 bytes 为0的情况下,会自动进行 free 操作。如下面的例子运行后会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdlib.h>
#include <stdio.h>

int main()
{
char *pre_ptr = (char *)malloc(20);
int bytes = 0;
if(NULL != pre_ptr) {
void *ptr = realloc(pre_ptr, bytes);
// 对pre_ptr进行了double free,这种条件下源码中会进行一次free
free(pre_ptr);
}

return 0;
}

所以为了保险起见,要对 bytes 进行判断再做处理。

传递给 realloc 的指针变量必须是先前通过 malloc()、 calloc() 或 realloc() 等alloc分配的,否则会报 pointer being realloc'd was not allocated. 错误。如下例子(运行直接报错):

1
2
char *p = "";
void *newP = realloc(p, 10);

3、reallocf() 函数

1
void * reallocf(void *ptr, size_t size);

reallocf() 函数是由 FreeBSD 实现的,它会在任何情况下释放输入的指针(即使是再分配失败之后)。reallocf() 一样会调用 realloc 函数,但是只有我们在获得空的指针之后才会调用 free 函数。

下面是 reallocf 函数具体的实现部分:

1
2
3
4
5
6
7
8
void * reallocf(void *p, size_t size) 
{
void *ptr = realloc(p, size);
if (!p) {
free(p);
}
return ptr;
}
1
2
3
The reallocf() function is identical to the realloc() function, except that it will free the passed pointer when the requested memory cannot be allocated.

This is a FreeBSD specific API designed to ease the problems with traditional coding styles for realloc causing memory leaks in libraries.

4、valloc() 函数

1
void * valloc(size_t size);

这个函数是最少见也是最少用的一个函数。

malloc 或 realloc 返回的是以8字节对齐的内存地址,在64bits上是16字节对齐。然而 memalign 或 valloc 可以更大的粒度进行字节对齐。

valloc 是一个废弃的函数,分配 size 大小的字节,返回已分配的内存地址指针,其内存地址将是页大小(page size)的倍数,如果分配失败返回 NULL。

1
2
3
The valloc() function allocates size bytes of memory and returns a pointer to the allocated memory. 

The allocated memory is aligned on a page boundary.

说了这么多,接下来我们看一下例子,如下代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 对其分配内存,这个时候pchar值是随机的垃圾值
char *pchar = (char *)malloc(16);
// 手动初始化pchar的值,下面的方法则不需要
memset(pchar, 0, 16);

// calloc分配内存,会自动设置为0,不需要memset
char *pchar_orig = (char *)calloc(12, sizeof(char));

// 在原内存基础上,在堆内存空间中连续增加内存
// 如果原内存没有连续空间可拓展,realloc会新分配一个空间,将原有内存copy到新空间,然后释放原内存
// 注意:realloc和malloc,只分配内存不进行赋值操作
char *pchar_dest = (char *)realloc(pchar_orig, 10);

// 相当于 malloc(60)
char *pchar_ini = (char *)realloc(NULL, 60);

free(pchar);
pchar = NULL;

free(pchar_orig);
pchar_orig = NULL;

free(pchar_dest);
pchar_dest = NULL;

free(pchar_ini);
pchar_ini = NULL;

glibc 与 libc

上面说过这些内存管理函数都在头文件 stdlib.h 中,函数实现都在 malloc.c 文件中,这些都可以在 glibc 中找到对应的代码,我下载的 glibc 是 2.29 这个版本。

libc 是 Stantard C Library 的简称,它是符合ANSI C标准的一个标准函数库。libc库提供C语言中所使用的宏,类型的定义,字符串操作符,数学计算函数以及输入输出函数等。正如ANSI C是C语言的标准一样,libc只是一个函数库标准,每个操作系统都会按照该标准对标准库进行具体实现。通常我们所说的libc是特指某个操作系统的标准库,比如:在Linux操作系统下所说的libc即glibc。glibc是类Unix操作系统中使用最广泛的libc库,它的全称是GNU C Library.

glibc是 Linux 下的 C 函数库。libc 库是Linux下的ANSI C的函数库也泛指一切符合C规范的库,glibc 是Linux下的GNU C函数库,是一种 libc 的实现。glibc 本身是GNU旗下的C标准库,后来逐渐成为了Linux的标准C库。Linux下面的标准C库不仅有这一个,如 uclibc、klibc,但是 glibc 无疑是用得最多的,用 glibc 作为系统的C函式库,是GNU/Linux演进的一个重要里程碑,可以查看 维基百科 对 glibc 的介绍。

还有比如Android的Bionic库也是一种libc的实现,只是在Android上面使用罢了。Bionic是Google为Android操作系统提供的BSD标准C库的衍生库,专门为移动计算而精心设计的,针对移动设备上有限的CPU周期和可用内存进行了裁剪以提高工作效率。

这里也说一下基本的C语言函数库,这个库可以根据头文件划分为 15 个部分,其中包括:

  • 文件 assert.h:包含宏和信息,用于进行诊断,帮助程序调试;

  • 文件 ctype.h:包含用来测试某个特征字符的函数的函数原型,以及用来转换大小写字母的函数原型;

  • 文件 errno.h:定义用来报告错误条件的宏;

  • 文件 float.h:包含系统的浮点数大小限制;

  • 文件 math.h:包含数学库函数的函数原型;

  • 文件 stddef.h:包含执行某些计算 C 所用的常见的函数定义;

  • 文件 stdio.h:包含标准输入输出库函数的函数原型,以及他们所用的信息;

  • 文件 stdlib.h:包含数字转换到文本,以及文本转换到数字的函数原型,还有内存分配、随机数字以及其他实用函数的函数原型;

  • 文件 string.h:包含字符串处理函数的函数原型;

  • 文件 time.h:包含时间和日期操作的函数原型和类型;

  • 文件 stdarg.h:包含函数原型和宏,用于处理未知数值和类型的函数的参数列表;

  • 文件 signal.h:包含函数原型和宏,用于处理程序执行期间可能出现的各种条件;

  • 文件 setjmp.h:包含可以绕过一般函数调用并返回序列的函数的原型,即非局部跳转;

  • 文件 locale.h:包含函数原型和其他信息,使程序可以针对所运行的地区进行修改。地区的表示方法可以使计算机系统处理不同的数据表达约定,如全世界的日期、时间、美元数和大数字;

  • 文件 limits.h:决定了各种变量类型的各种属性。定义在该头文件中的宏限制了各种变量类型(比如 char、int 和 long)的值;

在 glibc 库中可以找到 malloc.c 文件(路径是 glibc-2.29/malloc/malloc.c ),从中可以看到如下别名的定义:

1
2
3
4
5
6
7
8
9
10
11
strong_alias (__libc_calloc, __calloc) weak_alias (__libc_calloc, calloc)
strong_alias (__libc_free, __free) strong_alias (__libc_free, free)
strong_alias (__libc_malloc, __malloc) strong_alias (__libc_malloc, malloc)
strong_alias (__libc_memalign, __memalign)
weak_alias (__libc_memalign, memalign)
strong_alias (__libc_realloc, __realloc) strong_alias (__libc_realloc, realloc)
strong_alias (__libc_valloc, __valloc) weak_alias (__libc_valloc, valloc)
strong_alias (__libc_pvalloc, __pvalloc) weak_alias (__libc_pvalloc, pvalloc)
strong_alias (__libc_mallinfo, __mallinfo)
weak_alias (__libc_mallinfo, mallinfo)
strong_alias (__libc_mallopt, __mallopt) weak_alias (__libc_mallopt, mallopt)

__malloc 是 __libc_malloc 的别名,malloc 也是 __libc_malloc 的别名。其他以此类推。

所以看 malloc 函数的源码,直接看 __libc_realloc 函数实现即可,看 free 的源码可以看 __libc_free 的实现。

1
2
void *
__libc_realloc (void *oldmem, size_t bytes)

也可以在线查看 glibc 的源码,点击 这里 前往欣赏!

以后要是有人问你在哪里可以下载libc的源码,你就可以告诉他libc只是一个标准,不同的平台上面有不同的libc实现,比如glibc,bionic等。


锄禾日当午,汗滴禾下土,五一节快乐~

用Objective-C实现抽象类

发表于 2019-04-21 | 分类于 iOS |

插图的花是我上个月发工资当天晚上加班回家的路上从一个老人家那里买的,一枝独秀!

上周在微信公众号发布的一篇名为 反向代理 的文章,居然真被微信给封掉了,当时在写的时候我还在想是不是会被微信给封杀掉,真是怕什么来什么,完全符合墨菲定律!那篇文章我就是想给大家分享一下如何设置 Nginx 的反向代理,其中的一些概念可能是因为牵涉到了敏感词汇,本无恶意!

不说这件事情了,还是回到今天分享的主题上面来吧~

在软件设计或者架构设计领域,架构师都会跟大家强调对业务一定要抽象,把具体的业务场景抽象为程序实现很考验一个人的编程和思维能力。所以在面向对象的编程中,抽象是一种必备的能力。

设计框架的时候,一般我们都会约定一些协议,各个模块在交互通信中能够遵守和实现这些协议,就可以保证通信的畅通性,就好比大家遵守排队规则一样,一个一个来就不会乱。

协议可以使用接口的形式或者抽象类的形式都可以定义,不同的编程语言有不同的形式。例如,在Java中提供给开发者的有接口 Interface 还有抽象类 Abstract,示例如下:

1
2
3
4
5
6
7
8
9
/** 接口 */
interface ISeverListener {
public void covertService(String name);
}

/** 抽象类 */
abstract class AbstractServer {
protected abstract String destroyService(String serverID);
}

在 C++ 中也有抽象类,示例如下:

1
2
3
4
5
6
7
8
class Point
{
protected:
int x, y;
public:
//纯虚函数
virtual void disp()=0;
};

在 Objective-C 中提供了协议 protocol,没有提供抽象类的东西,但是我们可以借助 protocol 模拟实现抽象类。

DownloaderProtocol.h 协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <Foundation/Foundation.h>

@protocol DownloaderProtocol <NSObject>

@required

- (BOOL)checkDownloader;

- (void)startDownload:(id)url;

- (void)stopDownload;

- (void)deleteAllDownloadFile;

@end

AbstractDownloader.h

1
2
3
4
5
6
7
8
#import <Foundation/Foundation.h>
#import "DownloaderProtocol.h"

@interface AbstractDownloader : NSObject <DownloaderProtocol>

- (void)setDownloadUrl:(NSString *)url;

@end

AbstractDownloader.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#import "AbstractDownloader.h"

#define AbstractMethodNotImplemented() \
@throw [NSException exceptionWithName:NSInternalInconsistencyException \
reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
userInfo:nil]


@implementation AbstractDownloader

- (instancetype)init
{
NSAssert(![self isMemberOfClass:[AbstractDownloader class]], @"AbstractDownloader is an abstract class, you should not instantiate it directly.");

return [super init];
}

- (BOOL)checkDownloader
{
AbstractMethodNotImplemented();
}

- (void)startDownload:(id)url
{
AbstractMethodNotImplemented();
}

- (void)stopDownload
{
AbstractMethodNotImplemented();
}

- (void)deleteAllDownloadFile
{
AbstractMethodNotImplemented();
}

- (void)setDownloadUrl:(NSString *)url
{
NSLog(@"AbstractDownloader's url = %@", url);
}

@end

从上面的代码可以看出 AbstractMethodNotImplemented()是模拟抽象类的关键,有了这个宏动议之后,凡是需要继承AbstractDownloader 必须要实现对应的方法,否则会报错。ImageDownloader 继承 AbstractDownloader 并且实现对应的方法,示例如下:

ImageDownloader.h

1
2
3
4
5
#import "AbstractDownloader.h"

@interface ImageDownloader : AbstractDownloader

@end

ImageDownloader.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#import "ImageDownloader.h"

@implementation ImageDownloader

- (BOOL)checkDownloader
{
NSLog(@"ImageDownloader checkDownloader...");

return YES;
}

- (void)startDownload:(id)url
{
NSLog(@"ImageDownloader startDownload...");
}

- (void)stopDownload
{
NSLog(@"ImageDownloader stopDownload...");
}

- (void)deleteAllDownloadFile
{
NSLog(@"ImageDownloader deleteAllDownloadFile...");
}

@end

这样做的话,团队相关的开发人员就知道自己继承自 AbstractDownloader 应该做什么了。

这里只是给大家提供一种设计思路,有更好的思路欢迎交流,感谢每一位关注者、阅读者。

一直致力于让开发变得更美好的公众号~

Ubuntu19安装Swift5

发表于 2019-04-20 | 分类于 C/C++ |

内容概要

通过实践,分享如何在 Ubuntu19 下面如何安装 Swift5.

安装必备库

在使用 apt-get 之前,有必要做一次更新,如下:

1
sudo apt-get update

更新完成后,安装对应的库文件,如下:

1
sudo apt-get install git cmake ninja-build clang python uuid-dev libicu-dev icu-devtools libbsd-dev libedit-dev libxml2-dev libsqlite3-dev swig libpython-dev libncurses5-dev pkg-config libblocksruntime-dev libcurl4-openssl-dev systemtap-sdt-dev tzdata rsync

然后再安装下面的库,如下:

1
sudo apt-get install clang libicu-dev libpython2.7

最后,一定要安装下面的库,执行安装即可。

1
2
sudo apt-get install libtinfo5
sudo apt install python-dev

否则会报错:

1
2
3
/bin/lldb: error while loading shared libraries: libncurses.so.5: cannot open shared object file: No such file or directory

/bin/lldb: error while loading shared libraries: libpython2.7.so.1.0: cannot open shared object file: No such file or directory

下载 Swift

下载 Swift 源码,使用 wget 下载即可。

1
wget https://swift.org/builds/swift-5.0-release/ubuntu1804/swift-5.0-RELEASE/swift-5.0-RELEASE-ubuntu18.04.tar.gz

下载成功后,解压源文件,如下:

1
tar zxvf swift-5.0-RELEASE-ubuntu18.04.tar.gz

为了使用方便,可以对解压后的文件重新命名,这里我改为了 swift5.0

1
mv swift-5.0-RELEASE-ubuntu18.04 swift5.0

配置环境变量

将 swift 对应的加入到环境变量中,编辑 ~/.bashrc 文件即可。

1
vim ~/.bashrc

添加下面内容在文件最后,如下:

1
export PATH=/home/username/developer/swift_linux/swift5.0/usr/bin:${PATH}

注意:/home/username/developer/swift_linux/swift5.0/usr/bin 是你自己解压后的目录。

为了验证 Swift 是否安装成功,可以让 ~/.bashrc 文件 立即生效,命令如下:

1
source ~/.bashrc

在终端执行以下下面命令:

1
swift -version

成功会显示如下信息:

1
2
Swift version 5.0 (swift-5.0-RELEASE)
Target: x86_64-unknown-linux-gnu

Done.

人生总是在不断的上演着重逢和离别~

反向代理

发表于 2019-04-14 | 分类于 Server |

本篇内容概要

不知道大家有没有问过别人,或者是自己偷偷在网上搜过类似 “什么是正向和反向代理“ 类似的问题,即使问过、搜过是不是也很懵?

总结一句粗话:正向代理就是代理客户端的代理服务,反向代理就是代理服务端的代理服务。

那我们今天就来掰扯掰扯这个问题。

VPN 和正向代理

小明:“你昨天看了我给你发的视频链接了吗?超级好看哟!”。
小丽:“哦,那个链接根本打不开呀,看个pi!”,小明失望的看了看小丽。
小明:“好吧,肯定是你没有使用 VPN 软件,所以你看不了…”。

相信你也有经历过类似的场景,朋友给了一个需要你拥有 VPN 才能打开的链接,无论你是万兆光纤也好还是亿兆光纤也罢,死活都无法打开。

这里所说的 VPN 就是人们通常所说的正向代理服务器,VPN(虚拟私人网络 Virtual Private Network),是一种加密通讯技术。VPN 有很多的具体实现,比如 PPTP、L2TP、IPSec 和 openvpn。VPN 不是为了翻墙而生的,他只是一中技术,就好比菜刀本来是用来切菜的道理一样。使用 VPN 来爬楼梯基本已经作废了,但当大家谈及 fanqiang 的时候说得最多的还是 “你有好用的 VPN 吗?”,所以把 VPN 当成 fanqiang 工具的代名词也情有可原。

如果你有了 fanqiang 软件就可以轻松访问全世界的网站了,特别是程序员,可以在知识的海洋里遨游。简单来说正向代理服务器类似于下面图所示:

反向代理和 Nginx

这里可以给大家举个例子,假如你想让电信公司给你安装宽带或者网络电视,这个时候你可能需要打电话咨询一下相关的内容,于是你拿起了手机给电信公司打电话,经过一番复杂操作后终于可以等待人工服务了。电信的人工客服人员有很多,系统会根据一定的规则给你分配一个人,接下来可能是美眉也可能是汉子为你服务,究竟是谁给你提供语音服务取决与系统的分配,而不是你可以决定的。

系统分配和调度的功能就类似于反向代理服务器,对用户来说没有感知。类似于下面图所示:

在软件设计中,服务端开发经常会用到反向代理服务器,比如 Nginx、Apache 等,它们经常也被用作为 WEB 服务器,Nginx 被当做代理服务器比较多,所谓的代理服务器是架设在 WEB 服务器和网络中断(比如手机客户端)之间的服务器,换句话说,Nginx 提供了反向代理服务的能力。

由于 Nginx 和 Apache 各自的优势,现在很多开发者选择让两者在服务器中共存。在服务器端让 Nginx 在前 Apache 在后,由 Nginx 做负载均衡和反向代理,并且处理静态文件,将动态请求交给 Apache 去处理。

下图是一个典型的 Nginx 作为反向代理服务器部署 ELK 的架构图,如下:

用 Nginx 的反向代理,用户可以从外部访问到 Kibana,并且它本身具有负载均衡的作用,也能够提高了其性能。

再看一个微服务架构中的架构图(在网关前面也部署了 Nginx),如下图所示:

关于 Nginx 作为负载均衡器的配置和实例,下一次跟大家分享。你现在只需要聚焦在 Nginx 作为反向代理服务器上面的功能即可。

Nginx 配置反向代理

Nginx 本身已经提供了反向代理服务的能力,下面我们结合实例给大家分享一下,假设你本机已经安装了 Nginx 并且会基本的操作命令。

1、启动 Nginx

1
sudo nginx

在浏览器打开 http://localhost:80,确保 Nginx 启动成功。

2、配置 host

我们配置一下本机的 host,vim 编辑即可。

1
sudo vim /etc/hosts

增加下面一行:

1
127.0.0.1 www.ithome.com

在浏览器打开 http://www.ithome.com:80,跟刚才显示一样的WEB 界面。

3、配置 Nginx

编辑 Nginx 配置,如下:

1
sudo vim /usr/local/etc/nginx/nginx.conf

在 http { 节点下增加下面内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server {
# 监听80端口号
listen 80;

# 监听访问的域名
server_name www.ithome.com;

# 根据访问路径配置
location / {
# 设置 http 版本
proxy_http_version 1.1;
proxy_set_header Connection "";
# 把请求转发到 https://www.baidu.com
proxy_pass https://www.baidu.com;
}
}

重新启动 Nginx,重新加载配置文件,操作命令如下:

1
sudo nginx -s reload

在浏览器打开 http://www.ithome.com:80,此时打开的是百度首页。至此,简单的配置 Nginx 作为反向代理服务器的功能已经完成,当然想配置好 Nginx 还需要大家多去实践和看官方技术手册。

proxy_pass 默认使用的是 http 1.0,可以通过 proxy_http_version 指令让它使用 http 1.1,以便开启 keepalive 之类的功能。官方说明 如下:

1
2
3
Sets the HTTP protocol version for proxying. By default, version 1.0 is used. 

Version 1.1 is recommended for use with keepalive connections and NTLM authentication.

另外,nginx 还可以设置透明代理,可以参考 IP Transparency and Direct Server Return with NGINX and NGINX Plus as Transparent Proxy 这篇文章。

附录:Nginx 命令

1、查看 nginx 的配置信息

1
nginx -V

注意:是大写字母 V。

2、查看 nginx 版本信息

1
nginx -v

3、启动 nginx

1
sudo nginx

4、查看 nginx 进程

1
ps -ef|grep nginx

5、重启 nginx

1
nginx -s reload

6、完美停止 nginx

1
kill -QUIT `cat /var/run/nginx.pid`

7、快速停止 nginx

1
kill -TERM `cat /var/run/nginx.pid`

或者

1
kill -INT `cat /var/run/nginx.pid`

8、完美停止工作进程,主要用于平滑升级

1
kill -WINCH `cat /var/run/nginx.pid`

9、强制停止 nginx

1
pkill -9 nginx

10、检查 nginx.conf 修改是否正确

1
nginx -t

11、停止 nginx 的命令

1
nginx -s stop

推荐阅读

  • 图解正向代理、反向代理、透明代理:可能是你见过的讲解最透彻的代理技术教程
  • HttpProxy模块

不经历风雨怎么见彩虹,没有人能够随随便便成功~

<1…789…20>

193 日志
16 分类
163 标签
© 2024 veryitman