Talk about UVW Code (1)

Posted by monotoko on Fri, 24 May 2019 18:53:44 +0200

The blog Garden is a very good place to learn knowledge. I believe there are many people like me who have been in the blog Garden for 3 years. They do not know how many codes they have copied or how many blogs they have read, but they have never written a single blog.It's a crime.

This time I'm going to write a few articles about the reading and understanding of the source code of this project. You can learn from each other. I may not just write the source code and other things, but also do many extensions, such as the new c++ grammar, some other tools, etc. Don't bother your judges.We are not a bull either. When we encounter ambiguities or inaccuracies in the text, please leave a message in the comments area. Let's discuss and make improvements to avoid misleading children.

Don't talk too much. Start now.

 

Most people probably don't know the source code of a project uvw recently.Some people who have done network programming should know about libuv. uvw is a project I found on github that encapsulates libuv in c++, and the source code author is constantly updating.

A brief introduction:

Libuv: is a cross-platform web library that refers specifically to blogs: http://www.cnblogs.com/haippy/archive/2013/03/17/2963995.html and blogger's articles in the libuv series.

uvw: Using c++14 to encapsulate libuv, the author should be a foreigner, the code quality should not be mentioned, especially the comments, is very detailed and worth learning from other novice birds.

Github address: https://github.com/skypjack/uvw

  

You have to get the code out first.

1. Direct download at https://codeload.github.com/skypjack/uvw/zip/master

  2,git clone https://github.com/skypjack/uvw.git

 

Note: File path notation:. /src/uvw.hpp. The current directory is the code root directory.

The code is basically in the SRC file, cut into the src, right, you're not misreading it, it's all hpp files, so if you want to use this library, just copy the SRC directly into your project.It can be very convenient to use, but don't forget to include libuv's header files and link libuv libraries in your project.In addition, uvw has limitations on the version of libuv. You can view the corresponding version of libuv in github's tag or, if you are using method 2, you can use the command "git tag -l".(For git, if the viewer doesn't know it yet, you can refer to the novice bird tutorial at http://www.runoob.com/git/git-tutorial.html or go to the GIT website for very detailed information.)

  

First, let's see how to use it

