52coder
我爱程序员-立于山巅看内核,藏于闹市读算法
99a9f8f2c777cc2b1c85ada9e3d8761ac5a869b0-52coder.bitcron.com
2100-10-30T15:55:00Z
[置顶]关于博主
2100-10-30T15:55:00Z
about
52coder
<p class="md_block">
<span class="md_line md_line_start"> <a class="md_compiled" href="http://www.52coder.net">52coder.net</a>是很早之前与同学一起脑洞的域名:中文名可以叫做-我爱程序员。我记得那年冬天孟非主持的非诚勿扰很火,我信誓旦旦的说以后要做一个网站,专门去为程序员解决个人问题,于是就有了现在的这个域名52coder.net。当时比较热衷于论坛,折腾过Discuz,在读书时折腾过,最多的时候同时在线人数超过1000,论坛的注册人数达到了2w左右,现在却早已忘记当初因为什么原因关闭论坛。<br /></span>
<span class="md_line">博客开始于2017年6月,希望博客用来记录自己的学习过程,渐渐通过几个月的时间喜欢上写点东西,目前学习的内容主要有C语言、数据结构、Linux系统编程、算法、LeetCode等,如果针对文章中的内容有任何疑问与问题,都可以指出,我虽然长得帅,但是我是一个知错能改、以理服人的人。<br /></span>
<span class="md_line">因为看书而忘记吃饭这种事情在我过去18年的荒诞人生中是从未发生过的,我希望人类拥有时光机器,我可以乘着它回到从前,告诉那个懵懂的少年:虽然你很帅,但是你也要努力读书!可能因为年纪大了的原因,对于过去沉迷的事物现在已经没有了太大的热情,我常常在想,我当初对游戏的痴迷程度如果用来学点东西,就好比好好学点编程,到现在会不会是另一种情形。<br /></span>
<span class="md_line md_line_end">关于本站文章任何问题都可以留言回复,如果你发现了代码中的BUG,更将有机会获得博主的签名靓照一张,机会不多,且行且珍惜。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">偶然在kindle看到王小波的一篇文章《写给新的一年(1996年)》,每次读来都很有感触,附上文章节选:<br /></span>
<span class="md_line">我们读书、写作——1995年就这样过去了。这样提到过去的一年,带点感慨的语调,感叹生活的平淡。过去我们的生活可不是这样平淡。在我们年轻时,每一年的经历都能写成一本书,后来只能写成小册子,再后来变成了薄薄的几页纸。现在就是这样一句话:读书、写作。一方面是因为我们远离了动荡的年代,另一方面,我们也喜欢平淡的生活。对我们来说,这样的生活就够了。 <br /></span>
<span class="md_line">九十年代之初,我们的老师——一位历史学家——这样展望二十一世纪:理想主义的光辉已经暗淡,人类不再抱着崇高的理想,想要摘下天上的星星,而是把注意力放到了现实问题上去,当一切都趋于平淡,人类进入了哀乐中年。我们都不是历史学家,不会用这样宏观的态度来描述世界,但这些话也触动了我们的内心。过去,我们也想到过要摘下天上的星星,而现在我们的生活也趋于平淡。这是不是说,我们也进入了哀乐中年?假设如此,倒是件值得伤心的事。一位法国政治家说过这样一句话:一个人在二十岁时如果不是激进派,那他一辈子都不会有出息;假如他到了三十岁还是个激进派,那他也不会有什么大出息。我们这样理解他的话:一味的勇猛精进,不见得就有造就;相反,在平淡中冷静思索,倒更能解决问题。 <br /></span>
<span class="md_line">很多年轻人会说:平淡的生活哪里有幸福可言。对此,我们倒有不同的意见。罗素先生曾说:真正的幸福来自于建设性的工作。人能从毁灭里得到一些快乐,但这种快乐不能和建设带来的快乐相比。只有建设的快乐才能无穷无尽,毁灭则有它的极限。夸大狂和自恋都不能带来幸福,与此相反,它正是不幸的源泉。我们希望能远离偏执,从建设性和创造性的工作中获取幸福。创造性工作的快乐只有少数人才能获得,而我们恰恰有幸得到了可望获得这种快乐的机会——那就是做一个知识分子。 <br /></span>
<span class="md_line">转眼之间,我们从国外回来已经快八年了。对于当初回国的决定,我们从没有后悔过。这丝毫不说明我们比别人爱国。生活在国内的人,对祖国的感情反倒不像海外学人表现得那么强烈。假如举行爱国主义征文比赛,国内的人倒不一定能够获奖。人生在世,就如一本打开的书,我们更希望这本书的主题始终如一,不希望它在中途改变题目——到外文化中生活,人生的主题就会改变。与此同时,我们也希望生活更加真切,哪怕是变得平淡也罢,这就是我们回国的原因。这是我们的选择,不见得对别人也适用。 <br /></span>
<span class="md_line">假如别人来写这篇文章,可能是从当前的大好形势谈起,我们却在谈内心的感受。你若以为这种谈法层次很低,那也不见得。假如现在形势不大好,我们也不会改变对这个国家的感情。既然如此,就不急着提起。顺便说说,现在国家的形势当然是好的。但从我们的角度看来,假如在社会生活里再多一些理性的态度,再多一些公正和宽容,那就更好了。 <br /></span>
<span class="md_line">随着新年钟声响起,我们都又长了一岁。这正是回顾和总结的时机。对于过去的一年,还有我们在世上生活的这些年,总要有句结束语:虽然人生在世会有种种不如意,但你仍可以在幸福与不幸中作选择。<br /></span>
<span class="md_line">欢迎各位帅哥美女订阅我的微信订阅号:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/Algorithms/_image/2100-10-30/qrcode.jpg" alt="" title="" ><br /></span>
<span class="md_line md_line_dom_embed img_before only_img_before"><a class="md_compiled" href="https://raw.githubusercontent.com/52coder">github</a><br /></span>
<span class="md_line md_line_end">2017.12.25 52coder</span>
</p>
abseil C++ Tips
2024-03-06T13:18:41Z
abseil/abseil-c-tips
52coder
<p class="md_block">
<span class="md_line md_line_start">title abseil C++ Tips<br /></span>
<span class="md_line">date: 2022-10-28 00:00<br /></span>
<span class="md_line">url: abseil_cpp_tips<br /></span>
<span class="md_line md_line_end">学习<a class="md_compiled" href="https://abseil.io/tips/">abseil C++ Tips</a></span>
</p>
<h4 id="toc_0" class="h16">Tip of the Week #1: string_view</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">当函数参数为(const) string时,常见的一般有3种方法,第三种方法可能不太常见。</span>
</p>
<pre><code>// C Convention
void TakesCharStar(const char* s);
// Old Standard C++ convention
void TakesString(const std::string& s);
// string_view C++ conventions
void TakesStringView(absl::string_view s); // Abseil
void TakesStringView(std::string_view s); // C++17</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">前面两种方法只有当参数类型匹配时才比较方便,如果对void TakesCharStar(const char* s);函数传入std::string,需要调用c_str()函数。</span>
</p>
<pre><code>void AlreadyHasString(const std::string& s) {
TakesCharStar(s.c_str()); // explicit conversion
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">如果是对函数void TakesString(const std::string& s);传入const char*,可以直接调用,但是实际上会触发std::string的拷贝构造函数,程序效率存在问题。</span>
</p>
<pre><code>void AlreadyHasCharStar(const char* s) {
TakesString(s); // compiler will make a copy
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">What to Do ?<br /></span>
<span class="md_line md_line_end">可以使用c++17中的sting_view或absl::string_view,string_view可以支持两种类型直接调用,虽然存在隐士类型转换,但不会拷贝整个数据,所以不存在O(n)的内存消耗。string_view只是记录了自身对应字符串的地址信息。当const char*作为参数时,隐含传入了strlen()作为string_view的构造参数。</span>
</p>
<pre><code>void AlreadyHasString(const std::string& s) {
TakesStringView(s); // no explicit conversion; convenient!
}
void AlreadyHasCharStar(const char* s) {
TakesStringView(s); // no copy; efficient!
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">string_view详细教程参考:<a class="md_compiled" href="https://www.learncpp.com/cpp-tutorial/introduction-to-stdstring_view/">learn c++ string_view</a></span>
</p>
<h4 id="toc_1" class="h16">尽量以const,enum,inline替换#define</h4>
<p class="md_block">
<span class="md_line md_line_start">宁可以编译器替换预处理器。<br /></span>
<span class="md_line">例如#define ASPECT_RATIO 1.653<br /></span>
<span class="md_line md_line_end">符号ASPECT_RATIO从未被编译器看见,在预处理阶段已经完成替换,运行时错误时如果在代码中存在多个1.653将存在困惑。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">解决办法是使用const double AspectRatio = 1.653,编译后进入符号表。<br /></span>
<span class="md_line md_line_end">为了将常量的作用域限制于class内,你必须让它成为class的一个成员,为了确保此常量至多只有一份实例,你必须使用static.</span>
</p>
<pre><code>#include <iostream>
using namespace std;
class GamePlayer {
private:
static const int NumTurns = 5;//常量声明式
int score[NumTurns];
};
int main(){
return 0;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">如果需要取某个class专属常量的地址,必须使用如下方式:</span>
</p>
<pre><code>#include <iostream>
using namespace std;
class GamePlayer {
public:
static const int NumTurns = 5;
int score[NumTurns];
};
const int GamePlayer::NumTurns;
int main(){
cout << GamePlayer::NumTurns << ",address NumTurns:"<<&GamePlayer::NumTurns;
return 0;
}</code></pre>
<!--block_code_end-->
日常开发笔记总结(十四)
2023-10-14T10:00:00Z
weeknote-20231014
52coder
<p class="md_block">
<span class="md_line md_line_start md_line_end">文章来源于网络,略有修改,引用参考文末链接。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">引子<br /></span>
<span class="md_line">我们知道,std::vector之所以可以动态扩容,同时还可以保持顺序存储,主要取决于其扩容复制的机制。当容量满时,会重新划分一片更大的内存区域,然后将所有的元素拷贝过去。<br /></span>
<span class="md_line md_line_end">但是笔者却发现了一个奇怪的现象,std::vector扩容时,对其中的元素竟然进行的是深复制。请看示例代码:</span>
</p>
<pre><code>#include <iostream>
#include <vector>
struct Test {
Test() {std::cout << "Test" << std::endl;}
~Test() {std::cout << "~Test" << std::endl;}
Test(const Test &) {std::cout << "Test copy" << std::endl;}
Test(Test &&) {std::cout << "Test move" << std::endl;}
};
int main(int argc, const char *argv[]) {
std::vector<Test> ve;
//ve.reserve(3);
ve.emplace_back();
ve.emplace_back();
ve.emplace_back();
return 0;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">注释掉ve.reserve(3);后输出:</span>
</p>
<pre class="lang_test"><code>Test
Test copy
~Test
Test
Test copy
Test copy
~Test
~Test
~Test
~Test
~Test</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">未注释掉的情况下程序输出:</span>
</p>
<pre><code>Test
Test
Test
~Test
~Test
~Test </code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">如果我们设置了reserve(3),这里emplace_back调用3次,执行3次构造函数,程序结束时调用3次析构函数。如果没有设置reserve(3),默认只分配了一个元素的大小。第一次emplace_back时,仅进行了一次普通构造。第二次emplace_back时,就需要进行扩容,然后把第一个元素拷贝过去,再释放原来的对象。所以这里除了有一次新的构造以外,还有一次复制和释放。当第三次emplace_back时,进行了一次普通构造和拷贝前面插入的两个元素。<br /></span>
<span class="md_line">但关键问题就在于,Test类明明实现了移动构造(浅复制),可这里竟然调用了拷贝构造(深复制)。<br /></span>
<span class="md_line md_line_end">如果vector扩容调用拷贝构造,当已有元素个数较多时,全部拷贝就会变得非常低效。</span>
</p>
<h4 id="toc_0" class="h16">查找原因</h4>
<p class="md_block">
<span class="md_line md_line_start">基于上述理由,我认为STL的开发者不可能连这个问题都考虑不到,但想不通为什么我明明实现了移动构造,却不能调用。<br /></span>
<span class="md_line md_line_end">带着这样的疑问我去研读了STL的源码(GNU版本),在vector扩容时,会调用_M_realloc_insert函数,该函数在vector.tcc文件中实现。在这个函数里面对已有元素进行拷贝的时候,看到了类似这样的代码:</span>
</p>
<pre><code>__new_finish
= std::__uninitialized_move_if_noexcept_a
(__old_start, __position.base(),
__new_start, _M_get_Tp_allocator());
++__new_finish;</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">有趣的就是这个__uninitialized_move_if_noexcept_a,我们找到这个函数的实现:</span>
</p>
<pre><code>template<typename _InputIterator, typename _ForwardIterator,
typename _Allocator>
inline _ForwardIterator
__uninitialized_move_if_noexcept_a(_InputIterator __first,
_InputIterator __last,
_ForwardIterator __result,
_Allocator& __alloc)
{
return std::__uninitialized_copy_a
(_GLIBCXX_MAKE_MOVE_IF_NOEXCEPT_ITERATOR(__first),
_GLIBCXX_MAKE_MOVE_IF_NOEXCEPT_ITERATOR(__last), __result, __alloc);
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">再看一下_GLIBCXX_MAKE_MOVE_IF_NOEXCEPT_ITERATOR的实现</span>
</p>
<pre><code>#if __cplusplus >= 201103L
#define _GLIBCXX_MAKE_MOVE_IF_NOEXCEPT_ITERATOR(_Iter) std::__make_move_if_noexcept_iterator(_Iter)
#else
#define _GLIBCXX_MAKE_MOVE_IF_NOEXCEPT_ITERATOR(_Iter) (_Iter)
#endif // C++11</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">也就是说,在C++11以前,这玩意就是对象本身(毕竟C++11以前还没有移动构造),而在C++11以后被定义成了__make_move_if_noexcept_iterator,继续查看其定义。</span>
</p>
<pre><code>template<typename _Iterator, typename _ReturnType
= typename conditional<__move_if_noexcept_cond
<typename iterator_traits<_Iterator>::value_type>::value,
_Iterator, move_iterator<_Iterator>>::type>
inline _GLIBCXX17_CONSTEXPR _ReturnType
__make_move_if_noexcept_iterator(_Iterator __i)
{ return _ReturnType(__i); }</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">这里用了一个conditional,来判断这个迭代器的类型,如果__move_if_noexcept_cond为真,就取迭代器本身,否则就取移动迭代器。看起来问题就在这里了,之前我们的例程中的Test一定就是符合了这个__move_if_noexcept_cond,导致用了原始迭代器。<br /></span>
<span class="md_line md_line_end">继续深挖这个__move_if_noexcept_cond,看到这样的代码:</span>
</p>
<pre><code>template<typename _Tp>
struct __move_if_noexcept_cond
: public __and_<__not_<is_nothrow_move_constructible<_Tp>>,
is_copy_constructible<_Tp>>::type { };</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">也就是说,如果一个类,不存在不会抛出异常的移动构造函数并且可拷贝,那么就为真。<br /></span>
<span class="md_line md_line_end">Test类显然符合,所以vector<Test>在复制时用了普通的迭代器进行了遍历,自然就会调用拷贝构造函数进行复制了。</span>
</p>
<h4 id="toc_1" class="h16">解决办法</h4>
<p class="md_block">
<span class="md_line md_line_start">所以,我们需要让Test不符合__move_if_noexcept_cond的条件,也就是这里要将移动构造函数声明为noexcept表示它不会抛出异常,这样vector<Test>在复制时就会使用移动迭代器(就是会包装一层std::move),从而触发移动构造。<br /></span>
<span class="md_line md_line_end">顺道我们也看一眼移动迭代器的原理:</span>
</p>
<pre><code>template<typename _Iterator>
class move_iterator {
_Iterator _M_current;
// ...
public:
using iterator_type = _Iterator;
explicit _GLIBCXX17_CONSTEXPR
move_iterator(iterator_type __i)
: _M_current(std::move(__i)) { }
// ...
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">确实调用了std::move,证明我们的思路没错。<br /></span>
<span class="md_line md_line_end">所以,修改Test代码,实现noexcept移动构造:</span>
</p>
<pre><code>struct Test {
long a, b, c, d;
Test() {std::cout << "Test" << std::endl;}
~Test() {std::cout << "~Test" << std::endl;}
Test(const Test &) {std::cout << "Test copy" << std::endl;}
Test(Test &&) noexcept {std::cout << "Test move" << std::endl;}
};
int main(int argc, const char *argv[]) {
std::vector<Test> ve;
ve.emplace_back();
ve.emplace_back();
ve.emplace_back();
return 0;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">打印结果如下:</span>
</p>
<pre><code>Test
Test
Test move
~Test
Test
Test move
Test move
~Test
~Test
~Test
~Test
~Test</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这次如我们所愿,调用了移动构造。</span>
</p>
<h4 id="toc_2" class="h16">结论</h4>
<p class="md_block">
<span class="md_line md_line_start">STL中考虑到异常的情况,因此,像这种容器内部的复制行为,是要求不能够发生异常的,因此,只有当移动构造函数声明为noexcept的时候才会调用,否则将统一调用拷贝构造函数。<br /></span>
<span class="md_line md_line_end">然而,在移动构造函数中本来就不应该抛出异常,因此,在大多数情况下,移动构造函数都应该用noexcept来声明。</span>
</p>
Design Patterns in Modern C++20
2023-09-17T08:55:00Z
design-patterns-in-modern-c
52coder
<h5 id="toc_0" class="h16">奇异递归模版模式</h5>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start"><a class="md_compiled" href="https://zhuanlan.zhihu.com/p/54945314">奇异递归模板模式</a><br /></span>
<span class="md_line md_line_end">[enable_shared_from_this]https://en.cppreference.com/w/cpp/memory/enable_shared_from_this</span>
</p>
<h5 id="toc_1" class="h16">Mixin继承</h5>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在C++中,类可以继承它的模板参数,例如:</span>
</p>
<pre><code>template <typename T> struct Mixin : T
{
};</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">上述方法叫做Mixin继承,它允许不同类型的分层组合。</span>
</p>
MySQL基础知识
2023-07-22T02:00:00Z
mysql_45_basic_url
52coder
<h4 id="toc_0" class="h16">安装后修改密码</h4>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start md_line_end"><a class="md_compiled" href="https://stackoverflow.com/questions/33510184/how-to-change-the-mysql-root-account-password-on-centos7?spm=a2c6h.12873639.0.0.2e533bb8E6K9By">安装mysql后修改密码</a></span>
</p>
<h4 id="toc_1" class="h16">MySQL连接</h4>
<p class="md_block">
<span class="md_line md_line_start">使用mysql -u root -p 连接,-u指定用户root,-p 密码选项,如果设置了密码需要加-p选项<br /></span>
<span class="md_line md_line_end">连接方式一</span>
</p>
<pre><code>[root ~]#mysql -u root -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 5.7.34 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql></code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">连接方式二</span>
</p>
<pre><code>[root ~]#mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 11
Server version: 5.7.34 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql></code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">连接方式一种会在历史记录中留下mysql密码,更安全的方式是使用方式二。</span>
</p>
<h4 id="toc_2" class="h16">确认mysql中字符编码</h4>
<pre><code>mysql> status
--------------
mysql Ver 14.14 Distrib 5.7.34, for Linux (x86_64) using EditLine wrapper
Connection id: 8
Current database:
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 5.7.34 MySQL Community Server (GPL)
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: latin1
Db characterset: latin1
Client characterset: utf8
Conn. characterset: utf8
UNIX socket: /var/lib/mysql/mysql.sock
Uptime: 1 day 27 min 38 sec
Threads: 1 Questions: 17 Slow queries: 0 Opens: 113 Flush tables: 1 Open tables: 106 Queries per second avg: 0.000
--------------</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到客户端和服务端字符编码都是utf8.也可以通过SHOW VARIABLES LIKE 'char%';来确认</span>
</p>
<pre><code>mysql> SHOW VARIABLES LIKE 'char%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.01 sec)</code></pre>
<!--block_code_end--><h4 id="toc_3" class="h16">创建数据库</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">CREATE DATABASE 数据库名</span>
</p>
<pre><code>mysql> create database db1;
Query OK, 1 row affected (0.00 sec)</code></pre>
<!--block_code_end--><h4 id="toc_4" class="h16">查询数据库</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">SHOW DATABASES;</span>
</p>
<pre><code>mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| db1 |
+--------------------+
2 rows in set (0.00 sec)</code></pre>
<!--block_code_end--><h4 id="toc_5" class="h16">指定数据库</h4>
<pre><code>mysql> use db1
Database changed</code></pre>
<!--block_code_end--><h4 id="toc_6" class="h16">显示当前数据库</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">SELECT DATABASE();</span>
</p>
<pre><code>mysql> SELECT DATABASE();
+------------+
| DATABASE() |
+------------+
| db1 |
+------------+
1 row in set (0.00 sec)</code></pre>
<!--block_code_end--><h4 id="toc_7" class="h16">创建表</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">CREATE TABLE 表名 (列名1 数据类型1,列名2 数据类型2 ……)</span>
</p>
<pre><code>mysql> CREATE TABLE tb1 (empid VARCHAR(10),name VARCHAR(10),age INT);
Query OK, 0 rows affected (0.53 sec)</code></pre>
<!--block_code_end--><h4 id="toc_8" class="h16">显示所有的表</h4>
<pre><code>mysql> SHOW TABLES;
+---------------+
| Tables_in_db1 |
+---------------+
| tb1 |
+---------------+
1 row in set (0.00 sec)</code></pre>
<!--block_code_end--><h4 id="toc_9" class="h16">指定字符编码创建表</h4>
<pre><code>mysql> CREATE TABLE tb2 (empid VARCHAR(10),name VARCHAR(10),age INT) CHARSET=utf8;
Query OK, 0 rows affected (0.55 sec)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">当前由于默认字符编码是utf8,所以创建表不需要按照上面的设置来创建表。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">https://dy.wmyun.men/link/TbCrfF1NzBHR6bT7?mu=2</span>
</p>
<h5 id="toc_10" class="h16">关系数据库</h5>
<p class="md_block">
<span class="md_line md_line_start">现在使用最为广泛的数据库是关系数据库(Relational DataBase,RDB)。<br /></span>
<span class="md_line">在关系型数据库中,一条数据用多少个项目来表示。例如关系型数据库将一条会员数据分成会员编号、姓名、住址和出生年月等项目,然后将各个会员的相关数据收集起来。<br /></span>
<span class="md_line">一条数据成为记录(record),各个项目成为列(column),在刚才的例子中xxx先生的数据是记录,会员编号和姓名等项目是列。<br /></span>
<span class="md_line">如果想象成Excel的工作表(work sheet),横向的一行就相当于记录。注意,纵向的一列中输入的是相同类型的数据。<br /></span>
<span class="md_line md_line_end">我们把手机了这些数据的表格称为表(table)。一个数据库中可以包括多个表。</span>
</p>
<h5 id="toc_11" class="h16">创建数据库</h5>
<pre><code>mysql> create DATABASE test;
Query OK, 1 row affected (0.02 sec)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">Query OK提示成功,表示我们成功创建了数据库db1,数据库名和表名在Windows、Macos和linux上得处理方法并不相同,在windows和macOS环境中不区分字符的大小写,但是在Linux环境中却区分大小写。</span>
</p>
<h5 id="toc_12" class="h16">显示数据库</h5>
<pre><code>mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| test |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">mysql数据库是负责存储MySQL各种信息的数据库,它保存了管理用户信息的表user等。</span>
</p>
<h5 id="toc_13" class="h16">指定数据库</h5>
<pre><code>mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
5 rows in set (0.00 sec)
mysql> use test
Database changed
mysql></code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">格式:use 数据库名<br /></span>
<span class="md_line md_line_end">在使用use选择数据库的状态下也能够操作其他数据库中的表。这时可以像"数据库名.表名"这样把数据库名和表名用"."连接起来。例如,当从其他数据库访问数据库db2中的表table的所有记录时,可以使用下面的命令:</span>
</p>
<pre><code>select * from db2.table;</code></pre>
<!--block_code_end--><h5 id="toc_14" class="h16">显示当前使用的数据库</h5>
<pre><code>mysql> select database();
+------------+
| database() |
+------------+
| test |
+------------+
1 row in set (0.00 sec)</code></pre>
<!--block_code_end--><h5 id="toc_15" class="h16">启动时选择数据库</h5>
<pre><code>root@52coder:~# mysql test -u root -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 11
Server version: 8.0.33-0ubuntu0.22.04.2 (Ubuntu)
Copyright (c) 2000, 2023, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> select database();
+------------+
| database() |
+------------+
| test |
+------------+
1 row in set (0.00 sec)</code></pre>
<!--block_code_end--><h5 id="toc_16" class="h16">创建表 &&显示表结构</h5>
<pre><code>mysql> create table employ (empid VARCHAR(10),name VARCHAR(10),age INT);
Query OK, 0 rows affected (0.04 sec)
mysql> DESC employ;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| empid | varchar(10) | YES | | NULL | |
| name | varchar(10) | YES | | NULL | |
| age | int | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">NULL表示允许不输入任何值,Default表示如果什么值都不输入就用这个值。Field表示列名,Type表示数据类型。</span>
</p>
<h5 id="toc_17" class="h16">显示所有的表</h5>
<pre><code>mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| employ |
+----------------+
1 row in set (0.00 sec)
mysql></code></pre>
<!--block_code_end--><h5 id="toc_18" class="h16">创建表指定字符编码</h5>
<pre><code>mysql>create table employ (empid VARCHAR(10),name VARCHAR(10),age INT) CHARSET=utf8;</code></pre>
<!--block_code_end--><h5 id="toc_19" class="h16">插入数据</h5>
<p class="md_block">
<span class="md_line md_line_start md_line_end">INSERT INTO 表名 VALUES(数据1,数据2);</span>
</p>
<pre><code>mysql> desc employ;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| empid | varchar(10) | YES | | NULL | |
| name | varchar(10) | YES | | NULL | |
| age | int | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
mysql> insert into employ values('A101','jackma',50);
Query OK, 1 row affected (0.03 sec)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">从表结构看name和empid被设置成了VARCHAR(10),所以我们无法输入多余10个字符的数据,但是在MySQL中,即使输入了多于指定字符数的数据也不会报错,而是会忽略无法插入的字符。</span>
</p>
<h5 id="toc_20" class="h16">指定列名插入记录</h5>
<p class="md_block">
<span class="md_line md_line_start md_line_end">INSERT INTO 表名 (列名1,列名2,列名3) VALUES(数据1,数据2,数据3)</span>
</p>
<pre><code>mysql> insert into employ (age,name,empid) VALUES(23,'马云','A104');
Query OK, 1 row affected (0.02 sec)</code></pre>
<!--block_code_end--><h5 id="toc_21" class="h16">一次性输入多条数据</h5>
<p class="md_block">
<span class="md_line md_line_start md_line_end">INSERT INTO 表名 (列名1,列名2,列名3) VALUES(数据1,数据2,数据3),(数据1,数据2,数据3),(数据1,数据2,数据3),(数据1,数据2,数据3)</span>
</p>
<pre><code>mysql> insert into employ (age,name,empid) VALUES(24,'马化腾','A105'),(28,'丁磊 ','A106'),(29,'李彦宏','A107');
Query OK, 3 rows affected (0.02 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql></code></pre>
<!--block_code_end--><h5 id="toc_22" class="h16">显示数据</h5>
<pre><code>mysql> select * from employ;
+-------+-----------+------+
| empid | name | age |
+-------+-----------+------+
| A101 | jackma | 50 |
| A104 | 马云 | 23 |
| A105 | 马化腾 | 24 |
| A106 | 丁磊 | 28 |
| A107 | 李彦宏 | 29 |
+-------+-----------+------+
5 rows in set (0.00 sec)</code></pre>
<!--block_code_end--><h5 id="toc_23" class="h16">使用SELECT输出指定的值</h5>
<p class="md_block">
<span class="md_line md_line_start md_line_end">SELECT命令还能用于显示与数据库无关的值。比如:</span>
</p>
<pre><code>mysql> select '测试' ;
+--------+
| 测试 |
+--------+
| 测试 |
+--------+
1 row in set (0.00 sec)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这种方法适用于确认函数的值或计算结果。例如:</span>
</p>
<pre><code>mysql> select (2+3)*4;
+---------+
| (2+3)*4 |
+---------+
| 20 |
+---------+
1 row in set (0.00 sec)
mysql></code></pre>
<!--block_code_end--><h5 id="toc_24" class="h16">复制表</h5>
<pre><code>mysql> select * from employ;
+-------+-----------+------+
| empid | name | age |
+-------+-----------+------+
| A101 | jackma | 50 |
| A104 | 马云 | 23 |
| A105 | 马化腾 | 24 |
| A106 | 丁磊 | 28 |
| A107 | 李彦宏 | 29 |
+-------+-----------+------+
5 rows in set (0.00 sec)
mysql> create table employ1 select * from employ;
Query OK, 5 rows affected (0.03 sec)
Records: 5 Duplicates: 0 Warnings: 0
mysql> select * from employ1;
+-------+-----------+------+
| empid | name | age |
+-------+-----------+------+
| A101 | jackma | 50 |
| A104 | 马云 | 23 |
| A105 | 马化腾 | 24 |
| A106 | 丁磊 | 28 |
| A107 | 李彦宏 | 29 |
+-------+-----------+------+
5 rows in set (0.00 sec)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">使用命令CREATE TABLE table2 SELECT * FROM table1;</span>
</p>
<h5 id="toc_25" class="h16">修改表</h5><h6 id="toc_26" class="h16">修改列的数据类型</h6>
<pre><code>mysql> desc employ;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| empid | varchar(10) | YES | | NULL | |
| name | varchar(10) | YES | | NULL | |
| age | int | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
mysql> alter table employ modify name VARCHAR(100);
Query OK, 5 rows affected (0.06 sec)
Records: 5 Duplicates: 0 Warnings: 0
mysql> desc employ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">修改列的数据类型格式:<br /></span>
<span class="md_line">alter table 表名 modify 列名 数据类型;<br /></span>
<span class="md_line md_line_end">注意:数据类型的修改必须具有兼容性,不具有兼容性的修改会导致错误发生。如果将VARCHAR(100)修改为VARCHAR(50),第50个字符之后的数据就会丢失。</span>
</p>
<h6 id="toc_27" class="h16">添加列</h6>
<p class="md_block">
<span class="md_line md_line_start">添加列的格式:<br /></span>
<span class="md_line md_line_end">alter table 表名 add 列名 数据类型</span>
</p>
<pre><code>mysql> desc employ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
mysql> alter table employ add birth DATETIME;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc employ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
| birth | datetime | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
4 rows in set (0.01 sec)</code></pre>
<!--block_code_end--><h6 id="toc_28" class="h16">插入数据记录</h6>
<pre><code>mysql> desc employ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
mysql> alter table employ add birth DATETIME;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc employ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
| birth | datetime | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
4 rows in set (0.01 sec)
mysql> insert into employ VALUES('N1008','风清扬',25,'1991-08-03 22:00:00');
Query OK, 1 row affected (0.01 sec)
mysql> select * from employ;
+-------+-----------+------+---------------------+
| empid | name | age | birth |
+-------+-----------+------+---------------------+
| A101 | jackma | 50 | NULL |
| A104 | 马云 | 23 | NULL |
| A105 | 马化腾 | 24 | NULL |
| A106 | 丁磊 | 28 | NULL |
| A107 | 李彦宏 | 29 | NULL |
| N1008 | 风清扬 | 25 | 1991-08-03 22:00:00 |
+-------+-----------+------+---------------------+
6 rows in set (0.00 sec)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">由于birth是DATETIME类型,如果不输入时间,会被自动设置成"00:00:00",即0点0分0秒。</span>
</p>
<h5 id="toc_29" class="h16">修改列的位置</h5><h6 id="toc_30" class="h16">把列添加到最前面</h6>
<p class="md_block">
<span class="md_line md_line_start">格式:<br /></span>
<span class="md_line md_line_end">alter table 表名 add 字段 类型 first;</span>
</p>
<pre><code>mysql> desc employ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
| birth | datetime | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
4 rows in set (0.01 sec)
mysql> alter table employ add date DATE first;
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc employ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| date | date | YES | | NULL | |
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
| birth | datetime | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
5 rows in set (0.01 sec)</code></pre>
<!--block_code_end--><h6 id="toc_31" class="h16">把列添加到任意位置</h6>
<p class="md_block">
<span class="md_line md_line_start">格式:<br /></span>
<span class="md_line md_line_end">alter table 表名 add 字段 类型 after empid;</span>
</p>
<pre><code>mysql> desc employ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| date | date | YES | | NULL | |
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
| birth | datetime | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
5 rows in set (0.00 sec)
mysql> alter table employ add grade int after age;
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc employ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| date | date | YES | | NULL | |
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
| grade | int | YES | | NULL | |
| birth | datetime | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
6 rows in set (0.00 sec)</code></pre>
<!--block_code_end--><h6 id="toc_32" class="h16">修改列的顺序</h6>
<pre><code>mysql> desc employ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| date | date | YES | | NULL | |
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
| grade | int | YES | | NULL | |
| birth | datetime | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
6 rows in set (0.00 sec)
mysql> alter table employ modify birth datetime first;
Query OK, 0 rows affected (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc employ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| birth | datetime | YES | | NULL | |
| date | date | YES | | NULL | |
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
| grade | int | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
6 rows in set (0.00 sec)</code></pre>
<!--block_code_end--><h6 id="toc_33" class="h16">修改列名和数据类型</h6>
<p class="md_block">
<span class="md_line md_line_start md_line_end">alter table 表名 change 修改前的列名 修改后的列名 修改后的数据类型;</span>
</p>
<pre><code>mysql> desc employ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| date | date | YES | | NULL | |
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
| grade | int | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
5 rows in set (0.00 sec)
mysql> alter table employ change date birthday datetime;
Query OK, 6 rows affected (0.05 sec)
Records: 6 Duplicates: 0 Warnings: 0
mysql> desc employ;
+----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| birthday | datetime | YES | | NULL | |
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
| grade | int | YES | | NULL | |
+----------+--------------+------+-----+---------+-------+
5 rows in set (0.00 sec)</code></pre>
<!--block_code_end--><h6 id="toc_34" class="h16">删除列</h6>
<p class="md_block">
<span class="md_line md_line_start md_line_end">alter table 表名 drop 列名;</span>
</p>
<pre><code>mysql> desc employ;
+----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| datetime | date | YES | | NULL | |
| date | date | YES | | NULL | |
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
| grade | int | YES | | NULL | |
+----------+--------------+------+-----+---------+-------+
6 rows in set (0.01 sec)
mysql> alter table employ drop datetime;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc employ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| date | date | YES | | NULL | |
| empid | varchar(10) | YES | | NULL | |
| name | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
| grade | int | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
5 rows in set (0.00 sec)</code></pre>
<!--block_code_end--><h5 id="toc_35" class="h16">主键</h5>
<p class="md_block">
<span class="md_line md_line_start">创建唯一记录时,会给列设置一个用于和其他列进行区分的特殊属性。<br /></span>
<span class="md_line">在这种情况下需要用到的就是主键(PRIMARY KEY)。主键是在多条记录中用于确定一条记录时使用的标识符。主键的特点:<br /></span>
<span class="md_line md_line_end">没有重复的值、不允许输入空值(NULL)</span>
</p>
<h6 id="toc_36" class="h16">创建主键</h6>
<pre><code>mysql> create table t_pk (a INT PRIMARY KEY,b VARCHAR(10));
Query OK, 0 rows affected (0.04 sec)
mysql> desc t_pk;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| a | int | NO | PRI | NULL | |
| b | varchar(10) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">通过desc t_pk可以看到a 的key类型是PRIMARY KEY,NULL(是否为NULL)为no,不允许输入空值。 </span>
</p>
<h6 id="toc_37" class="h16">确认主键</h6>
<pre><code>mysql> desc t_pk;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| a | int | NO | PRI | NULL | |
| b | varchar(10) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
mysql> insert into t_pk VALUES(10,'阿');
Query OK, 1 row affected (0.02 sec)
mysql> select * from t_pk;
+----+------+
| a | b |
+----+------+
| 10 | 阿 |
+----+------+
1 row in set (0.00 sec)
mysql> insert into t_pk(a) VALUES(10);
ERROR 1062 (23000): Duplicate entry '10' for key 't_pk.PRIMARY'</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">因为列a作为主键不允许输入重复的值'10'和空值NULL,所以重复时会有Duplicate entry 这样的报错。</span>
</p>
<h6 id="toc_38" class="h16">设置唯一键</h6>
<pre><code>mysql> create table t_uniq1(a INT UNIQUE,b VARCHAR(10));
Query OK, 0 rows affected (0.04 sec)
mysql> desc t_uniq;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| a | int | YES | UNI | NULL | |
| b | varchar(10) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
mysql> insert into t_uniq (a) VALUES(NULL);
Query OK, 1 row affected (0.02 sec)
mysql> select * from t_uniq;
+------+------+
| a | b |
+------+------+
| NULL | NULL |
+------+------+
1 row in set (0.00 sec)
mysql></code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">唯一键虽然不允许列中有重复值,但允许输入为NULL。</span>
</p>
<h6 id="toc_39" class="h16">使列具有自动连续编号功能</h6>
<p class="md_block">
<span class="md_line md_line_start">要使列具有自动连续编号功能,就得在定义列的时候进行以下3项设置:<br /></span>
<span class="md_line">数据类型必须要为INT TINYINT SMALLINT等整数类型<br /></span>
<span class="md_line">创建表格时需要指定AUTO_INCREMENT<br /></span>
<span class="md_line md_line_end">设置PRIMARYKEY,使列具有唯一性</span>
</p>
<pre><code>mysql> insert into t_series(b) VALUES('子');
Query OK, 1 row affected (0.02 sec)
mysql> insert into t_series(b) VALUES('丑');
Query OK, 1 row affected (0.00 sec)
mysql> insert into t_series(b) VALUES(' 寅');
Query OK, 1 row affected (0.01 sec)
mysql> select * from t_series;
+---+------+
| a | b |
+---+------+
| 1 | 子 |
| 2 | 丑 |
| 3 | 寅 |
+---+------+
3 rows in set (0.00 sec)</code></pre>
<!--block_code_end--><h6 id="toc_40" class="h16">设置连续编号</h6>
<pre><code>mysql> select * from t_series;
+---+------+
| a | b |
+---+------+
| 1 | 子 |
| 2 | 丑 |
| 3 | 寅 |
+---+------+
3 rows in set (0.00 sec)
mysql> delete from t_series;
Query OK, 3 rows affected (0.02 sec)
mysql> insert into t_series(b) VALUES('武');
Query OK, 1 row affected (0.02 sec)
mysql> select * from t_series;
+---+------+
| a | b |
+---+------+
| 4 | 武 |
+---+------+
1 row in set (0.00 sec)
mysql> insert into t_series VALUES(10,'庚');
Query OK, 1 row affected (0.02 sec)
mysql> insert into t_series(b) VALUES('耄');
Query OK, 1 row affected (0.02 sec)
mysql> select * from t_series;
+----+------+
| a | b |
+----+------+
| 4 | 武 |
| 10 | 庚 |
| 11 | 耄 |
+----+------+
3 rows in set (0.00 sec)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">如果把表中的所有记录都删除,然后重新输入记录,编号会从之前的最大值+1开始分配。<br /></span>
<span class="md_line md_line_end">拥有自动连续编号功能的列还可以设置指定的值(唯一),例列a中输入10,然后从11开始分配。</span>
</p>
<h6 id="toc_41" class="h16">初始化AUTO_INCREMENT的值</h6>
<pre><code>mysql> alter table t_series AUTO_INCREMENT=1;
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> select * from t_series;
+----+------+
| a | b |
+----+------+
| 4 | 武 |
| 10 | 庚 |
| 11 | 耄 |
+----+------+
3 rows in set (0.00 sec)
mysql> insert into t_series(b) VALUES('子');
Query OK, 1 row affected (0.02 sec)
mysql> insert into t_series(b) VALUES('丑');
Query OK, 1 row affected (0.01 sec)
mysql> select * from t_series;
+----+------+
| a | b |
+----+------+
| 4 | 武 |
| 10 | 庚 |
| 11 | 耄 |
| 12 | 子 |
| 13 | 丑 |
+----+------+
5 rows in set (0.00 sec)
mysql> alter table t_series AUTO_INCREMENT=100;
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> insert into t_series(b) VALUES('正');
Query OK, 1 row affected (0.01 sec)
mysql> select * from t_series;
+-----+------+
| a | b |
+-----+------+
| 4 | 武 |
| 10 | 庚 |
| 11 | 耄 |
| 12 | 子 |
| 13 | 丑 |
| 100 | 正 |
+-----+------+
6 rows in set (0.00 sec)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">当表中存在数据时,如果设置的编号值比已经存在的值大,也可以通过上面的语句重新设置编号的初始值。如果设置的编号初始值比当前已存在的最大值大,则设置不生效。</span>
</p>
<h6 id="toc_42" class="h16">设置列的默认值</h6>
<pre><code>mysql> create table tb1G (empid VARCHAR(10),name VARCHAR(10),age INT) CHARSET=utf8;
Query OK, 0 rows affected, 1 warning (0.03 sec)
mysql> desc tb1G;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| empid | varchar(10) | YES | | NULL | |
| name | varchar(10) | YES | | NULL | |
| age | int | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
mysql> alter table tb1G MODIFY name VARCHAR(10) DEFAULT '未输入姓名';
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc tb1G;
+-------+-------------+------+-----+-----------------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+-----------------+-------+
| empid | varchar(10) | YES | | NULL | |
| name | varchar(10) | YES | | 未输入姓名 | |
| age | int | YES | | NULL | |
+-------+-------------+------+-----+-----------------+-------+
3 rows in set (0.00 sec)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">如果插入数据时不指定name,查看name中的值。</span>
</p>
<pre><code>mysql> insert into tb1G(empid,age) VALUES('64871',27);
Query OK, 1 row affected (0.02 sec)
mysql> select * from tb1G;
+-------+-----------------+------+
| empid | name | age |
+-------+-----------------+------+
| 64871 | 未输入姓名 | 27 |
+-------+-----------------+------+
1 row in set (0.00 sec)</code></pre>
<!--block_code_end--><h6 id="toc_43" class="h16">创建索引</h6>
<p class="md_block">
<span class="md_line md_line_start md_line_end">当查找表中的数据时,如果数据量过大,查找操作就会花费很多时间。如果实现在表上创建了索引,查找时就不用对全表进行扫描,而是利用索引进行扫描。</span>
</p>
<pre><code>mysql> create index my_ind on tb1G(empid);
Query OK, 0 rows affected (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show index from tb1G;
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| tb1G | 1 | my_ind | 1 | empid | A | 1 | NULL | NULL | YES | BTREE | | | YES | NULL |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
1 row in set (0.03 sec)
mysql> show index from tb1G \G
*************************** 1. row ***************************
Table: tb1G
Non_unique: 1
Key_name: my_ind
Seq_in_index: 1
Column_name: empid
Collation: A
Cardinality: 1
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
1 row in set (0.01 sec)
mysql></code></pre>
<!--block_code_end--><h6 id="toc_44" class="h16">删除索引</h6>
<pre><code>mysql> drop index my_ind on tb1G;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show index from tb1G \G
Empty set (0.00 sec)
mysql></code></pre>
<!--block_code_end--><h6 id="toc_45" class="h16">索引和处理速度</h6>
<p class="md_block">
<span class="md_line md_line_start md_line_end">创建了索引并不代表一定会缩短查找时间,因为根据查找条件的不同,有时候不需要用到索引,而且在某些情况下,使用索引反而会花费更多的时间。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">如果相同的值较多的情况下最好不要创建索引,当某列只有'YES'和'NO'这两个值,即使在该列上创建索引页不会提高处理速度。当对创建了索引的表进行更新时,也需要对已经存在的索引信息进行维护,所以,在使用索引的情况下,检索速度可能会变快,但与此同时,更新速度页很可能会变慢。</span>
</p>
<h5 id="toc_46" class="h16">查看已创建表的信息</h5>
<pre><code>mysql> show create table employ;
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| employ | CREATE TABLE `employ` (
`birthday` datetime DEFAULT NULL,
`empid` varchar(10) DEFAULT NULL,
`name` varchar(100) DEFAULT NULL,
`age` int DEFAULT NULL,
`grade` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> show table status from test where name='employ' \G
*************************** 1. row ***************************
Name: employ
Engine: InnoDB
Version: 10
Row_format: Dynamic
Rows: 6
Avg_row_length: 2730
Data_length: 16384
Max_data_length: 0
Index_length: 0
Data_free: 0
Auto_increment: NULL
Create_time: 2023-07-23 11:26:17
Update_time: NULL
Check_time: NULL
Collation: utf8mb4_0900_ai_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.02 sec)
一种更简单的查询方式:
mysql> show create table employ\G
*************************** 1. row ***************************
Table: employ
Create Table: CREATE TABLE `employ` (
`birthday` datetime DEFAULT NULL,
`empid` varchar(10) DEFAULT NULL,
`name` varchar(100) DEFAULT NULL,
`age` int DEFAULT NULL,
`grade` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)</code></pre>
<!--block_code_end-->
C协程库源码解析
2023-01-27T04:30:00Z
c-coroutine-lib
52coder
<h4 id="toc_0" class="h16">进程vs线程</h4>
<p class="md_block">
<span class="md_line md_line_start">我们知道,主机上资源有限,一颗 CPU、一块磁盘、一张网卡,如何同时服务上百个请求呢?<br /></span>
<span class="md_line md_line_end">多进程模式是最初的解决方案。内核把 CPU 的执行时间切分成许多时间片(timeslice),比如 1 秒钟可以切分为 100 个 10 毫秒的时间片,每个时间片再分发给不同的进程,通常,每个进程需要多个时间片才能完成一个请求。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这样,虽然微观上,比如说就这 10 毫秒时间 CPU 只能执行一个进程,但宏观上 1 秒钟执行了 100 个时间片,于是每个时间片所属进程中的请求也得到了执行,这就实现了请求的并发执行。不过,每个进程的内存空间都是独立的,这样用多进程实现并发就有两个缺点:一是内核的管理成本高,二是无法简单地通过内存同步数据,很不方便。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">于是多线程模式就出现了。多线程模式通过共享内存地址空间,解决了这两个问题。然而,共享地址空间虽然可以方便地共享对象,但这也导致一个问题,那就是任何一个线程出错时,进程中的所有线程会跟着一起崩溃。这也是如 Nginx 等强调稳定性的服务坚持使用多进程模式的原因。事实上,无论基于多进程还是多线程,都难以实现高并发,这由两个原因所致。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">首先,单个线程消耗的内存过多,比如,64 位的 Linux 为每个线程的栈分配了 8MB 的内存,还预分配了 64MB 的内存作为堆内存池。所以,我们没有足够的内存去开启几万个线程实现并发。其次,切换请求是内核通过切换线程实现的,什么时候会切换线程呢?不只时间片用尽,当调用阻塞方法时,内核为了让 CPU 充分工作,也会切换到其他线程执行。一次上下文切换的成本在几十纳秒到几微秒间,当线程繁忙且数量众多时,这些切换会消耗绝大部分的 CPU 运算能力。</span>
</p>
<h4 id="toc_1" class="h16">协程如何实现高并发</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">协程与异步编程相似的地方在于,它们必须使用非阻塞的系统调用与内核交互,把切换请求的权力牢牢掌握在用户态的代码中。但不同的地方在于,协程把异步化中的两段函数,封装为一个阻塞的协程函数。这个函数执行时,会使调用它的协程无感知地放弃执行权,由协程框架切换到其他就绪的协程继续执行。当这个函数的结果满足后,协程框架再选择合适的时机,切换回它所在的协程继续执行。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">那协程的切换是如何完成的呢?实际上,用户态的代码切换协程,与内核切换线程的原理是一样的。内核通过管理 CPU 的寄存器来切换线程,我们以最重要的栈寄存器和指令寄存器为例,看看协程切换时如何切换程序指令与内存。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">每个线程有独立的栈,而栈既保留了变量的值,也保留了函数的调用关系、参数和返回值,CPU 中的栈寄存器 SP 指向了当前线程的栈,而指令寄存器 IP 保存着下一条要执行的指令地址。因此,从线程 1 切换到线程 2 时,首先要把 SP、IP 寄存器的值为线程 1 保存下来,再从内存中找出线程 2 上一次切换前保存好的寄存器值,写入 CPU 的寄存器,这样就完成了线程切换。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">协程的切换与此相同,只是把内核的工作转移到协程框架实现而已。协程的切换与此相同,只是把内核的工作转移到协程框架实现而已,下图是协程切换前的状态:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/Linux/_image/2023-01-27-23-31-37.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before">从协程 1 切换到协程 2 后的状态如下图所示:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/Linux/_image/2023-01-27-23-31-08.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before md_line_end">创建协程时,会从进程的堆中分配一段内存作为协程的栈。线程的栈有 8MB,而协程栈的大小通常只有几十 KB。而且,C 库内存池也不会为协程预分配内存,它感知不到协程的存在。这样,更低的内存占用空间为高并发提供了保证,毕竟十万并发请求,就意味着 10 万个协程。当然,栈缩小后,就尽量不要使用递归函数,也不能在栈中申请过多的内存,这是实现高并发必须付出的代价。由此可见,协程就是用户态的线程。</span>
</p>
<pre><code>查看栈大小方法:
root@52coder:~/coroutine-master# ulimit -a | grep 'stack size'
stack size (kbytes, -s) 8192</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">然而,为了保证所有切换都在用户态进行,协程必须重新封装所有的阻塞系统调用,否则,一旦协程触发了线程切换,会导致这个线程进入休眠状态,进而其上的所有协程都得不到执行。<br /></span>
<span class="md_line md_line_end">比如,普通的 sleep 函数会让当前线程休眠,由内核来唤醒线程,而协程化改造后,sleep 只会让当前协程休眠,由协程框架在指定时间后唤醒协程。再比如,线程间的互斥锁是使用信号量实现的,而信号量也会导致线程休眠,协程化改造互斥锁后,同样由框架来协调、同步各协程的执行。所以,协程的高性能,建立在切换必须由用户态代码完成之上,这要求协程生态是完整的,要尽量覆盖常见的组件。比如 MySQL 官方提供的客户端 SDK,它使用了阻塞 socket 做网络访问,会导致线程休眠,必须用非阻塞 socket 把 SDK 改造为协程函数后,才能在协程中使用。</span>
</p>
<h4 id="toc_2" class="h16">小结</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我们从高并发的应用场景入手,分析了协程出现的背景和实现原理,以及它的应用范围。你会发现,协程融合了多线程与异步化编程的优点,既保证了开发效率,也提升了运行效率。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">有限的硬件资源下,多线程通过微观上时间片的切换,实现了同时服务上百个用户的能力。多线程的开发成本虽然低,但内存消耗大,切换次数过多,无法实现高并发。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">异步编程方式通过非阻塞系统调用和多路复用,把原本属于内核的请求切换能力,放在用户态的代码中执行。这样,不仅减少了每个请求的内存消耗,也降低了切换请求的成本,最终实现了高并发。然而,异步编程违反了代码的内聚性,还需要业务代码关注并发细节,开发成本很高。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">协程参考内核通过 CPU 寄存器切换线程的方法,在用户态代码中实现了协程的切换,既降低了切换请求的成本,也使得协程中的业务代码不用关注自己何时被挂起,何时被执行。相比异步编程中要维护一堆数据结构表示中间状态,协程直接用代码表示状态,大大提升了开发效率。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在协程中调用的所有 API,都需要做非阻塞的协程化改造。优秀的协程生态下,常用服务都有对应的协程 SDK,方便业务代码使用。开发高并发服务时,与 IO 多路复用结合的协程框架可以与这些 SDK 配合,自动挂起、切换协程,进一步提升开发效率。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">协程并不是完全与线程无关,首先线程可以帮助协程充分使用多核 CPU 的计算力,其次,遇到无法协程化、会导致内核切换的阻塞函数,或者计算太密集从而长时间占用 CPU 的任务,还是要放在独立的线程中执行,以防止它影响所有协程的执行。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">网上看到的一篇协程库源码解析,整理的非常详细。<br /></span>
<span class="md_line md_line_dom_embed"><a class="md_compiled" href="https://juejin.cn/post/7117267816981954574">C++学习记录:一个协程库的源码分析</a><br /></span>
<span class="md_line">参考资料:<br /></span>
<span class="md_line md_line_dom_embed md_line_end"><a class="md_compiled" href="https://time.geekbang.org/column/article/233629">8k</a></span>
</p>
C语言数组初始化的坑
2022-08-28T02:00:00Z
carrbug
52coder
<p class="md_block">
<span class="md_line md_line_start md_line_end">如下代码,输出结果是什么?</span>
</p>
<pre><code>#include <stdio.h>
int main()
{
int arr[10] = {10};
for(int i = 0;i < 10;i++)
printf("%d\n",arr[i]);
return 0;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">输出结果:</span>
</p>
<pre><code>10
0
0
0
0
0
0
0
0
0</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">本来以为应该全部输出的是10。<br /></span>
<span class="md_line">看到介绍:https://en.cppreference.com/w/c/language/array_initialization<br /></span>
<span class="md_line md_line_end">文中有几个例子,引以为戒:</span>
</p>
<pre><code>int x[] = {1,2,3}; // x has type int[3] and holds 1,2,3
int y[5] = {1,2,3}; // y has type int[5] and holds 1,2,3,0,0
int z[4] = {1}; // z has type int[4] and holds 1,0,0,0
int w[3] = {0}; // w has type int[3] and holds all zeroes</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">只有{0}时才全部初始化0,其余初始化是根据给定元素个数。</span>
</p>
网络编程中的SIGPIPE信号
2022-02-20T07:55:00Z
net-program-sigpipe
52coder
<p class="md_block">
<span class="md_line md_line_start">在极客时间中学习专栏<网络编程实战>中有一个思考题,觉得下面的评论回答的有点浅显,结合工作中排查过的实际问题,想结合自己的理解展开讲下。<br /></span>
<span class="md_line md_line_end">源代码(client.c):</span>
</p>
<pre><code>#include <stdio.h>
#include <error.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h> /* basic socket definitions */
#include <sys/time.h> /* timeval{} for select() */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <arpa/inet.h> /* inet(3) functions */
#include <string.h>
#include <unistd.h>
#include <sys/un.h> /* for Unix domain sockets */
#define SERV_PORT 43211
#define MAXLINE 4096
#define UNIXSTR_PATH "/var/lib/unixstream.sock"
#define LISTENQ 1024
#define BUFFER_SIZE 4096
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: unixstreamclient <local_path>");
}
int sockfd;
struct sockaddr_un servaddr;
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd < 0) {
error(1, errno, "create socket failed");
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, argv[1]);
if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
error(1, errno, "connect failed");
}
char send_line[MAXLINE];
bzero(send_line, MAXLINE);
char recv_line[MAXLINE];
while (fgets(send_line, MAXLINE, stdin) != NULL) {
int nbytes = sizeof(send_line);
if (write(sockfd, send_line, nbytes) != nbytes)
error(1, errno, "write error");
if (read(sockfd, recv_line, MAXLINE) == 0)
error(1, errno, "server terminated prematurely");
fputs(recv_line, stdout);
}
exit(0);
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">server.c</span>
</p>
<pre><code>#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include <sys/socket.h> /* basic socket definitions */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <sys/un.h> /* for Unix domain sockets */
#include <sys/select.h> /* for convenience */
#include <pthread.h>
#define SERV_PORT 43211
#define MAXLINE 4096
#define UNIXSTR_PATH "/var/lib/unixstream.sock"
#define LISTENQ 1024
#define BUFFER_SIZE 4096
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: unixstreamserver <local_path>");
}
int listenfd, connfd;
socklen_t clilen;
struct sockaddr_un cliaddr, servaddr;
//signal(SIGPIPE, SIG_IGN); // 忽略 SIGPIPE 信号
listenfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (listenfd < 0) {
error(1, errno, "socket create failed");
}
char *local_path = argv[1];
unlink(local_path);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, local_path);
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
error(1, errno, "bind failed");
}
if (listen(listenfd, LISTENQ) < 0) {
error(1, errno, "listen failed");
}
clilen = sizeof(cliaddr);
if ((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) < 0) {
if (errno == EINTR)
error(1, errno, "accept failed"); /* back to for() */
else
error(1, errno, "accept failed");
}
char buf[BUFFER_SIZE];
while (1) {
bzero(buf, sizeof(buf));
if (read(connfd, buf, BUFFER_SIZE) == 0) {
printf("client quit");
exit(0);
}
printf("Receive: %s", buf);
char send_line[MAXLINE];
sprintf(send_line, "Hi, %s", buf);
int nbytes = sizeof(send_line);
if (write(connfd, send_line, nbytes) != nbytes)
error(1, errno, "write error");
}
close(listenfd);
close(connfd);
exit(0);
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">编译运行:<br /></span>
<span class="md_line md_line_end">server端(先运行)</span>
</p>
<pre><code>[root net]#./server /var/lib/unixstream.sock
Receive: hello,world
Receive: fine,thank you
client quit[root net]#</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">client端</span>
</p>
<pre><code>[root net]#./client /var/lib/unixstream.sock
hello,world
Hi, hello,world
fine,thank you
Hi, fine,thank you
^C</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">思考题:<br /></span>
<span class="md_line">我们看到客户端被杀死后,服务器端也正常退出了。看下退出后打印的日志,你不妨判断一下引起服务器端正常退出的逻辑是什么?<br /></span>
<span class="md_line">解答:<br /></span>
<span class="md_line md_line_end">在服务端的代码中,对收到的客户端发送的数据长度做了判断,如果长度为0,则主动关闭服务端程序。这是杀死客户端后引发服务端关闭的原因。这也说明客户端在被强行终止的时候,会最后向服务端发送一条空消息,告知服务器自己这边的程序关闭了。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">那既然这里的代码对长度做了判断,如果长度为0则退出,那我们注释掉退出的代码是不是就不会退出了?<br /></span>
<span class="md_line md_line_end">server端(先启动)</span>
</p>
<pre><code>[root net]#./server /var/lib/unixstream.sock
Receive: g1
Receive: g2
[root net]#</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">client端</span>
</p>
<pre><code>[root net]#./client /var/lib/unixstream.sock
g1
Hi, g1
g2
Hi, g2
^C</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">我们看到及时我们把exit(0)代码注释掉,server端还是依然退出。这里的原因又是什么呢?<br /></span>
<span class="md_line">这里分享一个小技巧,可以使用gdb排查退出原因:<br /></span>
<span class="md_line md_line_end">server端</span>
</p>
<pre><code>[root net]#gdb ./server
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7Copyright (C) 2013 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/workspace/cplusplus/net/server...done.
(gdb) set args /var/lib/unixstream.sock
(gdb) r
Starting program: /root/workspace/cplusplus/net/./server /var/lib/unixstream.sock
Receive: g1
Receive: g2
Program received signal SIGPIPE, Broken pipe.
0x00007ffff7afcba0 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:81
81 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
(gdb)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">client端</span>
</p>
<pre><code>[root net]#./client /var/lib/unixstream.sock
g1
Hi, g1
g2
Hi, g2
^C</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">我们可以看到服务端收到信号SIGPIPE,进而信号默认行为退出程序。<br /></span>
<span class="md_line md_line_end">man 手册中关于该信号的解读:</span>
</p>
<pre><code>SIGPIPE P1990 Term Broken pipe: write to pipe with no readers; see pipe(7)</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">连接建立,若某一端关闭连接,而另一端仍然向它写数据,第一次写数据后会收到RST响应,此后再写数据,内核将向进程发出SIGPIPE信号,通知进程此连接已经断开。而SIGPIPE信号的默认处理是终止程序,导致上述问题的发生。<br /></span>
<span class="md_line md_line_end">我们添加代码屏蔽信号SIGPIPE</span>
</p>
<pre><code>/* Catch Signal Handler functio */
void signal_callback_handler(int signum)
{
printf("Caught signal SIGPIPE %d\n",signum);
}
//main函数中添加
/* Catch Signal Handler SIGPIPE */
signal(SIGPIPE, signal_callback_handler);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">重新编译运行后发现服务端程序依旧自动退出。<br /></span>
<span class="md_line md_line_end">完整代码如下:</span>
</p>
<pre><code>#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include <sys/socket.h> /* basic socket definitions */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <sys/un.h> /* for Unix domain sockets */
#include <signal.h>
#include <sys/select.h> /* for convenience */
#include <pthread.h>
#define SERV_PORT 43211
#define MAXLINE 4096
#define UNIXSTR_PATH "/var/lib/unixstream.sock"
#define LISTENQ 1024
#define BUFFER_SIZE 4096
/* Catch Signal Handler functio */
void signal_callback_handler(int signum)
{
printf("Caught signal SIGPIPE %d\n",signum);
}
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: unixstreamserver <local_path>");
}
int listenfd, connfd;
socklen_t clilen;
struct sockaddr_un cliaddr, servaddr;
/* Catch Signal Handler SIGPIPE */
signal(SIGPIPE, signal_callback_handler);
listenfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (listenfd < 0) {
error(1, errno, "socket create failed");
}
char *local_path = argv[1];
unlink(local_path);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, local_path);
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
error(1, errno, "bind failed");
}
if (listen(listenfd, LISTENQ) < 0) {
error(1, errno, "listen failed");
}
clilen = sizeof(cliaddr);
if ((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) < 0) {
if (errno == EINTR)
error(1, errno, "accept failed"); /* back to for() */
else
error(1, errno, "accept failed");
}
char buf[BUFFER_SIZE];
while (1) {
bzero(buf, sizeof(buf));
if (read(connfd, buf, BUFFER_SIZE) == 0) {
printf("client quit");
//exit(0);
}
printf("Receive: %s", buf);
char send_line[MAXLINE];
sprintf(send_line, "Hi, %s", buf);
int nbytes = sizeof(send_line);
if (write(connfd, send_line, nbytes) != nbytes)
error(1, errno, "write error");
}
close(listenfd);
close(connfd);
exit(0);
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">参考<a class="md_compiled" href="https://stackoverflow.com/a/1705705/3094117">stackoverflow</a>上的一篇回答将write换成send,在client端ctrl+c退出后server端不会退出。</span>
</p>
<pre><code>#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include <sys/socket.h> /* basic socket definitions */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <sys/un.h> /* for Unix domain sockets */
#include <signal.h>
#include <sys/select.h> /* for convenience */
#include <pthread.h>
#define SERV_PORT 43211
#define MAXLINE 4096
#define UNIXSTR_PATH "/var/lib/unixstream.sock"
#define LISTENQ 1024
#define BUFFER_SIZE 4096
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: unixstreamserver <local_path>");
}
int listenfd, connfd;
socklen_t clilen;
struct sockaddr_un cliaddr, servaddr;
listenfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (listenfd < 0) {
error(1, errno, "socket create failed");
}
char *local_path = argv[1];
unlink(local_path);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, local_path);
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
error(1, errno, "bind failed");
}
if (listen(listenfd, LISTENQ) < 0) {
error(1, errno, "listen failed");
}
clilen = sizeof(cliaddr);
if ((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) < 0) {
if (errno == EINTR)
error(1, errno, "accept failed"); /* back to for() */
else
error(1, errno, "accept failed");
}
char buf[BUFFER_SIZE];
while (1) {
bzero(buf, sizeof(buf));
if (read(connfd, buf, BUFFER_SIZE) == 0) {
usleep(100);
}
if(strlen(buf) <= 0) continue;
printf("Receive: %s", buf);
char send_line[MAXLINE];
sprintf(send_line, "Hi, %s", buf);
int nbytes = sizeof(send_line);
int n_written = send(connfd,send_line,nbytes,MSG_NOSIGNAL);
if (n_written <= 0) {
error(1, errno, "send failed");
}
}
close(listenfd);
close(connfd);
exit(0);
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">但是不知道为什么参考<a class="md_compiled" href="https://stackoverflow.com/questions/18935446/program-received-signal-sigpipe-broken-pipe/18963142">stackoverflow</a>中的答案没有屏蔽信号SIGPIPE.</span>
</p>
日常开发笔记总结(十三)
2022-02-19T01:30:00Z
weeknote-202201
52coder
<h4 id="toc_0" class="h16">向Linux登录终端发消息</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">同一台服务器,可能有很多个用户登录在上面,每个用户都是一个系统终端,可以向其他终端发送消息,同在服务器上开发的开发人员可以简单的互动(不能回复)一下哈!</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">一,效果<br /></span>
<span class="md_line md_line_end">先登录一个终端,如下:</span>
</p>
<pre><code>[root@localhost /]# who
root tty1 2013-02-16 18:14 (:0)
root pts/0 2013-02-17 02:01 (:0.0)
[root@localhost /]# </code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">登录的终端为pts/0。然后再打开一个终端,如下:</span>
</p>
<pre><code>[root@localhost /]# who
root tty1 2013-02-16 18:14 (:0)
root pts/0 2013-02-17 02:01 (:0.0)
root pts/1 2013-02-17 02:02 (:0.0)
[root@localhost /]#</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">此时登录的终端为pts/1。现在假设pts/1发消息给pts/0。</span>
</p>
<pre><code>[root@localhost /]# write root pts/0
hello</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">在pts/0终端上收到消息如下:</span>
</p>
<pre><code>[root@localhost /]#
Message from root@localhost.localdomain on pts/1 at 02:03 ...
hello</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">在pts/0上只是收到消息,不能回复的。<br /></span>
<span class="md_line md_line_end">再看一下write命令的解释吧</span>
</p>
<pre><code>WRITE(1) User Commands WRITE(1)
NAME
write - send a message to another user
SYNOPSIS
write user [ttyname]
DESCRIPTION
Write allows you to communicate with other users, by copying lines from
your terminal to theirs.
When you run the write command, the user you are writing to gets a mes‐
sage of the form:
Message from yourname@yourhost on yourtty at hh:mm ...</code></pre>
<!--block_code_end--><h4 id="toc_1" class="h16">aio_read未定义的引用</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">下载了一份代码学习,发现编不过,这里需要修改CMakeLists.txt,可以参考<a class="md_compiled" href="https://www.cnblogs.com/fortunely/p/14808689.html">编译错误: 对‘aio_read’未定义的引用</a></span>
</p>
<h4 id="toc_2" class="h16">APUE 第七章 进程环境</h4>
<p class="md_block">
<span class="md_line md_line_start">C程序总是从main函数开始执行,main函数的原型是:<br /></span>
<span class="md_line">int main(int argc,char *argv[]);<br /></span>
<span class="md_line md_line_end">其中argc是命令行参数的数目,argv是指向参数的各个指针所构成的数组。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">多通道融合merge逻辑测试代码:</span>
</p>
<pre><code>#include <iostream>
#include <queue>
#include <unordered_map>
#include <string>
class RecallItem {
public:
RecallItem() {};
RecallItem(const std::string &itemid_,const std::string &rec_id_,int score_) {
itemid = itemid_;
rec_id = rec_id_;
score = score_;
}
std::string itemid;
std::string rec_id;
int score;
};
struct ItemQueue {
std::vector<std::string> item_list;
std::string rec_id;
};
auto item_cmp = [](const RecallItem& a,const RecallItem& b) {
return a.score < b.score;
};
int main()
{
std::priority_queue<RecallItem,std::vector<RecallItem>,decltype(item_cmp)> queue_item(item_cmp);
std::unordered_map<std::string,ItemQueue > channel_map;
//权重信息
std::unordered_map<std::string,int > ch_weight {{"channel1",150},{"channel2",100}};
std::unordered_map<std::string,RecallItem> item_map;
//最大召回item个数
int max_merge_num = 100;
//构造两个召回通道 channel1,channel2
auto& channel1 = channel_map["channel1"];
channel1.rec_id = "1";
for(int i = 0;i < 200;i+=2)
channel1.item_list.push_back(std::to_string(i));
auto& channel2 = channel_map["channel2"];
channel2.rec_id = "2";
for(int i = 1;i < 200;i+=2)
channel2.item_list.push_back(std::to_string(i));
//遍历多个通道
for(auto ch : channel_map) {
int weight = 0;
//获取对应的权重信息
auto it = ch_weight.find(ch.first);
if(it != ch_weight.end()){
weight = ch_weight[ch.first];
}
int index = 0,count = 0;
int size = ch.second.item_list.size();
//对召回列表过滤 去重 调整分数权重
for(auto& item:ch.second.item_list) {
double rank_score = (0+weight) * 1.0 - index++;
auto iter = item_map.find(item);
if(iter != item_map.end()) {
//多个通道存在相同item,分数取较大者
if(iter->second.score < rank_score) {
iter->second.score = rank_score;
}
//记录多个通道命中的情况
iter->second.rec_id = iter->second.rec_id +"|" + ch.second.rec_id;
continue;
}
RecallItem ch_item(item,ch.second.rec_id,rank_score);
item_map.insert({item,ch_item});
}
}
for(auto item : item_map) {
queue_item.push(item.second);
}
std::vector<RecallItem> result;
while(!queue_item.empty() && result.size() < max_merge_num) {
auto item = queue_item.top();
auto iter = item_map.find(item.itemid);
if(iter != item_map.end()) {
item.rec_id = iter->second.rec_id;
}
result.emplace_back(item);
queue_item.pop();
}
for(auto res:result) {
std::cout <<"itemid:"<<res.itemid<<",rec_id:"<<res.rec_id<<std::endl;
}
#if 0
//debug print channel_map
for(auto ch:channel_map) {
for(auto& item:ch.second) {
std::cout << item <<" ";
}
std::cout <<std::endl;
}
#endif
return 0;
}</code></pre>
<!--block_code_end-->
日常开发笔记总结(十二)
2021-12-04T08:30:00Z
weeknote-202112
52coder
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以直接在命令行中定义函数,通过使用declare命令来打印出来,使用shell函数,只需要在命令行中输入函数名称。一旦不再需要某个shell函数,可以使用unset命令来删除它。</span>
</p>
<pre><code>[root cplusplus]#foo() { echo "Inside function"; }
[root cplusplus]#foo
Inside function
[root cplusplus]#declare -f foo
foo ()
{
echo "Inside function"
}
[root cplusplus]#unset foo
[root cplusplus]#declare -f foo</code></pre>
<!--block_code_end--><h4 id="toc_0" class="h16">向子进程传入一个函数</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">当shell进程新建一个子进程并在子进程中运行一个shell命令时,该函数定义就会被传给子shell进程。这种方式只是用于父进程也是shell的情况。</span>
</p>
<pre><code>[root cplusplus]#foo() { echo "Inside function"; }
[root cplusplus]#export -f foo
[root cplusplus]#declare -f foo
foo ()
{
echo "Inside function"
}
[root cplusplus]#bash
[root cplusplus]#declare -f foo
foo ()
{
echo "Inside function"
}
[root cplusplus]#</code></pre>
<!--block_code_end--><h4 id="toc_1" class="h16">C++合并两个vector</h4>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start"><a class="md_compiled" href="https://stackoverflow.com/questions/201718/concatenating-two-stdvectors">Concatenating two std::vectors</a><br /></span>
<span class="md_line md_line_end">If you are using C++11, and wish to move the elements rather than merely copying them, you can use std::move_iterator along with insert (or copy):</span>
</p>
<pre><code>#include <vector>
#include <iostream>
#include <iterator>
int main(int argc, char** argv) {
std::vector<int> dest{1,2,3,4,5};
std::vector<int> src{6,7,8,9,10};
// Move elements from src to dest.
// src is left in undefined but safe-to-destruct state.
dest.insert(
dest.end(),
std::make_move_iterator(src.begin()),
std::make_move_iterator(src.end())
);
// Print out concatenated vector.
std::copy(
dest.begin(),
dest.end(),
std::ostream_iterator<int>(std::cout, "\n")
);
return 0;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">使用google benchmark验证,使用make_move_iterator性能上会好一些。</span>
</p>
<pre><code>[root benchmark]#./a.out
2021-12-25T11:52:00+08:00
Running ./a.out
Run on (4 X 1900 MHz CPU s)
CPU Caches:
L1 Data 32 KiB (x2)
L1 Instruction 32 KiB (x2)
L2 Unified 256 KiB (x2)
L3 Unified 3072 KiB (x1)
Load Average: 0.00, 0.01, 0.05
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
---------------------------------------------------------------------
Benchmark Time CPU Iterations
---------------------------------------------------------------------
bench_vector_insert 3362466 ns 3362789 ns 206
bench_vector_move_iterator 3294766 ns 3295113 ns 212</code></pre>
<!--block_code_end-->