├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake └── easynetConfig.cmake.in ├── docs ├── easy_net.drawio ├── md │ ├── README.md │ ├── easynet │ │ ├── easy_net-业务接口.md │ │ ├── easy_net-日志模块.md │ │ ├── easy_net-网络模型.md │ │ └── roadmap.md │ └── generic │ │ ├── FAQ.md │ │ ├── HTTP协议.md │ │ ├── IP转换.md │ │ ├── SSL加密.md │ │ ├── curl工具使用.md │ │ ├── dns.md │ │ ├── epoll.md │ │ ├── gcc内置原子操作.md │ │ ├── http客户端.md │ │ ├── makefile变量.md │ │ ├── muduo学习.md │ │ ├── nc工具使用.md │ │ ├── reactor和proactor模式.md │ │ ├── socket传输类型.md │ │ ├── socket系列接口.md │ │ ├── spdlog使用.md │ │ ├── tcp关闭连接.md │ │ ├── tcp连接异常状态.md │ │ ├── tun和tap设备.md │ │ ├── url与uri.md │ │ ├── 主机字节序和网络字节序.md │ │ ├── 关于UDP处理模型.md │ │ ├── 关于回调.md │ │ ├── 同步-异步http.md │ │ ├── 同步与异步-阻塞与非阻塞.md │ │ ├── 处理signal.md │ │ ├── 定时器.md │ │ ├── 异步IO.md │ │ ├── 惊群问题.md │ │ ├── 监听地址.md │ │ ├── 线程安全.md │ │ ├── 网络模型设计讨论.md │ │ ├── 长连接服务vs短连接服务.md │ │ └── 非对称加密.md └── paper │ ├── Reactor1-93.pdf │ ├── Reactor2-93.pdf │ ├── awesome.md │ └── reactor-siemens.pdf ├── easy_net ├── base │ ├── buffer.cpp │ ├── buffer.h │ ├── copyable.h │ ├── countdown_latch.h │ ├── cyclic_barrier.h │ ├── inet_addr.cpp │ ├── inet_addr.h │ ├── log.h │ ├── non_copyable.h │ ├── sigleton.h │ ├── socket_opt.cpp │ ├── socket_opt.h │ ├── string_util.h │ ├── thread.h │ └── win_support.h ├── net │ ├── acceptor.cpp │ ├── acceptor.h │ ├── connection_owner.h │ ├── connector.cpp │ ├── connector.h │ ├── def.h │ ├── epoll_poller.cpp │ ├── epoll_poller.h │ ├── event_loop.cpp │ ├── event_loop.h │ ├── io_event.cpp │ ├── io_event.h │ ├── notify.cpp │ ├── notify.h │ ├── poller.cpp │ ├── poller.h │ ├── server_thread.cpp │ ├── server_thread.h │ ├── tcp_client.cpp │ ├── tcp_client.h │ ├── tcp_connection.cpp │ ├── tcp_connection.h │ ├── tcp_server.cpp │ ├── tcp_server.h │ ├── timer.cpp │ ├── timer.h │ ├── timer_policy.h │ ├── udp_client.h │ └── udp_server.h └── protocol │ ├── dns │ ├── dns.cpp │ └── dns.h │ └── http │ ├── http_client.cpp │ ├── http_client.h │ ├── http_context.cpp │ ├── http_context.h │ ├── http_def.h │ ├── http_headers.cpp │ ├── http_headers.h │ ├── http_parser.c │ ├── http_parser.h │ ├── http_request.cpp │ ├── http_request.h │ ├── http_response.cpp │ ├── http_response.h │ ├── http_router.h │ ├── http_server.cpp │ ├── http_server.h │ ├── http_url.cpp │ └── http_url.h ├── examples ├── CMakeLists.txt ├── dns │ └── dns.cpp ├── echo │ ├── echocli.cpp │ └── echosvr.cpp ├── http │ ├── httpcli.cpp │ └── httpsvr.cpp ├── tcp │ ├── tcpcli.cpp │ └── tcpsvr.cpp └── timer │ └── timer.cpp ├── format.sh ├── raw_examples ├── CMakeLists.txt ├── async_http │ └── async_http.cpp ├── c-ares │ ├── cares.h │ ├── main.cpp │ ├── posix.h │ └── time_cost.h ├── cpp-httplib │ ├── httplib.h │ └── httplib_client_test.cpp ├── epoll │ ├── epoll_cli.cpp │ ├── epoll_svr.cpp │ └── readme.md ├── eventfd │ └── eventfd_demo.cpp ├── http_parser │ ├── http_parser.c │ ├── http_parser.h │ └── http_parser_demo.cpp ├── noblocking_connect │ └── noblocking_connect.cpp ├── pipe_notifier │ ├── notifier.cpp │ ├── notify.h │ └── pipe.cpp ├── reuseport │ └── server.cpp ├── self-pipe-trick │ └── self-pipe-trick.cpp ├── signalfd │ └── signalfd_demo.cpp ├── thread_pool │ ├── data_container.h │ ├── main.cpp │ ├── msg.h │ ├── msg_qunue.h │ └── thread_pool.h ├── timer │ ├── list.h │ ├── miniheap.h │ ├── some_time_func.h │ ├── timer.h │ ├── timer_demo.cpp │ └── timewheel.h ├── timerfd │ └── timerfd_demo.cpp ├── udp │ ├── udpcli.cpp │ └── udpsvr.cpp └── unix_domain │ ├── client.cpp │ └── server.cpp ├── tapip ├── Makefile ├── README.md ├── TODO ├── app │ ├── Makefile │ ├── ping.c │ └── snc.c ├── arp │ ├── Makefile │ ├── arp.c │ └── arp_cache.c ├── doc │ ├── FAQ │ ├── brcfg.sh │ ├── net_topology │ ├── socket_design │ └── test ├── include │ ├── arp.h │ ├── cbuf.h │ ├── compile.h │ ├── ether.h │ ├── icmp.h │ ├── inet.h │ ├── ip.h │ ├── lib.h │ ├── list.h │ ├── netcfg.h │ ├── netif.h │ ├── raw.h │ ├── route.h │ ├── shell.h │ ├── sock.h │ ├── socket.h │ ├── tap.h │ ├── tcp.h │ ├── tcp_hash.h │ ├── tcp_timer.h │ ├── udp.h │ └── wait.h ├── ip │ ├── Makefile │ ├── icmp.c │ ├── ip_forward.c │ ├── ip_frag.c │ ├── ip_in.c │ ├── ip_out.c │ ├── raw.c │ └── route.c ├── lib │ ├── Makefile │ ├── cbuf.c │ ├── checksum.c │ └── lib.c ├── mmleak.sh ├── net │ ├── Makefile │ ├── loop.c │ ├── net.c │ ├── netdev.c │ ├── pkb.c │ ├── tap.c │ └── veth.c ├── shell │ ├── Makefile │ ├── main.c │ ├── net_command.c │ ├── ping_command.c │ └── shell.c ├── socket │ ├── Makefile │ ├── inet.c │ ├── raw_sock.c │ ├── sock.c │ └── socket.c ├── tcp │ ├── Makefile │ ├── tcp_in.c │ ├── tcp_out.c │ ├── tcp_reass.c │ ├── tcp_sock.c │ ├── tcp_state.c │ ├── tcp_text.c │ └── tcp_timer.c └── udp │ ├── Makefile │ ├── udp.c │ └── udp_sock.c ├── test ├── buffer_test.cpp ├── count_down_test.cpp ├── gtest_main.cpp ├── inet_addr_test.cpp └── url_test.cpp └── tools ├── README.md ├── cmd ├── echo.go ├── ethfilter.go ├── keepalive.go └── root.go ├── docs ├── echo.md └── ethfilter.md ├── go.mod ├── go.sum ├── main.go └── makefile /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp # this format style is targeted at. Cpp for c, c++, oc, oc++. 3 | BasedOnStyle: Google 4 | UseTab: Never 5 | IndentWidth: 4 6 | TabWidth: 4 7 | AllowShortIfStatementsOnASingleLine: false 8 | AllowShortLambdasOnASingleLine: None 9 | IndentCaseLabels: true 10 | ColumnLimit: 0 11 | AccessModifierOffset: -3 12 | FixNamespaceComments: true 13 | SpaceBeforeInheritanceColon: true 14 | BreakInheritanceList: AfterColon -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .clangd 2 | build 3 | tools/bin 4 | .vscode 5 | *.o 6 | easyip -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Holo_wo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eNET 2 | 3 | ## 1.序言 4 | 5 | 我认为网络编程的能力对于一名优秀的后端开发来说是必要的。后台开发,无非就是server串server,服务之间定义一些业务协议,然后通过tcp传输,这种模式能覆盖绝大部分的产品,因此有必要深入学习一把网络系统: 6 | - EasyNet的目标主要是用于学习网络编程中的各自细节,理解tcp是流式协议这一个概念,学会如何处理七层协议的组包拆包等 7 | - tapip的目标主要是深入理解网络协议栈的实现细节 8 | 9 | ## 2.支持平台 10 | 11 | - linux 12 | 13 | ## 3.依赖 14 | 15 | - gcc>=4.8.1(支持c++11) 16 | - git 17 | - cmake 18 | - geogletest(可选 tag:release-1.12.1 该版本是最后一个支持的c++11版本) 19 | - spdlog(必须):如果使用gcc4.8.1编译失败,见[issues-3050](https://github.com/gabime/spdlog/issues/3050) 20 | 21 | ## 4.编译运行 22 | 23 | ### 4.1 编译依赖 24 | 25 | ps:假定cmake、gtest、spdlog都安装在/opt/xxx目录下 26 | 27 | spdlog 28 | ``` 29 | git clone https://github.com/gabime/spdlog.git 30 | cd spdlog 31 | cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/opt/spdlog 32 | cmake --build build --target install 33 | ``` 34 | 35 | gtest 36 | ``` 37 | git clone https://github.com/google/googletest.git 38 | cd googletest 39 | git checkout release-1.12.1 40 | cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/opt/gtest 41 | cmake --build build --target install 42 | ``` 43 | 44 | ### 4.2 编译eNet 45 | ``` 46 | git clone https://github.com/HLhuanglang/eNET.git 47 | cmake -S . -B build -D{Options}=ON 48 | cmake --build build 49 | ``` 50 | 51 | 编译选项Option 52 | 53 | | 选项 | 描述 | 默认值 | 54 | | ---------------------- | -------------------------- | ------ | 55 | | WITH_DNS | 是否支持dns协议 | OFF | 56 | | WITH_HTTP | 是否支持http协议 | OFF | 57 | | BUILD_EXAMPLES | 是否编译使用案例 | OFF | 58 | | BUILD_RAWEXAMPLES | 是否编译原始socket使用案例 | OFF | 59 | | BUILD_UNITTEST | 是否编译单元测试 | OFF | 60 | 61 | 62 | 63 | ## 5.目录说明 64 | 65 | | 目录 | 作用 | 66 | | ------------ | -------------------------------------------------- | 67 | | cmake | cmake安装模板文件| 68 | | docs | 开发文档 | 69 | | tapip | 基于tun/tap设备实现简易的tcp/ip协议栈 | 70 | | easy_net | 网络库底层,目前主要支持tcp协议,udp暂未考虑 | 71 | | examples | 使用easynet编写的demo | 72 | | raw_examples | socket系列接口的原生使用方法、优秀的网络库使用案例 | 73 | | test | 测试代码 | 74 | | tools | 工具 | 75 | 76 | ## 6.参考项目/文章/论文 77 | 78 | - [Easy-Reactor](https://github.com/LeechanX/Easy-Reactor) 79 | - [muduo](https://github.com/chenshuo/muduo) 80 | - [sim_muduo](https://gitee.com/coolbaul/sim_muduo) 81 | - [trantor](https://github.com/an-tao/trantor) 82 | - [ZlToolKit](https://github.com/ZLMediaKit/ZLToolKit) 83 | - [tapip](https://github.com/chobits/tapip) 84 | - [level-ip](https://github.com/saminiir/level-ip) 85 | 86 | ## 7.授权许可 87 | 本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](LICENSE) 文件中。 88 | -------------------------------------------------------------------------------- /cmake/easynetConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include(CMakeFindDependencyMacro) 4 | 5 | if(@spdlog_FOUND@) 6 | find_dependency(spdlog) 7 | endif() 8 | 9 | set(config_targets_file @config_targets_file@) 10 | include("${CMAKE_CURRENT_LIST_DIR}/${config_targets_file}") 11 | -------------------------------------------------------------------------------- /docs/md/README.md: -------------------------------------------------------------------------------- 1 | # generic 2 | 3 | 网络编程中的通用知识概念 4 | 5 | - [同步与异步-阻塞与非阻塞](./generic/同步与异步-阻塞与非阻塞.md) 6 | - [网络模型设计讨论](./generic/网络模型设计讨论.md) 7 | 8 | # easynet 9 | 10 | easynet网络库的文档 11 | 12 | - [roadmap](./easynet/roadmap.md) 13 | - [网络模型](./easynet/easy_net网络模型.md) 14 | - [构建系统](./easynet/easy_net构建系统.md) 15 | - [日志模块](./easynet/easy_net日志模块.md) -------------------------------------------------------------------------------- /docs/md/easynet/easy_net-业务接口.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | 目前easynet的tcpsvr提供了如下接口: 4 | - set_new_connection_cb 5 | - set_recv_msg_cb 6 | - set_del_connection_cb 7 | - set_write_complete_cb 8 | 9 | 上层调用者一般来说主要是关注set_recv_msg_cb这个函数,所有的数据首发都在这个入口处进行操作。 10 | 11 | 由于easynet内部是多线程模式,会存在多个线程同时回调recv_msg,那么就需要业务方来确保自己的接口是线程安全的。 -------------------------------------------------------------------------------- /docs/md/easynet/easy_net-日志模块.md: -------------------------------------------------------------------------------- 1 | # 日志模块 2 | 3 | ## 功能需求 4 | 5 | 1. 日志消息有多个级别(log level) 6 | 2. 日志消息可能有多个目的地(appender),例如文件、终端等 7 | 3. 日志消息格式可配置(layout) 8 | 4. 日志可以设置运行时过滤器(filter),控制不同组件的日志消息级别和目的地 9 | 10 | ## 性能需求 11 | 12 | 1. 每秒写几千上万条日志的时候没有性能损耗 13 | 2. 能应对一个进程产生大量日志数据的场景 14 | 3. 不阻塞正常的执行流程(目前这个会阻塞在fwrite上) 15 | 4. 多线程中,不造成争用 16 | 17 | 假设日志库1秒能写100w条数据,如果当前的环境只需要10w/s的写入量,那么1s内日志占用10%的性能,剩余90%可以用来做业务。 18 | 19 | 假设日志库1秒能写10w条数据,如果当前的环境需要10w/s的写入量,那么这个机器就只能用来写日志了,干不了业务,日志库就严重的拖慢了性能,可能只有把日志写入量降到1w/s后,使用这个日志库才不会造成性能影响。 20 | 21 | ## 日志库设计 22 | 23 | 日志是一个典型的生产者-消费者模型,可以采用“异步”操作的思路,日志的生产者只需要把生产的日志丢到buffer中,由另外的线程来负责消费(写入磁盘),这样写日志时就无需阻塞在io操作上了。 24 | 25 | -------------------------------------------------------------------------------- /docs/md/easynet/easy_net-网络模型.md: -------------------------------------------------------------------------------- 1 | # easy_net网络模型 2 | 3 | 网络模型分析见[服务端架构设计](./../generic/网络模型设计讨论.md),这份分析主要是针对tcp协议的,对于udp协议见[udp处理模型](./../generic/关于UDP处理模型.md) 4 | 5 | ## TCP协议 6 | 7 | 采用multiReactors模型,一个服务有N个tcpsvr,每个tcpsvr都有一个acceptor和eventloop,使用reuseport监听同一个ip和port,由操作系统决定唤醒某一个阻塞在acceptor的进程上(避免惊群问题)。 8 | 9 | 目前easynet实现上,每一个tcpsvr都是一个reactor,但是不能使用其他空闲的eventloop,只有框架的上层使用者能够使用,因为所有的线程都是回调同一个接口(onNewConnection、onRecvMsg、...),可以把计算任务丢给不同的eventloop. 10 | 11 | ## UDP协议 12 | 13 | -------------------------------------------------------------------------------- /docs/md/easynet/roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | ## v1.0.0 4 | - tcp协议 5 | - tcpserver 6 | - tcpclient 7 | - udp协议 8 | - udpserver 9 | - udpclient 10 | 11 | ## v2.0.0 12 | - 支持http/https的服务端和客户端 13 | - dns 14 | - ssl/tls 15 | 16 | ## v3.0.0 17 | - 支持跨平台 18 | - epoll-linux 19 | - kqueue-OSX 20 | - iocp-windows -------------------------------------------------------------------------------- /docs/md/generic/FAQ.md: -------------------------------------------------------------------------------- 1 | # 网络编程FAQ 2 | 3 | ## 1. 为什么accept需要重新返回一个fd? 4 | 首先将套接字分个类: 5 | 6 | - 监听套接字:int listenfd = socket() 7 | - 连接套接字:int acceptfd = accept() 8 | 9 | 三次握手listenfd,相当于修建一座桥,而acceptfd是每一个连接,相当于运输的车辆。不可能每次通信都新建一座桥吧?这个设计,我认为是单一职责的一个表现。监听套接字,就负责处理处理连接事件,而连接套接字负责数据传输。因为有不同的运输需要,所以每一个连接都有一个单独的acceptfd。 10 | 11 | ## 2. 如何处理tcp数据流(粘包问题)? 12 | 首先明白一点,tcp的数据是没有边界的,tcp协议中也没有字段限制包的大小(需要注意MSS)。tcp依赖ip协议进行传输,而ip协议提供了分片重组的功能,建立了tcp连接就像搭起了一根水管,所以对端可以一直往水管里面灌水。 13 | 14 | 问题在于接收端怎么知道哪一片数据是一起的?所以需要在应用层自己做数据分块处理。 15 | 16 | ## 3. http协议粘包问题? 17 | 都知道http协议是基于tcp协议实现的,也就是说http协议是一个应用层协议。这个问题可以这样描述: 18 | 19 | "HTTP解析器一次读socket操作获得的数据可能并不直接对应一个完整的HTTP message”,那么应该说这个问题是必然会存在的。 20 | 21 | 在面向stream的协议基础上实现一个面向message的协议,那么一般来说应用层和底层之间必然存在一个缓冲区和定时器。于是解析的过程就是,从socket中读取一次数据放入缓冲区,并检查目前buffer中内容是否是一个完整的message,如果是,提交给上层并修改队列起始位置,如果不是,不提交数据给上层。 22 | 23 | 这里存在两个问题,一个是缓冲区必然要设定一个最大上限,另一方面是一般要设置定时器,一段时间内某个连接没有传输足够数据就断开连接并清除buffer,否则很容易被恶意请求占用过多内存而影响整体稳定性。 24 | 25 | 具体到HTTP协议,HTTP 1.0无keep alive或HTTP 0.9时,一个TCP连接只能传输一个HTTP message,所以一次性读到对端关闭写即可;有keep alive的情况下,一个TCP连接可能顺序传过来多个请求,就需要利用content-length等字段,于是文本协议解析部分,从解析整个报文变为解析HTTP头,并根据HTTP头中相关字段选择后续解析策略了,也就是上面提到的缓冲区和定时器的问题。 26 | 27 | 至于具体的流解析实现,可以采用有状态实现,即保存一个状态,记录当前扫描到何处,当前状态如何,之后再有新的数据传过来,就不必再把前面扫描过的数据再扫描一遍了;也可以采用无状态的实现,即不记录扫描到何处,不记录当前状态,某次解析后发现不足一个message,就丢弃当前状态,待下次读取数据进队列尾部,再重头开始解析。(对于每个message比较小的场景,无状态版更快,事实上NGINX也是如此实现的) 28 | 29 | 关于buffer的底层实现,最简单的版本无疑是算法与数据结构入门课上的单数组实现;更详细的优化版往往是元素为数组的链表结构,不过具体的性能表现需要经过非常多的测试之后设定参数与策略。关于HTTP body比较大的情况,一般需要对上层提供流读写的API,避免完全将超大报文体放在内存中。这样也可以传输超大文件,而不是 @寸光寸阴 所说的只能用chunked机制。 30 | 31 | 参考:https://www.zhihu.com/question/24598268/answer/625136503 32 | 33 | ## 4. 网络库本身是否需要进行拆包处理? 34 | 我目前的理解来说,网络库本身可以无需对数据进行粘包处理。 35 | 网络库本身需要维护一个buffer,把接收到的数据放到buffer里面,然后提供给上层协议去使用,上层协议在这个buffer中读取数据,自己做协议拆解。 36 | 37 | 如果解析数据发现格式错误,可以直接丢弃buffer中的数据(或是断开连接) 38 | 39 | 40 | ## 5. 发送数据大于socket缓冲区时,tcp会发送数据吗? 41 | 经过测试,通过write发送数据,对于数据的大小并没限制(write的参数len是size_t类型,受限于这个大小),一次发送的数据大于soket的发送缓冲区也没有问题,任然能够发送成功,并且返回发送数据的大小。(数据排队(可能分块,但是最后会完整的到达对端),等着被操作系统发送) 42 | 43 | 经过测试,read读取的时候,并不是一次性全部读取到数据,受到socket接收缓冲区大小以及用于接收数据的buf大小影响。一般情况下,我们使用epoll都是水平模式,也就是还有数据没读完就下一轮epoll_wait接着读(从上一次读取的位置接着读,前面已经读过的数据就无了.),直到read返回-1且errno=EAGAIN,此时就表示没数据了。 44 | 45 | 46 | 从不同的角度看,先看发送: 47 | - 对应用层来说,可能就是调用一下send_data就表示说我已经发送成功了。此时数据被缓存在网络库的buffer中 48 | - 对于网络库而言,将buffer中数据,通过write写入socket中就表示发送成功了。此时数据是存在socket的发送缓冲区的。 49 | - 可能出现说一次socket缓冲区满了,只发送了部分,此时write返回-1,-1 && errno == EAGAIN || errno == EWOULDBLOCK,那么再接着write就行了 50 | - 对于socket而言,什么时候把数据通过网卡发送出去是由操作系统来决定的... 51 | 52 | ## 6. 服务端超时处理? 53 | 54 | 假设客户端发送请求后,服务端与之建立了链接,但是由于此时并发比较高,且其他处理比较耗时,导致该请求还没被处理就超时了,此时客户端直接断开链接了,那么服务端是一个什么情况? 55 | 56 | ## 7. 主动断开连接? 57 | - 服务端:不主动断开,但是对每一个连接设置定时器,超过多少时间没有收发包则断开 58 | - 客户端:由上层应用决定是否断开,例如http协议可以选择keep-alive等 -------------------------------------------------------------------------------- /docs/md/generic/HTTP协议.md: -------------------------------------------------------------------------------- 1 | # http协议 2 | 3 | 参考:https://zhuanlan.zhihu.com/p/642759340 4 | 5 | https=http+tls+tcp 6 | 7 | httpclient:需要使用dns协议来查询域名对应的ip,端口都是80 -------------------------------------------------------------------------------- /docs/md/generic/IP转换.md: -------------------------------------------------------------------------------- 1 | # IP格式转换 2 | 3 | ## 系列一 4 | 5 | ### 头文件 6 | 7 | ```c 8 | #include 9 | #include 10 | #include 11 | ``` 12 | 13 | ### 结构体 14 | 15 | ```c 16 | typedef uint32_t in_addr_t; 17 | 18 | struct in_addr { 19 | in_addr_t s_addr; 20 | }; 21 | ``` 22 | 23 | ### 函数 24 | 25 | ┌───────────────────────────────┬───────────────┬────────────────┐ 26 | │Interface │ Attribute │ Value │ 27 | ├───────────────────────────────┼───────────────┼────────────────┤ 28 | │inet_aton(), inet_addr(), │ Thread safety │ MT-Safe locale │ 29 | │inet_network(), inet_ntoa() │ │ │ 30 | ├───────────────────────────────┼───────────────┼────────────────┤ 31 | │inet_makeaddr(), inet_lnaof(), │ Thread safety │ MT-Safe │ 32 | │inet_netof() │ │ │ 33 | └───────────────────────────────┴───────────────┴────────────────┘ 34 | 35 | ```c 36 | #define _BSD_SOURCE 37 | #include 38 | #include 39 | #include 40 | 41 | int 42 | main(int argc, char *argv[]) 43 | { 44 | struct in_addr addr; 45 | 46 | if (argc != 2) { 47 | fprintf(stderr, "%s \n", argv[0]); 48 | exit(EXIT_FAILURE); 49 | } 50 | 51 | if (inet_aton(argv[1], &addr) == 0) { 52 | fprintf(stderr, "Invalid address\n"); 53 | exit(EXIT_FAILURE); 54 | } 55 | 56 | printf("%d\n", addr.s_addr); 57 | exit(EXIT_SUCCESS); 58 | } 59 | 60 | //运行 ./main 127.0.0.1 61 | //输出 16777343 62 | ``` 63 | 64 | ## 系列二 65 | 66 | - inet_ntop: 67 | - inet_pton: 68 | 69 | ## 系列三 70 | 71 | - inet_net_pton 72 | - inet_net_ntop 73 | 74 | -------------------------------------------------------------------------------- /docs/md/generic/SSL加密.md: -------------------------------------------------------------------------------- 1 | # SSL/TLS加密 2 | 3 | ## 1. 什么是SSL/TLS 4 | 5 | SSL全称是Secure Sockets Layer,安全套接字层,它是由网景公司(Netscape)设计的主要用于Web的安全传输协议,目的是为网络通信提供机密性、认证性及数据完整性保障。如今,SSL已经成为互联网保密通信的工业标准。 6 | 7 | SSL最初的几个版本(SSL 1.0、SSL2.0、SSL 3.0)由网景公司设计和维护,从3.1版本开始,SSL协议由因特网工程任务小组(IETF)正式接管,并更名为TLS(Transport Layer Security),发展至今已有TLS 1.0、TLS1.1、TLS1.2这几个版本。 8 | 9 | SSL/TLS协议能够提供的安全目标主要包括如下几个: 10 | - 认证性——借助数字证书认证服务器端和客户端身份,防止身份伪造 11 | - 机密性——借助加密防止第三方窃听 12 | - 完整性——借助消息认证码(MAC)保障数据完整性,防止消息篡改 13 | - 重放保护——通过使用隐式序列号防止重放攻击 14 | 15 | 为了实现这些安全目标,SSL/TLS协议被设计为一个两阶段协议,分为握手阶段和应用阶段: 16 | - 握手阶段也称协商阶段,在这一阶段,客户端和服务器端会认证对方身份(依赖于PKI体系,利用数字证书进行身份认证),并协商通信中使用的安全参数、密码套件以及MasterSecret。后续通信使用的所有密钥都是通过MasterSecret生成。 17 | - 在握手阶段完成后,进入应用阶段。在应用阶段通信双方使用握手阶段协商好的密钥进行安全通信。 18 | 19 | SSL/TLS协议有一个高度模块化的架构,分为很多子协议,如下图所示: 20 | ![](https://hl1998-1255562705.cos.ap-shanghai.myqcloud.com/Img/20240608165727.png) 21 | - Handshake协议:包括协商安全参数和密码套件、服务器身份认证(客户端身份认证可选)、密钥交换; 22 | - ChangeCipherSpec 协议:一条消息表明握手协议已经完成; 23 | - Alert 协议:对握手协议中一些异常的错误提醒,分为fatal和warning两个级别,fatal类型的错误会直接中断SSL链接,而warning级别的错误SSL链接仍可继续,只是会给出错误警告; 24 | - Record 协议:包括对消息的分段、压缩、消息认证和完整性保护、加密等。 25 | 26 | ## 2. 协议流程 27 | 28 | ## 3. SSL与TLS区别 29 | 30 | ## 4. 常用的库 -------------------------------------------------------------------------------- /docs/md/generic/curl工具使用.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HLhuanglang/eNET/4cf85828a467273b1500615a4ddf0de931e3fdce/docs/md/generic/curl工具使用.md -------------------------------------------------------------------------------- /docs/md/generic/dns.md: -------------------------------------------------------------------------------- 1 | # dns协议 2 | 3 | ## 1. 概述 4 | 5 | ### 1.1 dns协议作用 6 | 7 | 用于处理ip地址和域名之间的转换 8 | 9 | ``` 10 | Local Host | Foreign 11 | | 12 | +---------+ +----------+ | +--------+ 13 | | | user queries | |queries | | | 14 | | User |-------------->| |---------|->|Foreign | 15 | | Program | | Resolver | | | Name | 16 | | |<--------------| |<--------|--| Server | 17 | | | user responses| |responses| | | 18 | +---------+ +----------+ | +--------+ 19 | | A | 20 | cache additions | | references | 21 | V | | 22 | +----------+ | 23 | | cache | | 24 | +----------+ | 25 | 26 | ``` 27 | 28 | ### 1.2 dns协议标准 29 | 30 | - [RFC1034](https://www.rfc-editor.org/rfc/rfc1034.html) 31 | - [RFC1035](https://www.rfc-editor.org/rfc/rfc1035.html) 32 | 33 | ## 2. 如何使用 34 | 35 | ### 2.1 POSIX接口 36 | 37 | `getaddrinfo` 和 `getnameinfo` 是 Linux 下用于处理域名和 IP 地址之间转换的两个函数,它们分别属于 `netdb.h` 头文件 38 | 39 | #### 2.1.1 getaddrinfo 40 | 41 | `getaddrinfo`:此函数用于将主机名和服务名转换为一个或多个 `sockaddr` 结构。给定一个主机名和一个服务名(例如端口号),`getaddrinfo` 会返回一个指向 `addrinfo` 结构的链表,其中包含了与给定主机名和服务名匹配的所有 IP 地址和端口号。这个函数通常用于客户端程序,以便在连接到服务器时获取服务器的 IP 地址和端口号。具体细节可以参考:[what-does-getaddrinfo-do](https://jameshfisher.com/2018/02/03/what-does-getaddrinfo-do/) 42 | 43 | 函数原型: 44 | 45 | ```c 46 | int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); 47 | ``` 48 | 49 | 参数说明: 50 | 51 | - `node`:主机名或 IP 地址的字符串。 52 | - `service`:服务名或端口号的字符串。 53 | - `hints`:一个指向 `addrinfo` 结构的指针,用于提供关于如何进行名称解析的附加信息。 54 | - `res`:一个指向 `addrinfo` 结构链表的指针,用于存储解析结果。 55 | 56 | #### 2.1.2 getnameinfo 57 | 58 | `getnameinfo`:此函数用于将 `sockaddr` 结构转换为主机名和服务名。给定一个 `sockaddr` 结构,`getnameinfo` 会返回与该结构匹配的主机名和服务名。这个函数通常用于服务器程序,以便在接受客户端连接时获取客户端的主机名和服务名。 59 | 60 | 函数原型: 61 | 62 | ```c 63 | int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags); 64 | ``` 65 | 66 | 参数说明: 67 | 68 | - `addr`:一个指向 `sockaddr` 结构的指针。 69 | - `addrlen`:`sockaddr` 结构的长度。 70 | - `host`:一个字符数组,用于存储解析后的主机名。 71 | - `hostlen`:`host` 数组的长度。 72 | - `serv`:一个字符数组,用于存储解析后的服务名。 73 | - `servlen`:`serv` 数组的长度。 74 | - `flags`:一个整数,用于指定解析过程中的行为。 75 | 76 | ### 2.2 c-ares库 -------------------------------------------------------------------------------- /docs/md/generic/gcc内置原子操作.md: -------------------------------------------------------------------------------- 1 | # gcc内置原子操作 2 | 3 | ## __sync系列函数 4 | 5 | [__sync系列函数官方文档](https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html) 6 | 7 | 从GCC4.1.版本之后就引入了内置的原子操作函数,可对x86_64架构(除此之外还有其他类型)1、2、4、8字节的integer scalar或pointer使用,可有效减少对锁机制的使用进一步而提升效率,这些函数以__sync开头,而在GCC4.7之后的版本,这些函数被替换成了以__atomic开头的一系列函数。 8 | 9 | 将__sync_系列17个函数声明整理简化如下: 10 | 11 | ```cpp 12 | type __sync_fetch_and_add (type *ptr, type value, ...) 13 | // 将value加到*ptr上,结果更新到*ptr,并返回操作之前*ptr的值 14 | 15 | type __sync_fetch_and_sub (type *ptr, type value, ...) 16 | // 从*ptr减去value,结果更新到*ptr,并返回操作之前*ptr的值 17 | 18 | type __sync_fetch_and_or (type *ptr, type value, ...) 19 | // 将*ptr与value相或,结果更新到*ptr, 并返回操作之前*ptr的值 20 | 21 | type __sync_fetch_and_and (type *ptr, type value, ...) 22 | // 将*ptr与value相与,结果更新到*ptr,并返回操作之前*ptr的值 23 | 24 | type __sync_fetch_and_xor (type *ptr, type value, ...) 25 | // 将*ptr与value异或,结果更新到*ptr,并返回操作之前*ptr的值 26 | 27 | type __sync_fetch_and_nand (type *ptr, type value, ...) 28 | // 将*ptr取反后,与value相与,结果更新到*ptr,并返回操作之前*ptr的值 29 | 30 | type __sync_add_and_fetch (type *ptr, type value, ...) 31 | // 将value加到*ptr上,结果更新到*ptr,并返回操作之后新*ptr的值 32 | 33 | type __sync_sub_and_fetch (type *ptr, type value, ...) 34 | // 从*ptr减去value,结果更新到*ptr,并返回操作之后新*ptr的值 35 | 36 | type __sync_or_and_fetch (type *ptr, type value, ...) 37 | // 将*ptr与value相或, 结果更新到*ptr,并返回操作之后新*ptr的值 38 | 39 | type __sync_and_and_fetch (type *ptr, type value, ...) 40 | // 将*ptr与value相与,结果更新到*ptr,并返回操作之后新*ptr的值 41 | 42 | type __sync_xor_and_fetch (type *ptr, type value, ...) 43 | // 将*ptr与value异或,结果更新到*ptr,并返回操作之后新*ptr的值 44 | 45 | type __sync_nand_and_fetch (type *ptr, type value, ...) 46 | // 将*ptr取反后,与value相与,结果更新到*ptr,并返回操作之后新*ptr的值 47 | 48 | bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...) 49 | // 比较*ptr与oldval的值,如果两者相等,则将newval更新到*ptr并返回true 50 | 51 | type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...) 52 | // 比较*ptr与oldval的值,如果两者相等,则将newval更新到*ptr并返回操作之前*ptr的值 53 | 54 | __sync_synchronize (...) 55 | // 发出完整内存栅栏 56 | 57 | type __sync_lock_test_and_set (type *ptr, type value, ...) 58 | // 将value写入*ptr,对*ptr加锁,并返回操作之前*ptr的值。即,try spinlock语义 59 | 60 | void __sync_lock_release (type *ptr, ...) 61 | // 将0写入到*ptr,并对*ptr解锁。即,unlock spinlock语义 62 | ``` 63 | 64 | ## __atomic系列函数 65 | 66 | [__atomic系列函数官方文档](https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html) -------------------------------------------------------------------------------- /docs/md/generic/http客户端.md: -------------------------------------------------------------------------------- 1 | # http客户端库 2 | 3 | 关于c++的常用的http客户端,这里做一个列举: 4 | - cpp-httplib:阻塞IO 5 | - cpr:基于libcurl封装 6 | 7 | 1,简单的同步请求:connect-->send-->recv-->parse-->logic 8 | 2,封装到框架之中,提供异步接口 9 | - 全局只有一个http_client实例 10 | - auto req = new Request,然后cli->SendRequest(req, callback) 11 | - 当服务器回包后,触发框架onMsg回调,然后先走http协议栈解析,再回调callback -------------------------------------------------------------------------------- /docs/md/generic/makefile变量.md: -------------------------------------------------------------------------------- 1 | # makefile-变量 2 | 3 | make的变量可以分为`环境变量`,`内置变量`,`自动变量` 4 | 5 | ## 2.1环境变量 6 | 7 | - `HOME`:/home/hl 8 | - `LANG`:zh_CN.UTF-8 9 | - ... 10 | 11 | makefile能访问到当前终端下的所有环境变量(通过env可以查看) 12 | 13 | ## 2.2内置变量 14 | 15 | ``` 16 | CURDIR := /home/hl # 记录当前路径 17 | SHELL = /bin/sh 18 | MAKEFILE_LIST := Makefile 19 | .DEFAULT_GOAL := all 20 | MAKEFLAGS = p 21 | HOSTARCH := x86_64 22 | RM = rm -f # 删除文件程序的名称 23 | 24 | CC = cc # C语言编译器的名称 25 | CPP = $(CC) -E # C语言预处理器的名称 $(CC) -E 26 | CFLAGS # C语言编译器的编译选项,无默认值 27 | CPPFLAGS # C语言预处理器的编译选项,无默认值 28 | 29 | CXX = g++ # C++语言的编译器名称 30 | CXXFLAGS # C++语言编译器的编译选项,无默认值 31 | ``` 32 | 内置变量可以在makefile中被重新定义赋值 33 | 34 | ## 2.3自动变量 35 | 36 | - `$*`:不包含拓展名的目标文件名称 37 | - `$+`:所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件 38 | - `$<`:第一个依赖文件的名称 39 | - `$?`:所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚 40 | - `$@`:目标的完整名称(目标:依赖项中的目标的全称) 41 | - `$^`:所有的依赖文件,以空格分开,不包含重复的依赖文件 42 | - `$%`:如果目标是归档成员,则该变量表示目标的归档成员名称 43 | - `$(Q)`:如果Q是@的话,其实就是忽略很多编译信息,和kbuild有点关系 -------------------------------------------------------------------------------- /docs/md/generic/muduo学习.md: -------------------------------------------------------------------------------- 1 | # muduo学习 2 | 3 | ## tcp编程本质论 4 | 看完之后感觉确实通透了很多,解答了一些编写easy_net过程中的问题。先来学习一下这个观点: 5 | 6 | 网络编程的本质是处理三件半的事情: 7 | - 连接建立:服务器被动接收连接,客户端主动发起连接,当连接建立完成后,双方就是平等的,可以互相收发消息了。 8 | - 连接断开:主动断开(close、shutdown)和被动断开(read返回0) 9 | - 消息到达:文件描述符可读 10 | - 消息发送完毕:这算半个。对于低流量的服务,可不必关心这个事件;另外,这里的“发送完毕”是指数据写入操作系统缓冲区(内核缓冲区),将由TCP协议栈负责数据的发送与重传,不代表对方已经接收到数据。 11 | 12 | ### 消息到达事件 13 | socket数据到来时,内核从网卡上先读取到数据(放到socket收发缓冲区里面),网络库事件循环中的可读事件被触发,将数据从内核缓冲区移动到应用缓冲区中,并且网络库会回调一个OnMessage函数表示有消息到达。 14 | 15 | 由于TCP接收到的可能是半包(即数据不完整),OnMessage要根据协议来判断这个数据包是否完整。 16 | 17 | 而如果TCP接收到的数据包是完整的,就直接将这个数据包取出来进行处理(read->decode->compute->encode->write);而如果TCP接收到的数据包不完整,则OnMessage立即返回,这样内核再接收到一些数据时,网络库事件循环中的可读事件又会被触发...直到OnMessage发现数据包已经完整了,才它取出来进行处理(read->decode->compute->encode->write)。 18 | 19 | ### 消息发送完成事件 20 | 假设现在应用层要发送数据,如果内核发送缓冲区足够大,则将要发送的数据全部填入内核缓冲区中,并触发一个发送完成事件,并且网络库会回调一个OnWriteComplete函数表示消息发送完毕; 21 | 22 | 而如果内核发送缓冲区不足以容纳这么多的数据,则只将其中一部分数据填入内核缓冲区,剩余部分追加到应用层的发送缓冲区中,等内核发送缓冲区将数据发送出去后,会触发一个可写事件,在这个事件中就会将应用层发送缓冲区中的数据填充到内核发送缓冲区中,这时如果内核发送缓冲区不足以容纳应用层发送缓冲区中的全部数据,则只将其中一部分数据填入内核缓冲区...就这样直到应用层发送缓冲区中的数据全部填充到内核缓冲区,网络库事件循环中的发送完成事件会被触发,并回调OnWriteComplete表示消息发送完毕。 23 | 24 | 这个事件我的理解就是为了解决接收方处理消息过慢,导致发送方内存暴涨的问题。发送方不能一直无脑发送,得做一点流量控制,可以在设置某个标记,在OnWriteComplete回调中修改这个标记表示发送消息完成了,允许发送下一个消息,这样就不会导致发送方自己消息积压,也可以避免丢包。 25 | 26 | 注意:应用层一定要在应用层发送缓冲区中的数据全部填入内核缓冲区并回调OnWriteComplete表示消息发送完毕后才能继续发送数据,这样做可以避免数据丢包。 27 | 28 | ## muduo结构 29 | 30 | ![](https://hl1998-1255562705.cos.ap-shanghai.myqcloud.com/Img/1384555-20181208140242131-1011363286.png) 31 | 32 | 如果只关注服务端的话,tcp_client可以去掉。 33 | 34 | ## runInLoop方法 35 | 36 | 任何一个线程创建并运行了一个EventLoop,该线程就是IO线程。 37 | 38 | 假设在主线程中调用EventLoopThread创建了一个子线程,子线程内部创建了EventLoop对象并返回对象的指针,此时在主线程就能通过指针调用EventLoop的方法了。 39 | 40 | 如果主线程调用loop->runInLoop()方法,那么runInLoop方法就会判断到调用者并非loop对象的实际运行线程,因此就会把cb塞到队列中,并且通过eventfd唤醒实际所属的线程来执行cb。 -------------------------------------------------------------------------------- /docs/md/generic/nc工具使用.md: -------------------------------------------------------------------------------- 1 | # nc工具使用 2 | 3 | ## nc工具的作用 4 | - 作为server,实现任任意tcp/udp端口的监听 5 | - 作为client,发起tcp/udp连接。 6 | 7 | 需要注意的是,建立连接后,可以输入消息发送给对方。 8 | 9 | ## 常用参数说明 10 | - -l:用于指定nc处于监听模式,表示当前充电一个tcp服务器 11 | - -p:指定监听的端口 12 | - -s:指定源地址(用于区分多网卡) 13 | - -z:用于扫描,不发送任何数据 14 | - -u:使用udp模式,默认为tcp模式 15 | - -U:使用域套接字模式 16 | - -w:超时时间 17 | - -W:限制收包个数,超过就关闭 18 | - -v:输出调试信息 19 | 20 | ## 使用例子 21 | ```bash 22 | nc www.baidu.com 80 //使用随机端口 23 | nc -p 12345 www.baidu.com 80 //使用本机12345端口访问 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/md/generic/reactor和proactor模式.md: -------------------------------------------------------------------------------- 1 | # 高效的IO处理模式 2 | 3 | - 同步IO模型通常用于实现reactor 4 | - 异步IO模型通常用于proactor 5 | 6 | ## reactor模式 7 | 8 | ### 概念 9 | Reactor 模型有三个重要的组件: 10 | - 多路复用器:由操作系统提供,在 linux 上一般是 select, poll, epoll 等系统调用。 11 | - 事件分发器:将多路复用器中返回的就绪事件分到对应的处理函数中 12 | - 事件处理器:负责处理特定事件的处理函数 13 | 14 | 运行流程: 15 | ![](https://hl1998-1255562705.cos.ap-shanghai.myqcloud.com/Img/20240328150330.png) 16 | 17 | ### 实现 18 | 具体拆分有结构如下: 19 | - Handle句柄:用来标识socket连接或是打开文件; 20 | - Synchronous Event Demultiplexer: 多路复用器。 21 | - Event Handler: 事件处理接口 22 | - Concrete Event HandlerA: 事件处理器,实现应用程序所提供的特定事件处理逻辑; 23 | - Reactor:事件分发器 24 | - 提供接口,供应用程序注册和删除关注的事件句柄; 25 | - 有就绪事件到来时,分发事件到具体的事件处理器; 26 | - 运行事件循环; 27 | 28 | ![](https://hl1998-1255562705.cos.ap-shanghai.myqcloud.com/Img/20240328145523.png) 29 | 30 | ## proactor模式 31 | 32 | ![](https://hl1998-1255562705.cos.ap-shanghai.myqcloud.com/Img/20240328145557.png) 33 | 34 | ## 使用同步IO模拟proactor模式 35 | 36 | 本质上就是在线程中把数据读取完成,然后再通知上层。 -------------------------------------------------------------------------------- /docs/md/generic/socket传输类型.md: -------------------------------------------------------------------------------- 1 | # socket传输类型 2 | 3 | ## 文本类型 4 | 5 | ### http 6 | 7 | 8 | ## 二进制类型 9 | 10 | ### c/c++自定义结构体,类对象 11 | 12 | 使用结构或类对象需要收发双方都有定义,原理在于结构体或者对象在内存中都是一块连续的区域。 13 | 14 | ```cpp 15 | struct Student 16 | { 17 | int iId; 18 | string strName; 19 | bool bSex; //为了节省内存空间,性别采用一个字节的BOOL类型表示 20 | }; 21 | ``` 22 | 23 | 客户端 24 | ```cpp 25 | struct Student stu; //声明一个Student结构体变量 26 | stu.iId = 1001; 27 | stu.bSex = true; //true表示男性,false表示女性,你反过来也行,别打拳 28 | stu.strName = "abcdefzzzzz"; 29 | 30 | //下面的m_sclient是客户端(发送方)的Socket套接字 31 | 32 | //方法一:推荐如下 33 | send(m_sclient, (char*)&stu, sizeof(Student), 0);//&stu取stu地址,(char*)转化为char类型的指针 34 | 35 | //方法二:或者增加一个中间变量sendBuff[]来传送,如下 36 | //char sendBuff[1024]; 37 | //memset(sendBuff,0,sizeof(sendBuff)); 38 | //memcpy(sendBuff, &stu, sizeof(Student)); 39 | //send(m_sclient, sendBuff, sizeof(sendBuff), 0); 40 | ``` 41 | 42 | 服务端 43 | ```cpp 44 | struct Student stu; //声明一个结构体变量,用于接收客户端(发送方)发来的数据 45 | char buffFromClient[1024]; //用于临时接收发送方的数据 46 | //方法一:(推荐) 47 | recv(clientSocket, (char*)&stu, sizeof(Student), 0); 48 | 49 | //方法二: 50 | //memset(buffFromClient,0,sizeof(buffFromClient)); 51 | //recv(clientSocket, buffFromClient, sizeof(Student), 0); 52 | //memset(&stu,buffFromClent,sizeof(Student)); 53 | ``` 54 | 55 | ### pb 56 | pb提供了序列化的结构 57 | 58 | ```cpp 59 | class MessageLite { 60 | public: 61 | //序列化: 62 | bool SerializeToOstream(ostream* output) const; 63 | bool SerializeToArray(void *data, int size) const; //使用这个接口就能发送了 64 | bool SerializeToString(string* output) const; 65 | 66 | //反序列化: 67 | bool ParseFromIstream(istream* input); 68 | bool ParseFromArray(const void* data, int size); 69 | bool ParseFromString(const string& data); 70 | }; 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/md/generic/spdlog使用.md: -------------------------------------------------------------------------------- 1 | # spdlog使用 2 | 3 | 偷了一张图,然后结合看一遍wiki即可掌握:https://github.com/gabime/spdlog/wiki 4 | ![](https://hl1998-1255562705.cos.ap-shanghai.myqcloud.com/Img/20241127113621.png) -------------------------------------------------------------------------------- /docs/md/generic/tcp关闭连接.md: -------------------------------------------------------------------------------- 1 | # tcp连接断开的处理 2 | 3 | TCP关闭连接: 4 | - 正常关闭 5 | - 异常关闭 6 | - 连接未被监听的端口 7 | - 服务端程序崩溃 8 | - 主机断电 9 | - tcp连接队列已满 10 | - 半连接队列满了 11 | - 全连接队列满了 12 | - 开启SO_LINGER选项 13 | - 主动关闭方关闭连接时,接收缓冲区还有未处理的数据 14 | - 主动关闭方close关闭,但在FIN_WAIT2状态接收到数据 15 | - 半关闭 16 | - 主动发起TCP半关闭流程 17 | - 被动处理TCP半关闭流程 18 | 19 | 20 | ## 正常关闭(close) 21 | 22 | 23 | 24 | ## 异常关闭 25 | 26 | 27 | 28 | ## 半关闭(shutdown) 29 | 30 | 参考:https://stackoverflow.com/questions/52976152/tcp-when-is-epollhup-generated 31 | 32 | About shutdown: 33 | - Doing shutdown(SHUT_WR) sends a FIN and marks the socket with SEND_SHUTDOWN. 34 | - Doing shutdown(SHUT_RD) sends nothing and marks the socket with RCV_SHUTDOWN. 35 | - Receiving a FIN marks the socket with RCV_SHUTDOWN. 36 | 37 | And about epoll: 38 | - If the socket is marked with SEND_SHUTDOWN and RCV_SHUTDOWN, poll will return EPOLLHUP. 39 | - If the socket is marked with RCV_SHUTDOWN, poll will return EPOLLRDHUP. 40 | - So the HUP events can be read as: 41 | - EPOLLRDHUP: you have received FIN or you have called shutdown(SHUT_RD). In any case your reading half-socket is hung, that is, you will read no more data. 42 | - EPOLLHUP: you have both half-sockets hung. The reading half-socket is just like the previous point, For the sending half-socket you did something like shutdown(SHUT_WR). 43 | 44 | To complete a a graceful shutdown I would do: 45 | - Do shutdown(SHUT_WR) to send a FIN and mark the end of sending data. 46 | - Wait for the peer to do the same by polling until you get a EPOLLRDHUP. 47 | - Now you can close the socket with grace. 48 | 49 | ## FAQ 50 | Q:如何感知tcp是全部断开了还是半关闭状态 51 | A: -------------------------------------------------------------------------------- /docs/md/generic/tcp连接异常状态.md: -------------------------------------------------------------------------------- 1 | # TCP异常状态 2 | 3 | tcp协议标准:https://datatracker.ietf.org/doc/html/rfc793 4 | 5 | ## TCP报文类型 6 | 7 | tcp有如下报文: 8 | - SYN 9 | - FIN 10 | - ACK 11 | - PSH 12 | - RST 13 | 14 | ## -------------------------------------------------------------------------------- /docs/md/generic/tun和tap设备.md: -------------------------------------------------------------------------------- 1 | # tun/tap设备 2 | 3 | 虚拟网卡<--tun/tap驱动:包括网卡驱动和字符设备驱动 4 | 5 | ## 什么是tun/tap 6 | tun和tap是操作系统内核中的虚拟网络设备(虚拟网卡),用软件模拟实现,提供和硬件设备完全相同的功能 7 | - tap:等同于以太网设备,操作第二层数据包,例如以太网帧 8 | - tun:模拟网络层设备,操作第三层数据包,如ip数据报 9 | 10 | ## 如何使用tun/tap 11 | 12 | 使用tun/tap设备的示例程序(摘自openvpn开源项目 http://openvpn.sourceforge.net ,tun.c 文件) 13 | ```c 14 | int open_tun (const char *dev, char *actual, int size) 15 | { 16 | struct ifreq ifr; int fd; 17 | char *device = "/dev/net/tun"; 18 | if ((fd = open(device, O_RDWR)) < 0) // 创建描述符 19 | msg(M_ERR, "Cannot open TUN/TAP dev %s", device); 20 | 21 | memset(&ifr, 0, sizeof (ifr)); 22 | ifr.ifr_flags = IFF_NO_PI; 23 | if (!strncmp(dev, "tun", 3)) { 24 | ifr.ifr_flags |= IFF_TUN; 25 | } else if (!strncmp(dev, "tap", 3)) { 26 | ifr.ifr_flags |= IFF_TAP; 27 | } else { 28 | msg(M_FATAL, "I don't recognize device %s as a TUN or TAP device",dev); 29 | } 30 | if (strlen(dev) > 3) /* unit number specified? */ 31 | strncpy(ifr.ifr_name, dev, IFNAMSIZ); 32 | if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0) //打开虚拟网卡 33 | msg(M_ERR, "Cannot ioctl TUNSETIFF %s", dev); 34 | 35 | set_nonblock(fd); 36 | msg(M_INFO, "TUN/TAP device %s opened", ifr.ifr_name); 37 | strncpynt(actual, ifr.ifr_name, size); 38 | 39 | return fd; 40 | } 41 | ``` 42 | 通过ifconfig就能查看到虚拟网卡信息了,后续操作使用read/write对fd进行读写 43 | 44 | ## 原理 45 | 46 | ![](https://hl1998-1255562705.cos.ap-shanghai.myqcloud.com/Img/20250121172710.png) 47 | -------------------------------------------------------------------------------- /docs/md/generic/url与uri.md: -------------------------------------------------------------------------------- 1 | # uri 2 | 3 | https://datatracker.ietf.org/doc/html/rfc3986 -------------------------------------------------------------------------------- /docs/md/generic/主机字节序和网络字节序.md: -------------------------------------------------------------------------------- 1 | ## 主机字节序-网络字节序 2 | 3 | 字节序,顾名思义,指字节在内存中存储的顺序。比如一个int32_t类型的数值占用4个字节,这4个字节在内存中的排列顺序就是字节序。字节序有两种: 4 | 5 | - 小端字节序(Little endinan),数值低位存储在内存的低地址,高位存储在内存的高地址; 6 | - 大端字节序(Big endian),数值高位存储在内存的低地址,低位存储在内存的高地址。 7 | 8 | 9 | **主机字节序**,即CPU存储数据时采用的字节顺序。主机字节序可以采用little endian或者big endian的排序方式 10 | 11 | **网络字节序**,是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian排序方式。 12 | 13 | ![](https://hl1998-1255562705.cos.ap-shanghai.myqcloud.com/Img/20230619113253.png) 14 | 15 | 16 | ## 区别 17 | 18 | 网络字节序和主机序(主机字节序)不仅是大小端的区别,还涉及了字节对齐(对齐方式)的问题。 19 | 20 | 在内存中,数据类型的存储通常需要考虑对齐方式,以使得数据访问效率更高。不同的系统和编译器可能采用不同的对齐方式。例如,某个结构体中包含了一些成员,其中某些成员需要按照4字节对齐,某些成员需要按照8字节对齐。这样,结构体实际在内存中按照一定规则进行了填充。 21 | 22 | 但是,网络字节序中,数据是没有填充或对齐的,它会串行地将数据每个字节一个一个传输,这样便可以保证跨平台之间的兼容性。因此,在进行网络通信时,需要将数据转换为网络字节序,这也就意味着需要将数据按照字节一个一个传输,并与主机端进行大小端转换,再在接收端进行反向操作。以确保在不同平台间数据的传输的正确性。 23 | 24 | 25 | 26 | ## 互相转换 27 | 28 | Linux系统下使用如下API进行主机和网络字节序的转换 29 | 30 | ```c 31 | //The advantage of the byteorder(3) functions is that they are standard functions available on all UNIX systems 32 | //缺点是不支持64位 33 | #include 34 | uint32_t htonl(uint32_t hostlong);      //把uint32_t类型从主机序转换到网络序 35 | uint16_t htons(uint16_t hostshort);     //把uint16_t类型从主机序转换到网络序 36 | uint32_t ntohl(uint32_t netlong);       //把uint32_t类型从网络序转换到主机序    【当前机器是什么端,就返回什么端】 37 | uint16_t ntohs(uint16_t netshort);      //把uint16_t类型从网络序转换到主机序    【当前机器是什么端,就返回什么端】 38 | 39 | 40 | //支持64位,非标准的(依赖bswap实现的) 41 | #include 42 | uint16_t htobe16(uint16_t host_16bits); //把uint16_t类型从主机序转成大端 43 | uint16_t htole16(uint16_t host_16bits); //把uint16_t类型从主机序转成小端 44 | uint16_t be16toh(uint16_t big_endian_16bits); //把uint16_t类型从大端转成主机序 45 | uint16_t le16toh(uint16_t little_endian_16bits); //把uint16_t类型从小端转成主机序 46 | 47 | uint32_t htobe32(uint32_t host_32bits); 48 | uint32_t htole32(uint32_t host_32bits); 49 | uint32_t be32toh(uint32_t big_endian_32bits); 50 | uint32_t le32toh(uint32_t little_endian_32bits); 51 | 52 | uint64_t htobe64(uint64_t host_64bits); 53 | uint64_t htole64(uint64_t host_64bits); 54 | uint64_t be64toh(uint64_t big_endian_64bits); 55 | uint64_t le64toh(uint64_t little_endian_64bits); 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /docs/md/generic/关于UDP处理模型.md: -------------------------------------------------------------------------------- 1 | # udp服务处理模型 2 | 3 | ## 关于udp 4 | 5 | - 一个UDP socket没有服务器客户端之分,对于服务器来说,一个socket就可以接受所有的客户端数据 6 | - UDP的每个包必然包含了一个完整的包,否则会发送失败。 7 | - 使用四元组来标记通信的双方(单播的情况下) 8 | - udp相比于tcp,在互联网上传输时存在mtu限制的问题,也就是说一个逻辑数据包必须拆分成若干个小于mtu的udp包发送,会有一些问题: 9 | - udp包乱序以及丢包问题。 业界成熟方案可以考虑用quic、kcp、srt(udt)等 10 | - 存在应用层分包、组包、排序、重传等机制,并且无粘包问题,需要一个包一次read操作,不好批量收包 11 | 12 | ## api 13 | 14 | udp需要使用到的接口: 15 | - socket():创建套接字 16 | - bind():绑定套接字到ip+port 17 | - connect():⭐连接对端,不同于tcp,对于udp而言,connect是相当于在本地构建起了连接四元组. 18 | - sendto():发送数据 19 | - recvfrom():接受数据 20 | 21 | 对于服务器来说的一般流程: 22 | - fd = socket() 23 | - bind(fd,服务器本机ip,服务器本机port) 24 | - connect(fd,对端ip,对端port):【这一操作需要看框架模型】 25 | - sendto/recvfrom 26 | 27 | 对于客户端来说的一般流程: 28 | 客户端流程有多种,原因在于源ip+port有多种设置方式。可以自己指定也可以由操作系统决定。虽然使用方式很多,但是归根到底还是对四元组设置的管理。 29 | 30 | 1. 显示指定ip和端口: socket-->bind-->sendto 31 | - fd = socket() 32 | - bind(fd,客户端本机ip,客户端本机port) 33 | - sendto(fd, 服务器ip,服务器端口,data) 34 | 2. 由操作系统决定:socket-->sendto or socket-->connect-->write/sendto 35 | - fd = socket() 36 | - sendto(fd, 服务器ip,服务器端口,data) 37 | - connect(fd,服务器ip,服务器端口) 38 | - write(fd,data) or sendto(fd,null,null,data) 39 | 40 | ## 模型设计 41 | 42 | ### 单线程模式 43 | 44 | 单线程模式下,服务只能一个一个处理请求,由于是无连接的,所以流程大概简化成了如下 45 | ``` 46 | recvfrom->onRecvMsg->sendto->onWriteComplete 47 | ``` 48 | 49 | ### 多线程模式 50 | 51 | 问题: 52 | 1. 服务端如何区分不同的客户端请求 53 | 54 | 解法: 55 | 1. 模拟tcp。当serverfd(逻辑上)有请求时(此时可以拿到对端的ip+port),新建一个socketfd,该socketfd和serverfd绑定相同的地址和端口(SO_REUSEADDR),然后调用connect连接客户端,这样子确定唯一的四元组,后续数据收发都在这个socketfd上。 56 | - 客户端必须固定ip和port,不然四元组会失效。 57 | - serverfd可能会继续收到对端发过来的数据,因此需要额外的机制来确保当serverfd收到数据时,能将数据转到对应socetfd的poller线程去。 58 | 59 | 60 | 61 | 62 | ## 参考阅读 63 | - [深入地理解UDP协议并用好它](http://www.52im.net/thread-1024-1-1.html) 64 | - [如何让不可靠的UDP变的可靠?](http://www.52im.net/thread-1293-1-1.html) 65 | - [基于多线程的udp协议accept模型](https://andycong.top/2020/01/05/%E5%9F%BA%E4%BA%8E%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%9A%84udp%E5%8D%8F%E8%AE%AEaccept%E6%A8%A1%E5%9E%8B/) 66 | - [UDP协议疑难杂症全景解析](https://blog.csdn.net/dog250/article/details/6896949) 67 | - 告知你不为人知的 UDP:疑难杂症和使用 68 | - [上](https://cloud.tencent.com/developer/article/1004555) 69 | - [下](https://cloud.tencent.com/developer/article/1004554) -------------------------------------------------------------------------------- /docs/md/generic/关于回调.md: -------------------------------------------------------------------------------- 1 | # 回调函数 2 | 3 | 举例子说明: 4 | 5 | A---(注册回调)-->B 6 | 7 | A进程向B进程注册一个回调,当B执行回调时,这个回调函数是在B进程中执行的,不是A中执行的。注册回调相当于把A的方法传授给了B,只是告诉B进程怎么运行而已。 -------------------------------------------------------------------------------- /docs/md/generic/同步-异步http.md: -------------------------------------------------------------------------------- 1 | # 同步http请求: 2 | 3 | - 客户端调用发送请求接口,然后自己阻塞住在请求接口上 4 | - 服务器处理完后返回数据 5 | - 阻塞接口返回,客户端执行后续逻辑 6 | 7 | 一般为了避免服务器不返回造成客户端阻塞,请求都会设置一个超时时间,当时间到达后服务端还没返回,就认为这次请求失败了。 8 | 9 | 10 | 11 | ```c++ 12 | void test_http_sync_client(http_client* cli) { 13 | http_request req; 14 | req.method = HTTP_POST; 15 | req.url = "http://127.0.0.1:8080/echo"; 16 | req.headers["Connection"] = "keep-alive"; 17 | req.body = "This is a sync request."; 18 | req.timeout = 10; 19 | http_response rsp; 20 | int ret = cli->send(&req, &rsp); 21 | if (ret != 0) { 22 | printf("request failed!\n"); 23 | } else { 24 | printf("%d %s\r\n", resp.status_code, resp.status_message()); 25 | printf("%s\n", resp.body.c_str()); 26 | } 27 | } 28 | ``` 29 | 这种同步请求, 需要等待发送完成,然后再拿数据做后面的逻辑。http网络访问可能很耗时(tcp连接),在这段时间里,这个线程就会阻塞在这里,浪费线程资源。如果同时发起多个请求,就会有很多线程被创建浪费...,或是使用任务队列,队列可能暴涨。 30 | 31 | # 异步http请求 32 | 33 | 异步请求的核心在于,调用请求时传入一个回调。当服务端返回后,主要的逻辑在回调中进行处理。这种方式会创建线程,所以需要关注线程的生命周期。 34 | 35 | 要考虑一个问题:在回调中又执行了发送请求,套娃几层后,会导致回调链路太长了 36 | 37 | 一般来说业务逻辑上不应该这么写,最多在回调中套一个同步请求。 38 | 39 | ```cpp 40 | void test_http_async_client(Http_client* cli, int* resp_cnt) { 41 | http_request* req = new http_request(); 42 | req->method = HTTP_POST; 43 | req->url = "http://127.0.0.1:8080/echo"; 44 | req->headers["Connection"] = "keep-alive"; 45 | req->body = "This is an async request."; 46 | req->timeout = 10; 47 | cli->sendAsync(req, [resp_cnt](const HttpResponsePtr& resp) { 48 | if (resp == NULL) { 49 | printf("request failed!\n"); 50 | } else { 51 | printf("%d %s\r\n", resp->status_code, resp->status_message()); 52 | printf("%s\n", resp->body.c_str()); 53 | // 54 | //auto ret_json = json_parse(resp->body); 55 | // 然后根据返回的数据判断是否再请求其他的服务 56 | // if(ret_json["xxxx"] = xxxx) { 57 | // req2->url = "xxxx"; 58 | // cli->sendAsyc(req2,[resp_cnt](const HttpResponsePtr& resp) { 59 | // //再次根据返回的数据,判断后续的业务逻辑 60 | // }); 61 | // } 62 | // 63 | } 64 | *resp_cnt += 1; 65 | }); 66 | } 67 | ``` 68 | 69 | 70 | 71 | 参考文章 72 | 73 | 1,https://dongshao.blog.csdn.net/article/details/106357786 -------------------------------------------------------------------------------- /docs/md/generic/同步与异步-阻塞与非阻塞.md: -------------------------------------------------------------------------------- 1 | 2 | # 操作系统层面 3 | 4 | 程序最基本的能力是交互--->IO,针对IO功能,操作系统提供了接口 5 | 6 | ![](https://hl1998-1255562705.cos.ap-shanghai.myqcloud.com/Img/20230915102347.png) 7 | 8 | 9 | 可参考:[怎样理解阻塞非阻塞与同步异步的区别? - bin的技术小屋的回答 - 知乎](https://www.zhihu.com/question/19732473/answer/2400344486) 10 | 11 | 12 | # 框架层面 13 | 14 | - 同步:指一个执行序1在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个执行序将会一直等待下去,直到收到返回信息才继续执行下去; 15 | - 异步:指执行序不需要一直等下去,而是继续执行下面的操作,不管其他执行序的状态。当有消息返回时系统会通知指定执行序进行处理。这样在等待操作完成的过渡事件,系统可以有效利用cpu的资源。 16 | 17 | 从以上定义可以看出同步和异步之间的区别在于主动权的所属,基本上可以理解为: 18 | 19 | - 同步操作主动权在发起者,操作者需要检索指定条件是否满足,如果满足则继续执行,否则继续等待。 20 | - 异步操作主动权切换,操作者发起异步操作之后,主动权归还系统,当系统满足条件之后,系统会继续调用接下来的处理步骤。 21 | 22 | -------------------------------------------------------------------------------- /docs/md/generic/异步IO.md: -------------------------------------------------------------------------------- 1 | # 异步IO 2 | 3 | 4 | 参考阅读: 5 | 1. https://blog.libtorrent.org/2012/10/asynchronous-disk-io/#comments -------------------------------------------------------------------------------- /docs/md/generic/监听地址.md: -------------------------------------------------------------------------------- 1 | # 关于localhost、0.0.0.0、127.0.0.1地址说明 2 | 3 | see [stackoverflow](https://stackoverflow.com/questions/20778771/what-is-the-difference-between-0-0-0-0-127-0-0-1-and-localhost) 4 | 5 | ## 127.0.0.1 6 | 7 | 127.0.0.1 is normally the IP address assigned to the "loopback" or local-only interface. This is a "fake" network adapter that can only communicate within the same host. It's often used when you want a network-capable application to only serve clients on the same host. A process that is listening on 127.0.0.1 for connections will only receive local connections on that socket. 8 | 9 | ## localhost 10 | 11 | "localhost" is normally the hostname for the 127.0.0.1 IP address. It's usually set in /etc/hosts (or the Windows equivalent named "hosts" somewhere under %WINDIR%). You can use it just like any other hostname - try "ping localhost" to see how it resolves to 127.0.0.1. 12 | 13 | ## 0.0.0.0 14 | 15 | when a server is told to listen on 0.0.0.0 that means "listen on every available network interface". -------------------------------------------------------------------------------- /docs/md/generic/线程安全.md: -------------------------------------------------------------------------------- 1 | # 线程安全 2 | 3 | -------------------------------------------------------------------------------- /docs/md/generic/长连接服务vs短连接服务.md: -------------------------------------------------------------------------------- 1 | # 长短连接服务 2 | 3 | 最主要的一个区别就是服务端是否有状态: 4 | - 短连接: 5 | - 客户端的每一次请求都是独立的,如果涉及到数据读写,也都是在一次请求后数据会落存储,无需为缓存客户端的状态信息 6 | - 长连接: 7 | - 客户端-服务端之间建立TCP连接后不断开,一直使用这条tcp连接进行数据交互 8 | - 服务端需要为N个客户端进行服务 9 | - 不同的客户端发送的消息有不同的用处,那么为了能够正确处理不同客户端的数据,就需要为每一个客户端保存会话的上下文 -------------------------------------------------------------------------------- /docs/md/generic/非对称加密.md: -------------------------------------------------------------------------------- 1 | # 非对称加密 2 | 3 | image-20220726130445698 4 | 5 | A和B双方都有一个public key和private key。private key保留在各自的本地,**public key传递给对方**。但是这个直接传递公钥的方式会有问题,中间人可以劫持A、B两个人的公钥然后做一个伪造。所以实际的使用中,公钥是走CA机构来获取的,保证双方不会被窃取。 6 | 7 | 非对称加密算法比对称加密算法要复杂的多,处理起来也要慢得多。如果所有的网络数据都用非对称加密算法来加密,那效率会很低。所以在实际中,非对称加密只会用来传递一条信息,那就是用于对称加密的密钥。当用于对称加密的密钥确定了,A和B还是通过对称加密算法进行网络通信。这样,既保证了网络通信的安全性,又不影响效率。。 8 | 所以,在现代,A和B之间要进行安全,省心的网络通信,需要经过以下几个步骤: 9 | 10 | - 通过CA体系交换public key 11 | - 通过非对称加密算法,交换用于对称加密的密钥 12 | - 通过对称加密算法,加密正常的网络通信 -------------------------------------------------------------------------------- /docs/paper/Reactor1-93.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HLhuanglang/eNET/4cf85828a467273b1500615a4ddf0de931e3fdce/docs/paper/Reactor1-93.pdf -------------------------------------------------------------------------------- /docs/paper/Reactor2-93.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HLhuanglang/eNET/4cf85828a467273b1500615a4ddf0de931e3fdce/docs/paper/Reactor2-93.pdf -------------------------------------------------------------------------------- /docs/paper/awesome.md: -------------------------------------------------------------------------------- 1 | - [PSOA2](https://www.dre.vanderbilt.edu/~schmidt/POSA/POSA2/) 2 | - [scalable-networking](http://bulk.fefe.de/scalable-networking.pdf) 3 | - [C10K](http://www.kegel.com/c10k.html) 4 | - [NIO](https://gee.cs.oswego.edu/dl/cpjslides/nio.pdf) -------------------------------------------------------------------------------- /docs/paper/reactor-siemens.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HLhuanglang/eNET/4cf85828a467273b1500615a4ddf0de931e3fdce/docs/paper/reactor-siemens.pdf -------------------------------------------------------------------------------- /easy_net/base/buffer.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "buffer.h" 3 | 4 | #include 5 | #include 6 | 7 | using namespace EasyNet; 8 | 9 | void Buffer::Clear() { 10 | m_data.clear(); 11 | m_writeidx = 0; 12 | m_readidx = 0; 13 | } 14 | 15 | Buffer::Buffer(size_t initialSize) : m_readidx(0), m_writeidx(0) { 16 | m_data.resize(initialSize); 17 | } 18 | 19 | void Buffer::Append(const std::string &data) { 20 | Append(data.c_str(), data.size()); 21 | } 22 | 23 | void Buffer::Append(const char *data, size_t sz) { 24 | ensureEnoughBytes(sz); 25 | std::copy(data, data + sz, GetWriteableAddr()); 26 | AdvanceWriter(sz); 27 | } 28 | 29 | std::string Buffer::RetriveAsString(size_t sz) { 30 | std::string retStr; 31 | retStr.assign(GetReadableAddr(), sz); 32 | AdvanceReader(sz); 33 | return retStr; 34 | } 35 | 36 | std::string Buffer::RetriveAllAsString() { 37 | std::string retStr; 38 | auto contentLen = GetReadableSize(); 39 | retStr.assign(GetReadableAddr(), contentLen); 40 | reset(); 41 | return retStr; 42 | } 43 | 44 | size_t Buffer::GetPrependableSize() const { 45 | return m_readidx; 46 | } 47 | 48 | size_t Buffer::GetReadableSize() const { 49 | return m_writeidx - m_readidx; 50 | } 51 | 52 | size_t Buffer::GetWriteableSize() const { 53 | return m_data.size() - m_writeidx; 54 | } 55 | 56 | size_t Buffer::GetBufferSize() const { 57 | return m_data.size(); 58 | } 59 | 60 | char *Buffer::GetReadableAddr() { 61 | return begin() + m_readidx; 62 | } 63 | 64 | char *Buffer::GetWriteableAddr() { 65 | return begin() + m_writeidx; 66 | } 67 | 68 | void Buffer::SetReaderAddr(int idx) { 69 | m_readidx = idx; 70 | } 71 | 72 | void Buffer::SetWriterAddr(int idx) { 73 | m_writeidx = idx; 74 | } 75 | 76 | void Buffer::AdvanceReader(int setp) { 77 | m_readidx += setp; 78 | } 79 | 80 | void Buffer::AdvanceWriter(int setp) { 81 | m_writeidx += setp; 82 | } 83 | 84 | void Buffer::ensureEnoughBytes(int needsz) { 85 | if (needsz > GetWriteableSize()) { 86 | if (GetPrependableSize() + GetWriteableSize() >= needsz) { 87 | // 整个剩余空间还足够存储,则将现有数据全部向前移动. 88 | auto contentlen = GetReadableSize(); 89 | std::copy(GetReadableAddr(), GetWriteableAddr(), m_data.begin()); 90 | m_readidx = 0; 91 | m_writeidx = contentlen; 92 | } else { 93 | // 不够空间了,则直接扩容 94 | m_data.resize((m_writeidx << 1) + needsz); 95 | } 96 | } 97 | } 98 | 99 | char *Buffer::begin() { 100 | return &*m_data.begin(); 101 | } 102 | 103 | void Buffer::reset() { 104 | m_readidx = 0; 105 | m_writeidx = 0; 106 | } -------------------------------------------------------------------------------- /easy_net/base/copyable.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_COPYABLE_H 2 | #define __EASYNET_COPYABLE_H 3 | 4 | namespace EasyNet { 5 | // 允许拷贝 6 | class Copyable { 7 | public: 8 | Copyable() = default; 9 | ~Copyable() = default; 10 | }; 11 | } // namespace EasyNet 12 | 13 | #endif -------------------------------------------------------------------------------- /easy_net/base/countdown_latch.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_COUNTDOWN_LATCH_H 2 | #define __EASYNET_COUNTDOWN_LATCH_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace EasyNet { 11 | ///@brief 减法计数器 12 | ///@note 统计有多少个线程被执行了,当所有线程都执行完毕后,才能继续执行主线程 13 | class CountDownLatch { 14 | public: 15 | explicit CountDownLatch(int count) : m_count(count) { 16 | assert(count > 0); 17 | } 18 | 19 | // 禁止拷贝构造和赋值 20 | // 用于线程之间同步,所有线程共享一个CountDownLatch对象,不允许拷贝 21 | // 一般使用引用或者指针传递给线程 22 | CountDownLatch(const CountDownLatch &) = delete; 23 | CountDownLatch &operator=(const CountDownLatch &) = delete; 24 | 25 | public: 26 | // 等待,直至count减为0 27 | void Wait() { 28 | if (m_count == 0) { 29 | return; 30 | } 31 | std::unique_lock lock(m_mtx); 32 | m_cv.wait(lock, [&]() { 33 | return m_count == 0; 34 | }); 35 | } 36 | 37 | // count减1,(最小为0) 38 | void CountDown() { 39 | int old_c = m_count.load(); 40 | while (old_c > 0) { 41 | // 尝试将计数器的值减1(原子操作,将m_count值从old_c更新为old_c-1,更新成功返回true) 42 | if (m_count.compare_exchange_strong(old_c, old_c - 1)) { 43 | if (old_c == 1) { 44 | std::unique_lock lock(m_mtx); 45 | m_cv.notify_all(); 46 | } 47 | break; // 成功减1后退出循环 48 | } 49 | old_c = m_count.load(); // 如果减1失败,重新读取计数器的值并重试 50 | } 51 | } 52 | 53 | private: 54 | std::atomic m_count; 55 | std::mutex m_mtx; 56 | std::condition_variable m_cv; 57 | }; 58 | } // namespace EasyNet 59 | 60 | #endif // !__EASYNET_COUNTDOWN_LATCH_H 61 | -------------------------------------------------------------------------------- /easy_net/base/cyclic_barrier.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_CYCLIC_BARRIER_H 2 | #define __EASYNET_CYCLIC_BARRIER_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace EasyNet { 12 | ///@brief 循环加法计数器 13 | ///@note 用于多个线程之间的同步,当所有线程都到达屏障后,才能继续执行 14 | class CyclicBarrier { 15 | public: 16 | typedef std::function OnCompletion; 17 | 18 | public: 19 | explicit CyclicBarrier(int count) : m_expected_count(count) { 20 | assert(m_expected_count > 0); 21 | } 22 | 23 | explicit CyclicBarrier(int count, OnCompletion on_completion) 24 | : m_expected_count(count), m_on_completion_fn(on_completion) { 25 | assert(m_expected_count > 0); 26 | } 27 | 28 | CyclicBarrier(const CyclicBarrier &) = delete; 29 | CyclicBarrier &operator=(const CyclicBarrier &) = delete; 30 | 31 | public: 32 | ///@brief 到达屏障,将计数器加1 33 | void Arrive() { 34 | // TODO 35 | } 36 | 37 | ///@brief 等待当前循环中的所有线程到达屏障(阻塞住) 38 | void Wait() { 39 | // TODO 40 | } 41 | 42 | ///@brief 到达屏障并减少期待数,然后等待 43 | void ArriveAndWait() { 44 | // TODO 45 | } 46 | 47 | ///@brief 到达屏障,将后继阶段的初始期待计数和当前阶段的期待计数均减少一 48 | void ArriveAndDrop() { 49 | // TODO 50 | } 51 | 52 | private: 53 | OnCompletion m_on_completion_fn; // 当前线程到达屏障后的回调函数 54 | size_t m_cyclic_count; // 循环的次数 55 | size_t m_expected_count; // 当前循环的期待数 56 | std::atomic m_count; // 到达屏障的线程数 57 | std::mutex m_mtx; 58 | std::condition_variable m_cv; 59 | }; 60 | } // namespace EasyNet 61 | 62 | #endif // !__EASYNET_CYCLIC_BARRIER_H 63 | -------------------------------------------------------------------------------- /easy_net/base/log.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_LOG_H 2 | #define __EASYNET_LOG_H 3 | 4 | #include 5 | #include 6 | 7 | namespace EasyNet { 8 | #ifdef _WIN32 9 | #define DIR_SEPARATOR '\\' 10 | #define DIR_SEPARATOR_STR "\\" 11 | #else 12 | #define DIR_SEPARATOR '/' 13 | #define DIR_SEPARATOR_STR "/" 14 | #endif 15 | 16 | #ifndef __FILENAME__ 17 | // #define __FILENAME__ (strrchr(__FILE__, DIR_SEPARATOR) ? strrchr(__FILE__, DIR_SEPARATOR) + 1 : __FILE__) 18 | #define __FILENAME__ (strrchr(DIR_SEPARATOR_STR __FILE__, DIR_SEPARATOR) + 1) 19 | #endif 20 | 21 | #define LOG_TRACE(fmt, ...) spdlog::log({__FILENAME__, __LINE__, __FUNCTION__}, spdlog::level::trace, fmt, ##__VA_ARGS__) 22 | #define LOG_DEBUG(fmt, ...) spdlog::log({__FILENAME__, __LINE__, __FUNCTION__}, spdlog::level::debug, fmt, ##__VA_ARGS__) 23 | #define LOG_INFO(fmt, ...) spdlog::log({__FILENAME__, __LINE__, __FUNCTION__}, spdlog::level::info, fmt, ##__VA_ARGS__) 24 | #define LOG_WARN(fmt, ...) spdlog::log({__FILENAME__, __LINE__, __FUNCTION__}, spdlog::level::warn, fmt, ##__VA_ARGS__) 25 | #define LOG_ERROR(fmt, ...) spdlog::log({__FILENAME__, __LINE__, __FUNCTION__}, spdlog::level::err, fmt, ##__VA_ARGS__) 26 | 27 | enum level { 28 | trace = 0, 29 | debug = 1, 30 | info = 2, 31 | warn = 3, 32 | err = 4, 33 | critical = 5, 34 | off = 6, 35 | n_levels 36 | }; 37 | 38 | inline void LogInit(level lv) { 39 | spdlog::level::level_enum spdlogLevel = static_cast(lv); 40 | spdlog::set_level(spdlogLevel); 41 | spdlog::set_pattern("[%D %H:%M:%S.%e][%L][%t][%s:%# %!] %^%v%$"); 42 | } 43 | 44 | } // namespace EasyNet 45 | 46 | #endif // !__EASYNET_LOG_H -------------------------------------------------------------------------------- /easy_net/base/non_copyable.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_NON_COPYABLE_H 2 | #define __EASYNET_NON_COPYABLE_H 3 | 4 | #include 5 | 6 | namespace EasyNet { 7 | /// @brief 屏蔽拷贝构造和赋值函数 8 | class NonCopyable { 9 | protected: 10 | NonCopyable() = default; 11 | ~NonCopyable() = default; 12 | 13 | public: 14 | NonCopyable(const NonCopyable &) = delete; 15 | NonCopyable &operator=(const NonCopyable &) = delete; 16 | }; 17 | 18 | /// @brief 创建unique_ptr 19 | /// @note cpp11只有make_shared,缺少make_unique cpp14才有 20 | template 21 | inline std::unique_ptr make_unique(Args &&...args) { 22 | return std::unique_ptr(new T(std::forward(args)...)); 23 | } 24 | } // namespace EasyNet 25 | 26 | #endif -------------------------------------------------------------------------------- /easy_net/base/sigleton.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_SIGLETON_H 2 | #define __EASYNET_SIGLETON_H 3 | 4 | #include 5 | #include 6 | 7 | namespace EasyNet { 8 | 9 | template 10 | class Singleton { 11 | public: 12 | static T *GetInstance() { 13 | if (nullptr == instance) { 14 | std::call_once(once_f, [&]() { 15 | Singleton::instance.reset(new T()); 16 | }); 17 | } 18 | return instance.get(); 19 | } 20 | 21 | public: 22 | Singleton() = delete; 23 | Singleton(const Singleton &rhs) = delete; 24 | Singleton &operator=(const Singleton &rhs) = delete; 25 | 26 | private: 27 | static std::unique_ptr instance; 28 | static std::once_flag once_f; 29 | }; 30 | 31 | template 32 | std::unique_ptr Singleton::instance = nullptr; 33 | 34 | template 35 | std::once_flag Singleton::once_f; 36 | 37 | } // namespace EasyNet 38 | 39 | #endif -------------------------------------------------------------------------------- /easy_net/base/string_util.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_STRING_UTIL_H 2 | #define __EASYNET_STRING_UTIL_H 3 | 4 | #include 5 | #include 6 | 7 | namespace EasyNet { 8 | void split_string(const std::string& s, std::vector& v, const std::string& c) { 9 | std::string::size_type pos1, pos2; 10 | pos2 = s.find(c); 11 | pos1 = 0; 12 | while (std::string::npos != pos2) { 13 | v.push_back(s.substr(pos1, pos2 - pos1)); 14 | 15 | pos1 = pos2 + c.size(); 16 | pos2 = s.find(c, pos1); 17 | } 18 | if (pos1 != s.length()) 19 | v.push_back(s.substr(pos1)); 20 | } 21 | } // namespace EasyNet 22 | #endif // !__EASYNET_STRING_UTIL_H 23 | -------------------------------------------------------------------------------- /easy_net/base/thread.h: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef _WIN32 3 | #include 4 | #elif defined(__linux__) 5 | #include 6 | #include 7 | #else 8 | #include 9 | #endif 10 | 11 | namespace EasyNet { 12 | inline size_t GetCurrentThreadId() { 13 | #ifdef _WIN32 14 | return static_cast(::GetCurrentThreadId()); 15 | #elif defined(__linux__) 16 | return static_cast(syscall(SYS_gettid)); 17 | #else // Default to standard C++11 (other Unix) 18 | return static_cast(std::hash()(std::this_thread::get_id())); 19 | #endif 20 | } 21 | } // namespace EasyNet -------------------------------------------------------------------------------- /easy_net/base/win_support.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_WIN_SUPPORT_H 2 | #define __EASYNET_WIN_SUPPORT_H 3 | 4 | #ifdef _WIN32 5 | #include 6 | 7 | namespace EasyNet { 8 | struct iovec { 9 | void *iov_base; /* Starting address */ 10 | int iov_len; /* Number of bytes */ 11 | }; 12 | 13 | int win32_read_socket(int fd, void *buf, int n) { 14 | int rc = recv(fd, reinterpret_cast(buf), n, 0); 15 | if (rc == SOCKET_ERROR) { 16 | _set_errno(WSAGetLastError()); 17 | } 18 | return rc; 19 | } 20 | 21 | int readv(int fd, const struct iovec *vector, int count) { 22 | int ret = 0; /* Return value */ 23 | int i; 24 | for (i = 0; i < count; i++) { 25 | int n = vector[i].iov_len; 26 | int rc = win32_read_socket(fd, vector[i].iov_base, n); 27 | if (rc == n) { 28 | ret += rc; 29 | } else { 30 | if (rc < 0) { 31 | ret = (ret == 0 ? rc : ret); 32 | } else { 33 | ret += rc; 34 | } 35 | break; 36 | } 37 | } 38 | return ret; 39 | } 40 | } // namespace EasyNet 41 | #endif 42 | #endif // !__EASYNET_WIN_SUPPORT_H 43 | -------------------------------------------------------------------------------- /easy_net/net/acceptor.cpp: -------------------------------------------------------------------------------- 1 | #include "acceptor.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "socket_opt.h" 10 | 11 | using namespace EasyNet; 12 | 13 | void Acceptor::ProcessReadEvent() { 14 | InetAddress peerAddr; 15 | // accept是从已经完成三次握手的tcp队列中取出一个连接的。 16 | // 假设在accept之前,客户端主动发送RST终止连接,如果监听套接字是阻塞的,那么服务器就会一直阻塞在accept调用上,直到新的连接到来accept才会返回。 17 | // 所以一般socket()创建监听套接字后,都会设置成非阻塞的。 18 | // 当客户在服务器调用 accept 之前中止某个连接时,accept 调用可以立即返回 -1 19 | // 这时源自 Berkeley 的实现会在内核中处理该事件,并不会将该事件通知给 epoll 20 | // 而其他实现把 errno 设置为 ECONNABORTED 或者 EPROTO 错误,我们应该忽略这两个错误。 21 | int acceptfd = SocketOpt::Accept(m_fd, peerAddr); 22 | LOG_TRACE("acceptfd={}", acceptfd); 23 | if (acceptfd < 0) { 24 | if (errno == EMFILE) { 25 | m_idle->ReAccept(m_fd); 26 | } 27 | } else { 28 | // 通知所属的TcpServer有新的连接到来 29 | // 因为acceptor是属于tcpserver的,由内核决定唤醒哪个线程来处理新的连接 30 | // 因此,新建立的链接一定是和某个tcpserver关联的. 31 | m_server->NewConn(acceptfd, peerAddr); 32 | } 33 | } 34 | 35 | void Acceptor::StartListen() { 36 | // SOMAXCONN定义了系统中每一个端口最大的监听队列的长度 37 | // cat /proc/sys/net/core/somaxconn 也可以查看 38 | // https://www.cnxct.com/something-about-phpfpm-s-backlog/ 39 | int ret = SocketOpt::Listen(m_fd, SOMAXCONN); 40 | if (ret < 0) { 41 | LOG_ERROR("listen error!"); 42 | } 43 | 44 | EnableRead(); 45 | } 46 | -------------------------------------------------------------------------------- /easy_net/net/acceptor.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_ACCEPTOR_H 2 | #define __EASYNET_ACCEPTOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "io_event.h" 12 | #include "log.h" 13 | #include "non_copyable.h" 14 | #include "socket_opt.h" 15 | #include "tcp_server.h" 16 | 17 | namespace EasyNet { 18 | class IdleFD { 19 | public: 20 | IdleFD() { 21 | m_idlefd = ::open("/tmp/easy_net_idlefd", O_CREAT | O_RDONLY | O_CLOEXEC, 0666); 22 | if (m_idlefd < 0) { 23 | LOG_ERROR("create idlefd failed!"); 24 | } 25 | LOG_TRACE("idlefd={}", m_idlefd); 26 | } 27 | 28 | ~IdleFD() { 29 | SocketOpt::Close(m_idlefd); 30 | } 31 | 32 | void ReAccept(int fd) { 33 | // 1,释放fd用于处理新的链接 34 | SocketOpt::Close(m_idlefd); 35 | // 2,处理链接 36 | m_idlefd = ::accept(fd, nullptr, nullptr); 37 | // 3,处理完毕,重新占位 38 | SocketOpt::Close(m_idlefd); 39 | m_idlefd = ::open("/tmp/easy_net_idlefd", O_CREAT | O_RDONLY | O_CLOEXEC, 0666); 40 | if (m_idlefd < 0) { 41 | LOG_ERROR("create idlefd failed!"); 42 | } 43 | LOG_WARN("ReAccept new idlefd={}", m_idlefd); 44 | } 45 | 46 | private: 47 | int m_idlefd; 48 | }; 49 | 50 | class Acceptor : public IOEvent { 51 | public: 52 | // fd为listenfd 53 | Acceptor(TcpServer *svr, const InetAddress &listenAddr, bool isReusePort) 54 | : IOEvent(svr->GetEventLoop(), SocketOpt::CreateNonBlockSocket(listenAddr.family())), 55 | m_server(svr) { 56 | m_idle = make_unique(); 57 | 58 | // 1,socket:初始化时候已经创建完成 59 | SocketOpt::SetReuseAddr(m_fd); 60 | if (isReusePort) { 61 | SocketOpt::SetReusePort(m_fd); 62 | } 63 | 64 | // 2,bind 65 | int ret = ::bind(m_fd, listenAddr.GetAddr(), listenAddr.GetAddrSize()); 66 | if (ret < 0) { 67 | LOG_ERROR("bindAddress error:{}", strerror(errno)); 68 | exit(EXIT_FAILURE); // 关键路径 69 | } 70 | 71 | // 3,StartListen,由tcp_server来控制开启时机 72 | } 73 | 74 | public: 75 | void StartListen(); 76 | 77 | public: 78 | /// @brief 处理新连接 79 | void ProcessReadEvent() override; 80 | 81 | private: 82 | std::unique_ptr m_idle; 83 | TcpServer *m_server; // 当前acceptor属于哪一个TcpServer,生命周期由TcpServer控制 84 | }; 85 | } // namespace EasyNet 86 | 87 | #endif -------------------------------------------------------------------------------- /easy_net/net/connection_owner.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_CONNECTION_OWNER_H 2 | #define __EASYNET_CONNECTION_OWNER_H 3 | 4 | #include "def.h" 5 | #include "inet_addr.h" 6 | 7 | namespace EasyNet { 8 | 9 | // 前置声明 10 | class EventLoop; 11 | 12 | class ConnOwner { 13 | public: 14 | ConnOwner() = default; 15 | virtual ~ConnOwner() = default; 16 | 17 | public: 18 | // 新建连接 19 | virtual void NewConn(int fd, const InetAddress &peerAddr) = 0; 20 | 21 | // 断开连接 22 | virtual void DelConn(const TcpConnSPtr &conn) = 0; 23 | 24 | // 收到消息 25 | virtual void RecvMsg(const TcpConnSPtr &conn) = 0; 26 | 27 | // 发送缓冲区数据已写入内核(不一定发送到对端) 28 | virtual void WriteComplete(const TcpConnSPtr &conn) = 0; 29 | 30 | // 获取当前连接属于哪个loop 31 | virtual EventLoop *GetEventLoop() const = 0; 32 | }; 33 | } // namespace EasyNet 34 | 35 | #endif -------------------------------------------------------------------------------- /easy_net/net/connector.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_CONNECTOR_H 2 | #define __EASYNET_CONNECTOR_H 3 | 4 | #include "inet_addr.h" 5 | #include "io_event.h" 6 | #include "tcp_client.h" 7 | 8 | namespace EasyNet { 9 | class Connector : public IOEvent { 10 | public: 11 | Connector(TcpClient *client, const InetAddress &addr) 12 | : IOEvent(client->GetEventLoop(), -1), // Connector本身不持有fd 13 | m_client(client), 14 | m_status(ConnectState::DISCONNECTED) { 15 | m_addr = addr; 16 | m_retry_delay_ms = KInitRetryTimeMS; 17 | } 18 | 19 | ~Connector() = default; 20 | 21 | public: 22 | void ProcessWriteEvent() override; 23 | 24 | public: 25 | // connector负责连接对端 26 | // fixme:考虑多线程问题? 但是TcpClient是单线程的,因此这里不需要考虑多线程问题 27 | void Start(); // 开始主动发起连接 28 | 29 | private: 30 | void Retry(); 31 | void ReConnect(); 32 | 33 | private: 34 | enum class ConnectState { 35 | DISCONNECTED, 36 | CONNECTING, 37 | CONNECTED, 38 | CONNERROR, 39 | }; 40 | ConnectState m_status; // 当前连接状态 41 | InetAddress m_addr; // 服务端地址 42 | TcpClient *m_client; // 当前connector属于哪一个TcpClient,生命周期由TcpClient控制 43 | int m_retry_delay_ms; // 重试时间 44 | }; 45 | } // namespace EasyNet 46 | 47 | #endif // !__EASYNET_CONNECTOR_H 48 | -------------------------------------------------------------------------------- /easy_net/net/def.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_DEF_H 2 | #define __EASYNET_DEF_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace EasyNet { 9 | 10 | class TcpConn; 11 | 12 | using TcpConnSPtr = std::shared_ptr; 13 | using EventCallBack = std::function; 14 | using TimerCallBack = std::function; 15 | 16 | const int KThreadPoolSize = 2 * std::thread::hardware_concurrency(); 17 | const constexpr int KDefaultWaitTimeMS = 10; 18 | const constexpr int KMaxRetryTimeMS = 5 * 1000; 19 | const constexpr int KInitRetryTimeMS = 500; 20 | const constexpr int KInvalidFD = -1; 21 | 22 | } // namespace EasyNet 23 | 24 | #endif // !__EASYNET_DEF_H 25 | -------------------------------------------------------------------------------- /easy_net/net/epoll_poller.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_EPOLL_POLLER_H 2 | #define __EASYNET_EPOLL_POLLER_H 3 | 4 | #include "poller.h" 5 | 6 | namespace EasyNet { 7 | class EpollPoller : public Poller { 8 | public: 9 | EpollPoller(EventLoop *loop); 10 | ~EpollPoller(); 11 | 12 | public: 13 | void AddEvent(IOEvent *ev) override; 14 | void DelEvent(IOEvent *ev) override; 15 | void ModEvent(IOEvent *ev) override; 16 | void Polling(int timeout_ms, active_events_t &events) override; 17 | 18 | public: 19 | // 获取具体的事件 20 | std::string GetEventString(uint32_t ev) const; 21 | 22 | private: 23 | int m_epollfd; 24 | }; 25 | } // namespace EasyNet 26 | 27 | #endif -------------------------------------------------------------------------------- /easy_net/net/event_loop.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_EVENT_LOOP_H 2 | #define __EASYNET_EVENT_LOOP_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "io_event.h" 14 | #include "non_copyable.h" 15 | #include "notify.h" 16 | #include "poller.h" 17 | #include "timer.h" 18 | #include "timer_policy.h" 19 | 20 | namespace EasyNet { 21 | 22 | using pending_func_t = std::function; 23 | 24 | class EventLoop : public NonCopyable { 25 | friend class IOEvent; 26 | 27 | public: 28 | EventLoop(std::string nameArg); 29 | ~EventLoop() = default; 30 | bool operator==(const EventLoop &rhs) { 31 | return m_threadid == rhs.m_threadid; 32 | } 33 | 34 | public: 35 | // loop 36 | void Loop(); 37 | 38 | // 判断当前进程是否是绑定的当前loop 39 | bool IsThreadInLoop(); 40 | 41 | // 提供将函数注入loop的接口 42 | void RunInLoop(const pending_func_t &cb); 43 | void QueueInLoop(const pending_func_t &cb); 44 | 45 | // 提供定时任务 46 | TimerID TimerAfter(const TimerCallBack &cb, int interval); 47 | TimerID TimerEvery(const TimerCallBack &cb, int interval); 48 | void CancelTimer(TimerID id); 49 | 50 | // 当向event_loop中添加了任务后,可立即唤醒event_loop 51 | void WakeUp() { 52 | m_notifyer->WakeUp(); 53 | } 54 | 55 | // 退出当前循环 56 | void Quit(); 57 | 58 | std::string GetLoopName() { return m_name; } 59 | 60 | // 获取poller:(返回unique_ptr引用只会获得访问权,不会导致所有权转移.) 61 | std::unique_ptr &GetPoller() { return m_poller; } 62 | 63 | private: 64 | void DoPendiongFunc(); 65 | 66 | bool IsRegistered(int fd) { 67 | return m_registered_events.count(fd); 68 | } 69 | void Register(int fd) { 70 | m_registered_events.insert(fd); 71 | } 72 | void UnRegister(int fd) { 73 | m_registered_events.erase(fd); 74 | } 75 | 76 | private: 77 | std::string m_name; // loop名称 78 | size_t m_threadid; // 当前loop绑定的线程id 79 | bool m_quit; // 退出标志位 80 | bool m_looping; // 是否正在循环 81 | active_events_t m_ready_events; // 当前loop上就绪的fd 82 | std::unique_ptr m_poller; // 管理IO多路复用 83 | bool m_pending_func; // 是否正在执行 84 | std::vector m_pending_func_list; // 待处理的任务列表 85 | std::unique_ptr m_notifyer; // 使用eventfd或者pipe来唤醒event_loop 86 | std::unordered_set m_registered_events; // 当前loop上已注册监听的fd 87 | std::mutex m_mtx; 88 | std::unique_ptr m_timer_queue; // 定时器队列 89 | }; 90 | } // namespace EasyNet 91 | 92 | #endif -------------------------------------------------------------------------------- /easy_net/net/io_event.h: -------------------------------------------------------------------------------- 1 | /// Copyright (c) Holo_wo. 2024. All rights reserved. 2 | /// 3 | /// @file io_event.h 4 | /// @brief IO读写事件封装 5 | /// @author Holo_wo 6 | /// @date 2024-02-05 7 | 8 | #ifndef __EASYNET_FD_EVENT_H 9 | #define __EASYNET_FD_EVENT_H 10 | 11 | #include 12 | #include 13 | 14 | #include "def.h" 15 | #include "non_copyable.h" 16 | #include "socket_opt.h" 17 | 18 | #ifdef __linux__ 19 | #include 20 | #endif 21 | 22 | namespace EasyNet { 23 | 24 | const constexpr int IOEvent_NONE = 0; /* No events registered. */ 25 | const constexpr int IOEvent_READABLE = POLLIN | POLLPRI; /* Fire when descriptor is readable. */ 26 | const constexpr int IOEvent_WRITEABLE = POLLOUT; /* Fire when descriptor is writable. */ 27 | 28 | // 前置声明 29 | class EventLoop; 30 | class IOEvent; 31 | 32 | // 可读写的IO事件集合 33 | using active_events_t = std::vector; 34 | 35 | // IO事件类 36 | class IOEvent : public EasyNet::NonCopyable { 37 | public: 38 | // 虚函数,派生类可选择是否重写 39 | virtual void ProcessReadEvent(); 40 | virtual void ProcessWriteEvent(); 41 | 42 | public: 43 | IOEvent(EventLoop *loop, int fd) 44 | : m_ioloop(loop), 45 | m_fd(fd) { 46 | // nothing todo 47 | } 48 | virtual ~IOEvent() { 49 | if (m_fd != KInvalidFD) { 50 | SocketOpt::Close(m_fd); 51 | } 52 | } 53 | 54 | /// @brief 分发处理IO事件 55 | void DispatchEvent(); 56 | 57 | // 从poller中添加、修改、删除 58 | void EnableRead(); 59 | void EnableWrite(); 60 | void DisableRead(); 61 | void DisableWrite(); 62 | void DisableReadAndWrite(); 63 | void RemoveEvent(); 64 | 65 | int GetFD() const { return m_fd; } 66 | void SetFD(int fd) { m_fd = fd; } 67 | int GetExpectEvent() const { return m_expect_event; } 68 | void SetFiredEvents(int revt) { m_actual_event = revt; } 69 | 70 | private: 71 | enum class update_opt_e { 72 | ENABLE, 73 | DISABLE, 74 | REMOVE, 75 | }; 76 | void update_event_status(update_opt_e opt); 77 | std::string cover_opt_to_string(update_opt_e opt); 78 | 79 | protected: 80 | EventLoop *m_ioloop; // 负责处理本描述符的IO线程 81 | int m_fd; // 被监控的文件描述符 82 | int m_expect_event{}; // 用户设置期望监听的事件 83 | int m_actual_event{}; // Poller返回实际监听得到的事件 84 | }; 85 | } // namespace EasyNet 86 | 87 | #endif -------------------------------------------------------------------------------- /easy_net/net/notify.cpp: -------------------------------------------------------------------------------- 1 | #include "notify.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "event_loop.h" 9 | #include "io_event.h" 10 | #include "log.h" 11 | 12 | using namespace EasyNet; 13 | 14 | Notify::Notify(EventLoop *loop) : IOEvent(loop, 0) { 15 | #ifdef HAVE_EVENTFD 16 | int event_fd = ::eventfd(0, EFD_NONBLOCK); 17 | LOG_TRACE("eventfd={}", event_fd); 18 | this->SetFD(event_fd); 19 | #else 20 | int fd[2]; 21 | int r = pipe(fd); 22 | assert(!r); 23 | this->SetFD(fd[0]); 24 | m_notifier = fd[1]; 25 | #endif 26 | this->EnableRead(); 27 | } 28 | 29 | Notify::~Notify() { 30 | this->RemoveEvent(); 31 | #ifndef HAVE_EVENTFD 32 | if (this->m_notifier >= 0) { 33 | close(this->m_notifier); 34 | } 35 | this->m_notifier = -1; 36 | #endif 37 | } 38 | 39 | void Notify::ProcessReadEvent() { 40 | #ifdef HAVE_EVENTFD 41 | eventfd_t tmp; 42 | eventfd_read(this->m_fd, &tmp); 43 | #else 44 | char buf[128]; 45 | ssize_t r; 46 | while ((r = ::read(this->GetFD(), buf, sizeof(buf))) != 0) { 47 | if (r > 0) { 48 | continue; 49 | } 50 | switch (errno) { 51 | case EAGAIN: 52 | return; 53 | case EINTR: 54 | continue; 55 | default: 56 | return; 57 | } 58 | } 59 | #endif 60 | } 61 | 62 | void Notify::WakeUp() { 63 | #ifdef HAVE_EVENTFD 64 | eventfd_write(this->GetFD(), 1); 65 | #else 66 | char c = 0; 67 | while (::write(m_notifier, &c, 1) != 1 && errno == EINTR) { 68 | } 69 | #endif 70 | } -------------------------------------------------------------------------------- /easy_net/net/notify.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_NOTIFY_H 2 | #define __EASYNET_NOTIFY_H 3 | 4 | #include 5 | 6 | #include "io_event.h" 7 | 8 | #if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 9) 9 | #define HAVE_EVENTFD 1 10 | #include 11 | #endif 12 | 13 | namespace EasyNet { 14 | class Notify : public IOEvent { 15 | public: 16 | Notify(EventLoop *loop); 17 | ~Notify() override; 18 | 19 | void WakeUp(); 20 | 21 | public: 22 | void ProcessReadEvent() override; 23 | 24 | private: 25 | #ifndef HAVE_EVENTFD 26 | int m_notifier; // 使用pipe时 27 | #endif 28 | }; 29 | } // namespace EasyNet 30 | 31 | #endif // !__EASYNET_NOTIFY_H 32 | -------------------------------------------------------------------------------- /easy_net/net/poller.cpp: -------------------------------------------------------------------------------- 1 | #include "poller.h" 2 | 3 | #include "epoll_poller.h" 4 | 5 | using namespace EasyNet; 6 | 7 | Poller::Poller(EventLoop *loop) : m_owner_loop(loop) { 8 | // nothing todo 9 | } 10 | 11 | Poller *Poller::CreatePoller(EventLoop *loop) { 12 | Poller *p = nullptr; 13 | 14 | #ifdef __linux__ 15 | p = new EpollPoller(loop); 16 | #elif defined(__APPLE__) 17 | // TODO: kqueue 18 | #elif defined(_WIN32) 19 | // TODO: iocp 20 | #endif 21 | return p; 22 | } -------------------------------------------------------------------------------- /easy_net/net/poller.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_POLLER_H 2 | #define __EASYNET_POLLER_H 3 | 4 | #include "io_event.h" 5 | #include "non_copyable.h" 6 | 7 | namespace EasyNet { 8 | 9 | class EventLoop; 10 | class IOEvent; 11 | 12 | class Poller : public NonCopyable { 13 | public: 14 | static Poller *CreatePoller(EventLoop *loop); 15 | 16 | public: 17 | Poller(EventLoop *loop); 18 | virtual ~Poller() = default; 19 | virtual void AddEvent(IOEvent *ev) = 0; 20 | virtual void DelEvent(IOEvent *ev) = 0; 21 | virtual void ModEvent(IOEvent *ev) = 0; 22 | virtual void Polling(int timeout_ms, active_events_t &events) = 0; 23 | 24 | private: 25 | EventLoop *m_owner_loop; // poller所属的event_loop,不由poller控制生命周期 26 | }; 27 | 28 | } // namespace EasyNet 29 | 30 | #endif -------------------------------------------------------------------------------- /easy_net/net/server_thread.cpp: -------------------------------------------------------------------------------- 1 | #include "server_thread.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "event_loop.h" 7 | #include "tcp_server.h" 8 | 9 | using namespace EasyNet; 10 | 11 | ServerThread::~ServerThread() { 12 | } 13 | 14 | EventLoop *ServerThread::StartServerThread() { 15 | // 1,创建线程 16 | m_thread = std::thread([this] { 17 | threadEntry(); 18 | }); 19 | 20 | // 2,等待获取EventLoop 21 | { 22 | std::unique_lock uqlk(m_mtx); 23 | m_cv.wait(uqlk, [&] { 24 | return m_ready; 25 | }); 26 | } 27 | return m_loop; 28 | } 29 | 30 | void ServerThread::Join() { 31 | if (m_thread.joinable()) { 32 | m_thread.join(); 33 | } 34 | } 35 | 36 | void ServerThread::Detach() { 37 | m_thread.detach(); 38 | } 39 | 40 | void ServerThread::threadEntry() { 41 | // 创建TcpServer ps:如果callback要做成成员变量,那么这里就要设置一堆回调,因此使用static来和类绑定而非对象 42 | TcpServer svr(m_name, 0, m_addr); 43 | 44 | // 3,运行server 45 | { 46 | std::unique_lock uqlk(m_mtx); 47 | m_loop = svr.GetEventLoop(); 48 | m_ready = true; 49 | m_cv.notify_all(); 50 | } 51 | svr.start(); 52 | } -------------------------------------------------------------------------------- /easy_net/net/server_thread.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_SERVER_THREAD_H 2 | #define __EASYNET_SERVER_THREAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "event_loop.h" 9 | #include "inet_addr.h" 10 | #include "tcp_server.h" 11 | 12 | namespace EasyNet { 13 | 14 | class ServerThread { 15 | public: 16 | ServerThread(std::string name, const InetAddress &addr) : m_name(name), m_addr(addr) {} 17 | ~ServerThread(); 18 | 19 | public: 20 | EventLoop *StartServerThread(); 21 | void Join(); 22 | void Detach(); 23 | 24 | private: 25 | void threadEntry(); 26 | 27 | private: 28 | EventLoop *m_loop; 29 | InetAddress m_addr; 30 | std::thread m_thread; 31 | std::mutex m_mtx; 32 | std::string m_name; 33 | std::condition_variable_any m_cv; 34 | 35 | bool m_ready; 36 | }; 37 | 38 | } // namespace EasyNet 39 | 40 | #endif // !__EASYNET_SERVER_THREAD_H 41 | -------------------------------------------------------------------------------- /easy_net/net/tcp_client.cpp: -------------------------------------------------------------------------------- 1 | #include "tcp_client.h" 2 | 3 | #include "connector.h" 4 | 5 | using namespace EasyNet; 6 | 7 | TcpClient::TcpClient(EventLoop *loop, const InetAddress &addr) 8 | : m_loop(loop) { 9 | m_connector = make_unique(this, addr); 10 | } 11 | TcpClient::~TcpClient() { 12 | // 放在头文件会因为Connector类只是前置申明导致unique_ptr断言失败 13 | } 14 | 15 | void TcpClient::connect() { 16 | m_connector->Start(); 17 | } 18 | 19 | void TcpClient::NewConn(int fd, const InetAddress &peerAddr) { 20 | // fixme:如果每次有连接事件都新建conn,那么新连接到来时旧连接会被释放...或许应该用map存所有的conn? 21 | m_conn = std::make_shared(this, fd, peerAddr); 22 | m_conn->SetStatus(TcpConn::ConnStatus::CONNECTED); 23 | m_conn->EnableRead(); 24 | if (onNewConnection != nullptr) { 25 | onNewConnection(m_conn); 26 | } 27 | } 28 | 29 | void TcpClient::DelConn(const TcpConnSPtr &conn) { 30 | if (onDelConnection != nullptr) { 31 | onDelConnection(conn); 32 | } 33 | conn->RemoveEvent(); 34 | } 35 | 36 | void TcpClient::RecvMsg(const TcpConnSPtr &conn) { 37 | if (onRecvMsg != nullptr) { 38 | onRecvMsg(conn); 39 | } 40 | } 41 | 42 | void TcpClient::WriteComplete(const TcpConnSPtr &conn) { 43 | if (onWriteComplete != nullptr) { 44 | onWriteComplete(conn); 45 | } 46 | } 47 | 48 | EventLoop *TcpClient::GetEventLoop() const { 49 | return m_loop; 50 | } -------------------------------------------------------------------------------- /easy_net/net/tcp_client.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_TCP_CLIENT_H 2 | #define __EASYNET_TCP_CLIENT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "connection_owner.h" 9 | #include "def.h" 10 | #include "event_loop.h" 11 | #include "inet_addr.h" 12 | #include "tcp_connection.h" 13 | 14 | namespace EasyNet { 15 | // 前置声明 16 | class Connector; 17 | 18 | class TcpClient : public ConnOwner { 19 | public: 20 | TcpClient(EventLoop *loop, const InetAddress &addr); 21 | ~TcpClient(); 22 | 23 | public: 24 | // 框架本身需要关心的接口 25 | void NewConn(int fd, const InetAddress &peerAddr) override; 26 | void DelConn(const TcpConnSPtr &conn) override; 27 | void RecvMsg(const TcpConnSPtr &conn) override; 28 | void WriteComplete(const TcpConnSPtr &conn) override; 29 | EventLoop *GetEventLoop() const override; 30 | 31 | public: 32 | // tcp_client使用者需要关心的接口,由框架回调 33 | EventCallBack onNewConnection; 34 | EventCallBack onDelConnection; 35 | EventCallBack onRecvMsg; 36 | EventCallBack onWriteComplete; 37 | 38 | public: 39 | void connect(); 40 | 41 | private: 42 | EventLoop *m_loop; 43 | std::unique_ptr m_connector; 44 | TcpConnSPtr m_conn; // 必须持有一下,不然NewConn结束链接就释放了 45 | std::map m_conn_map; 46 | }; 47 | } // namespace EasyNet 48 | 49 | #endif -------------------------------------------------------------------------------- /easy_net/net/tcp_connection.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_TCP_CONNECTION_H 2 | #define __EASYNET_TCP_CONNECTION_H 3 | 4 | #include 5 | #include 6 | 7 | #include "buffer.h" 8 | #include "connection_owner.h" 9 | #include "inet_addr.h" 10 | #include "io_event.h" 11 | 12 | namespace EasyNet { 13 | 14 | // 前置声明 15 | class EventLoop; 16 | 17 | class TcpConn : public std::enable_shared_from_this, public IOEvent { 18 | public: 19 | enum class ConnStatus { 20 | CONNECTING, // 正在连接 21 | CONNECTED, // 已经连接 22 | DISCONNECTING, // 正在断开连接(给客户端使用的,就是只关闭写端) 23 | DISCONNECTED, // 断开连接 24 | }; 25 | 26 | public: 27 | ///@brief 构造函数 28 | ///@param owner 当前这条tcp连接的拥有者,可以是tcpclient或者tcpserver 29 | ///@param fd 当前这条tcp连接的文件描述符 30 | ///@param perrAddr 对端地址 31 | TcpConn(ConnOwner *owner, int fd, const InetAddress &perrAddr); 32 | 33 | ///@brief 析构函数 34 | ~TcpConn() override; 35 | 36 | public: 37 | ///@brief 延长tcp连接的生命周期 38 | ///@note 为了防止恶意客户端一直占据连接,服务端需要控制tcp连接的生命周期,当连接闲置(无数据收发)时,服务端主动断开连接 39 | void KeepAlive(); 40 | 41 | ///@brief 发送数据 42 | ///@param data 指向存放数据的指针 43 | ///@param data_size 待发送的数据大小 44 | void SendData(const char *data, size_t data_size); 45 | 46 | ///@brief 发送数据 47 | ///@param data 待发送的数据,使用std::string进行存储 48 | void SendData(const std::string &data); 49 | 50 | ///@brief 获取读缓存 51 | ///@return 返回当前tcp连接中的读缓存(可读数据) 52 | Buffer &GetBuffer() { return *m_read_buf; } 53 | 54 | ///@brief 获取当前这条tcp连接的名字 55 | ///@return 连接名字 56 | std::string GetConnName() { return m_name; } 57 | 58 | ///@brief 设置当前tcp连接的状态 59 | ///@param status tcp状态枚举 60 | ///@see ConnStatus 61 | void SetStatus(ConnStatus status) { m_status = status; } 62 | 63 | ///@brief 获取当前这条tcp连接属于哪一个loop 64 | ///@return 所属的loop 65 | EventLoop *GetOwnerLoop() { return m_ioloop; } 66 | 67 | public: 68 | void ProcessWriteEvent() override; 69 | void ProcessReadEvent() override; 70 | 71 | private: 72 | ConnOwner *m_owner; // tcpclient或tcpserver会传入this指针,此时owner_指向tcpclient或tcpserver 73 | Buffer *m_read_buf; // 读buffer(存储对端发送给本端的数据) 74 | Buffer *m_write_buf; // 写buffer(存储本端将要发送给对端的数据) 75 | std::string m_name; // 连接的name 76 | ConnStatus m_status; // 连接的状态 77 | }; 78 | 79 | } // namespace EasyNet 80 | 81 | #endif -------------------------------------------------------------------------------- /easy_net/net/tcp_server.h: -------------------------------------------------------------------------------- 1 | /* 2 | tcp_server在main函数中创建运行,也就是说一整个服务程序都是tcp_server的资产 3 | 要考虑到所有的回调设置来源都是tcp_server传入的,设置完回调后,如何从main传递 4 | 到子线程中去。 5 | */ 6 | #ifndef __EASYNET_TCP_SERVER_H 7 | #define __EASYNET_TCP_SERVER_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "connection_owner.h" 15 | #include "def.h" 16 | #include "event_loop.h" 17 | #include "inet_addr.h" 18 | #include "tcp_connection.h" 19 | 20 | namespace EasyNet { 21 | 22 | // 前置声明 23 | class TcpConn; 24 | class Acceptor; 25 | class ServerThread; 26 | 27 | class TcpServer : public ConnOwner { 28 | public: 29 | TcpServer(const std::string &svrname, unsigned int numEventThreads, const InetAddress &listenAddr); 30 | ~TcpServer(); 31 | 32 | public: 33 | // 框架本身需要关心的接口 34 | void NewConn(int fd, const InetAddress &peerAddr) override; 35 | void DelConn(const TcpConnSPtr &conn) override; 36 | void RecvMsg(const TcpConnSPtr &conn) override; 37 | void WriteComplete(const TcpConnSPtr &conn) override; 38 | EventLoop *GetEventLoop() const override; 39 | 40 | public: 41 | // tcp_server使用者需要关心的接口,由框架回调 42 | static EventCallBack onNewConnection; 43 | static EventCallBack onDelConnection; 44 | static EventCallBack onRecvMsg; 45 | static EventCallBack onWriteComplete; 46 | 47 | public: 48 | // 运行tcp server 49 | void start(); 50 | 51 | // 停止tcp server 52 | void stop(); 53 | 54 | // 获取loop(round-robin:获取主线程和子线程的loop) 55 | EventLoop *get_loop(); 56 | 57 | // 获取主线程loop 58 | EventLoop *get_main_loop(); 59 | 60 | private: 61 | void startThreadPool(); 62 | // join/detach 子线程 63 | void JoinThread(); 64 | void DetachThread(); 65 | 66 | private: 67 | EventLoop *m_loop; // 每一个tcp服务器都应该有一个loop,用来处理各种事件 68 | std::unique_ptr m_acceptor; // 主线程中负责处理链接请求 69 | InetAddress m_addr; // 服务器监听的地址 70 | std::string m_name; // 服务器名称 71 | unsigned int m_thread_cnt; // 服务器启动的线程数量 72 | std::set> m_connections_map; // 当前持有的tcp链接[防止智能指针被释放] 73 | std::vector> m_child_svr_vec; // 子线程 74 | std::vector m_worker_loop; // 工作线程loop 75 | }; 76 | } // namespace EasyNet 77 | 78 | #endif -------------------------------------------------------------------------------- /easy_net/net/timer.cpp: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | using namespace EasyNet; 4 | 5 | timespec Timer::s_current_time; -------------------------------------------------------------------------------- /easy_net/net/udp_client.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_UDP_CLIENT_H 2 | #define __EASYNET_UDP_CLIENT_H 3 | 4 | namespace EasyNet { 5 | class UdpClient {}; 6 | 7 | } // namespace EasyNet 8 | 9 | #endif // !__EASYNET_UDP_CLIENT_H 10 | -------------------------------------------------------------------------------- /easy_net/net/udp_server.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_UDP_SERVER_H 2 | #define __EASYNET_UDP_SERVER_H 3 | 4 | namespace EasyNet { 5 | class UdpServer {}; 6 | 7 | } // namespace EasyNet 8 | 9 | #endif // !__EASYNET_UDP_SERVER_H 10 | -------------------------------------------------------------------------------- /easy_net/protocol/dns/dns.cpp: -------------------------------------------------------------------------------- 1 | #include "dns.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "log.h" 9 | 10 | using namespace EasyNet; 11 | 12 | DNSResolverPtr DNSResolver::CreateResolver() { 13 | return std::make_shared(); 14 | } 15 | 16 | /* 17 | 一般机器上可以使用nslookup和dig来查询域名对应的Ip地址,在ubuntu上一般是请求named服务: 18 | - https://www.cnblogs.com/heyongshen/p/17794738.html 19 | 使用getaddrinfo和getnameinfo函数来进行域名解析,比较慢,并且是阻塞式的,同时也不推荐使用gethostbyname: 20 | - https://jameshfisher.com/2018/02/03/what-does-getaddrinfo-do/ 21 | - http://blog.gerryyang.com/tcp/ip/2022/05/12/c-ares-in-action.html 22 | - https://skarnet.org/software/s6-dns/getaddrinfo.html 23 | */ 24 | void DNSResolver::resolve(const std::string& hostname, const Callback& callback) { 25 | std::vector ips; 26 | struct addrinfo hints, *servinfo, *p; 27 | // 设置hints结构体以获取IPv4和IPv6地址 28 | memset(&hints, 0, sizeof(hints)); 29 | hints.ai_family = AF_UNSPEC; // 不指定地址族,允许IPv4和IPv6 30 | hints.ai_socktype = SOCK_STREAM; // 指定套接字类型为流式套接字 31 | int result = getaddrinfo(hostname.c_str(), nullptr, &hints, &servinfo); 32 | if (result != 0) { 33 | LOG_ERROR("getaddrinfo: {}", gai_strerror(result)); 34 | goto end; 35 | } 36 | 37 | // 遍历所有返回的地址信息 38 | for (p = servinfo; p != nullptr; p = p->ai_next) { 39 | if (p->ai_family == AF_INET) { // IPv4地址 40 | struct sockaddr_in* ipv4 = reinterpret_cast(p->ai_addr); 41 | ips.emplace_back(*ipv4); 42 | } else if (p->ai_family == AF_INET6) { // IPv6地址 43 | struct sockaddr_in6* ipv6 = reinterpret_cast(p->ai_addr); 44 | ips.emplace_back(*ipv6); 45 | } 46 | } 47 | 48 | end: 49 | callback(ips); 50 | freeaddrinfo(servinfo); 51 | } 52 | 53 | void DNSResolver::resolve(const std::string& hostname, const std::string& nameserver, const Callback& callback) { 54 | // TODO: 自己实现dns协议请求 55 | } -------------------------------------------------------------------------------- /easy_net/protocol/dns/dns.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_DNS_H 2 | #define __EASYNET_DNS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "inet_addr.h" 9 | #include "non_copyable.h" 10 | 11 | namespace EasyNet { 12 | 13 | const constexpr int DNS_PORT = 53; // dns服务器的端口号,一般默认都是53 14 | 15 | class DNSResolver : public NonCopyable { 16 | using Callback = std::function&)>; 17 | 18 | public: 19 | static std::shared_ptr CreateResolver(); 20 | void resolve(const std::string& hostname, const Callback& callback); 21 | void resolve(const std::string& hostname, const std::string& nameserver, const Callback& callback); 22 | }; 23 | 24 | using DNSResolverPtr = std::shared_ptr; 25 | 26 | } // namespace EasyNet 27 | 28 | #endif // !__EASYNET_DNS_H 29 | -------------------------------------------------------------------------------- /easy_net/protocol/http/http_client.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_HTTP_CLIENT_H 2 | #define __EASYNET_HTTP_CLIENT_H 3 | 4 | #include 5 | #include 6 | 7 | #include "event_loop.h" 8 | #include "http_headers.h" 9 | #include "http_request.h" 10 | #include "http_response.h" 11 | #include "tcp_client.h" 12 | 13 | namespace EasyNet { 14 | ///@brief 异步http客户端 15 | ///@note 需要由外部传入event_loop 16 | class AsyncHttpClient { 17 | public: 18 | using CallBack = std::function; 19 | 20 | ///@brief 使用外部传入的event_loop构建http_client 21 | ///@param loop 事件循环 22 | ///@param url 请求的url 23 | AsyncHttpClient(std::shared_ptr loop, const std::string url); 24 | 25 | ~AsyncHttpClient() = default; 26 | 27 | void AddRequest(const HttpRequestPtr &req, const CallBack &cb, double timeout = 0.0); 28 | void Run(); 29 | 30 | private: 31 | void _on_connection(const TcpConnSPtr &conn); 32 | void _on_recv_msg(const TcpConnSPtr &conn); 33 | void _on_send_complete(const TcpConnSPtr &conn); 34 | void _on_del_connection(const TcpConnSPtr &conn); 35 | 36 | private: 37 | HttpHeaders m_headers; 38 | HttpRequest m_req; 39 | std::shared_ptr m_loop; 40 | std::unique_ptr m_client; 41 | }; 42 | 43 | ///@brief 同步http客户端 44 | ///@note [GET/POST/...]-> connect -> send -> recv -> http_parser -> HttpResponse 45 | class SyncHttpClient { 46 | public: 47 | SyncHttpClient(const std::string url); 48 | 49 | ~SyncHttpClient() = default; 50 | 51 | private: 52 | }; 53 | 54 | } // namespace EasyNet 55 | 56 | #endif // !__EASYNET_HTTP_CLIENT_H 57 | -------------------------------------------------------------------------------- /easy_net/protocol/http/http_context.h: -------------------------------------------------------------------------------- 1 | #ifndef __HTTP_CONTEXT_H 2 | #define __HTTP_CONTEXT_H 3 | 4 | #include 5 | 6 | #include "http_parser.h" 7 | #include "http_request.h" 8 | #include "http_response.h" 9 | 10 | namespace EasyNet { 11 | class HttpContext { 12 | public: 13 | class Status { 14 | public: 15 | enum ParseSatatus { 16 | PARSE_OK = 0, // 解析完成 17 | PARSE_ERROR, // 解析出错 18 | }; 19 | 20 | public: 21 | Status(ParseSatatus status, size_t parsed_len) 22 | : m_status(status), m_parsed_len(parsed_len) { 23 | } 24 | bool ParseComplete() const { 25 | return m_status == PARSE_OK; 26 | } 27 | 28 | size_t GetParsedLen() const { 29 | return m_parsed_len; 30 | } 31 | 32 | private: 33 | ParseSatatus m_status; 34 | size_t m_parsed_len; 35 | }; 36 | 37 | public: 38 | HttpContext(http_parser_type type); 39 | Status Parse(const char *data, size_t len, HttpRequest &req); 40 | Status Parse(const char *data, size_t len, HttpResponse &rsp); 41 | 42 | public: 43 | http_parser_type m_type; 44 | HttpRequest *m_req; 45 | HttpResponse *m_rsp; 46 | std::string m_header_filed; 47 | std::string m_header_value; 48 | 49 | private: 50 | static http_parser_settings s_parser_settings; 51 | http_parser m_parser; 52 | }; 53 | } // namespace EasyNet 54 | 55 | #endif -------------------------------------------------------------------------------- /easy_net/protocol/http/http_def.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_HTTP_H 2 | #define __EASYNET_HTTP_H 3 | 4 | #include 5 | 6 | namespace EasyNet { 7 | 8 | class HttpRequest; 9 | class HttpResponse; 10 | 11 | // 不能修改请求,因此设置为const。此时req只能调用const成员函数 12 | using HttpCallBack = std::function; 13 | } // namespace EasyNet 14 | 15 | #endif // !__EASYNET_HTTP_H 16 | -------------------------------------------------------------------------------- /easy_net/protocol/http/http_headers.cpp: -------------------------------------------------------------------------------- 1 | #include "http_headers.h" 2 | 3 | using namespace EasyNet; 4 | 5 | std::string HttpHeaders::SerializeToString() { 6 | std::string res; 7 | for (const auto &header : m_headers) { 8 | res += header.first + ": " + header.second + "\r\n"; 9 | } 10 | return res; 11 | } 12 | 13 | void HttpHeaders::SetHeader(const std::string &key, const std::string &val) { 14 | m_headers[key] = val; 15 | } 16 | 17 | void HttpHeaders::SetHeader(const char *key, const std::string &val) { 18 | m_headers[key] = val; 19 | } 20 | 21 | void HttpHeaders::SetHeader(std::map headers) { 22 | for (auto &header : headers) { 23 | m_headers[header.first] = header.second; 24 | } 25 | } -------------------------------------------------------------------------------- /easy_net/protocol/http/http_headers.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_HTTP_HEADERS_H 2 | #define __EASYNET_HTTP_HEADERS_H 3 | 4 | #include 5 | #include 6 | 7 | namespace EasyNet { 8 | class HttpHeaders { 9 | public: 10 | void SetHeader(const std::string &key, const std::string &val); 11 | void SetHeader(const char *key, const std::string &val); 12 | void SetHeader(std::map headers); 13 | 14 | public: 15 | std::string SerializeToString(); 16 | 17 | private: 18 | std::map m_headers; 19 | }; 20 | } // namespace EasyNet 21 | 22 | #endif // !__EASYNET_HTTP_HEADERS_H 23 | -------------------------------------------------------------------------------- /easy_net/protocol/http/http_request.cpp: -------------------------------------------------------------------------------- 1 | #include "http_request.h" 2 | 3 | using namespace EasyNet; 4 | 5 | std::string HttpRequest::SerializeToString() { 6 | std::string res; 7 | res += m_method + " " + m_url + " " + m_version + "\r\n"; 8 | res += m_headers.SerializeToString(); 9 | res += "\r\n"; 10 | res += m_body; 11 | return res; 12 | } 13 | 14 | std::string HttpRequest::SerializeToString() const { 15 | std::string res; 16 | res += m_method + " " + m_url + " " + m_version + "\r\n"; 17 | res += m_headers.SerializeToString(); 18 | res += "\r\n"; 19 | res += m_body; 20 | return res; 21 | } 22 | -------------------------------------------------------------------------------- /easy_net/protocol/http/http_request.h: -------------------------------------------------------------------------------- 1 | #ifndef __HTTP_REQUEST_H 2 | #define __HTTP_REQUEST_H 3 | 4 | #include 5 | #include 6 | 7 | #include "http_headers.h" 8 | 9 | namespace EasyNet { 10 | class HttpRequest { 11 | public: 12 | public: 13 | std::string GetMethod() const { return m_method; } 14 | std::string GetUrl() const { return m_url; } 15 | void SetBody(const std::string &body) { m_body = body; } 16 | 17 | public: 18 | std::string SerializeToString(); 19 | std::string SerializeToString() const; 20 | 21 | public: 22 | std::string m_method; 23 | std::string m_url; 24 | std::string m_version{"HTTP/1.1"}; 25 | mutable HttpHeaders m_headers; // key-val 26 | std::string m_body; 27 | }; 28 | 29 | using HttpRequestPtr = std::shared_ptr; 30 | } // namespace EasyNet 31 | 32 | #endif -------------------------------------------------------------------------------- /easy_net/protocol/http/http_response.cpp: -------------------------------------------------------------------------------- 1 | #include "http_response.h" 2 | 3 | #include 4 | 5 | using namespace EasyNet; 6 | 7 | std::string HttpResponse::SerializeToString() { 8 | // 处理头 9 | m_headers.SetHeader("Content-Length", std::to_string(m_body.size())); 10 | // 客户端或服务器发现对方一段时间没有活动,就可以主动关闭连接 11 | // 不过,规范的做法是,客户端在最后一个请求时,发送 Connection: close,明确要求服务器关闭 TCP 连接。 12 | // http1.1默认是keep-alive 13 | m_headers.SetHeader("Connection", "keep-alive"); 14 | m_headers.SetHeader("Content-Type", "text/html"); 15 | 16 | std::string res; 17 | res += m_version + " " + m_status_code + " " + m_status_code_msg + "\r\n"; 18 | res += m_headers.SerializeToString(); 19 | res += "\r\n"; 20 | res += m_body; 21 | return res; 22 | } 23 | 24 | std::string HttpResponse::SerializeToString() const { 25 | // 处理头 26 | m_headers.SetHeader("Content-Length", std::to_string(m_body.size())); 27 | m_headers.SetHeader("Connection", "keep-alive"); // http1.1默认是keep-alive 28 | m_headers.SetHeader("Content-Type", "text/html"); 29 | 30 | std::string res; 31 | res += m_version + " " + m_status_code + " " + m_status_code_msg + "\r\n"; 32 | res += m_headers.SerializeToString(); 33 | res += "\r\n"; 34 | res += m_body; 35 | return res; 36 | } 37 | -------------------------------------------------------------------------------- /easy_net/protocol/http/http_response.h: -------------------------------------------------------------------------------- 1 | /* 2 | ** res一般是服务端发送给客户端的消息,当我们构造了http_responese对象并填充完数据后 3 | ** 其实最终需要转换成一坨char数据发送,所以应该有一个http_res_to_buffer的方法。 4 | */ 5 | #ifndef __HTTP_RESPONSE_H 6 | #define __HTTP_RESPONSE_H 7 | 8 | #include 9 | 10 | #include "http_headers.h" 11 | 12 | namespace EasyNet { 13 | class HttpResponse { 14 | public: 15 | void SetStatusCode(const std::string &code) { m_status_code = code; } 16 | void SetStatusCodeMsg(const std::string &msg) { m_status_code_msg = msg; } 17 | void SetBody(const std::string &body) { m_body = body; } 18 | 19 | public: 20 | std::string SerializeToString(); 21 | std::string SerializeToString() const; 22 | 23 | public: 24 | std::string m_version{"HTTP/1.1"}; // http协议版本1.0、1.1 25 | std::string m_status_code; // 状态码200、301、404等 26 | std::string m_status_code_msg; // 状态码描述信息,例如200的描述信息是ok 27 | mutable HttpHeaders m_headers; // key-val 28 | std::string m_body; // body的大小写入Content-Length 29 | }; 30 | } // namespace EasyNet 31 | 32 | #endif -------------------------------------------------------------------------------- /easy_net/protocol/http/http_router.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_HTTP_ROUTER_H 2 | #define __EASYNET_HTTP_ROUTER_H 3 | 4 | #include 5 | #include 6 | 7 | #include "http_def.h" 8 | #include "http_request.h" 9 | #include "http_response.h" 10 | #include "log.h" 11 | 12 | namespace EasyNet { 13 | class HttpServer; 14 | class HttpRouter { 15 | friend class HttpServer; 16 | 17 | public: 18 | // 支持通配符 19 | void GET(const std::string &cmd, const HttpCallBack &cb) { 20 | m_get_router[cmd] = cb; 21 | } 22 | 23 | // 支持通配符 24 | void POST(const std::string &cmd, const HttpCallBack &cb) { 25 | m_post_router[cmd] = cb; 26 | } 27 | 28 | private: 29 | void Routing(const HttpRequest &req, HttpResponse &resp) { 30 | auto method = req.GetMethod(); 31 | auto cmd = req.GetUrl(); 32 | LOG_DEBUG("Routing: {} {}", method, cmd); 33 | if (method == "GET") { 34 | auto it = m_get_router.find(cmd); 35 | if (it != m_get_router.end()) { 36 | it->second(req, resp); 37 | return; 38 | } 39 | } 40 | if (method == "POST") { 41 | auto it = m_post_router.find(cmd); 42 | if (it != m_post_router.end()) { 43 | it->second(req, resp); 44 | return; 45 | } 46 | } 47 | 48 | // 默认处理 49 | resp.SetStatusCode("404"); 50 | resp.SetStatusCodeMsg("Not Found"); 51 | resp.SetBody("Not Found"); 52 | } 53 | 54 | private: 55 | std::map m_get_router; 56 | std::map m_post_router; 57 | }; 58 | } // namespace EasyNet 59 | 60 | #endif // !__EASYNET_HTTP_ROUTER_H 61 | -------------------------------------------------------------------------------- /easy_net/protocol/http/http_server.cpp: -------------------------------------------------------------------------------- 1 | #include "http_server.h" 2 | 3 | #include "http_context.h" 4 | #include "http_parser.h" 5 | #include "http_request.h" 6 | #include "http_response.h" 7 | #include "log.h" 8 | #include "tcp_server.h" 9 | 10 | using namespace EasyNet; 11 | 12 | HttpServer::HttpServer(const std::string &ip, size_t port) 13 | : m_server("httpsvr", KThreadPoolSize, {ip.c_str(), static_cast(port)}) { 14 | m_server.onNewConnection = std::bind(&HttpServer::_on_connection, this, std::placeholders::_1); 15 | m_server.onRecvMsg = std::bind(&HttpServer::_on_recv_msg, this, std::placeholders::_1); 16 | m_server.onWriteComplete = std::bind(&HttpServer::_on_send_complete, this, std::placeholders::_1); 17 | m_server.onDelConnection = std::bind(&HttpServer::_on_del_connection, this, std::placeholders::_1); 18 | } 19 | 20 | void HttpServer::_on_connection(const TcpConnSPtr &conn) { 21 | LOG_DEBUG("Get New Conn"); 22 | } 23 | 24 | void HttpServer::_on_recv_msg(const TcpConnSPtr &conn) { 25 | LOG_DEBUG("Recv Msg: {}", conn->GetConnName()); 26 | HttpContext ctx(http_parser_type::HTTP_REQUEST); 27 | HttpRequest req; 28 | // PS:buff里面的数据有多种情况: 29 | // 1,完整的http请求 30 | // 2,不完整的http请求 31 | // 3,多个http请求 32 | HttpContext::Status status = ctx.Parse(conn->GetBuffer().GetReadableAddr(), conn->GetBuffer().GetReadableSize(), req); 33 | if (status.ParseComplete()) { 34 | // 1,解析成功,那么消费掉对应的数据 35 | conn->GetBuffer().AdvanceReader(status.GetParsedLen()); 36 | 37 | // 2,根据url找到对应的处理函数 38 | HttpResponse resp; 39 | m_router.Routing(req, resp); 40 | 41 | // 3,回包给客户端 42 | conn->SendData(resp.SerializeToString()); 43 | } 44 | } 45 | 46 | void HttpServer::_on_send_complete(const TcpConnSPtr &conn) { 47 | LOG_DEBUG("Sent Complete: {}", conn->GetConnName()); 48 | } 49 | 50 | void HttpServer::_on_del_connection(const TcpConnSPtr &conn) { 51 | LOG_DEBUG("Remove Conn:{}", conn->GetConnName()); 52 | } 53 | 54 | void HttpServer::Run(const HttpRouter &router) { 55 | m_router = router; 56 | m_server.start(); 57 | } -------------------------------------------------------------------------------- /easy_net/protocol/http/http_server.h: -------------------------------------------------------------------------------- 1 | #ifndef __HTTP_SERVER_H 2 | #define __HTTP_SERVER_H 3 | 4 | #include "http_router.h" 5 | #include "tcp_server.h" 6 | 7 | namespace EasyNet { 8 | class HttpServer { 9 | public: 10 | HttpServer(const std::string &ip, size_t port); 11 | 12 | public: 13 | void Run(const HttpRouter &router); 14 | 15 | private: 16 | void _on_connection(const TcpConnSPtr &conn); 17 | void _on_recv_msg(const TcpConnSPtr &conn); 18 | void _on_send_complete(const TcpConnSPtr &conn); 19 | void _on_del_connection(const TcpConnSPtr &conn); 20 | 21 | private: 22 | TcpServer m_server; 23 | HttpRouter m_router; 24 | }; 25 | } // namespace EasyNet 26 | 27 | #endif -------------------------------------------------------------------------------- /easy_net/protocol/http/http_url.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_URL_H 2 | #define __EASYNET_URL_H 3 | 4 | #include 5 | #include 6 | 7 | namespace EasyNet { 8 | 9 | constexpr int KProtocolMinLen = 7; // http:// 10 | const std::string HTTP = "http"; 11 | const std::string HTTPS = "https"; 12 | 13 | typedef struct url_data { 14 | std::string protocol; // http, https 15 | std::string host; // www.hlllz.cn 16 | std::string port; // 80, 443 17 | std::string path; // /v1/api 18 | std::map query; // ?name=xxx&age=18 19 | std::string fragment; // #fragment 20 | } url_data_t; 21 | 22 | typedef enum url_state { 23 | URL_STATE_START = 0, 24 | URL_STATE_PROTOCOL, 25 | URL_STATE_HOST, 26 | URL_STATE_PORT, 27 | URL_STATE_PATH, 28 | URL_STATE_QUERY, 29 | URL_STATE_FRAGMENT, 30 | URL_STATE_END, 31 | } url_state_t; 32 | 33 | class HttpUrlParser { 34 | public: 35 | HttpUrlParser(const std::string &url) : m_url(url) {} 36 | bool Parse(); 37 | 38 | public: 39 | url_data_t url; 40 | 41 | private: 42 | std::string m_url; 43 | url_state_t m_state = URL_STATE_START; 44 | }; 45 | } // namespace EasyNet 46 | 47 | #endif // !__EASYNET_URL_H 48 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # echo 2 | # - echosvr 3 | add_executable(echosvr echo/echosvr.cpp) 4 | target_include_directories(echosvr PUBLIC ${CMAKE_SOURCE_DIR}/easy_net) 5 | target_link_libraries(echosvr ${EASYNET_LIB_NAME}) 6 | 7 | # - echocli 8 | add_executable(echocli echo/echocli.cpp) 9 | target_include_directories(echocli PUBLIC ${CMAKE_SOURCE_DIR}/easy_net) 10 | target_link_libraries(echocli ${EASYNET_LIB_NAME}) 11 | 12 | # timer 13 | add_executable(timer timer/timer.cpp) 14 | target_include_directories(timer PUBLIC ${CMAKE_SOURCE_DIR}/easy_net) 15 | target_link_libraries(timer ${EASYNET_LIB_NAME}) 16 | 17 | # http 18 | # -httpsvr 19 | # -httpcli 20 | if(WITH_HTTP) 21 | add_executable(httpsvr http/httpsvr.cpp) 22 | target_include_directories(httpsvr PUBLIC ${CMAKE_SOURCE_DIR}/easy_net) 23 | target_link_libraries(httpsvr ${EASYNET_LIB_NAME}) 24 | add_executable(httpcli http/httpcli.cpp) 25 | target_include_directories(httpcli PUBLIC ${CMAKE_SOURCE_DIR}/easy_net) 26 | target_link_libraries(httpcli ${EASYNET_LIB_NAME}) 27 | endif(WITH_HTTP) 28 | 29 | 30 | # tcp 31 | # - tcpsvr 32 | add_executable(tcpsvr tcp/tcpsvr.cpp) 33 | target_include_directories(tcpsvr PUBLIC ${CMAKE_SOURCE_DIR}/easy_net) 34 | target_link_libraries(tcpsvr ${EASYNET_LIB_NAME}) 35 | 36 | # - tcpcli 37 | add_executable(tcpcli tcp/tcpcli.cpp) 38 | target_include_directories(tcpcli PUBLIC ${CMAKE_SOURCE_DIR}/easy_net) 39 | target_link_libraries(tcpcli ${EASYNET_LIB_NAME}) 40 | 41 | # dns 42 | if(WITH_DNS) 43 | add_executable(dns dns/dns.cpp) 44 | target_include_directories(dns PUBLIC ${CMAKE_SOURCE_DIR}/easy_net) 45 | target_link_libraries(dns ${EASYNET_LIB_NAME}) 46 | endif(WITH_DNS) -------------------------------------------------------------------------------- /examples/dns/dns.cpp: -------------------------------------------------------------------------------- 1 | #include "dns.h" 2 | 3 | #include 4 | 5 | #include "log.h" 6 | 7 | using namespace EasyNet; 8 | 9 | int main() { 10 | LogInit(level::debug); 11 | auto dns = DNSResolver::CreateResolver(); 12 | dns->resolve("www.baidu.com", [](const std::vector& addr) { 13 | for (auto& ip : addr) { 14 | LOG_DEBUG("ip: {}", ip.SerializationToIpPort()); 15 | } 16 | }); 17 | return 0; 18 | } -------------------------------------------------------------------------------- /examples/echo/echocli.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "event_loop.h" 6 | #include "inet_addr.h" 7 | #include "log.h" 8 | #include "tcp_client.h" 9 | 10 | void sighandler(int signum) { 11 | std::cout << "Bye~" << std::endl; 12 | exit(1); 13 | } 14 | 15 | int main() { 16 | signal(SIGINT, sighandler); 17 | 18 | // 设置日志 19 | EasyNet::LogInit(EasyNet::level::debug); 20 | 21 | EasyNet::EventLoop loop("echocli_loop"); 22 | EasyNet::TcpClient cli(&loop, {"127.0.0.1", 8888}); 23 | 24 | cli.onNewConnection = ([](const EasyNet::TcpConnSPtr &conn) { 25 | LOG_DEBUG("Get New Conn"); 26 | std::string send_msg; 27 | std::cin >> send_msg; 28 | conn->SendData(send_msg); 29 | }); 30 | 31 | cli.onRecvMsg = ([](const EasyNet::TcpConnSPtr &conn) { 32 | auto msg = conn->GetBuffer().RetriveAllAsString(); 33 | LOG_DEBUG("recv msg={}", msg); 34 | std::string send_msg; 35 | std::cin >> send_msg; 36 | conn->SendData(send_msg); 37 | }); 38 | 39 | cli.onDelConnection = ([](const EasyNet::TcpConnSPtr &conn) { 40 | LOG_DEBUG("Remove Conn:{}", conn->GetConnName()); 41 | }); 42 | 43 | cli.onWriteComplete = ([](const EasyNet::TcpConnSPtr &conn) { 44 | LOG_DEBUG("Sent Complete: {}", conn->GetConnName()); 45 | }); 46 | 47 | cli.connect(); 48 | loop.Loop(); 49 | } -------------------------------------------------------------------------------- /examples/echo/echosvr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "inet_addr.h" 8 | #include "log.h" 9 | #include "tcp_server.h" 10 | 11 | // 后缀的参数只能是unsigned long long、long double、const char*或者const char* + size_t 12 | unsigned long long operator"" _s(unsigned long long s) { 13 | return s * 1000; 14 | } 15 | 16 | unsigned long long operator"" _ms(unsigned long long ms) { 17 | return ms; 18 | } 19 | 20 | std::atomic global_counter(0); 21 | 22 | void sighandler(int signum) { 23 | printf("\nCaught signal %d, coming out...\n", signum); 24 | std::cout << "global_counter=" << global_counter << std::endl; 25 | exit(1); 26 | } 27 | 28 | int main() { 29 | signal(SIGINT, sighandler); 30 | 31 | // 设置日志 32 | EasyNet::LogInit(EasyNet::level::debug); 33 | 34 | // 创建tcpsvr 35 | EasyNet::TcpServer svr("echosvr", 2 * std::thread::hardware_concurrency() - 1, {"127.0.0.1", 8888}); 36 | 37 | // 设置业务回调 38 | svr.onNewConnection = ([](const EasyNet::TcpConnSPtr &conn) { 39 | LOG_DEBUG("Get New Conn"); 40 | }); 41 | 42 | svr.onRecvMsg = ([](const EasyNet::TcpConnSPtr &conn) { 43 | // fixme-hl:出现回包为空的情况 44 | auto msg = conn->GetBuffer().RetriveAllAsString(); 45 | if (msg.empty()) { 46 | global_counter.fetch_add(1, std::memory_order_relaxed); 47 | } else { 48 | LOG_DEBUG("msg={}", msg); 49 | conn->SendData(msg); 50 | } 51 | }); 52 | 53 | svr.onDelConnection = ([](const EasyNet::TcpConnSPtr &conn) { 54 | LOG_DEBUG("Remove Conn:{}", conn->GetConnName()); 55 | }); 56 | 57 | svr.onWriteComplete = ([](const EasyNet::TcpConnSPtr &conn) { 58 | LOG_DEBUG("Sent Complete: {}", conn->GetConnName()); 59 | }); 60 | 61 | svr.get_main_loop()->TimerAfter([&] { 62 | svr.stop(); 63 | }, 64 | 10_s); 65 | 66 | // 启动服务 67 | svr.start(); 68 | } -------------------------------------------------------------------------------- /examples/http/httpcli.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "event_loop.h" 6 | #include "http_client.h" 7 | #include "http_request.h" 8 | #include "log.h" 9 | 10 | int main() { 11 | EasyNet::LogInit(EasyNet::level::trace); 12 | 13 | // 同步http客户端 14 | // EasyNet::SyncHttpClient sync_client("http://127.0.0.1:8888"); 15 | // auto rsp1 = sync_client.Get("/Hi"); 16 | // LOG_DEBUG("Recv rsp1:\n{}", rsp1.SerializeToString()); 17 | // auto rsp2 = sync_client.Get("/Hello"); 18 | // LOG_DEBUG("Recv rsp1:\n{}", rsp2.SerializeToString()); 19 | 20 | // 异步http客户端 21 | auto loop = std::make_shared("async_http_cli"); 22 | EasyNet::AsyncHttpClient async_client(loop, "http://127.0.0.1:8888"); 23 | auto get_req = std::make_shared(); 24 | get_req->m_method = "GET"; 25 | get_req->m_url = "/Hi"; 26 | async_client.AddRequest(get_req, [](const EasyNet::HttpResponse &rsp) { 27 | LOG_DEBUG("Recv rsp:\n{}", rsp.SerializeToString()); 28 | }); 29 | auto post_req = std::make_shared(); 30 | post_req->m_method = "POST"; 31 | post_req->m_url = "/Hello"; 32 | post_req->m_body = "Hello, World!"; 33 | post_req->m_headers.SetHeader("Content-Length", std::to_string(post_req->m_body.size())); 34 | post_req->m_headers.SetHeader("Content-Type", "text/plain"); 35 | async_client.AddRequest(post_req, [](const EasyNet::HttpResponse &rsp) { 36 | LOG_DEBUG("Recv rsp:\n{}", rsp.SerializeToString()); 37 | }); 38 | async_client.Run(); 39 | loop->Loop(); 40 | 41 | return 0; 42 | } -------------------------------------------------------------------------------- /examples/http/httpsvr.cpp: -------------------------------------------------------------------------------- 1 | #include "http_router.h" 2 | #include "http_server.h" 3 | #include "log.h" 4 | 5 | int main() { 6 | EasyNet::LogInit(EasyNet::level::trace); 7 | EasyNet::HttpRouter route; 8 | route.GET("/Hi", [](const EasyNet::HttpRequest &req, EasyNet::HttpResponse &res) { 9 | // 1,处理请求 10 | LOG_DEBUG("Recv Request:\n{}", req.SerializeToString()); 11 | 12 | // 2,回包 13 | res.SetStatusCode("200"); 14 | res.SetStatusCodeMsg("OK"); 15 | res.SetBody("Hi!"); 16 | }); 17 | route.POST("/Hello", [](const EasyNet::HttpRequest &req, EasyNet::HttpResponse &res) { 18 | // 1,处理请求 19 | LOG_DEBUG("Recv Request:\n{}", req.SerializeToString()); 20 | 21 | // fixme 22 | // 如果在这里需要发送http请求其他服务,应该怎么做? 23 | // 嵌套请求怎么处理比较优雅? AB、AC、AD 24 | // cli -->[A --> B --> C --> D]--> cli 25 | 26 | // 2,回包 27 | res.SetStatusCode("200"); 28 | res.SetStatusCodeMsg("OK"); 29 | res.SetBody("Hello!"); 30 | }); 31 | 32 | EasyNet::HttpServer svr("127.0.0.1", 8888); 33 | svr.Run(route); 34 | } -------------------------------------------------------------------------------- /examples/tcp/tcpcli.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "event_loop.h" 6 | #include "inet_addr.h" 7 | #include "log.h" 8 | #include "tcp_client.h" 9 | 10 | void sighandler(int signum) { 11 | std::cout << "Bye~" << std::endl; 12 | exit(1); 13 | } 14 | 15 | int main() { 16 | signal(SIGINT, sighandler); 17 | 18 | // 设置日志 19 | EasyNet::LogInit(EasyNet::level::debug); 20 | 21 | EasyNet::EventLoop loop("tcpcli_loop"); 22 | EasyNet::TcpClient cli(&loop, {"127.0.0.1", 8888}); 23 | 24 | cli.onNewConnection = ([](const EasyNet::TcpConnSPtr &conn) { 25 | LOG_DEBUG("Get New Conn"); 26 | std::string send_msg; 27 | std::cin >> send_msg; 28 | conn->SendData(send_msg); 29 | }); 30 | 31 | cli.onRecvMsg = ([](const EasyNet::TcpConnSPtr &conn) { 32 | auto msg = conn->GetBuffer().RetriveAllAsString(); 33 | LOG_DEBUG("recv msg={}", msg); 34 | }); 35 | 36 | cli.onDelConnection = ([](const EasyNet::TcpConnSPtr &conn) { 37 | LOG_DEBUG("Remove Conn:{}", conn->GetConnName()); 38 | }); 39 | 40 | cli.onWriteComplete = ([](const EasyNet::TcpConnSPtr &conn) { 41 | LOG_DEBUG("Sent Complete: {}", conn->GetConnName()); 42 | }); 43 | 44 | cli.connect(); 45 | loop.Loop(); 46 | } -------------------------------------------------------------------------------- /examples/tcp/tcpsvr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "inet_addr.h" 8 | #include "log.h" 9 | #include "tcp_server.h" 10 | 11 | std::atomic global_counter(0); 12 | 13 | void sighandler(int signum) { 14 | printf("\nCaught signal %d, coming out...\n", signum); 15 | std::cout << "global_counter=" << global_counter << std::endl; 16 | exit(1); 17 | } 18 | 19 | int main() { 20 | signal(SIGINT, sighandler); 21 | 22 | // 设置日志 23 | EasyNet::LogInit(EasyNet::level::trace); 24 | 25 | // 创建tcpsvr 26 | EasyNet::TcpServer svr("tcpsvr", 2 * std::thread::hardware_concurrency() - 1, {"127.0.0.1", 8888}); 27 | 28 | // 设置业务回调 29 | svr.onNewConnection = ([](const EasyNet::TcpConnSPtr &conn) { 30 | LOG_DEBUG("{}: Get New Conn", conn->GetConnName()); 31 | }); 32 | 33 | svr.onRecvMsg = ([](const EasyNet::TcpConnSPtr &conn) { 34 | auto msg = conn->GetBuffer().RetriveAllAsString(); 35 | if (msg.empty()) { 36 | global_counter.fetch_add(1, std::memory_order_relaxed); 37 | } else { 38 | LOG_DEBUG("{}: msg={}", conn->GetConnName(), msg); 39 | conn->SendData(msg); 40 | } 41 | }); 42 | 43 | svr.onDelConnection = ([](const EasyNet::TcpConnSPtr &conn) { 44 | LOG_DEBUG("{}: Remove Conn", conn->GetConnName()); 45 | }); 46 | 47 | svr.onWriteComplete = ([](const EasyNet::TcpConnSPtr &conn) { 48 | LOG_DEBUG("{}: Sent Complete", conn->GetConnName()); 49 | }); 50 | 51 | // 启动服务 52 | svr.start(); 53 | } -------------------------------------------------------------------------------- /examples/timer/timer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "event_loop.h" 4 | #include "log.h" 5 | 6 | unsigned long long operator"" _s(unsigned long long s) { 7 | return s * 1000; 8 | } 9 | 10 | unsigned long long operator"" _ms(unsigned long long ms) { 11 | return ms; 12 | } 13 | 14 | int main() { 15 | EasyNet::LogInit(EasyNet::level::debug); 16 | EasyNet::EventLoop loop("timer_loop"); 17 | 18 | auto t1 = loop.TimerAfter([]() { 19 | LOG_DEBUG("TimerAfter 2s"); 20 | }, 21 | 2_s); 22 | loop.CancelTimer(t1); 23 | 24 | auto t2 = loop.TimerEvery([]() { 25 | LOG_DEBUG("TimerEvery 2s"); 26 | sleep(3); 27 | }, 28 | 2_s); 29 | 30 | auto t3 = loop.TimerEvery([]() { 31 | LOG_DEBUG("TimerEvery 3s"); 32 | sleep(1); 33 | }, 34 | 3_s); 35 | 36 | loop.Loop(); 37 | } -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # You can customize the clang-format path by setting the CLANG_FORMAT environment variable 4 | CLANG_FORMAT=${CLANG_FORMAT:-clang-format} 5 | 6 | find easy_net examples raw_examples test -name *.h -o -name *.cpp -exec dos2unix {} \; 7 | find easy_net examples raw_examples test \( -name *.h -o -name *.cpp \) -not -name http_parser.h -not -name httplib.h | xargs $CLANG_FORMAT -i -style=file -------------------------------------------------------------------------------- /raw_examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # async_http 2 | add_executable(async_http async_http/async_http.cpp) 3 | target_link_libraries(async_http pthread) 4 | 5 | # cpp-httplib 6 | add_executable(cpp-httplib cpp-httplib/httplib_client_test.cpp) 7 | target_link_libraries(cpp-httplib pthread) 8 | 9 | # epoll 10 | add_executable(epoll_cli epoll/epoll_cli.cpp) 11 | target_link_libraries(epoll_cli pthread) 12 | add_executable(epoll_svr epoll/epoll_svr.cpp) 13 | target_link_libraries(epoll_svr pthread) 14 | 15 | # eventfd 16 | add_executable(eventfd eventfd/eventfd_demo.cpp) 17 | target_link_libraries(eventfd pthread) 18 | 19 | # http_parser 20 | add_executable(http_parser http_parser/http_parser_demo.cpp http_parser/http_parser.c) 21 | target_link_libraries(http_parser pthread) 22 | 23 | # noblock_connect 24 | add_executable(noblocking_connect noblocking_connect/noblocking_connect.cpp) 25 | target_link_libraries(noblocking_connect pthread) 26 | 27 | # pipe_notifier 28 | add_executable(pipe_notifier pipe_notifier/pipe.cpp pipe_notifier/notifier.cpp) 29 | target_link_libraries(pipe_notifier pthread) 30 | 31 | # reuseport 32 | add_executable(reuseport reuseport/server.cpp) 33 | target_link_libraries(reuseport pthread) 34 | 35 | # self-pipe-trick 36 | add_executable(self-pipe-trick self-pipe-trick/self-pipe-trick.cpp) 37 | target_link_libraries(self-pipe-trick pthread) 38 | 39 | # signalfd 40 | add_executable(signalfd signalfd/signalfd_demo.cpp) 41 | target_link_libraries(signalfd pthread) 42 | 43 | # thread_pool 44 | add_executable(thread_pool thread_pool/main.cpp) 45 | target_link_libraries(thread_pool pthread) 46 | 47 | # timer 48 | add_executable(timer_demo timer/timer_demo.cpp) 49 | target_link_libraries(timer_demo pthread) 50 | 51 | # timerfd 52 | add_executable(timerfd timerfd/timerfd_demo.cpp) 53 | target_link_libraries(timerfd pthread) 54 | 55 | # unix_domain 56 | add_executable(unix_domain_cli unix_domain/client.cpp) 57 | target_link_libraries(unix_domain_cli pthread) 58 | add_executable(unix_domain_svr unix_domain/server.cpp) 59 | target_link_libraries(unix_domain_svr pthread) 60 | 61 | # c-ares 62 | find_package(c-ares) 63 | if(c-ares_FOUND) 64 | add_executable(c-ares c-ares/main.cpp) 65 | target_link_libraries(c-ares pthread c-ares::cares_static) 66 | target_link_directories(c-ares PRIVATE "${c-ares_INCLUDE_DIR}") 67 | endif() 68 | 69 | # udp 70 | add_executable(udp_cli udp/udpcli.cpp) 71 | target_link_libraries(udp_cli pthread) 72 | add_executable(udp_svr udp/udpsvr.cpp) 73 | target_link_libraries(udp_svr pthread) -------------------------------------------------------------------------------- /raw_examples/c-ares/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "cares.h" 3 | #include "posix.h" 4 | #include "time_cost.h" 5 | 6 | // 使用cares库实现域名解析 7 | void resolve1() { 8 | ScopedTimer timer("resolve1"); 9 | char strIP[INET_ADDRSTRLEN] = {0}; 10 | struct sockaddr_in sa = {}; 11 | std::string domain = "hlllz.cn"; 12 | int timeout_ms = 1000; 13 | DNSResolver dr; 14 | if (dr) { 15 | auto ret = dr.lookup(domain, timeout_ms, &sa.sin_addr.s_addr, sizeof(sa.sin_addr.s_addr)); 16 | if (0 != ret) { 17 | printf("resolve ret(%d) err(%s)\n", ret, dr.error_info().c_str()); 18 | return; 19 | } 20 | inet_ntop(AF_INET, &(sa.sin_addr), strIP, INET_ADDRSTRLEN); 21 | printf("strIP=%s\n", strIP); 22 | } else { 23 | printf("dns_resolver_t init err(%s)\n", dr.error_info().c_str()); 24 | return; 25 | } 26 | } 27 | 28 | // 使用POSIX接口实现域名解析 29 | void resolve2() { 30 | ScopedTimer timer("resolve2"); 31 | posix("hlllz.cn"); 32 | } 33 | 34 | int main() { 35 | resolve1(); 36 | resolve2(); 37 | } -------------------------------------------------------------------------------- /raw_examples/c-ares/posix.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_POSIX_H 2 | #define __EASYNET_POSIX_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | inline void posix(std::string domain = "hlllz.cn") { 12 | char strIP[INET_ADDRSTRLEN] = {0}; 13 | struct addrinfo* addr; 14 | int result = getaddrinfo(domain.c_str(), NULL, NULL, &addr); 15 | if (result != 0) { 16 | printf("Error from getaddrinfo: %s\n", gai_strerror(result)); 17 | } 18 | struct sockaddr_in* psa = (struct sockaddr_in*)addr->ai_addr; 19 | inet_ntop(AF_INET, &(psa->sin_addr), strIP, INET_ADDRSTRLEN); 20 | freeaddrinfo(addr); 21 | printf("Found address: %s\n", strIP); 22 | } 23 | #endif // !__EASYNET_POSIX_H 24 | -------------------------------------------------------------------------------- /raw_examples/c-ares/time_cost.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_TIME_COST_H 2 | #define __EASYNET_TIME_COST_H 3 | 4 | #include 5 | #include 6 | 7 | class ScopedTimer { 8 | public: 9 | ScopedTimer(const char* name) : m_name(name), m_begin(std::chrono::high_resolution_clock::now()) {} 10 | ~ScopedTimer() { 11 | auto end = std::chrono::high_resolution_clock::now(); 12 | auto dur = std::chrono::duration_cast(end - m_begin); 13 | std::cout << m_name << " : " << dur.count() << " ns\n"; 14 | } 15 | 16 | private: 17 | const char* m_name; 18 | std::chrono::time_point m_begin; 19 | }; 20 | 21 | #endif // !__EASYNET_TIME_COST_H 22 | -------------------------------------------------------------------------------- /raw_examples/cpp-httplib/httplib_client_test.cpp: -------------------------------------------------------------------------------- 1 | #include "httplib.h" 2 | 3 | using namespace httplib; 4 | 5 | int main() { 6 | httplib::Client cli("localhost", 12345); 7 | httplib::Headers headers{ 8 | {"Accept-Encoding", "gzip, deflate"}, 9 | {"Holo_wo", "hhhhh"}}; 10 | std::string body; 11 | if (auto res = cli.Get("/hi", headers)) { 12 | if (res->status == 200) { 13 | std::cout << res->body << std::endl; 14 | } 15 | } else { 16 | auto err = res.error(); 17 | std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; 18 | } 19 | } -------------------------------------------------------------------------------- /raw_examples/epoll/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HLhuanglang/eNET/4cf85828a467273b1500615a4ddf0de931e3fdce/raw_examples/epoll/readme.md -------------------------------------------------------------------------------- /raw_examples/eventfd/eventfd_demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using msg_type_t = std::uint64_t; 13 | 14 | void handle_sig(int) { 15 | std::cout << "bye~" << std::endl; 16 | exit(0); 17 | } 18 | 19 | void thread_func(int fd) { 20 | int ep_fd = ::epoll_create1(0); 21 | std::vector ready_events(16); 22 | struct epoll_event ev; 23 | ev.events = EPOLLIN; 24 | ev.data.fd = fd; 25 | epoll_ctl(ep_fd, EPOLL_CTL_ADD, fd, &ev); 26 | for (;;) { 27 | int nfd = ::epoll_wait(ep_fd, &*ready_events.begin(), static_cast(ready_events.size()), 1 * 1000); 28 | if (nfd > 0) { 29 | for (int i = 0; i < nfd; i++) { 30 | if (ready_events[i].events & (EPOLLHUP | EPOLLERR)) { 31 | // todo 32 | } 33 | if (ready_events[i].events & EPOLLIN) { 34 | msg_type_t msg; 35 | int ret = ::read(ready_events[i].data.fd, &msg, sizeof(msg_type_t)); 36 | if (ret == -1) { 37 | perror("eventfd write"); 38 | } else { 39 | std::cout << "reciv main thread notify!" << std::endl; 40 | } 41 | } 42 | } 43 | } else if (nfd == 0) { 44 | // nothing happen 45 | } else { 46 | // error happen 47 | } 48 | } 49 | } 50 | 51 | int main(int argc, char* argv[]) { 52 | signal(SIGINT, handle_sig); 53 | int event_fd = ::eventfd(0, EFD_NONBLOCK); 54 | std::thread t(thread_func, event_fd); 55 | for (int i = 0; i < 5; i++) { 56 | msg_type_t msg = 1; // 必须赋值,否则无法触发. 57 | int ret = ::write(event_fd, &msg, sizeof(msg_type_t)); 58 | if (ret == -1) { 59 | perror("eventfd write"); 60 | } else { 61 | std::cout << "send signal to sub thread!" << std::endl; 62 | } 63 | std::this_thread::sleep_for(std::chrono::seconds(1)); 64 | } 65 | t.join(); 66 | } -------------------------------------------------------------------------------- /raw_examples/pipe_notifier/notifier.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "notify.h" 7 | 8 | notifier::notifier() { 9 | int fd[2]; 10 | int r = pipe(fd); 11 | assert(!r); 12 | m_notifier = fd[1]; 13 | m_reciver = fd[0]; 14 | } 15 | 16 | void notifier::wakeup() const { 17 | uint64_t msg = 1; 18 | ::write(m_notifier, &msg, sizeof(msg)); 19 | } 20 | 21 | void notifier::handle_read() const { 22 | uint64_t msg = 1; 23 | ::read(m_reciver, &msg, sizeof(msg)); 24 | } -------------------------------------------------------------------------------- /raw_examples/pipe_notifier/notify.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_NOTIFY_H 2 | #define __EASYNET_NOTIFY_H 3 | 4 | class notifier { 5 | public: 6 | notifier(); 7 | void handle_read() const; // 通常情况下是封装好,在事件循环中使用 8 | 9 | int get_reciver() const { return m_reciver; } 10 | 11 | public: 12 | void wakeup() const; 13 | 14 | private: 15 | int m_notifier; // pipe[1] 16 | int m_reciver; // pipe[0] 17 | }; 18 | 19 | #endif // !__EASYNET_NOTIFY_H 20 | -------------------------------------------------------------------------------- /raw_examples/pipe_notifier/pipe.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "notify.h" 12 | 13 | void do_some_work() { 14 | std::cout << time(nullptr) << " :hello,world!" << std::endl; 15 | } 16 | 17 | int main() { 18 | notifier notify; 19 | 20 | std::thread t([&]() { 21 | sleep(17); 22 | notify.wakeup(); 23 | }); 24 | t.detach(); 25 | 26 | int epfd = ::epoll_create1(0); 27 | 28 | // 将notifier注册到epoll中 29 | struct epoll_event ev; 30 | ev.events = EPOLLIN; 31 | ev.data.fd = notify.get_reciver(); 32 | epoll_ctl(epfd, EPOLL_CTL_ADD, notify.get_reciver(), &ev); 33 | 34 | struct epoll_event ee[1024]; 35 | while (true) { 36 | int n = epoll_wait(epfd, ee, 1024, 5 * 1000); 37 | if (n < 0 && errno == EINTR) { 38 | continue; 39 | } 40 | for (int i = 0; i < n; i++) { 41 | if (ee[i].events & (EPOLLHUP | EPOLLERR)) { 42 | // todo 43 | } 44 | if (ee[i].events & EPOLLIN) { 45 | if (ee[i].data.fd == notify.get_reciver()) { 46 | notify.handle_read(); 47 | } 48 | } 49 | } 50 | do_some_work(); 51 | } 52 | 53 | return 0; 54 | } -------------------------------------------------------------------------------- /raw_examples/reuseport/server.cpp: -------------------------------------------------------------------------------- 1 | // 测试:telnet 127.0.0.1 12345 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | const int k_max_threads = 4; 13 | const int k_port = 12345; 14 | 15 | void worker(int sock) { 16 | char buf[1024]; 17 | while (true) { 18 | int n = recv(sock, buf, sizeof(buf), 0); 19 | if (n <= 0) { 20 | break; 21 | } 22 | std::cout << "Recv:" << buf << std::endl; 23 | send(sock, buf, n, 0); 24 | } 25 | close(sock); 26 | } 27 | 28 | int main() { 29 | int listen_sock = socket(AF_INET, SOCK_STREAM, 0); 30 | if (listen_sock < 0) { 31 | std::cerr << "Failed to create socket" << std::endl; 32 | return -1; 33 | } 34 | 35 | int optval = 1; 36 | if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)) < 0) { 37 | std::cerr << "Failed to set SO_REUSEPORT option" << std::endl; 38 | return -1; 39 | } 40 | 41 | sockaddr_in addr; 42 | std::memset(&addr, 0, sizeof(addr)); 43 | addr.sin_family = AF_INET; 44 | addr.sin_port = htons(k_port); 45 | addr.sin_addr.s_addr = INADDR_ANY; 46 | 47 | if (bind(listen_sock, (sockaddr *)&addr, sizeof(addr)) < 0) { 48 | std::cerr << "Failed to bind to port " << k_port << std::endl; 49 | return -1; 50 | } 51 | 52 | if (listen(listen_sock, SOMAXCONN) < 0) { 53 | std::cerr << "Failed to listen on socket" << std::endl; 54 | return -1; 55 | } 56 | 57 | std::vector threads; 58 | threads.reserve(k_max_threads); 59 | for (int i = 0; i < k_max_threads; ++i) { 60 | threads.emplace_back([i, &listen_sock]() { 61 | while (true) { 62 | std::cout << "waiting..." << i << std::endl; 63 | int sock = accept(listen_sock, nullptr, nullptr); 64 | std::cout << "working..." << i << std::endl; 65 | if (sock < 0) { 66 | break; 67 | } 68 | worker(sock); 69 | } 70 | }); 71 | } 72 | 73 | for (auto &thread : threads) { 74 | thread.join(); 75 | } 76 | 77 | close(listen_sock); 78 | return 0; 79 | } -------------------------------------------------------------------------------- /raw_examples/self-pipe-trick/self-pipe-trick.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void signal_handler(int signum) { 5 | printf("Received signal %d\n", signum); 6 | } 7 | 8 | int main() { 9 | struct sigaction sa; 10 | sa.sa_handler = signal_handler; 11 | sigfillset(&sa.sa_mask); 12 | sa.sa_flags = 0; 13 | sigaction(SIGINT, &sa, nullptr); 14 | sigaction(SIGTERM, &sa, nullptr); 15 | sigaction(SIGQUIT, &sa, nullptr); 16 | // 在这里注册所有需要处理的信号 17 | 18 | while (true) { 19 | // 等待信号 20 | } 21 | 22 | return 0; 23 | } -------------------------------------------------------------------------------- /raw_examples/signalfd/signalfd_demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define handle_error(msg) \ 10 | do { \ 11 | perror(msg); \ 12 | exit(EXIT_FAILURE); \ 13 | } while (0) 14 | 15 | int main() { 16 | sigset_t mask; 17 | int sfd; 18 | struct signalfd_siginfo fdsi; 19 | ssize_t s; 20 | 21 | sigemptyset(&mask); 22 | sigaddset(&mask, SIGINT); 23 | sigaddset(&mask, SIGQUIT); 24 | 25 | /* Block signals so that they aren't handled 26 | according to their default dispositions */ 27 | 28 | if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) { 29 | handle_error("sigprocmask"); 30 | } 31 | 32 | sfd = signalfd(-1, &mask, 0); 33 | if (sfd == -1) { 34 | handle_error("signalfd"); 35 | } 36 | 37 | for (;;) { 38 | s = read(sfd, &fdsi, sizeof(struct signalfd_siginfo)); 39 | if (s != sizeof(struct signalfd_siginfo)) { 40 | handle_error("read"); 41 | } 42 | 43 | if (fdsi.ssi_signo == SIGINT) { 44 | printf("Got SIGINT\n"); 45 | } else if (fdsi.ssi_signo == SIGQUIT) { 46 | printf("Got SIGQUIT\n"); 47 | exit(EXIT_SUCCESS); 48 | } else { 49 | printf("Read unexpected signal\n"); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /raw_examples/thread_pool/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "msg.h" 7 | #include "thread_pool.h" 8 | 9 | int main() { 10 | thread_pool pool(100, 2, nullptr, nullptr); 11 | 12 | for (int i = 0; i < 10; i++) { 13 | sleep(5); 14 | test_msg msg; 15 | msg.set_msg("hello,world! " + std::to_string(i)); 16 | pool.commit(msg); // 主线程投递信息,工作线程应该被唤醒才对 17 | std::cout << msg << std::endl; 18 | } 19 | } -------------------------------------------------------------------------------- /raw_examples/thread_pool/msg.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_MSG_H 2 | #define __EASYNET_MSG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class test_msg { 10 | public: 11 | test_msg() = default; 12 | test_msg(test_msg &&msg) = default; 13 | test_msg(const test_msg &msg) = default; 14 | test_msg &operator=(test_msg &&) = default; 15 | 16 | public: 17 | void set_msg(std::string msg) { 18 | this->m_msg = std::move(msg); 19 | } 20 | void consume() { 21 | std::cout << "consume: " << m_msg << " " << std::this_thread::get_id() << std::endl; 22 | } 23 | 24 | friend std::ostream &operator<<(std::ostream &os, const test_msg &msg) { 25 | os << "{ " << msg.m_msg << " }"; 26 | return os; 27 | } 28 | 29 | private: 30 | std::string m_msg; 31 | }; 32 | 33 | #endif // !__EASYNET_MSG_H 34 | -------------------------------------------------------------------------------- /raw_examples/thread_pool/thread_pool.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "msg.h" 9 | #include "msg_qunue.h" 10 | 11 | class thread_pool { 12 | public: 13 | thread_pool(size_t max_items, size_t thread_cnt, const std::function &on_thread_start, const std::function &on_thread_stop) : m_msg_queue(max_items) { 14 | auto cpu_cnt = std::thread::hardware_concurrency(); 15 | if (thread_cnt == 0 || thread_cnt > cpu_cnt * 2) { 16 | std::cerr << "thread_cnt " << thread_cnt << " is invalid: (0," << cpu_cnt * 2 << "]" << std::endl; 17 | exit(-1); 18 | } 19 | for (int i = 0; i < thread_cnt; i++) { 20 | m_threads.emplace_back([this, on_thread_start, on_thread_stop] { 21 | if (on_thread_start) { 22 | on_thread_start(); 23 | } 24 | this->_work_loop(); 25 | if (on_thread_stop) { 26 | on_thread_stop(); 27 | } 28 | }); 29 | } 30 | } 31 | 32 | ~thread_pool() { 33 | for (int i = 0; i < m_threads.size(); i++) { 34 | for (auto &it : m_threads) { 35 | it.join(); // 主线程需等待子线程执行完成 36 | } 37 | } 38 | } 39 | 40 | public: 41 | void commit(test_msg msg) { 42 | m_msg_queue.enqueue(std::move(msg)); // 提交任务到线程池,并唤醒一个线程 43 | } 44 | 45 | private: 46 | void _work_loop() { 47 | while (_prcess_next_msg()) { 48 | // 确保工作线程只在线程池释放或者主动设置关闭时才结束 49 | }; 50 | } 51 | 52 | bool _prcess_next_msg() { 53 | // fixme:增加不同类型的消息,有些任务使得线程执行完了就退出 54 | test_msg msg; 55 | m_msg_queue.dequeue(msg); // 阻塞在这里,等待生产者投递消息 56 | msg.consume(); 57 | return true; 58 | } 59 | 60 | private: 61 | std::vector m_threads; 62 | msg_queue m_msg_queue; 63 | }; -------------------------------------------------------------------------------- /raw_examples/timer/miniheap.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_MINIHEAP_H 2 | #define __EASYNET_MINIHEAP_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "timer.h" 12 | 13 | // 基于最小堆的定时器 14 | // 堆顶一顶是即将过期的定时器 15 | // 1s 16 | // /\ 17 | // 2s 3s 18 | // /\ \ 19 | // 4s 5s 6s 20 | 21 | class compare { 22 | public: 23 | bool operator()(const timer &a, const timer &b) { 24 | return a > b; 25 | } 26 | }; 27 | 28 | class miniheap_timer : public timer_policy, compare>> { 29 | public: 30 | std::priority_queue, compare> &get_timers() override { 31 | return m_timers; 32 | } 33 | 34 | int find_timer() override { 35 | if (m_timers.empty()) { 36 | return 1 * 1000; // 默认1s,如果直接返回0,会导致epoll_wait立即返回,整个进程的CPU占用率会很高 37 | } 38 | return m_timers.top().get_expired_time(); // 不能使用get_interval,因为get_interval是下一次超时的时间间隔,而不是当前的实际超时时间 39 | } 40 | 41 | void add_timer(int tm, timer_type type, const timer::timer_cb &cb) override { 42 | m_timers.emplace(_gen_timer_id(), type, cb, tm); // 插入元素时,排序的规则是根据过期时间来进行判断的 43 | } 44 | 45 | void handle_expired_timer() override { 46 | while (!m_timers.empty()) { 47 | auto t = m_timers.top(); 48 | t.update_curr_time(); 49 | if (t.is_expired()) { 50 | switch (t.get_type()) { 51 | case timer_type::E_EVERY: { 52 | t.run_task(); 53 | t.get_next_expired_time(); 54 | m_timers.pop(); 55 | m_timers.push(t); 56 | break; 57 | } 58 | case timer_type::E_AFTER: { 59 | t.run_task(); 60 | m_timers.pop(); 61 | break; 62 | } 63 | case timer_type::E_AT: 64 | // TODO 65 | break; 66 | } 67 | } else { 68 | return; 69 | } 70 | } 71 | } 72 | }; 73 | 74 | #endif // !__EASYNET_MINIHEAP_H 75 | -------------------------------------------------------------------------------- /raw_examples/timer/some_time_func.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_SOME_TIME_FUNC_H 2 | #define __EASYNET_SOME_TIME_FUNC_H 3 | 4 | #include //gettimeofday() 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | // 时间换算关系 11 | // 1s[second] = 1000ms[millisecond ] = 1000,000us[microsecond] = 1000,000,000ns[nanosecond] 12 | inline void some_function_to_get_curr_time() { 13 | // (bt) time()函数,C库函数,精度秒,返回自1970年1月1日以来经过的秒数 14 | // https://man7.org/linux/man-pages/man2/time.2.html 15 | time_t t; 16 | t = time(nullptr); 17 | std::cout << t << std::endl; 18 | 19 | // ftime()函数,精度毫秒。C库函数,不过已经被弃用了 20 | // https://man7.org/linux/man-pages/man3/ftime.3.html 21 | // 22 | // 23 | 24 | // (bt)gettimeofday(), 精度微妙,C库函数 25 | // https://man7.org/linux/man-pages/man2/settimeofday.2.html 26 | timeval tv; 27 | gettimeofday(&tv, nullptr); 28 | std::cout << tv.tv_sec << "s " << tv.tv_usec << "us " << std::endl; 29 | 30 | // (bt)clock_gettime(), 精度纳秒,C库函数 31 | // https://man7.org/linux/man-pages/man2/clock_gettime.2.html 32 | timespec ts; 33 | clock_gettime(CLOCK_REALTIME, &ts); 34 | std::cout << ts.tv_sec << "s " << ts.tv_nsec << "ns " << std::endl; 35 | 36 | // Time Stamp Counter 37 | // https://stackoverflow.com/questions/42436059/using-time-stamp-counter-to-get-the-time-stamp 38 | // 39 | // 40 | } 41 | 42 | inline void some_function_to_impl_timer() { 43 | // sleep(), 精度秒,C库函数 44 | // https://man7.org/linux/man-pages/man3/sleep.3.html 45 | sleep(5); 46 | 47 | // usleep(), 精度微妙,C库函数 48 | // 49 | usleep(1000); 50 | 51 | // nanosleep(), 精度纳秒,C库函数 52 | timespec ts; 53 | ts.tv_sec = 1; 54 | ts.tv_nsec = 0; 55 | nanosleep(&ts, nullptr); 56 | } 57 | 58 | #endif // !__EASYNET_SOME_TIME_FUNC_H 59 | -------------------------------------------------------------------------------- /raw_examples/timer/timer_demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "miniheap.h" 6 | #include "timer.h" 7 | 8 | // 后缀的参数只能是unsigned long long、long double、const char*或者const char* + size_t 9 | unsigned long long operator"" _s(unsigned long long s) { 10 | return s * 1000; 11 | } 12 | 13 | unsigned long long operator"" _ms(unsigned long long ms) { 14 | return ms; 15 | } 16 | 17 | int main() { 18 | // 基于有序链表的定时器 19 | timer_manager tm; 20 | 21 | tm.run_after(10_s, [](int) { 22 | std::cout << "10s" << std::endl; 23 | }); 24 | 25 | tm.run_every(1_s, [](int) { 26 | std::cout << "1s" << std::endl; 27 | sleep(2); 28 | }); 29 | 30 | tm.run_every(5_s, [](int) { 31 | std::cout << "5s" << std::endl; 32 | }); 33 | 34 | tm.start(); 35 | } -------------------------------------------------------------------------------- /raw_examples/timer/timewheel.h: -------------------------------------------------------------------------------- 1 | #ifndef __EASYNET_TIMEWHEEL_H 2 | #define __EASYNET_TIMEWHEEL_H 3 | 4 | class timewheel_timer {}; 5 | 6 | #endif // !__EASYNET_TIMEWHEEL_H 7 | -------------------------------------------------------------------------------- /raw_examples/timerfd/timerfd_demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | void handle_sig(int) { 13 | std::cout << "bye~" << std::endl; 14 | exit(0); 15 | } 16 | 17 | int main(int argc, char *argv[]) { 18 | signal(SIGINT, handle_sig); 19 | int timer_fd = ::timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK); 20 | struct itimerspec newtimer; 21 | memset(&newtimer, 0, sizeof(itimerspec)); 22 | struct timespec now; 23 | clock_gettime(CLOCK_REALTIME, &now); 24 | std::cout << "timer begin:" << now.tv_sec << std::endl; 25 | // 程序运行后第一次超时时间(用这个作为定时器时间即可) 26 | newtimer.it_value.tv_nsec = 0; 27 | newtimer.it_value.tv_sec = 5; 28 | // 之后每次超时间隔(不使用) 29 | // newtimer.it_interval.tv_nsec = 0; 30 | // newtimer.it_interval.tv_sec = 5; 31 | 32 | int ret = ::timerfd_settime(timer_fd, 0, &newtimer, nullptr); 33 | 34 | int ep_fd = epoll_create(1); // 创建epoll实例对象 35 | struct epoll_event ev; 36 | std::vector ready_events(16); 37 | ev.data.fd = timer_fd; 38 | ev.events = EPOLLIN | EPOLLET; 39 | epoll_ctl(ep_fd, EPOLL_CTL_ADD, timer_fd, &ev); // 添加到epoll事件集合 40 | for (;;) { 41 | int nfd = epoll_wait(ep_fd, &*ready_events.begin(), static_cast(ready_events.size()), 0); 42 | if (nfd > 0) { 43 | for (int i = 0; i < nfd; i++) { 44 | if (ready_events[i].events & EPOLLIN) { 45 | if (ready_events[i].data.fd == timer_fd) { 46 | // 如果不读取数据,之后epoll_wait会阻塞,但是可以采取重新设置的方式来设定超时时间 47 | // uint64_t exp; 48 | // uint64_t s = read(ready_events[i].data.fd, &exp, sizeof(uint64_t)); 49 | // assert(s == sizeof(uint64_t)); 50 | clock_gettime(CLOCK_REALTIME, &now); 51 | std::cout << "time:" << now.tv_sec << std::endl; 52 | 53 | newtimer.it_value.tv_sec = 5; 54 | ::timerfd_settime(timer_fd, 0, &newtimer, nullptr); 55 | } 56 | } 57 | } 58 | } else if (nfd == 0) { 59 | // nothing happen 60 | } else { 61 | // error happen 62 | } 63 | } 64 | close(timer_fd); 65 | close(ep_fd); 66 | 67 | return 0; 68 | } -------------------------------------------------------------------------------- /raw_examples/udp/udpcli.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * 编译: g++ udp_client.cc -o udp_client 3 | * 运行: ./udp_client port 字符串 4 | * 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | using namespace std; 19 | 20 | #define exit_if(r, ...) \ 21 | if (r) { \ 22 | printf(__VA_ARGS__); \ 23 | printf("%s:%d error no: %d error msg %s\n", __FILE__, __LINE__, errno, strerror(errno)); \ 24 | exit(1); \ 25 | } 26 | std::string serverIp = "127.0.0.1"; 27 | int serverPort = 8080; 28 | 29 | int main(int argc, const char *argv[]) { 30 | if (argc < 3) { 31 | printf("usage: ./udp_client client_port sendSomething"); 32 | return 0; 33 | } 34 | unsigned short local_port = atoi(argv[1]); 35 | // udp 36 | int fd = socket(AF_INET, SOCK_DGRAM, 0); 37 | exit_if(fd < 0, "socket failed"); 38 | struct sockaddr_in local_addr; 39 | memset(&local_addr, 0, sizeof local_addr); 40 | local_addr.sin_family = AF_INET; 41 | local_addr.sin_port = htons(local_port); 42 | local_addr.sin_addr.s_addr = INADDR_ANY; 43 | int r = ::bind(fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr)); 44 | exit_if(r, "bind to 0.0.0.0:%d failed %d %s", local_port, errno, strerror(errno)); 45 | 46 | struct sockaddr_in other_addr; 47 | memset(&other_addr, 0, sizeof other_addr); 48 | other_addr.sin_family = AF_INET; 49 | other_addr.sin_port = htons(serverPort); 50 | other_addr.sin_addr.s_addr = inet_addr(serverIp.c_str()); 51 | 52 | int i = 2; 53 | while (1) { 54 | int r = sendto(fd, argv[2], sizeof(argv[2]), 0, (struct sockaddr *)&other_addr, sizeof(other_addr)); 55 | exit_if(r < 0, "sendto error"); 56 | 57 | struct sockaddr_in other; 58 | socklen_t rsz = sizeof(other); 59 | char recv_buf[1024]; 60 | int recv_sive = recvfrom(fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&other, &rsz); 61 | exit_if(recv_sive < 0, "recvfrom error"); 62 | printf("read %d bytes context is : %s\n", recv_sive, recv_buf); 63 | sleep(5); 64 | } 65 | close(fd); 66 | return 0; 67 | } -------------------------------------------------------------------------------- /raw_examples/unix_domain/client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #define SOCKET_NAME "/tmp/my_socket" 10 | 11 | int main() { 12 | int client_fd; 13 | struct sockaddr_un server_addr; 14 | 15 | // 创建域套接字 16 | client_fd = socket(AF_UNIX, SOCK_STREAM, 0); 17 | if (client_fd == -1) { 18 | perror("socket"); 19 | exit(EXIT_FAILURE); 20 | } 21 | 22 | // 连接到服务端的套接字 23 | server_addr.sun_family = AF_UNIX; 24 | strncpy(server_addr.sun_path, SOCKET_NAME, sizeof(server_addr.sun_path) - 1); 25 | if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_un)) == -1) { 26 | perror("connect"); 27 | exit(EXIT_FAILURE); 28 | } 29 | 30 | // 发送消息给服务端 31 | const char *msg = "Hello, server!"; 32 | ssize_t n = write(client_fd, msg, strlen(msg)); 33 | if (n == -1) { 34 | perror("write"); 35 | exit(EXIT_FAILURE); 36 | } 37 | 38 | // 关闭套接字 39 | close(client_fd); 40 | 41 | return 0; 42 | } -------------------------------------------------------------------------------- /raw_examples/unix_domain/server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #define SOCKET_NAME "/tmp/my_socket" 10 | 11 | int main() { 12 | int server_fd; 13 | int client_fd; 14 | struct sockaddr_un server_addr; 15 | struct sockaddr_un client_addr; 16 | socklen_t client_len; 17 | 18 | // 创建域套接字 19 | server_fd = socket(AF_UNIX, SOCK_STREAM, 0); 20 | if (server_fd == -1) { 21 | perror("socket"); 22 | exit(EXIT_FAILURE); 23 | } 24 | 25 | // 绑定套接字 26 | server_addr.sun_family = AF_UNIX; 27 | strncpy(server_addr.sun_path, SOCKET_NAME, sizeof(server_addr.sun_path) - 1); 28 | unlink(SOCKET_NAME); // bind时会创建文件,如果不提前删除了就会导致bind失败... 29 | if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_un)) == -1) { 30 | perror("bind"); 31 | exit(EXIT_FAILURE); 32 | } 33 | 34 | // 监听套接字 35 | if (listen(server_fd, 5) == -1) { 36 | perror("listen"); 37 | exit(EXIT_FAILURE); 38 | } 39 | 40 | // 接受客户端连接 41 | client_len = sizeof(struct sockaddr_un); 42 | client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len); 43 | if (client_fd == -1) { 44 | perror("accept"); 45 | exit(EXIT_FAILURE); 46 | } 47 | 48 | // 读取客户端发送的数据 49 | char buf[1024]; 50 | ssize_t n = read(client_fd, buf, sizeof(buf)); 51 | if (n == -1) { 52 | perror("read"); 53 | exit(EXIT_FAILURE); 54 | } 55 | printf("Received message: %s\n", buf); 56 | 57 | // 关闭套接字 58 | close(client_fd); 59 | close(server_fd); 60 | unlink(SOCKET_NAME); 61 | 62 | return 0; 63 | } -------------------------------------------------------------------------------- /tapip/Makefile: -------------------------------------------------------------------------------- 1 | #### User configure ############### 2 | CONFIG_DEBUG = n 3 | CONFIG_DEBUG_PKB = n 4 | CONFIG_DEBUG_WAIT = n 5 | CONFIG_DEBUG_SOCK = n 6 | CONFIG_DEBUG_ARP_LOCK = n 7 | CONFIG_DEBUG_ICMPEXCFRAGTIME = n 8 | CONFIG_TOPLOGY = 2 9 | #### End of User configure ######### 10 | 11 | # Use 'make V=1' to see the full commands 12 | ifeq ("$(origin V)", "command line") 13 | Q = 14 | else 15 | Q = @ 16 | endif 17 | export Q 18 | 19 | MAKEFLAGS += --no-print-directory 20 | 21 | LD = ld 22 | CC = gcc 23 | CFLAGS = -Wall -I../include 24 | LFLAGS = -pthread 25 | export LD CC CFLAGS 26 | 27 | ifeq ($(CONFIG_DEBUG), y) 28 | CFLAGS += -g 29 | endif 30 | 31 | ifeq ($(CONFIG_DEBUG), y) 32 | CFLAGS += -DDEBUG_PKB 33 | endif 34 | 35 | ifeq ($(CONFIG_DEBUG_SOCK), y) 36 | CFLAGS += -DSOCK_DEBUG 37 | endif 38 | 39 | ifeq ($(CONFIG_DEBUG_ICMPEXCFRAGTIME), y) 40 | CFLAGS += -DICMP_EXC_FRAGTIME_TEST 41 | endif 42 | 43 | ifeq ($(CONFIG_DEBUG_WAIT), y) 44 | CFLAGS += -DWAIT_DEBUG 45 | endif 46 | 47 | ifeq ($(CONFIG_DEBUG_ARP_LOCK), y) 48 | CFLAGS += -DDEBUG_ARPCACHE_LOCK 49 | endif 50 | 51 | ifeq ($(CONFIG_TOPLOGY), 1) 52 | CFLAGS += -DCONFIG_TOP1 53 | else 54 | CFLAGS += -DCONFIG_TOP2 55 | endif 56 | 57 | NET_STACK_OBJS = shell/shell_obj.o \ 58 | net/net_obj.o \ 59 | arp/arp_obj.o \ 60 | ip/ip_obj.o \ 61 | socket/socket_obj.o \ 62 | udp/udp_obj.o \ 63 | tcp/tcp_obj.o \ 64 | app/app_obj.o \ 65 | lib/lib_obj.o 66 | 67 | all:easyip 68 | easyip:$(NET_STACK_OBJS) 69 | @echo " [BUILD] $@" 70 | $(Q)$(CC) $(LFLAGS) $^ -o $@ 71 | 72 | shell/shell_obj.o:shell/*.c 73 | @make -C shell/ 74 | net/net_obj.o:net/*.c 75 | @make -C net/ 76 | arp/arp_obj.o:arp/*.c 77 | @make -C arp/ 78 | ip/ip_obj.o:ip/*.c 79 | @make -C ip/ 80 | udp/udp_obj.o:udp/*.c 81 | @make -C udp/ 82 | tcp/tcp_obj.o:tcp/*.c 83 | @make -C tcp/ 84 | lib/lib_obj.o:lib/*.c 85 | @make -C lib/ 86 | socket/socket_obj.o:socket/*.c 87 | @make -C socket/ 88 | app/app_obj.o:app/*.c 89 | @make -C app/ 90 | 91 | test:cbuf 92 | # test program for circul buffer 93 | cbuf:lib/cbuf.c lib/lib.c 94 | @echo " [CC] $@" 95 | $(Q)$(CC) -DCBUF_TEST -Iinclude/ $^ -o $@ 96 | 97 | tag: 98 | ctags -R * 99 | 100 | clean: 101 | find . -name *.o | xargs rm -f 102 | rm -f easyip cbuf 103 | 104 | lines: 105 | @echo "code lines:" 106 | @wc -l `find . -name \*.[ch]` | sort -n 107 | 108 | -------------------------------------------------------------------------------- /tapip/README.md: -------------------------------------------------------------------------------- 1 | tapip 2 | ===== 3 | A user-mode TCP/IP stack based on linux tap device. 4 | 5 | 6 | * :exclamation: It's not a production-ready networking stack, only my practical project after learning network stack. 7 | * :memo: If you have already learned the TCP/IP stack and want to 8 | * build one to replace the Linux network stack to play, try [network toplogy (1)](doc/net_topology#L2). 9 | * build one to communicate with localhost (the Linux network stack and apps on it), try [network toplogy (2)](doc/net_topology#L126). 10 | 11 | Dependence 12 | ========== 13 | * linux tun/tap device (/dev/net/tun exists if supported) 14 | * pthreads 15 | 16 | Quick start 17 | =========== 18 | ``` 19 | $ make 20 | # ./tapip (run as root) 21 | [net shell]: ping 10.0.0.2 (ping linux kernel TCP/IP stack) 22 | (waiting icmp echo reply...) 23 | [net shell]: help (see all embedded commands) 24 | ... 25 | ``` 26 | 27 | The corresponding network topology is here: [network topology (2)](doc/net_topology#L126). 28 | 29 | More information for hacking TCP/UDP/IP/ICMP: 30 | See [doc/net_topology](doc/net_topology), and select a script from [doc/test](doc/test) to do. 31 | 32 | Socket Api 33 | ========== 34 | _socket,_read,_write,_send,_recv,_connect,_bind,_close and _listen are provided now. 35 | Three socket types(SOCK_STREAM, SOCK_DGRAM, SOCK_RAW) can be used. 36 | You can use these apis to write applications working on tapip. 37 | Good examples are app/ping.c and app/snc.c. 38 | 39 | How to implement 40 | ================ 41 | I refer to xinu and Linux 2.6.11 TCP/IP stack and use linux tap device to simulate net device(for l2). 42 | A small shell is embedded in tapip. 43 | So this is just user-mode TCP/IP stack :) 44 | 45 | Any use 46 | ======= 47 | Tapip makes it easy to hack TCP/IP stack, just compiling it to run. 48 | It can also do some network test: arp snooping, proxy, and NAT! 49 | I think the most important is that tapip helps me learn TCP/IP, and understand linux TCP/IP stack. 50 | 51 | Other 52 | ===== 53 | You can refer to [saminiir/level-ip](https://github.com/saminiir/level-ip) and its blog posts for more details. 54 | -------------------------------------------------------------------------------- /tapip/TODO: -------------------------------------------------------------------------------- 1 | 1. TCP implementation 2 | not implemented: 3 | timer(retransmission/...) 4 | packet retransmission 5 | tcp option handling 6 | --- 7 | implemented: 8 | tcp three-way handshake connection 9 | tcp connection terminal 10 | tcp data receiving and sending 11 | tcp TIME-WAIT timer 12 | 13 | 2. Application support 14 | network debug tools: traceroute, arping, ss, route, arp, ip [...] 15 | 16 | 3. 17 | In TOP1, l2 cannot receive its reply packet sometimes! 18 | (Should we use a single thread to receive packet and 19 | another thread to parse it?) 20 | 21 | 5. Add bridge programming, then we will not use brctl and can delete doc/brctl.sh 22 | 23 | 6. test udp port allocation alogrithm 24 | 25 | 7. multicast/broadcast ip address handling 26 | ip option handling 27 | 28 | 8. icmp error report to l4(TCP/UDP/RawIP) 29 | 30 | 9. add cache layer for internal structure 31 | 32 | 10. remove timer thread. use poll timeout to implement it 33 | 34 | 11. support select, epoll, kqueue ... 35 | or use a third-party event library 36 | -------------------------------------------------------------------------------- /tapip/app/Makefile: -------------------------------------------------------------------------------- 1 | OBJS = ping.o snc.o 2 | SUBDIR = app 3 | 4 | all:app_obj.o 5 | app_obj.o:$(OBJS) 6 | @echo " [LD] $(SUBDIR)/$@" 7 | $(Q)$(LD) -r -o $@ $^ 8 | 9 | %.o:%.c 10 | @echo " [CC] $(SUBDIR)/$@" 11 | $(Q)$(CC) $(CFLAGS) -c -o $@ $< 12 | 13 | clean: 14 | rm -f *.o 15 | -------------------------------------------------------------------------------- /tapip/arp/Makefile: -------------------------------------------------------------------------------- 1 | OBJS = arp.o arp_cache.o 2 | SUBDIR = arp 3 | 4 | all:arp_obj.o 5 | arp_obj.o:$(OBJS) 6 | @echo " [LD] $(SUBDIR)/$@" 7 | $(Q)$(LD) -r -o $@ $^ 8 | 9 | %.o:%.c 10 | @echo " [CC] $(SUBDIR)/$@" 11 | $(Q)$(CC) $(CFLAGS) -c -o $@ $< 12 | 13 | clean: 14 | rm -f *.o 15 | -------------------------------------------------------------------------------- /tapip/doc/brcfg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # See doc/net_topology #TOP1 for more information 3 | # 4 | # TOPOLOGY: 5 | # 6 | # br0 7 | # / \ 8 | # eth0 tap0 9 | # / \ 10 | # network(gw) /dev/net/tun 11 | # \ 12 | # veth (./tapip) 13 | # 14 | # NOTE: We view tap0, /dev/net/tun and veth(./tapip) as one interface, 15 | # They should have only one mac address(eth0 mac), which will 16 | # handle arp protocol right. 17 | # Then eth0, br0, tap0, /dev/net/tun and veth(./tapip) can be 18 | # viewed as only one interface, which is similar as the original 19 | # real eth0 interface, and ./tapip will become its internal 20 | # TCP/IP stack! 21 | # 22 | # veth mac address must _NOT_ be eth0 mac address, 23 | # otherwise br0 dont work, in which case arp packet will not be passed 24 | # to veth 25 | # 26 | # WARNING: This test will suspend real kernel TCP/IP stack! 27 | # And we dont need kernel route table and arp cache. 28 | 29 | openbr() { 30 | #create tap0 31 | tunctl -b -t tap0 > /dev/null 32 | 33 | #create bridge 34 | brctl addbr br0 35 | 36 | #bridge config 37 | brctl addif br0 eth0 38 | brctl addif br0 tap0 39 | 40 | # net interface up (clear ip, ./tapip will set it) 41 | ifconfig eth0 0.0.0.0 up 42 | ifconfig tap0 0.0.0.0 up 43 | ifconfig br0 0.0.0.0 up 44 | echo "open ok" 45 | } 46 | 47 | closebr() { 48 | ifconfig tap0 down 49 | ifconfig br0 down 50 | brctl delif br0 tap0 51 | brctl delif br0 eth0 52 | brctl delbr br0 53 | tunctl -d tap0 > /dev/null 54 | echo "close ok" 55 | } 56 | 57 | usage() { 58 | echo "Usage: $0 [OPTION]" 59 | echo "OPTION:" 60 | echo " open config TOP1" 61 | echo " close unconfig TOP1" 62 | echo " help display help information" 63 | } 64 | 65 | if [ $UID != 0 ] 66 | then 67 | echo "Need root permission" 68 | exit 0 69 | fi 70 | 71 | if [[ $# != 1 ]] 72 | then 73 | usage 74 | fi 75 | 76 | case $1 in 77 | open) 78 | openbr 79 | ;; 80 | close) 81 | closebr 82 | ;; 83 | *) 84 | usage 85 | ;; 86 | esac 87 | -------------------------------------------------------------------------------- /tapip/doc/socket_design: -------------------------------------------------------------------------------- 1 | Linux socket model 2 | ==================================================== 3 | socket 4 | `------> +---------+-+-+ 5 | | sock | | | 6 | +---------+ | | 7 | | inet_sock | | --> PF_INET [family] 8 | +-----------+ | 9 | | tcp_sock | --> SOCK_STREAM [type] 10 | +-------------+ IPPROTO_TCP [protocol] 11 | 12 | tcp_sock is Transmission Control Block(TCB) of RFC 793 13 | 14 | ---------------------------------------------------- 15 | inet_protosw (inetsw_array[]) 16 | +---------+ 17 | | type | (SOCK_[type]) [type] 18 | +---------+ 19 | |protocol | (IPPROTO_[protocol]) [protocol] 20 | +---------+ 21 | | ops | (inet_[type]_ops) [type] 22 | +---------+ 23 | | prot -----> proto (tcp_prot) [protocol] 24 | +---------+ +-------------+ 25 | | operations | 26 | | .accept | 27 | | .close | 28 | | ....... | 29 | +-------------+ 30 | 31 | Fucntion calling path: 32 | sys_connect 33 | .. -> inet_[type]_ops->connect() 34 | .. -> sock->sk_prot->connect() 35 | (sock->sk_prot == inet_protosw->prot) 36 | 37 | 38 | Tapip socket model 39 | ==================================================== 40 | 41 | socket 42 | `------> +---------+-+ 43 | | sock | | --> PF_INET [family] 44 | +---------+ | 45 | | tcp_sock | --> SOCK_STREAM [type] 46 | | | 47 | | /* tcb */ | IPPROTO_TCP [protocol] 48 | | | 49 | +-----------+ 50 | 51 | tcp_sock::somefield is Transmission Control Block(TCB) of RFC 793 52 | ---------------------------------------------------- 53 | socket->ops (inet_ops) 54 | sock->ops (tcp_ops) 55 | 56 | Fucntion calling path: 57 | _send() 58 | -> socket->ops->send() [inet_send] 59 | -> sock->ops->send() [tcp_send] 60 | -------------------------------------------------------------------------------- /tapip/include/cbuf.h: -------------------------------------------------------------------------------- 1 | #ifndef __CBUF_H 2 | #define __CBUF_H 3 | 4 | /* 5 | * circular buffer: 6 | * .<------------size-------------->. 7 | * | | 8 | * |<-x->|<-CBUFUSED->|<-----y----->| 9 | * | | |<-CBUFRIGHT->| 10 | * +-----++++++++++++++-------------+ 11 | * | | 12 | * CBUFTAIL CBUFHEAD 13 | * (CBUFFREE = x+y) 14 | */ 15 | struct cbuf { 16 | int head; /* write point */ 17 | int tail; /* read point */ 18 | int size; 19 | char buf[0]; 20 | }; 21 | 22 | #define _CBUFHEAD(cbuf) ((cbuf)->head % (cbuf)->size) 23 | #define _CBUFTAIL(cbuf) ((cbuf)->tail % (cbuf)->size) 24 | #define CBUFUSED(cbuf) ((cbuf)->head - (cbuf)->tail) 25 | #define CBUFFREE(cbuf) ((cbuf)->size - CBUFUSED(cbuf)) 26 | #define CBUFHEAD(cbuf) &(cbuf)->buf[_CBUFHEAD(cbuf)] 27 | #define CBUFTAIL(cbuf) &(cbuf)->buf[_CBUFTAIL(cbuf)] 28 | /* head right space for writing */ 29 | #define CBUFHEADRIGHT(cbuf)\ 30 | ((CBUFHEAD(cbuf) >= CBUFTAIL(cbuf)) ?\ 31 | ((cbuf->size - _CBUFHEAD(cbuf))) :\ 32 | (_CBUFTAIL(cbuf) - _CBUFHEAD(cbuf))) 33 | /* tail righ space for reading */ 34 | #define CBUFTAILRIGHT(cbuf)\ 35 | ((CBUFHEAD(cbuf) > CBUFTAIL(cbuf)) ?\ 36 | (CBUFUSED(cbuf)) :\ 37 | ((cbuf)->size - _CBUFTAIL(cbuf))) 38 | 39 | extern int read_cbuf(struct cbuf *cbuf, char *buf, int size); 40 | extern int write_cbuf(struct cbuf *cbuf, char *buf, int size); 41 | extern struct cbuf *alloc_cbuf(int size); 42 | extern void free_cbuf(struct cbuf *cbuf); 43 | 44 | extern int alloc_cbufs; 45 | extern int free_cbufs; 46 | 47 | #endif /* cbuf.h */ 48 | -------------------------------------------------------------------------------- /tapip/include/compile.h: -------------------------------------------------------------------------------- 1 | #ifndef __COMPILE_H 2 | #define __COMPILE_H 3 | 4 | #undef _inline 5 | #define _inline inline __attribute__((always_inline)) 6 | 7 | #define containof(ptr, type, member)\ 8 | ((type *)((char *)(ptr) - (size_t)&((type *)0)->member)) 9 | 10 | #endif /* complie.h */ 11 | -------------------------------------------------------------------------------- /tapip/include/ether.h: -------------------------------------------------------------------------------- 1 | #ifndef __ETHER_H 2 | #define __ETHER_H 3 | 4 | #include 5 | 6 | #define ETH_HRD_SZ sizeof(struct ether) 7 | #define ETH_ALEN 6 /* ether address len */ 8 | 9 | // 以太网帧的类型 10 | // https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml#ieee-802-numbers-1 11 | #define ETH_P_IP 0x0800 12 | #define ETH_P_IPV6 0x86dd 13 | #define ETH_P_ARP 0x0806 14 | #define ETH_P_RARP 0x8035 15 | 16 | /* 17 | +-----------+-----------+-------------+--------------------+----------+ 18 | | DMAC | SMAC | Type | Data | FCS | 19 | | 6 Bytes | 6 Bytes | 2 Bytes | Variable length | 4 Bytes | 20 | +-----------+-----------+-------------+--------------------+----------+ 21 | Data:可以是arp或者ip包 22 | */ 23 | struct ether { 24 | unsigned char eth_dst[ETH_ALEN]; /* destination ether addr */ 25 | unsigned char eth_src[ETH_ALEN]; /* source ether addr */ 26 | unsigned short eth_pro; /* packet type ID */ 27 | unsigned char eth_data[0]; /* data field */ 28 | } __attribute__((packed)); 29 | 30 | static inline void hwacpy(void *dst, void *src) 31 | { 32 | memcpy(dst, src, ETH_ALEN); 33 | } 34 | 35 | static inline void hwaset(void *dst, int val) 36 | { 37 | memset(dst, val, ETH_ALEN); 38 | } 39 | 40 | static inline int hwacmp(void *hwa1, void *hwa2) 41 | { 42 | return memcmp(hwa1, hwa2, ETH_ALEN); 43 | } 44 | 45 | #define macfmt(ha) (ha)[0], (ha)[1], (ha)[2], (ha)[3], (ha)[4], (ha)[5] 46 | #define MACFMT "%02x:%02x:%02x:%02x:%02x:%02x" 47 | 48 | static inline char *ethpro(unsigned short proto) 49 | { 50 | if (proto == ETH_P_IP) 51 | return "IP"; 52 | else if (proto == ETH_P_ARP) 53 | return "ARP"; 54 | else if (proto == ETH_P_RARP) 55 | return "RARP"; 56 | else 57 | return "unknown"; 58 | } 59 | 60 | static inline int is_eth_multicast(unsigned char *hwa) 61 | { 62 | return (hwa[0] & 0x01); 63 | } 64 | 65 | static inline int is_eth_broadcast(unsigned char *hwa) 66 | { 67 | /* 68 | * fast compare method 69 | * ethernet mac broadcast is FF:FF:FF:FF:FF:FF 70 | */ 71 | return (hwa[0] & hwa[1] & hwa[2] & hwa[3] & hwa[4] & hwa[5]) == 0xff; 72 | } 73 | 74 | #endif /* ether.h */ 75 | -------------------------------------------------------------------------------- /tapip/include/inet.h: -------------------------------------------------------------------------------- 1 | #ifndef __INET_H 2 | #define __INET_H 3 | 4 | #include "socket.h" 5 | 6 | struct inet_type { 7 | struct sock *(*alloc_sock)(int); 8 | int type; 9 | int protocol; 10 | }; 11 | 12 | extern struct socket_ops inet_ops; 13 | extern void inet_init(void); 14 | 15 | #endif /* inet.h */ 16 | -------------------------------------------------------------------------------- /tapip/include/netcfg.h: -------------------------------------------------------------------------------- 1 | #ifndef __NETCFG_H 2 | #define __NETCFG_H 3 | 4 | /* see doc/brcfg.sh & doc/net_topology # TOP1 */ 5 | #ifdef CONFIG_TOP1 6 | #define FAKE_TAP_ADDR 0x00000000 /* 0.0.0.0 */ 7 | #define FAKE_IPADDR 0x1585140a /* 10.20.133.21 */ 8 | /* veth mac cannot be eth0 mac, otherwise eth0 will received arp packet */ 9 | #define FAKE_HWADDR "\x00\x34\x45\x67\x89\xab" 10 | #define DEFAULT_GW 0x1485140a /* 10.20.133.20 */ 11 | #endif 12 | 13 | /* doc/net_topology # TOP2 */ 14 | #ifdef CONFIG_TOP2 15 | #define FAKE_TAP_ADDR 0x0200000a /* 10.0.0.2 */ 16 | #define FAKE_IPADDR 0x0100000a /* 10.0.0.1 */ 17 | #define FAKE_HWADDR "\x00\x34\x45\x67\x89\xab" 18 | #endif 19 | 20 | #define FAKE_TAP_NETMASK 0x00ffffff /* 255.255.255.0 */ 21 | #define FAKE_NETMASK 0x00ffffff /* 255.255.255.0 */ 22 | 23 | #endif /* netcfg.h */ 24 | -------------------------------------------------------------------------------- /tapip/include/raw.h: -------------------------------------------------------------------------------- 1 | #ifndef __RAW_H 2 | #define __RAW_H 3 | 4 | #include "list.h" 5 | #include "sock.h" 6 | 7 | struct sock; 8 | struct raw_sock { 9 | struct sock sk; 10 | struct list_head list; /* raw sock hash table node */ 11 | }; 12 | 13 | extern void raw_in(struct pkbuf *pkb); 14 | extern struct sock *raw_lookup_sock(unsigned int, unsigned int, int); 15 | extern struct sock *raw_lookup_sock_next(struct sock *, 16 | unsigned int, unsigned int, int); 17 | extern void raw_init(void); 18 | extern struct sock *raw_alloc_sock(int); 19 | 20 | extern struct tapip_wait raw_send_wait; 21 | extern struct list_head raw_send_queue; 22 | 23 | extern void raw_out(void); 24 | 25 | #define RAW_DEFAULT_TTL 64 26 | #define RAW_MAX_BUFSZ 65536 27 | 28 | #endif /* raw.h */ 29 | -------------------------------------------------------------------------------- /tapip/include/route.h: -------------------------------------------------------------------------------- 1 | #ifndef __ROUTE_H 2 | #define __ROUTE_H 3 | 4 | #include "netif.h" 5 | 6 | struct rtentry { 7 | struct list_head rt_list; 8 | unsigned int rt_net; /* network address */ 9 | unsigned int rt_netmask; /* mask */ 10 | unsigned int rt_gw; /* gateway(next-hop) */ 11 | unsigned int rt_flags; /* route entry flags */ 12 | int rt_metric; /* distance metric */ 13 | struct netdev *rt_dev; /* output net device or local net device */ 14 | }; 15 | 16 | #define RT_NONE 0x00000000 17 | #define RT_LOCALHOST 0x00000001 18 | #define RT_DEFAULT 0x00000002 19 | 20 | extern struct rtentry *rt_lookup(unsigned int); 21 | extern void rt_add(unsigned int, unsigned int, unsigned int, 22 | int, unsigned int, struct netdev *); 23 | extern void rt_init(void); 24 | extern int rt_output(struct pkbuf *); 25 | extern int rt_input(struct pkbuf *); 26 | extern void rt_traverse(void); 27 | 28 | #endif /* route.h */ 29 | -------------------------------------------------------------------------------- /tapip/include/shell.h: -------------------------------------------------------------------------------- 1 | struct command { 2 | int cmd_num; 3 | void (*cmd_func)(int, char **); 4 | char *cmd_str; 5 | char *cmd_help; 6 | }; 7 | 8 | #define CMD_NONUM -1 /* Arguments is parsed in command function. */ 9 | -------------------------------------------------------------------------------- /tapip/include/socket.h: -------------------------------------------------------------------------------- 1 | #ifndef __SOCKET_H 2 | #define __SOCKET_H 3 | 4 | #include "wait.h" 5 | 6 | enum socket_state { 7 | SS_UNCONNECTED = 1, 8 | SS_BIND, 9 | SS_LISTEN, 10 | SS_CONNECTING, 11 | SS_CONNECTED, 12 | SS_MAX 13 | }; 14 | 15 | enum sock_type { 16 | SOCK_STREAM = 1, // TCP 17 | SOCK_DGRAM, // UDP 18 | SOCK_RAW, // 原始套接字 19 | SOCK_MAX 20 | }; 21 | 22 | enum socket_family { 23 | AF_INET = 1 24 | }; 25 | 26 | struct socket; 27 | struct sock_addr; 28 | /* protocol dependent socket apis */ 29 | struct socket_ops { 30 | int (*socket)(struct socket *, int); 31 | int (*close)(struct socket *); 32 | int (*accept)(struct socket *, struct socket *, struct sock_addr *); 33 | int (*listen)(struct socket *, int); 34 | int (*bind)(struct socket *, struct sock_addr *); 35 | int (*connect)(struct socket *, struct sock_addr *); 36 | int (*read)(struct socket *, void *, int); 37 | int (*write)(struct socket *, void *, int); 38 | int (*send)(struct socket *, void *, int, struct sock_addr *); 39 | struct pkbuf *(*recv)(struct socket *); 40 | }; 41 | 42 | struct socket { 43 | unsigned int state; 44 | unsigned int family; /* socket family: always AF_INET */ 45 | unsigned int type; /* l4 protocol type: stream, dgram, raw */ 46 | struct tapip_wait sleep; 47 | struct socket_ops *ops; // 相当于绑定了结构体的方法 48 | struct sock *sk; // 这里则通过基类,来访问udp_sock、tcp_sock、raw_sock子类,然后通过子类的ops结构体来访问具体的函数 49 | int refcnt; /* refer to linux file::f_count */ 50 | }; 51 | 52 | extern struct socket *_socket(int, int, int); 53 | extern int _listen(struct socket *, int); 54 | extern void _close(struct socket *); 55 | extern int _bind(struct socket *, struct sock_addr *); 56 | extern struct socket *_accept(struct socket *, struct sock_addr *); 57 | extern int _send(struct socket *, void *, int, struct sock_addr *); 58 | extern int _connect(struct socket *, struct sock_addr *); 59 | extern int _read(struct socket *, void *, int); 60 | extern int _write(struct socket *, void *, int); 61 | extern struct pkbuf *_recv(struct socket *); 62 | extern void socket_init(void); 63 | 64 | #endif /* socket.h */ 65 | -------------------------------------------------------------------------------- /tapip/include/tap.h: -------------------------------------------------------------------------------- 1 | #ifndef __TAP_H 2 | #define __TAP_H 3 | 4 | #define TUNTAPDEV "/dev/net/tun" 5 | 6 | extern void unset_tap(void); 7 | extern void set_tap(void); 8 | extern void delete_tap(int tapfd); 9 | extern int alloc_tap(char *dev); 10 | extern void gethwaddr_tap(int tapfd, unsigned char *ha); 11 | extern void getname_tap(int tapfd, unsigned char *name); 12 | extern void getipaddr_tap(unsigned char *name, unsigned int *ipaddr); 13 | extern void setipaddr_tap(unsigned char *name, unsigned int ipaddr); 14 | extern void getmtu_tap(unsigned char *name, int *mtu); 15 | extern void setup_tap(unsigned char *name); 16 | extern void setdown_tap(unsigned char *name); 17 | extern void setflags_tap(unsigned char *name, unsigned short flags, int set); 18 | extern void setnetmask_tap(unsigned char *name, unsigned int netmask); 19 | extern int setpersist_tap(int fd); 20 | 21 | #endif /* tap.h */ 22 | -------------------------------------------------------------------------------- /tapip/include/tcp_hash.h: -------------------------------------------------------------------------------- 1 | #ifndef __TCP_HASH_H 2 | #define __TCP_HASH_H 3 | 4 | #include "sock.h" 5 | 6 | /* How to select hash size? or hash algorithm? */ 7 | #define TCP_EHASH_SIZE 0x40 8 | #define TCP_EHASH_MASK (TCP_EHASH_SIZE - 1) 9 | 10 | #define TCP_LHASH_SIZE 0x20 11 | #define TCP_LHASH_MASK (TCP_LHASH_SIZE - 1) 12 | 13 | #define TCP_BHASH_SIZE 0x100 14 | #define TCP_BHASH_MASK (TCP_BHASH_SIZE - 1) 15 | #define TCP_BPORT_MIN 0x8000 16 | #define TCP_BPORT_MAX 0xf000 17 | 18 | #define tcp_ehash_head(hash) (&tcp_table.etable[hash]) 19 | #define tcp_bhash_head(hash) (&tcp_table.btable[hash]) 20 | #define tcp_lhash_head(hash) (&tcp_table.ltable[hash]) 21 | 22 | struct tcp_hash_table { 23 | struct hlist_head etable[TCP_EHASH_SIZE]; /* establish hash table */ 24 | struct hlist_head ltable[TCP_LHASH_SIZE]; /* listen hash table */ 25 | struct hlist_head btable[TCP_BHASH_SIZE]; /* bind hash table */ 26 | int bfree; /* [bmin, bmax] */ 27 | /* FIXME: rw lock */ 28 | }; 29 | 30 | /* reference to Linux */ 31 | static __inline unsigned int tcp_ehashfn(unsigned int src, unsigned int dst, 32 | unsigned short src_port, unsigned short dst_port) { 33 | unsigned int hash; 34 | hash = (src ^ src_port) ^ (dst ^ dst_port); 35 | /* make all bits xor to lowest 8 bit */ 36 | hash ^= hash >> 16; 37 | hash ^= hash >> 8; 38 | return hash & TCP_EHASH_MASK; 39 | } 40 | 41 | static _inline int tcp_ehash_conflict(struct hlist_head *head, struct sock *sk) { 42 | struct hlist_node *node; 43 | struct sock *tmpsk; 44 | hlist_for_each_sock(tmpsk, node, head) { 45 | if (sk->sk_saddr == tmpsk->sk_saddr && 46 | sk->sk_daddr == tmpsk->sk_daddr && 47 | sk->sk_sport == tmpsk->sk_sport && 48 | sk->sk_dport == tmpsk->sk_dport) 49 | return 1; 50 | } 51 | return 0; 52 | } 53 | 54 | #endif /* tcp_hash.h */ 55 | -------------------------------------------------------------------------------- /tapip/include/tcp_timer.h: -------------------------------------------------------------------------------- 1 | #ifndef __TCP_TIMER_H 2 | #define __TCP_TIMER_H 3 | 4 | struct tcp_sock; 5 | 6 | struct tcp_timer_head { 7 | struct tcp_timer *next; 8 | }; 9 | 10 | struct tcp_timer { 11 | struct tcp_timer *next; 12 | int timeout; /* micro second */ 13 | }; 14 | 15 | #define timewait2tsk(t) timer2tsk(t, timewait) 16 | #define timer2tsk(t, member) containof(t, struct tcp_sock, member) 17 | #define TCP_TIMER_DELTA 200000 /* 200ms */ 18 | #define TCP_MSL 1000000 /* 1sec */ 19 | #define TCP_TIMEWAIT_TIMEOUT (2 * TCP_MSL) /* 2MSL */ 20 | 21 | extern void tcp_set_timewait_timer(struct tcp_sock *); 22 | 23 | #endif /* tcp_timer.h */ 24 | -------------------------------------------------------------------------------- /tapip/include/udp.h: -------------------------------------------------------------------------------- 1 | #ifndef __UDP_H 2 | #define __UDP_H 3 | 4 | #include "sock.h" 5 | 6 | struct udp { 7 | unsigned short src; /* source port */ 8 | unsigned short dst; /* destination port */ 9 | unsigned short length; /* udp head and data */ 10 | unsigned short checksum; 11 | unsigned char data[0]; // 18-1472字节 12 | } __attribute__((packed)); 13 | 14 | struct udp_sock { 15 | struct sock sk; 16 | }; 17 | 18 | #define UDP_HRD_SZ sizeof(struct udp) 19 | #define ip2udp(ip) ((struct udp *)ipdata(ip)) 20 | #define UDP_MAX_BUFSZ (0xffff - UDP_HRD_SZ) 21 | #define UDP_DEFAULT_TTL 64 22 | 23 | extern struct sock *udp_lookup_sock(unsigned short port); 24 | extern void udp_in(struct pkbuf *pkb); 25 | extern void udp_init(void); 26 | extern struct sock *udp_alloc_sock(int protocol); 27 | 28 | #endif /* udp.h */ 29 | -------------------------------------------------------------------------------- /tapip/include/wait.h: -------------------------------------------------------------------------------- 1 | #ifndef __WAIT_H 2 | #define __WAIT_H 3 | 4 | #include 5 | 6 | #include "compile.h" 7 | #include "lib.h" 8 | 9 | /* simulating thread blocking(used for _accept(), _read(), _recv()) */ 10 | struct tapip_wait { 11 | pthread_mutex_t mutex; 12 | pthread_cond_t cond; 13 | int notified; /* log whether wait is waken up already */ 14 | int dead; /* for safe exiting wait state */ 15 | int sleep; 16 | }; 17 | 18 | /* 19 | * 1. @notified avoid double calling of wake_up() 20 | * 2. If wake_up() is called before sleep_on(), 21 | * @notified can skip next waiting of sleep_on(). 22 | */ 23 | #ifdef WAIT_DEBUG 24 | #define wake_up(w) \ 25 | do { \ 26 | dbg("[*]wake_up " #w); \ 27 | _wake_up(w); \ 28 | } while (0) 29 | static _inline int _wake_up(struct tapip_wait *w) 30 | #else 31 | static _inline int wake_up(struct tapip_wait *w) 32 | #endif 33 | { 34 | pthread_mutex_lock(&w->mutex); 35 | /* Should we put this code before locking? */ 36 | if (w->dead) 37 | goto unlock; 38 | if (!w->notified) { 39 | w->notified = 1; 40 | if (w->sleep) 41 | pthread_cond_signal(&w->cond); 42 | } 43 | unlock: 44 | pthread_mutex_unlock(&w->mutex); 45 | return -(w->dead); 46 | } 47 | 48 | #ifdef WAIT_DEBUG 49 | #define sleep_on(w) \ 50 | ({ \ 51 | int ret; \ 52 | dbg("[+]sleep on " #w); \ 53 | ret = _sleep_on(w); \ 54 | dbg("[-]Be waken up " #w); \ 55 | ret; \ 56 | }) 57 | static _inline int _sleep_on(struct tapip_wait *w) 58 | #else 59 | static _inline int sleep_on(struct tapip_wait *w) 60 | #endif 61 | { 62 | pthread_mutex_lock(&w->mutex); 63 | if (w->dead) 64 | goto unlock; 65 | w->sleep = 1; 66 | if (!w->notified) 67 | pthread_cond_wait(&w->cond, &w->mutex); 68 | w->notified = 0; 69 | w->sleep = 0; 70 | unlock: 71 | pthread_mutex_unlock(&w->mutex); 72 | return -(w->dead); 73 | } 74 | 75 | static _inline void wait_init(struct tapip_wait *w) { 76 | /* XXX: Should it need error checking? */ 77 | pthread_cond_init(&w->cond, NULL); 78 | pthread_mutex_init(&w->mutex, NULL); 79 | w->dead = 0; 80 | w->notified = 0; 81 | w->sleep = 0; 82 | } 83 | 84 | static _inline void wait_exit(struct tapip_wait *w) { 85 | pthread_mutex_lock(&w->mutex); 86 | if (w->dead) 87 | goto unlock; 88 | w->dead = 1; 89 | if (w->sleep) 90 | pthread_cond_broadcast(&w->cond); 91 | unlock: 92 | pthread_mutex_unlock(&w->mutex); 93 | } 94 | 95 | #endif /* wait.h */ 96 | -------------------------------------------------------------------------------- /tapip/ip/Makefile: -------------------------------------------------------------------------------- 1 | OBJS = ip_in.o ip_out.o ip_forward.o ip_frag.o icmp.o raw.o route.o 2 | SUBDIR = ip 3 | 4 | all:ip_obj.o 5 | ip_obj.o:$(OBJS) 6 | @echo " [LD] $(SUBDIR)/$@" 7 | $(Q)$(LD) -r -o $@ $^ 8 | 9 | %.o:%.c 10 | @echo " [CC] $(SUBDIR)/$@" 11 | $(Q)$(CC) $(CFLAGS) -c -o $@ $< 12 | 13 | clean: 14 | rm -f *.o 15 | -------------------------------------------------------------------------------- /tapip/ip/ip_forward.c: -------------------------------------------------------------------------------- 1 | #include "icmp.h" 2 | #include "ip.h" 3 | #include "lib.h" 4 | #include "netif.h" 5 | #include "route.h" 6 | 7 | /* Assert pkb is net-order */ 8 | void ip_forward(struct pkbuf *pkb) { 9 | struct ip *iphdr = pkb2ip(pkb); 10 | struct rtentry *rt = pkb->pk_rtdst; 11 | struct netdev *indev = pkb->pk_indev; 12 | unsigned int dst; 13 | #ifdef CONFIG_TOP1 14 | ipdbg("host doesnt support forward!"); 15 | goto drop_pkb; 16 | #endif 17 | ipdbg(IPFMT " -> " IPFMT "(%d/%d bytes) forwarding", 18 | ipfmt(iphdr->ip_src), ipfmt(iphdr->ip_dst), 19 | iphlen(iphdr), _ntohs(iphdr->ip_len)); 20 | 21 | if (iphdr->ip_ttl <= 1) { 22 | icmp_send(ICMP_T_TIMEEXCEED, ICMP_EXC_TTL, 0, pkb); 23 | goto drop_pkb; 24 | } 25 | 26 | /* FIXME: ajacent checksum for decreased ttl */ 27 | iphdr->ip_ttl--; 28 | ip_set_checksum(iphdr); 29 | 30 | /* default route or remote dst */ 31 | if ((rt->rt_flags & RT_DEFAULT) || rt->rt_metric > 0) 32 | dst = rt->rt_gw; 33 | else 34 | dst = iphdr->ip_dst; 35 | ipdbg("forward to next-hop " IPFMT, ipfmt(dst)); 36 | if (indev == rt->rt_dev) { 37 | /* 38 | * ICMP REDIRECT conditions(RFC 1812): 39 | * 1. The packet is being forwarded out the same physical 40 | * interface that it was received from. 41 | * 2. The IP source address in the packet is on the same Logical IP 42 | * (sub)network as the next-hop IP address. 43 | * 3. The packet does not contain an IP source route option. 44 | * (Not implemented) 45 | */ 46 | struct rtentry *srt = rt_lookup(iphdr->ip_src); 47 | if (srt && srt->rt_metric == 0 && 48 | equsubnet(srt->rt_netmask, iphdr->ip_src, dst)) { 49 | if (srt->rt_dev != indev) { 50 | ipdbg("Two NIC are connected to the same LAN"); 51 | } 52 | icmp_send(ICMP_T_REDIRECT, ICMP_REDIRECT_HOST, dst, pkb); 53 | } 54 | } 55 | /* ip fragment */ 56 | if (_ntohs(iphdr->ip_len) > rt->rt_dev->net_mtu) { 57 | if (iphdr->ip_fragoff & _htons(IP_FRAG_DF)) { 58 | icmp_send(ICMP_T_DESTUNREACH, ICMP_FRAG_NEEDED, 0, pkb); 59 | goto drop_pkb; 60 | } 61 | ip_send_frag(rt->rt_dev, pkb); 62 | } else { 63 | ip_send_dev(rt->rt_dev, pkb); 64 | } 65 | return; 66 | drop_pkb: 67 | free_pkb(pkb); 68 | } 69 | -------------------------------------------------------------------------------- /tapip/ip/ip_out.c: -------------------------------------------------------------------------------- 1 | #include "arp.h" 2 | #include "ether.h" 3 | #include "ip.h" 4 | #include "lib.h" 5 | #include "netif.h" 6 | #include "route.h" 7 | 8 | void ip_send_dev(struct netdev *dev, struct pkbuf *pkb) { 9 | struct arpentry *ae; 10 | unsigned int dst; 11 | struct rtentry *rt = pkb->pk_rtdst; 12 | 13 | if (rt->rt_flags & RT_LOCALHOST) { 14 | ipdbg("To loopback"); 15 | netdev_tx(dev, pkb, pkb->pk_len - ETH_HRD_SZ, 16 | ETH_P_IP, dev->net_hwaddr); 17 | return; 18 | } 19 | 20 | /* next-hop: default route or remote dst */ 21 | if ((rt->rt_flags & RT_DEFAULT) || rt->rt_metric > 0) 22 | dst = rt->rt_gw; 23 | else 24 | dst = pkb2ip(pkb)->ip_dst; 25 | 26 | ae = arp_lookup(ETH_P_IP, dst); 27 | if (!ae) { 28 | arpdbg("not found arp cache"); 29 | ae = arp_alloc(); 30 | if (!ae) { 31 | ipdbg("arp cache is full"); 32 | free_pkb(pkb); 33 | return; 34 | } 35 | ae->ae_ipaddr = dst; 36 | ae->ae_dev = dev; 37 | list_add_tail(&pkb->pk_list, &ae->ae_list); 38 | arp_request(ae); 39 | } else if (ae->ae_state == ARP_WAITING) { 40 | arpdbg("arp entry is waiting"); 41 | list_add_tail(&pkb->pk_list, &ae->ae_list); 42 | } else { 43 | netdev_tx(dev, pkb, pkb->pk_len - ETH_HRD_SZ, 44 | ETH_P_IP, ae->ae_hwaddr); 45 | } 46 | } 47 | 48 | /* Assert pkb is net-order & pkb->pk_pro == ETH_P_IP */ 49 | void ip_send_out(struct pkbuf *pkb) { 50 | struct ip *iphdr = pkb2ip(pkb); 51 | pkb->pk_pro = ETH_P_IP; 52 | if (!pkb->pk_rtdst && rt_output(pkb) < 0) { 53 | free_pkb(pkb); 54 | return; 55 | } 56 | ip_set_checksum(iphdr); 57 | ipdbg(IPFMT " -> " IPFMT "(%d/%d bytes)", 58 | ipfmt(iphdr->ip_src), ipfmt(iphdr->ip_dst), 59 | iphlen(iphdr), _ntohs(iphdr->ip_len)); 60 | /* ip fragment */ 61 | if (_ntohs(iphdr->ip_len) > pkb->pk_rtdst->rt_dev->net_mtu) 62 | ip_send_frag(pkb->pk_rtdst->rt_dev, pkb); // ip包超过mtu,需要分片发送 63 | else 64 | ip_send_dev(pkb->pk_rtdst->rt_dev, pkb); 65 | } 66 | 67 | static unsigned short ipid = 0; 68 | void ip_send_info(struct pkbuf *pkb, unsigned char tos, unsigned short len, 69 | unsigned char ttl, unsigned char pro, unsigned int dst) { 70 | struct ip *iphdr = pkb2ip(pkb); 71 | /* fill header information */ 72 | iphdr->ip_ver = IP_VERSION_4; 73 | iphdr->ip_hlen = IP_HRD_SZ / 4; 74 | iphdr->ip_tos = tos; 75 | iphdr->ip_len = _htons(len); 76 | iphdr->ip_id = _htons(ipid++); 77 | iphdr->ip_fragoff = 0; 78 | iphdr->ip_ttl = ttl; 79 | iphdr->ip_pro = pro; 80 | iphdr->ip_dst = dst; 81 | 82 | ip_send_out(pkb); 83 | } 84 | -------------------------------------------------------------------------------- /tapip/ip/raw.c: -------------------------------------------------------------------------------- 1 | #include "raw.h" 2 | 3 | #include "ip.h" 4 | #include "list.h" 5 | #include "netif.h" 6 | #include "socket.h" 7 | 8 | static void raw_recv(struct pkbuf *pkb, struct sock *sk) { 9 | /* FIFO queue */ 10 | list_add_tail(&pkb->pk_list, &sk->recv_queue); 11 | /* Should we get sk? */ 12 | pkb->pk_sk = sk; 13 | sk->ops->recv_notify(sk); 14 | } 15 | 16 | void raw_in(struct pkbuf *pkb) { 17 | struct ip *iphdr = pkb2ip(pkb); 18 | struct pkbuf *rawpkb; 19 | struct sock *sk; 20 | /* FIXME: lock for raw lookup */ 21 | sk = raw_lookup_sock(iphdr->ip_src, iphdr->ip_dst, iphdr->ip_pro); 22 | while (sk) { 23 | rawpkb = copy_pkb(pkb); 24 | raw_recv(rawpkb, sk); 25 | /* for all matched raw sock */ 26 | sk = raw_lookup_sock_next(sk, iphdr->ip_src, iphdr->ip_dst, 27 | iphdr->ip_pro); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tapip/lib/Makefile: -------------------------------------------------------------------------------- 1 | OBJS = lib.o checksum.o cbuf.o 2 | SUBDIR = lib 3 | 4 | all:lib_obj.o 5 | lib_obj.o:$(OBJS) 6 | @echo " [LD] $(SUBDIR)/$@" 7 | $(Q)$(LD) -r -o $@ $^ 8 | 9 | %.o:%.c 10 | @echo " [CC] $(SUBDIR)/$@" 11 | $(Q)$(CC) $(CFLAGS) -c -o $@ $< 12 | 13 | clean: 14 | rm -f *.o 15 | -------------------------------------------------------------------------------- /tapip/lib/lib.c: -------------------------------------------------------------------------------- 1 | #include "lib.h" 2 | 3 | #include "netif.h" 4 | 5 | void perrx(char *str) { 6 | if (errno) 7 | perror(str); 8 | else 9 | ferr("ERROR:%s\n", str); 10 | exit(EXIT_FAILURE); 11 | } 12 | 13 | void *xmalloc(int size) { 14 | void *p = malloc(size); 15 | if (!p) 16 | perrx("malloc"); 17 | return p; 18 | } 19 | 20 | void *xzalloc(int size) { 21 | void *p = calloc(1, size); 22 | if (!p) 23 | perrx("calloc"); 24 | return p; 25 | } 26 | 27 | /* format and print mlen-max-size data (spaces will fill the buf) */ 28 | static char *_space = " "; 29 | void printfs(int mlen, const char *fmt, ...) { 30 | char buf[256]; 31 | va_list ap; 32 | int slen; 33 | va_start(ap, fmt); 34 | slen = vsprintf(buf, fmt, ap); 35 | va_end(ap); 36 | printf("%.*s", mlen, buf); 37 | if (mlen > slen) 38 | printf("%.*s", mlen - slen, _space); 39 | } 40 | 41 | int str2ip(char *str, unsigned int *ip) { 42 | unsigned int a, b, c, d; 43 | if (sscanf(str, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) 44 | return -1; 45 | if (a > 255 || b > 255 || c > 255 || d > 255) 46 | return -1; 47 | *ip = a | (b << 8) | (c << 16) | (d << 24); 48 | return 0; 49 | } 50 | 51 | int parse_ip_port(char *str, unsigned int *addr, unsigned short *nport) { 52 | char *port; 53 | if ((port = strchr(str, ':')) != NULL) { 54 | *nport = _htons(atoi(&port[1])); 55 | *port = '\0'; 56 | } 57 | if (str2ip(str, addr) < 0) 58 | return -1; 59 | if (port) 60 | *port = ':'; 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /tapip/mmleak.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # script for memory leak test 3 | # -v verbose model 4 | sudo valgrind -q --leak-check=full --show-reachable=yes ./tapip 5 | -------------------------------------------------------------------------------- /tapip/net/Makefile: -------------------------------------------------------------------------------- 1 | OBJS = net.o netdev.o veth.o loop.o pkb.o tap.o 2 | SUBDIR = net 3 | 4 | all:net_obj.o 5 | net_obj.o:$(OBJS) 6 | @echo " [LD] $(SUBDIR)/$@" 7 | $(Q)$(LD) -r -o $@ $^ 8 | 9 | %.o:%.c 10 | @echo " [CC] $(SUBDIR)/$@" 11 | $(Q)$(CC) $(CFLAGS) -c -o $@ $< 12 | 13 | clean: 14 | rm -f *.o 15 | -------------------------------------------------------------------------------- /tapip/net/loop.c: -------------------------------------------------------------------------------- 1 | #include "ip.h" 2 | #include "lib.h" 3 | #include "netif.h" 4 | 5 | #define LOOPBACK_MTU 1500 6 | #define LOOPBACK_IPADDR 0x0100007F /* 127.0.0.1 */ 7 | #define LOOPBACK_NETMASK 0x000000FF /* 255.0.0.0 */ 8 | 9 | struct netdev *loop; 10 | 11 | static int loop_dev_init(struct netdev *dev) { 12 | /* init veth: information for our netstack */ 13 | dev->net_mtu = LOOPBACK_MTU; 14 | dev->net_ipaddr = LOOPBACK_IPADDR; 15 | dev->net_mask = LOOPBACK_NETMASK; 16 | dbg("%s ip address: " IPFMT, dev->net_name, ipfmt(dev->net_ipaddr)); 17 | dbg("%s netmask: " IPFMT, dev->net_name, ipfmt(dev->net_mask)); 18 | /* net stats have been zero */ 19 | return 0; 20 | } 21 | 22 | static void loop_recv(struct netdev *dev, struct pkbuf *pkb) { 23 | dev->net_stats.rx_packets++; 24 | dev->net_stats.rx_bytes += pkb->pk_len; 25 | net_in(dev, pkb); 26 | } 27 | 28 | static int loop_xmit(struct netdev *dev, struct pkbuf *pkb) { 29 | get_pkb(pkb); 30 | /* loop back to itself */ 31 | loop_recv(dev, pkb); 32 | dev->net_stats.tx_packets++; 33 | dev->net_stats.tx_bytes += pkb->pk_len; 34 | return pkb->pk_len; 35 | } 36 | 37 | static struct netdev_ops loop_ops = { 38 | .init = loop_dev_init, 39 | .xmit = loop_xmit, 40 | .exit = NULL, 41 | }; 42 | 43 | void loop_init(void) { 44 | loop = netdev_alloc("lo", &loop_ops); 45 | } 46 | 47 | void loop_exit(void) { 48 | netdev_free(loop); 49 | } 50 | -------------------------------------------------------------------------------- /tapip/net/net.c: -------------------------------------------------------------------------------- 1 | /* 2 | * special net device independent L2 code 3 | */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "arp.h" 10 | #include "ether.h" 11 | #include "ip.h" 12 | #include "lib.h" 13 | #include "netif.h" 14 | 15 | /* referred to eth_trans_type() in linux */ 16 | static struct ether *eth_init(struct netdev *dev, struct pkbuf *pkb) { 17 | struct ether *ehdr = (struct ether *)pkb->pk_data; 18 | if (pkb->pk_len < ETH_HRD_SZ) { 19 | free_pkb(pkb); 20 | dbg("received packet is too small:%d bytes", pkb->pk_len); 21 | return NULL; 22 | } 23 | /* hardware address type */ 24 | if (is_eth_multicast(ehdr->eth_dst)) { 25 | if (is_eth_broadcast(ehdr->eth_dst)) 26 | pkb->pk_type = PKT_BROADCAST; 27 | else 28 | pkb->pk_type = PKT_MULTICAST; 29 | } else if (!hwacmp(ehdr->eth_dst, dev->net_hwaddr)) { 30 | pkb->pk_type = PKT_LOCALHOST; 31 | } else { 32 | pkb->pk_type = PKT_OTHERHOST; 33 | } 34 | /* packet protocol */ 35 | pkb->pk_pro = _ntohs(ehdr->eth_pro); 36 | return ehdr; 37 | } 38 | 39 | /* L2 protocol parsing */ 40 | void net_in(struct netdev *dev, struct pkbuf *pkb) { 41 | struct ether *ehdr = eth_init(dev, pkb); 42 | if (!ehdr) 43 | return; 44 | l2dbg(MACFMT " -> " MACFMT "(%s)", 45 | macfmt(ehdr->eth_src), 46 | macfmt(ehdr->eth_dst), 47 | ethpro(pkb->pk_pro)); 48 | pkb->pk_indev = dev; 49 | switch (pkb->pk_pro) { 50 | // 以太网帧的类型 51 | // https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml#ieee-802-numbers-1 52 | case ETH_P_RARP: 53 | // rarp_in(dev, pkb); 54 | break; 55 | case ETH_P_ARP: 56 | arp_in(dev, pkb); 57 | break; 58 | case ETH_P_IP: 59 | ip_in(dev, pkb); 60 | break; 61 | default: 62 | l2dbg("drop unkown-type packet"); 63 | free_pkb(pkb); 64 | break; 65 | } 66 | } 67 | 68 | void net_timer(void) { 69 | /* timer runs */ 70 | while (1) { 71 | sleep(1); 72 | arp_timer(1); 73 | ip_timer(1); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tapip/net/netdev.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Lowest net device code: 3 | * independent net device layer 4 | */ 5 | #include "ether.h" 6 | #include "lib.h" 7 | #include "list.h" 8 | #include "netif.h" 9 | 10 | /* localhost net device list */ 11 | struct list_head net_devices; 12 | 13 | extern void loop_init(void); 14 | extern void veth_init(void); 15 | extern void loop_exit(void); 16 | extern void veth_exit(void); 17 | extern void veth_poll(void); 18 | 19 | /* Alloc localhost net devices */ 20 | struct netdev *netdev_alloc(char *devstr, struct netdev_ops *netops) { 21 | struct netdev *dev; 22 | dev = xzalloc(sizeof(*dev)); 23 | /* add into localhost net device list */ 24 | list_add_tail(&dev->net_list, &net_devices); 25 | /* set name */ 26 | dev->net_name[NETDEV_NLEN - 1] = '\0'; 27 | strncpy((char *)dev->net_name, devstr, NETDEV_NLEN - 1); 28 | dev->net_ops = netops; 29 | if (netops && netops->init) 30 | netops->init(dev); 31 | return dev; 32 | } 33 | 34 | void netdev_free(struct netdev *dev) { 35 | if (dev->net_ops && dev->net_ops->exit) 36 | dev->net_ops->exit(dev); 37 | list_del(&dev->net_list); 38 | free(dev); 39 | } 40 | 41 | void netdev_interrupt(void) { 42 | veth_poll(); 43 | } 44 | 45 | /* create veth and lo */ 46 | void netdev_init(void) { 47 | list_init(&net_devices); 48 | loop_init(); 49 | veth_init(); 50 | } 51 | 52 | void netdev_exit(void) { 53 | veth_exit(); 54 | loop_exit(); 55 | } 56 | 57 | #ifdef DEBUG_PKB 58 | void _netdev_tx(struct netdev *dev, struct pkbuf *pkb, int len, 59 | unsigned short proto, unsigned char *dst) 60 | #else 61 | void netdev_tx(struct netdev *dev, struct pkbuf *pkb, int len, 62 | unsigned short proto, unsigned char *dst) 63 | #endif 64 | { 65 | struct ether *ehdr = (struct ether *)pkb->pk_data; 66 | 67 | /* first copy to eth_dst, maybe eth_src will be copied to eth_dst */ 68 | ehdr->eth_pro = _htons(proto); 69 | hwacpy(ehdr->eth_dst, ""); 70 | hwacpy(ehdr->eth_src, dev->net_hwaddr); 71 | 72 | l2dbg(MACFMT " -> " MACFMT "(%s)", 73 | macfmt(ehdr->eth_src), 74 | macfmt(ehdr->eth_dst), 75 | ethpro(proto)); 76 | 77 | pkb->pk_len = len + ETH_HRD_SZ; 78 | /* real transmit packet */ 79 | dev->net_ops->xmit(dev, pkb); // 可能发送给lo或者tap0 80 | free_pkb(pkb); 81 | } 82 | 83 | int local_address(unsigned int addr) { 84 | struct netdev *dev; 85 | /* wildchar */ 86 | if (!addr) 87 | return 1; 88 | /* loop back address */ 89 | if (LOCALNET(loop) == (loop->net_mask & addr)) 90 | return 1; 91 | /* nic bind address */ 92 | list_for_each_entry(dev, &net_devices, net_list) { 93 | if (dev->net_ipaddr == addr) 94 | return 1; 95 | } 96 | return 0; 97 | } 98 | -------------------------------------------------------------------------------- /tapip/shell/Makefile: -------------------------------------------------------------------------------- 1 | OBJS = main.o net_command.o ping_command.o shell.o 2 | SUBDIR = shell 3 | 4 | all:shell_obj.o 5 | shell_obj.o:$(OBJS) 6 | @echo " [LD] $(SUBDIR)/$@" 7 | $(Q)$(LD) -r -o $@ $^ 8 | 9 | %.o:%.c 10 | @echo " [CC] $(SUBDIR)/$@" 11 | $(Q)$(CC) $(CFLAGS) -c -o $@ $< 12 | 13 | clean: 14 | rm -f *.o 15 | -------------------------------------------------------------------------------- /tapip/shell/main.c: -------------------------------------------------------------------------------- 1 | #include "arp.h" 2 | #include "lib.h" 3 | #include "netif.h" 4 | #include "route.h" 5 | #include "socket.h" 6 | 7 | extern void shell_master(char *); 8 | extern void *shell_worker(void *); 9 | extern void shell_init(void); 10 | extern void tcp_timer(void); 11 | 12 | /* 13 | * 0 timer for ip and arp 14 | * 1 timer for tcp 15 | * 2 core stack 16 | * 3 shell worker 17 | */ 18 | pthread_t threads[4]; 19 | 20 | pthread_t newthread(pfunc_t thread_func) { 21 | pthread_t tid; 22 | if (pthread_create(&tid, NULL, thread_func, NULL)) 23 | perrx("pthread_create"); 24 | return tid; 25 | } 26 | 27 | void net_stack_init(void) { 28 | netdev_init(); // 初始化lo、tap0网卡设备 29 | arp_cache_init(); 30 | rt_init(); 31 | socket_init(); 32 | shell_init(); 33 | } 34 | 35 | void net_stack_run(void) { 36 | /* create timer thread */ 37 | threads[0] = newthread((pfunc_t)net_timer); 38 | dbg("thread 0: net_timer"); 39 | /* tcp timer */ 40 | threads[1] = newthread((pfunc_t)tcp_timer); 41 | dbg("thread 1: tcp_timer"); 42 | /* create netdev thread */ 43 | threads[2] = newthread((pfunc_t)netdev_interrupt); // 协议栈处理入口 44 | dbg("thread 2: netdev_interrupt"); 45 | /* shell worker thread */ 46 | threads[3] = newthread((pfunc_t)shell_worker); 47 | dbg("thread 3: shell worker"); 48 | /* net shell runs! */ 49 | shell_master(NULL); 50 | } 51 | 52 | void net_stack_exit(void) { 53 | if (pthread_cancel(threads[0])) 54 | perror("kill child 0"); 55 | if (pthread_cancel(threads[1])) 56 | perror("kill child 1"); 57 | if (pthread_cancel(threads[2])) 58 | perror("kill child 2"); 59 | /* shell work will be killed by shell master */ 60 | if (pthread_join(threads[3], NULL)) 61 | perror("kill child 3"); 62 | netdev_exit(); 63 | } 64 | 65 | int main(int argc, char **argv) { 66 | net_stack_init(); 67 | net_stack_run(); 68 | net_stack_exit(); 69 | dbg("wait system exit"); 70 | /* FIXME: release all alloced resources */ 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /tapip/socket/Makefile: -------------------------------------------------------------------------------- 1 | OBJS = socket.o sock.o raw_sock.o inet.o 2 | SUBDIR = tcp 3 | 4 | all:socket_obj.o 5 | socket_obj.o:$(OBJS) 6 | @echo " [LD] $(SUBDIR)/$@" 7 | $(Q)$(LD) -r -o $@ $^ 8 | 9 | %.o:%.c 10 | @echo " [CC] $(SUBDIR)/$@" 11 | $(Q)$(CC) $(CFLAGS) -c -o $@ $< 12 | 13 | clean: 14 | rm -f *.o 15 | -------------------------------------------------------------------------------- /tapip/socket/sock.c: -------------------------------------------------------------------------------- 1 | #include "sock.h" 2 | 3 | #include "list.h" 4 | 5 | int alloc_socks = 0; 6 | int free_socks = 0; 7 | 8 | void sock_add_hash(struct sock *sk, struct hlist_head *head) { 9 | get_sock(sk); 10 | hlist_add_head(&sk->hash_list, head); 11 | } 12 | 13 | void sock_del_hash(struct sock *sk) { 14 | /* Must check whether sk is hashed! */ 15 | if (!hlist_unhashed(&sk->hash_list)) { 16 | hlist_del(&sk->hash_list); 17 | free_sock(sk); 18 | } 19 | } 20 | 21 | #ifdef SOCK_DEBUG 22 | struct sock *_get_sock(struct sock *sk) 23 | #else 24 | struct sock *get_sock(struct sock *sk) 25 | #endif 26 | { 27 | sk->refcnt++; 28 | return sk; 29 | } 30 | 31 | #ifdef SOCK_DEBUG 32 | void _free_sock(struct sock *sk) 33 | #else 34 | void free_sock(struct sock *sk) 35 | #endif 36 | { 37 | if (--sk->refcnt <= 0) { 38 | free_socks++; 39 | free(sk); 40 | } 41 | } 42 | 43 | /* common sock ops */ 44 | void sock_recv_notify(struct sock *sk) { 45 | if (!list_empty(&sk->recv_queue) && sk->recv_wait) 46 | wake_up(sk->recv_wait); 47 | } 48 | 49 | struct pkbuf *sock_recv_pkb(struct sock *sk) { 50 | struct pkbuf *pkb = NULL; 51 | 52 | while (1) { 53 | if (!list_empty(&sk->recv_queue)) { 54 | pkb = list_first_entry(&sk->recv_queue, struct pkbuf, pk_list); 55 | list_del_init(&pkb->pk_list); 56 | break; 57 | } 58 | /* FIXME: sock state handling */ 59 | if (sleep_on(sk->recv_wait) < 0) // 收到数据时, L4协议调用recv_notify,也就是绑定到了sock_recv_notify来唤醒。 60 | break; 61 | } 62 | return pkb; 63 | } 64 | 65 | int sock_close(struct sock *sk) { 66 | struct pkbuf *pkb; 67 | sk->recv_wait = NULL; 68 | /* stop receiving packet */ 69 | if (sk->ops->unhash) 70 | sk->ops->unhash(sk); 71 | /* clear receive queue */ 72 | while (!list_empty(&sk->recv_queue)) { 73 | pkb = list_first_entry(&sk->recv_queue, struct pkbuf, pk_list); 74 | list_del(&pkb->pk_list); 75 | free_pkb(pkb); 76 | } 77 | return 0; 78 | } 79 | 80 | int sock_autobind(struct sock *sk) { 81 | if (sk->ops->set_port) 82 | return sk->ops->set_port(sk, 0); 83 | return -1; 84 | } 85 | -------------------------------------------------------------------------------- /tapip/tcp/Makefile: -------------------------------------------------------------------------------- 1 | OBJS = tcp_in.o tcp_out.o tcp_state.o tcp_sock.o tcp_text.o tcp_timer.o tcp_reass.o 2 | SUBDIR = tcp 3 | 4 | all:tcp_obj.o 5 | tcp_obj.o:$(OBJS) 6 | @echo " [LD] $(SUBDIR)/$@" 7 | $(Q)$(LD) -r -o $@ $^ 8 | 9 | %.o:%.c 10 | @echo " [CC] $(SUBDIR)/$@" 11 | $(Q)$(CC) $(CFLAGS) -c -o $@ $< 12 | 13 | clean: 14 | rm -f *.o 15 | -------------------------------------------------------------------------------- /tapip/tcp/tcp_reass.c: -------------------------------------------------------------------------------- 1 | #include "netif.h" 2 | #include "tcp.h" 3 | 4 | struct tcp_reass_head { 5 | struct list_head list; /* list of reassamble tcp segment */ 6 | void *data; /* memory address of tcp segment text */ 7 | unsigned int seq; /* sequence number of tcp segment text */ 8 | unsigned int len; /* data length of tcp segment text */ 9 | }; 10 | 11 | void tcp_free_reass_head(struct tcp_sock *tsk) { 12 | struct tcp_reass_head *trh; 13 | while (!list_empty(&tsk->rcv_reass)) { 14 | trh = list_first_entry(&tsk->rcv_reass, struct tcp_reass_head, list); 15 | list_del(&trh->list); 16 | free_pkb(containof(trh, struct pkbuf, pk_data)); 17 | } 18 | } 19 | 20 | void tcp_segment_reass(struct tcp_sock *tsk, struct tcp_segment *seg, struct pkbuf *pkb) { 21 | /* reuse ether header as tcp_reass_head magically */ 22 | struct tcp_reass_head *trh, *ctrh, *prev, *next; 23 | int rlen, len; 24 | 25 | /* TODO: how much text data is cached in reass list */ 26 | 27 | list_for_each_entry(trh, &tsk->rcv_reass, list) { 28 | if (seg->seq < trh->seq) { 29 | prev = list_last_entry(&trh->list, struct tcp_reass_head, list); 30 | ADJACENT_SEGMENT_HEAD(prev->seq + prev->len); 31 | break; 32 | } 33 | } 34 | 35 | list_for_each_entry_safe_continue(trh, next, &tsk->rcv_reass, list) { 36 | if (seg->seq + seg->dlen < trh->seq + trh->len) { 37 | if (seg->seq + seg->dlen > trh->seq) { 38 | seg->dlen = trh->seq - seg->seq; 39 | if (seg->dlen == 0) 40 | goto out; 41 | assert(seg->dlen > 0); 42 | } 43 | break; 44 | } 45 | /* delete duplicate segment from reass list */ 46 | list_del(&trh->list); 47 | free_pkb(containof(trh, struct pkbuf, pk_data)); 48 | } 49 | 50 | /* insert segment into prev of trh */ 51 | ctrh = (struct tcp_reass_head *)pkb->pk_data; 52 | list_init(&ctrh->list); 53 | ctrh->data = seg->text; 54 | ctrh->seq = seg->seq; 55 | ctrh->len = seg->dlen; 56 | list_add_tail(&ctrh->list, &trh->list); 57 | get_pkb(pkb); 58 | 59 | /* Can it write reass segment to cbuf */ 60 | len = rlen = 0; 61 | list_for_each_entry_safe(trh, next, &tsk->rcv_reass, list) { 62 | if (trh->seq > tsk->rcv_nxt) 63 | break; 64 | assert(trh->seq == tsk->rcv_nxt); 65 | rlen = tcp_write_buf(tsk, trh->data, trh->len); 66 | if (rlen <= 0) 67 | break; 68 | len += rlen; 69 | list_del(&trh->list); 70 | free_pkb(containof(trh, struct pkbuf, pk_data)); 71 | } 72 | 73 | if (len > 0 && seg->tcphdr->psh) 74 | tsk->flags |= TCP_F_PUSH; 75 | 76 | out: 77 | return; 78 | } 79 | -------------------------------------------------------------------------------- /tapip/tcp/tcp_timer.c: -------------------------------------------------------------------------------- 1 | #include "tcp.h" 2 | 3 | static struct tcp_timer_head timewait; 4 | /* static struct tcp_timer_head retrans; */ 5 | 6 | /* TIME-WAIT TIMEOUT */ 7 | void tcp_timewait_timer(int delta) { 8 | struct tcp_timer *t, *next, **pprev; 9 | struct tcp_sock *tsk; 10 | for (pprev = &timewait.next, t = timewait.next; t; t = next) { 11 | next = t->next; /* for safe deletion */ 12 | t->next = NULL; 13 | t->timeout -= delta; 14 | if (t->timeout <= 0) { 15 | /* TIME-WAIT expires: tcb deletion */ 16 | tsk = timewait2tsk(t); 17 | if (!tsk->parent) 18 | tcp_unbhash(tsk); 19 | tcp_unhash(&tsk->sk); 20 | tcp_set_state(tsk, TCP_CLOSED); 21 | free_sock(&tsk->sk); 22 | *pprev = next; 23 | } else { 24 | pprev = &t->next; 25 | } 26 | } 27 | } 28 | 29 | void tcp_set_timewait_timer(struct tcp_sock *tsk) { 30 | tcp_set_state(tsk, TCP_TIME_WAIT); 31 | /* FIXME: lock */ 32 | tsk->timewait.timeout = TCP_TIMEWAIT_TIMEOUT; 33 | tsk->timewait.next = timewait.next; 34 | timewait.next = &tsk->timewait; 35 | /* reference for TIME-WAIT TIMEOUT releasing */ 36 | get_tcp_sock(tsk); 37 | } 38 | 39 | void tcp_timer(void) { 40 | unsigned int i = 0; 41 | /* init */ 42 | timewait.next = NULL; 43 | while (1) { 44 | usleep(TCP_TIMER_DELTA); 45 | i++; 46 | /* 1 second timeout for TIME-WAIT timer */ 47 | if ((i % (1000000 / TCP_TIMER_DELTA)) == 0) 48 | tcp_timewait_timer(1000000); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tapip/udp/Makefile: -------------------------------------------------------------------------------- 1 | OBJS = udp.o udp_sock.o 2 | SUBDIR = udp 3 | 4 | all:udp_obj.o 5 | udp_obj.o:$(OBJS) 6 | @echo " [LD] $(SUBDIR)/$@" 7 | $(Q)$(LD) -r -o $@ $^ 8 | 9 | %.o:%.c 10 | @echo " [CC] $(SUBDIR)/$@" 11 | $(Q)$(CC) $(CFLAGS) -c -o $@ $< 12 | 13 | clean: 14 | rm -f *.o -------------------------------------------------------------------------------- /tapip/udp/udp.c: -------------------------------------------------------------------------------- 1 | #include "udp.h" 2 | 3 | #include "icmp.h" 4 | #include "ip.h" 5 | #include "list.h" 6 | #include "netif.h" 7 | #include "sock.h" 8 | 9 | static void udp_recv(struct pkbuf *pkb, struct ip *iphdr, struct udp *udphdr) { 10 | struct sock *sk; 11 | sk = udp_lookup_sock(udphdr->dst); // udp需要按照端口进行查找 12 | if (!sk) { 13 | icmp_send(ICMP_T_DESTUNREACH, ICMP_PORT_UNREACH, 0, pkb); 14 | goto drop; 15 | } 16 | /* FIFO receive queue */ 17 | list_add_tail(&pkb->pk_list, &sk->recv_queue); 18 | sk->ops->recv_notify(sk); 19 | /* We have handled the input packet with sock, so release it */ 20 | free_sock(sk); 21 | return; 22 | drop: 23 | free_pkb(pkb); 24 | } 25 | 26 | void udp_in(struct pkbuf *pkb) { 27 | struct ip *iphdr = pkb2ip(pkb); 28 | struct udp *udphdr = ip2udp(iphdr); 29 | int udplen = ipdlen(iphdr); 30 | 31 | if (udplen < UDP_HRD_SZ || udplen < _ntohs(udphdr->length)) { 32 | udpdbg("udp length is too small"); 33 | goto drop_pkb; 34 | } 35 | /* Maybe ip data has pad bytes. */ 36 | if (udplen > _ntohs(udphdr->length)) 37 | udplen = _ntohs(udphdr->length); 38 | if (udphdr->checksum && udp_chksum(iphdr->ip_src, iphdr->ip_dst, 39 | udplen, (unsigned short *)udphdr) != 0) { 40 | udpdbg("udp packet checksum corrupts"); 41 | goto drop_pkb; 42 | } 43 | /* 44 | * Should we check source ip address? 45 | * Maybe ip layer does it for us! 46 | */ 47 | 48 | udpdbg("from " IPFMT 49 | ":%d" 50 | " to " IPFMT ":%d", 51 | ipfmt(iphdr->ip_src), _ntohs(udphdr->src), 52 | ipfmt(iphdr->ip_dst), _ntohs(udphdr->dst)); 53 | udp_recv(pkb, iphdr, udphdr); 54 | return; 55 | drop_pkb: 56 | free_pkb(pkb); 57 | } 58 | -------------------------------------------------------------------------------- /test/buffer_test.cpp: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | TEST(BufferTest, BasicTest) { 9 | EasyNet::Buffer *buff = new EasyNet::Buffer(); 10 | EXPECT_EQ(buff->GetReadableSize(), 0); 11 | EXPECT_EQ(buff->GetWriteableSize(), EasyNet::BufferDetail::KInitalSize); 12 | 13 | const std::string str(200, 'x'); 14 | buff->Append(str); 15 | EXPECT_EQ(buff->GetReadableSize(), str.size()); 16 | EXPECT_EQ(buff->GetWriteableSize(), EasyNet::BufferDetail::KInitalSize - str.size()); 17 | 18 | const std::string str2 = buff->RetriveAsString(50); 19 | EXPECT_EQ(buff->GetPrependableSize(), 50); 20 | EXPECT_EQ(str2.size(), 50); 21 | EXPECT_EQ(buff->GetReadableSize(), str.size() - str2.size()); 22 | EXPECT_EQ(buff->GetWriteableSize(), EasyNet::BufferDetail::KInitalSize - str.size()); 23 | EXPECT_EQ(str2, std::string(50, 'x')); 24 | 25 | buff->Append(str); 26 | EXPECT_EQ(buff->GetReadableSize(), 2 * str.size() - str2.size()); 27 | EXPECT_EQ(buff->GetWriteableSize(), EasyNet::BufferDetail::KInitalSize - 2 * str.size()); 28 | 29 | const std::string str3 = buff->RetriveAllAsString(); 30 | EXPECT_EQ(str3.size(), 350); 31 | EXPECT_EQ(buff->GetReadableSize(), 0); 32 | EXPECT_EQ(buff->GetWriteableSize(), EasyNet::BufferDetail::KInitalSize); 33 | EXPECT_EQ(str3, std::string(350, 'x')); 34 | } 35 | 36 | TEST(BufferTest, AppendTest) { 37 | EasyNet::Buffer *buff = new EasyNet::Buffer(); 38 | buff->Append("Hello,word!"); 39 | auto retStr1 = buff->RetriveAsString(6); 40 | auto retStr = buff->RetriveAllAsString(); 41 | EXPECT_EQ(retStr1, "Hello,"); 42 | EXPECT_EQ(retStr, "word!"); 43 | } -------------------------------------------------------------------------------- /test/count_down_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "countdown_latch.h" 6 | 7 | class Job { 8 | public: 9 | Job(std::string name) : name{std::move(name)} {} 10 | 11 | const std::string name; 12 | std::string product{"未工作"}; 13 | std::thread action{}; 14 | }; 15 | 16 | TEST(CountDown, BasicTest) { 17 | Job jobs[]{{"Annika"}, {"Buru"}, {"Chuck"}}; 18 | 19 | EasyNet::CountDownLatch work_done{sizeof(jobs) / sizeof(jobs[0])}; 20 | EasyNet::CountDownLatch start_clean_up{1}; 21 | 22 | auto work = [&](Job& my_job) { 23 | my_job.product = my_job.name + " 已工作"; 24 | work_done.CountDown(); 25 | start_clean_up.Wait(); 26 | my_job.product = my_job.name + " 已清理"; 27 | }; 28 | 29 | std::string actual; 30 | actual += "工作启动... "; 31 | for (auto& job : jobs) 32 | job.action = std::thread{work, std::ref(job)}; 33 | 34 | work_done.Wait(); 35 | actual += "完成:\n"; 36 | for (auto const& job : jobs) 37 | actual += " " + job.product + '\n'; 38 | 39 | actual += "清理工作线程... "; 40 | start_clean_up.CountDown(); 41 | for (auto& job : jobs) 42 | job.action.join(); 43 | 44 | actual += "完成:\n"; 45 | for (auto const& job : jobs) 46 | actual += " " + job.product + '\n'; 47 | 48 | std::string excepted = "工作启动... 完成:\n Annika 已工作\n Buru 已工作\n Chuck 已工作\n清理工作线程... 完成:\n Annika 已清理\n Buru 已清理\n Chuck 已清理\n"; 49 | EXPECT_EQ(actual, excepted); 50 | } -------------------------------------------------------------------------------- /test/gtest_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char *argv[]) { 4 | testing::InitGoogleTest(&argc, argv); 5 | return RUN_ALL_TESTS(); 6 | } -------------------------------------------------------------------------------- /test/inet_addr_test.cpp: -------------------------------------------------------------------------------- 1 | #include "inet_addr.h" 2 | 3 | #include 4 | 5 | TEST(InetAddress, BasicTest) { 6 | auto addr_ipv4 = EasyNet::InetAddress("127.0.0.1", 8888, false); 7 | EXPECT_EQ(addr_ipv4.family(), AF_INET); 8 | EXPECT_EQ(addr_ipv4.SerializationToIP(), "127.0.0.1"); 9 | EXPECT_EQ(addr_ipv4.SerializationToPort(), "8888"); 10 | EXPECT_EQ(addr_ipv4.SerializationToIpPort(), "127.0.0.1:8888"); 11 | 12 | auto addr_ipv6 = EasyNet::InetAddress("2001:db8:85a3::8a2e:370:7334", 8888, true); 13 | EXPECT_EQ(addr_ipv6.family(), AF_INET6); 14 | EXPECT_EQ(addr_ipv6.SerializationToIP(), "2001:db8:85a3::8a2e:370:7334"); 15 | EXPECT_EQ(addr_ipv6.SerializationToPort(), "8888"); 16 | EXPECT_EQ(addr_ipv6.SerializationToIpPort(), "2001:db8:85a3::8a2e:370:7334:8888"); 17 | } 18 | 19 | TEST(InetAddress, loopbackOnly) { 20 | auto addr1_ipv4 = EasyNet::InetAddress(1111, true, false); 21 | EXPECT_EQ(addr1_ipv4.SerializationToPort(), "1111"); 22 | EXPECT_EQ(addr1_ipv4.SerializationToIP(), "127.0.0.1"); 23 | 24 | auto addr1_ipv6 = EasyNet::InetAddress(2222, true, true); 25 | EXPECT_EQ(addr1_ipv6.SerializationToPort(), "2222"); 26 | EXPECT_EQ(addr1_ipv6.SerializationToIP(), "::1"); 27 | 28 | auto addr2_ipv4 = EasyNet::InetAddress(3333, false, false); 29 | EXPECT_EQ(addr2_ipv4.SerializationToPort(), "3333"); 30 | EXPECT_EQ(addr2_ipv4.SerializationToIP(), "0.0.0.0"); 31 | 32 | auto addr2_ipv6 = EasyNet::InetAddress(4444, false, true); 33 | EXPECT_EQ(addr2_ipv6.SerializationToPort(), "4444"); 34 | EXPECT_EQ(addr2_ipv6.SerializationToIP(), "::"); 35 | } -------------------------------------------------------------------------------- /test/url_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "http_url.h" 4 | 5 | TEST(HttpTest, BasicTest) { 6 | EasyNet::HttpUrlParser parser("https://www.hlllz.cn/v1/api/Hi?key1=val1&key2=val2#fragment"); 7 | EXPECT_TRUE(parser.Parse()); 8 | EXPECT_EQ(parser.url.protocol, "https"); 9 | EXPECT_EQ(parser.url.host, "www.hlllz.cn"); 10 | EXPECT_EQ(parser.url.port, "443"); 11 | EXPECT_EQ(parser.url.path, "/v1/api/Hi"); 12 | int i = 0; 13 | for (auto &query : parser.url.query) { 14 | switch (i) { 15 | case 0: 16 | EXPECT_EQ(query.first, "key1"); 17 | EXPECT_EQ(query.second, "val1"); 18 | case 1: 19 | EXPECT_EQ(query.first, "key2"); 20 | EXPECT_EQ(query.second, "val2"); 21 | } 22 | i++; 23 | } 24 | EXPECT_EQ(parser.url.fragment, "fragment"); 25 | } 26 | 27 | TEST(HttpTest, OnlyHasHost) { 28 | EasyNet::HttpUrlParser parser("http://www.hlllz.cn"); 29 | EXPECT_TRUE(parser.Parse()); 30 | EXPECT_EQ(parser.url.protocol, "http"); 31 | EXPECT_EQ(parser.url.host, "www.hlllz.cn"); 32 | EXPECT_EQ(parser.url.port, "80"); 33 | } 34 | 35 | TEST(HttpTest, LocalHost) { 36 | EasyNet::HttpUrlParser parser("http://127.0.0.1"); 37 | EXPECT_TRUE(parser.Parse()); 38 | EXPECT_EQ(parser.url.protocol, "http"); 39 | EXPECT_EQ(parser.url.host, "127.0.0.1"); 40 | EXPECT_EQ(parser.url.port, "80"); 41 | } 42 | 43 | TEST(HttpTest, LocalHostWithPort) { 44 | EasyNet::HttpUrlParser parser("http://127.0.0.1:8888"); 45 | EXPECT_TRUE(parser.Parse()); 46 | EXPECT_EQ(parser.url.protocol, "http"); 47 | EXPECT_EQ(parser.url.host, "127.0.0.1"); 48 | EXPECT_EQ(parser.url.port, "8888"); 49 | } -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | # eTools 2 | 3 | 方便EasyNet开发的一些小工具合集: 4 | - [ethfilter](./docs/ethfilter.md):网卡抓包工具,可以打印IP层、TCP/UDP层、应用层包的信息 5 | - [echo](./docs/echo.md):方便压测echosvr -------------------------------------------------------------------------------- /tools/cmd/echo.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | "sync/atomic" 11 | 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | const ( 16 | message = "Ping" 17 | ) 18 | 19 | var ( 20 | ip string 21 | port string 22 | gcnt int 23 | ) 24 | 25 | var echoCmd = &cobra.Command{ 26 | Use: "echo", 27 | Short: "Stress test echo server", 28 | Example: ` 29 | eg1: eTools echo -i 127.0.0.1 -p 8888 -g 1000`, 30 | Run: func(cmd *cobra.Command, args []string) { 31 | wg := sync.WaitGroup{} 32 | var count int64 33 | for i := 1; i <= gcnt; i++ { 34 | wg.Add(1) 35 | go func(i int) { 36 | defer wg.Done() 37 | if !SocketClient(ip, port, i) { 38 | atomic.AddInt64(&count, 1) 39 | } 40 | }(i) 41 | } 42 | wg.Wait() 43 | fmt.Printf("Total:%d Fail:%d\n", gcnt, count) 44 | }, 45 | } 46 | 47 | func SocketClient(ip, port string, index int) bool { 48 | // 根据端口拼接网络地址 49 | addr := strings.Join([]string{ip, port}, ":") 50 | 51 | // 连接服务器 52 | conn, err := net.Dial("tcp", addr) 53 | if err != nil { 54 | return false 55 | } 56 | defer func() { 57 | fmt.Println(conn.LocalAddr().String() + " Close") 58 | conn.Close() 59 | }() 60 | 61 | // 写入发送消息 62 | msg := message + "_" + strconv.Itoa(index) 63 | _, err = conn.Write([]byte(msg)) 64 | if err != nil { 65 | log.Printf("Write Err:%+v\n", err) 66 | return false 67 | } 68 | 69 | // 读取返回消息 70 | buff := make([]byte, 1024) 71 | n, err := conn.Read(buff) 72 | if err != nil { 73 | log.Printf("Read Err:%+v\n", err) 74 | return false 75 | } 76 | recv_msg := string(buff[:n]) 77 | if recv_msg != msg { 78 | log.Printf("LocalAddr=%+v send_msg=%v recv_msg=%v\n", conn.LocalAddr().String(), msg, recv_msg) 79 | return false 80 | } 81 | return true 82 | } 83 | 84 | func init() { 85 | rootCmd.AddCommand(echoCmd) 86 | echoCmd.Flags().StringVarP(&ip, "ip", "i", "", "server ip") 87 | echoCmd.Flags().StringVarP(&port, "port", "p", "", "server port") 88 | echoCmd.Flags().IntVarP(&gcnt, "goroutine", "g", 1, "req goroutine") 89 | } 90 | -------------------------------------------------------------------------------- /tools/cmd/ethfilter.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "log" 5 | "strings" 6 | 7 | "github.com/google/gopacket" 8 | "github.com/google/gopacket/layers" 9 | "github.com/google/gopacket/pcap" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var ( 14 | device string = "eth0" //网卡设备名 15 | filter string = "" //过滤器 16 | pkgcnt int16 = 0 //捕获数据包的个数,0表示一直捕获 17 | snapshot int32 = 65535 //读取一个数据包的最大值,一般设置成这65535即可 18 | promisc bool = true //是否开启混杂模式 19 | ) 20 | 21 | var ethfilterCmd = &cobra.Command{ 22 | Use: "ethfilter", 23 | Short: "Capture packets on a network interface, Support BPF filter", 24 | Long: `Capture packets on a network interface, Support BPF filter. 25 | 26 | PS:if your svr/cli running in the same machine, capture the loopback. 27 | also you can use 'tcpdump -Xnnlps0 -iany' to capture packets on all interfaces.`, 28 | Example: ` 29 | eg1: eTools ethfilter -d eth0 tcp and port 80 //use bpf filter 30 | eg2: eTools ethfilter -d eth0 //capture all packets 31 | eg3: eTools ethfilter -d eth0 -c 10 //capture 10 packets 32 | `, 33 | Run: func(cmd *cobra.Command, args []string) { 34 | //获取一个网卡句柄 35 | handle, err := pcap.OpenLive(device, snapshot, promisc, pcap.BlockForever) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | defer handle.Close() 40 | 41 | // 设置过滤器,像tcpdump设置过滤器一样的 42 | filter = strings.Join(args, " ") 43 | err = handle.SetBPFFilter(filter) //设置过滤器 44 | if err != nil { 45 | log.Printf("Error setting BPF filter[%+v] for devInterface %s: %v", filter, device, err) 46 | return 47 | } 48 | 49 | //NewPacketSource新建一个数据包数据源 50 | packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) 51 | 52 | //捕捉一个数据包 53 | // packet, err := packetSource.NextPacket() //返回一个数据包 54 | // if err != nil { 55 | // log.Fatal(err) 56 | // } 57 | // fmt.Println(packet) 58 | 59 | //捕捉数据包 60 | always := false 61 | if pkgcnt == 0 { 62 | always = true 63 | } 64 | captureCnt := 0 65 | p := packetSource.Packets() //返回一个channel 66 | for data := range p { 67 | if !always && captureCnt >= int(pkgcnt) { 68 | break 69 | } 70 | captureCnt++ 71 | ipLayer := data.Layer(layers.LayerTypeIPv4) 72 | if ipLayer != nil { 73 | ip, _ := ipLayer.(*layers.IPv4) 74 | log.Printf("IP layer %+v\n", ip) 75 | } 76 | tcpLayer := data.Layer(layers.LayerTypeTCP) 77 | if tcpLayer != nil { 78 | tcp, _ := tcpLayer.(*layers.TCP) 79 | log.Printf("TCP layer %+v\n", tcp) 80 | } 81 | appLayer := data.ApplicationLayer() 82 | if appLayer != nil { 83 | log.Printf("Application layer %+v\n\n", appLayer.Payload()) 84 | } 85 | } 86 | }, 87 | } 88 | 89 | func init() { 90 | rootCmd.AddCommand(ethfilterCmd) 91 | ethfilterCmd.Flags().StringVarP(&device, "device", "d", "eth0", "device name") 92 | ethfilterCmd.Flags().Int16VarP(&pkgcnt, "count", "c", 1, "capture packet count, 0 means always capture") 93 | } 94 | -------------------------------------------------------------------------------- /tools/cmd/keepalive.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var addr string 11 | var maxConn int 12 | 13 | var keepaliveCmd = &cobra.Command{ 14 | Use: "keepalive", 15 | Short: "Http keepalive test", 16 | Run: func(cmd *cobra.Command, args []string) { 17 | for i := 0; i < 3; i++ { 18 | resp, err := http.Get("http://127.0.0.1:8888/Hi") 19 | if err != nil { 20 | panic(err) 21 | } 22 | // 空读,注:golang里一定要把数据读完,否则不会复用TCP连接 23 | _, _ = io.Copy(io.Discard, resp.Body) 24 | resp.Body.Close() 25 | } 26 | }, 27 | } 28 | 29 | func init() { 30 | rootCmd.AddCommand(keepaliveCmd) 31 | keepaliveCmd.Flags().StringVarP(&addr, "addr", "a", "http://127.0.0.1:8888/Hi", "http url") 32 | keepaliveCmd.Flags().IntVarP(&maxConn, "maxConn", "m", 1, "max connection") 33 | } 34 | -------------------------------------------------------------------------------- /tools/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var rootCmd = &cobra.Command{ 11 | Use: os.Args[0], 12 | Short: "A tool for EasyNet Develop and Debug", 13 | } 14 | 15 | func Execute() { 16 | rootCmd.CompletionOptions.DisableDefaultCmd = true 17 | err := rootCmd.Execute() 18 | if err != nil { 19 | log.Fatalf("Execute err:%+v", err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tools/docs/echo.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HLhuanglang/eNET/4cf85828a467273b1500615a4ddf0de931e3fdce/tools/docs/echo.md -------------------------------------------------------------------------------- /tools/docs/ethfilter.md: -------------------------------------------------------------------------------- 1 | # 网卡抓包工具 2 | 3 | 网卡驱动将数据链路层的包后copy了一份,通过原始套接字丢给了libpacp库,然后gopacket库做了一些ip组包之类的操作,提供了一个方便使用的接口。整个抓包结构大概如下: 4 | 5 | ![](https://hl1998-1255562705.cos.ap-shanghai.myqcloud.com/Img/1717839294964(1).jpg) 6 | 7 | ## 1. 安装pcap库 8 | - On Ubuntu or other Debian-based systems, you can use the following command: 9 | 10 | ```bash 11 | sudo apt-get install libpcap-dev 12 | ``` 13 | 14 | - On CentOS, Fedora, or other RHEL-based systems, you can use the following command: 15 | 16 | ```bash 17 | sudo yum install libpcap-devel 18 | ``` 19 | 20 | ## 2. 使用gopacket -------------------------------------------------------------------------------- /tools/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/HLhuanglang/EasyNet/tools 2 | 3 | go 1.21.1 4 | 5 | require ( 6 | github.com/google/gopacket v1.1.19 7 | github.com/spf13/cobra v1.8.0 8 | ) 9 | 10 | require ( 11 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 12 | github.com/spf13/pflag v1.0.5 // indirect 13 | golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /tools/go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= 3 | github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= 4 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 5 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 6 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 7 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 8 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 9 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 10 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 11 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 12 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 13 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 14 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 15 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 16 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= 17 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 18 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 19 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 20 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= 21 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 22 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 23 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 24 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 25 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 26 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 27 | -------------------------------------------------------------------------------- /tools/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/HLhuanglang/EasyNet/tools/cmd" 10 | ) 11 | 12 | func main() { 13 | // 设置日志输出到文件 14 | executablePath, err := os.Executable() 15 | if err != nil { 16 | fmt.Println("Error getting executable path:", err) 17 | return 18 | } 19 | executableDir := filepath.Dir(executablePath) // 获取可执行文件所在的目录路径 20 | logFilePath := filepath.Join(executableDir, "eTools.log") // 构建日志文件的完整路 21 | logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 22 | if err != nil { 23 | log.Fatal("open eTool.log fail:", err) 24 | } 25 | defer logFile.Close() 26 | log.SetOutput(logFile) 27 | 28 | // 执行命令 29 | cmd.Execute() 30 | } 31 | -------------------------------------------------------------------------------- /tools/makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go build -o bin/eTools main.go 3 | --------------------------------------------------------------------------------