Copy a piece of code (. /test/main.cpp):

  1 #include "../src/uvw.hpp"
  2 #include <cassert>
  3 #include <iostream>
  4 #include <memory>
  5 #include <chrono>
  6 
  7 
  8 void listen(uvw::Loop &loop) {
  9     std::shared_ptr<uvw::TcpHandle> tcp = loop.resource<uvw::TcpHandle>();            //Create a TcpHandle
 10 
 11     tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) {        //The registration error occurred in the function,
 12         std::cout << "error " << std::endl;
 13     });
 14 
 15     tcp->once<uvw::ListenEvent>([](const uvw::ListenEvent &, uvw::TcpHandle &srv) {    //Register listening event functions
 16         std::cout << "listen" << std::endl;
 17 
 18         std::shared_ptr<uvw::TcpHandle> client = srv.loop().resource<uvw::TcpHandle>();        //Create a TcpHandle,For new client Connect
 19 
 20         client->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) {            //by client Registration Error Occurring Function
 21             std::cout << "error " << std::endl;
 22         });
 23 
 24         client->on<uvw::CloseEvent>([ptr = srv.shared_from_this()](const uvw::CloseEvent &, uvw::TcpHandle &) {    //register client Close function
 25             std::cout << "close" << std::endl;
 26             ptr->close();    //Here when client When closed, it will also close server
 27         });
 28 
 29         srv.accept(*client);    //server accept
 30 
 31         uvw::Addr local = srv.sock();
 32         std::cout << "local: " << local.ip << " " << local.port << std::endl;
 33 
 34         uvw::Addr remote = client->peer();
 35         std::cout << "remote: " << remote.ip << " " << remote.port << std::endl;
 36 
 37         client->on<uvw::DataEvent>([](const uvw::DataEvent &event, uvw::TcpHandle &) {        //register client Receive Data Event Function
 38             std::cout.write(event.data.get(), event.length) << std::endl;                    //event Read data is saved and can be used directly
 39             std::cout << "data length: " << event.length << std::endl;
 40         });
 41 
 42         client->on<uvw::EndEvent>([](const uvw::EndEvent &, uvw::TcpHandle &handle) {        //register client Data read end function, when socket Send this event when no data is readable
 43             std::cout << "end" << std::endl;
 44             int count = 0;
 45             handle.loop().walk([&count](uvw::BaseHandle &) { ++count; });                    //Getter loop Medium active sockets, here are server and client Two
 46             std::cout << "still alive: " << count << " handles" << std::endl;
 47             handle.close();        //Close client Connect
 48         });
 49 
 50         client->read();            //Start reading data, here and uv_read_start The effect is the same, This and the above registered event actions are invoked sequentially.
 51     });
 52 
 53     tcp->once<uvw::CloseEvent>([](const uvw::CloseEvent &, uvw::TcpHandle &) {
 54         std::cout << "close" << std::endl;
 55     });
 56 
 57     tcp->bind("127.0.0.1", 4242);        //bind,Support here IPv4 and IPv6,bind Is a template function
 58     tcp->listen();                        //listen
 59 }
 60 
 61 
 62 void conn(uvw::Loop &loop) {
 63     auto tcp = loop.resource<uvw::TcpHandle>();                //The following basic sum listen Similar in, no more comments
 64 
 65     tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) {
 66         std::cout << "error " << std::endl;
 67     });
 68 
 69     tcp->once<uvw::WriteEvent>([](const uvw::WriteEvent &, uvw::TcpHandle &handle) {
 70         std::cout << "write" << std::endl;
 71         handle.close();
 72     });
 73 
 74     tcp->once<uvw::ConnectEvent>([](const uvw::ConnectEvent &, uvw::TcpHandle &handle) {
 75         std::cout << "connect" << std::endl;
 76 
 77         auto dataTryWrite = std::unique_ptr<char[]>(new char[1]{ 'a' });        //The following actions are directed server send data
 78         int bw = handle.tryWrite(std::move(dataTryWrite), 1);
 79         std::cout << "written: " << ((int)bw) << std::endl;
 80 
 81         auto dataWrite = std::unique_ptr<char[]>(new char[2]{ 'b', 'c' });
 82         handle.write(std::move(dataWrite), 2);
 83     });
 84 
 85     tcp->once<uvw::CloseEvent>([](const uvw::CloseEvent &, uvw::TcpHandle &) {
 86         std::cout << "close" << std::endl;
 87     });
 88 
 89     tcp->connect("127.0.0.1", 4242);
 90 }
 91 
 92 void g() {
 93     auto loop = uvw::Loop::getDefault();        //Get Default Event Loop
 94     listen(*loop);
 95     conn(*loop);
 96     loop->run();        //Start Event Loop
 97     loop = nullptr;
 98 }
 99 
100 int main() {
101     g();
102 }

 

(What if I don't have my favorite code font)

Looks like it's quite long. This structure looks fairly clear.

 

2. Look carefully

1. server-side operations

The listen() function basically contains all the server-side operations, and the basic flow is:

Create TcpHandle (line 9) --> bind (line 57) --> listen (line 58)

 

In ListenEvent, you can see line 18 and create a TcpHandle client to receive client connections:

Create TcpHandle (line 18) --> accept (line 29) --> read (line 50)

 

In addition to these other codes, which are the process of event handling, event handling is written using Lamda expressions, such as:

1 tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) {        //The registration error occurred in the function,
2         std::cout << "error " << std::endl;
3     });

Lamda has two parameters:

{Event}: Events, there are many types of events you can see in your code, such as CloseEvent, ConnectEvent, etc. (You know what events are by name)

{Handle}: Source type, there may also be types in libuv such as UdpHandle.Talk about it later when you see the source code.

After running debugging, {Handle} in the event handling anonymous function is actually the same as the TcpHandle created.

 

2. client side operation

The conn function basically creates a TcpHandle, then calls connect to connect to the server, and everything else is related.

Additionally, client data sending:

1         std::cout << "connect" << std::endl;
2 
3         auto dataTryWrite = std::unique_ptr<char[]>(new char[1]{ 'a' });        //The following actions are directed server send data
4         int bw = handle.tryWrite(std::move(dataTryWrite), 1);
5         std::cout << "written: " << ((int)bw) << std::endl;
6 
7         auto dataWrite = std::unique_ptr<char[]>(new char[2]{ 'b', 'c' });
8         handle.write(std::move(dataWrite), 2);

You can see that two data write functions called, tryWrite and write, are equivalent to uv_write and uv_try_write. I give the author's comment on tryWrite:.

/**
     * @brief Queues a write request if it can be completed immediately.
     *
     * Same as `write()`, but won't queue a write request if it can't be
     * completed immediately.<br/>
     * An ErrorEvent event will be emitted in case of errors.
     *
     * @param data The data to be written to the stream.
     * @param len The lenght of the submitted data.
     * @return Number of bytes written.
     */
This means that tryWrite will also send data, but it will not complete immediately, nor will it guarantee that all data will be sent at once.write will add the data to the loop again, waiting for the next send.

3. Summary

You can see that the author has replaced various callbacks in libuv with a large number of Lambda, which in contrast increases readability a lot.

Additionally, a large number of template functions are used in the code to distinguish event types, and many generics should be used in the author's source code.

 

3. Relevant knowledge

  1,Lambda

Lambda, also known as the anonymous function, is a blog post that you can learn a little about or review: http://www.cnblogs.com/langzou/p/5962033.html

PS: The English name of this anonymous function has a lot of spellings: Lamda, Lamba, Lamdba, Ladbda.It's really strange.

Here's to emphasize that although the exact way the name is written doesn't have much impact on our learning, his English name should be spelled correctly in a rigorous manner

Lambda Pronunciation: lan b(m)da (Lammda) ['l md]

It is the translation of `lambda', which is also the spelling on Baidu Encyclopedia. You can also see it on page 346 of `C++ Primer 5th Edition', so don't forget it later, to avoid being joked.Ha-ha.

    

In line 24:

1 client->on<uvw::CloseEvent>([ptr = srv.shared_from_this()](const uvw::CloseEvent &, uvw::TcpHandle &) {    //register client Close function
2              std::cout << "close" << std::endl;
3              ptr->close();    //Here when client When closed, it will also close server
4          });

Did you notice what this ptr = srv.shared_from_this() is?

Shouldn't [] in Lambda be used to capture external variables? Why does this seem to define a ptr variable and initialize it with shared_from_this().However, it is clear that this ptr has no parameter type and no declaration of ptr in the context.Isn't that very strange.

After consulting a large amount of book materials, the http://zh.cppreference.com/w/cpp/language/lambda The following paragraph was found in:

 1 Capture with initializer, acts like it declares and displays the captured variable declared with type auto, the declarative region of the variable is the body of the lambda expression (that is, it is not in the scope of its initializer), except:
 2 If captured for replication, the non-static data members of the closure are another way to refer to the automatic variable.
 3 If captured to reference in, the lifetime of the reference variable ends at the end of the lifetime of the closing object.
 4 This is used to capture only mobile types, such as x = std::move(x) capture
 5 int x = 4;
 6 auto y = [&r = x, x = x + 1]()->int
 7     {
 8         r += 2;
 9         return x * x;
10}(); //Update:: x is 6 and initialization y is 25.

You can see that this capture with initializer is used, which is a newly added feature in c++14

In the first line, you know that this capture with an initializer automatically declares the variable as auto type and initializes the declared variable.Remember, it's initialization.For the example above, if written as follows:

1 int x = 4;
2 auto y = [&r = x, r = x + 1]()->int    //error
3     {
4         r += 2;
5         return x * x;
6     }();    

* Wrong.Additionally, if you do not initialize, compilation errors will occur.

Now look at line 24 of the source code. Is that OK?

After learning about this, I found another article describing this type of capture: http://blog.csdn.net/big_yellow_duck/article/details/52473055

You can learn and refer to it together, so I must buy a copy of Effective Modern C++.

 

2. Smart Pointer

I won't tell you about this, or blog Park articles, so you can learn or review them at http://www.cnblogs.com/q329914874/p/6653412.html

 

4. Next Article

Thank you all for being able to see this. My pen is not very good. It's really a grievance to you.

Next we'll talk about Emitter, a basic class in UVW

You have not had enough time lately. You may have to wait a few days.

Topics: C++ Lambda git github network