helio_echo_server
Helio echo_server
echo_server
📦 소개 Introduction
echo_server는 thread, fiber를 사용해서 메시지를 주고, 받습니다.
echo_server 파일은 실행 옵션에 따라 Server 또는 Client로 역할을 합니다.
성능 테스트
📦 실행 옵션
- --logtostderr (log messages go to stderr instead of logfiles); default: false;
로그가 화면으로 나온다. "--logtostderr"만 지정하면 된다. (뒤에 true 필요없음)
helio/build-dbg/_deps/glog-src/src/logging.cc - --v (Show all VLOG(m) messages for m <= this. Overridable by --vmodule.); default: 0;
helio/build-opt/_deps/glog-src/src/vlog_is_on.cc:
GOOGLE_NAMESPACE::absl_proxy_v = absl::GetFlag(FLAGS_v);
• LOG(INFO): --v를 지정하지 않아도 찍힌다.
• VLOG(1), DVLOG(1): --v=1을 지정하면 찍힌다.
• VLOG(2), DVLOG(2): --v=2을 지정하면 1,2 모두 찍힌다. - --connect (hostname or ip address to connect to in client mode); default: "";
main()함수에서 connect가 없으면 Server mode로 실행하고, 있으면 Client mode로 실행한다.
Client mode로 실행하려면 localhost 또는 서버의 IP를 지정한다. - --raw (If true, does not send/receive size parameter during the connection handshake); default: true;
Server/Client 동일하게 지정해야 함.
- --size (Message size, 0 for hardcoded 4 byte pings); default: 1;
Server/Client 동일하게 지정해야 함.
- --c (Number of connections per thread); default: 10; For Client mode
서버도 대응해서 같은 개수로 connection이 생긴다.
- --n (Number of requests per connection); default: 1000; For Client mode
- --p (pipelining factor); default: 1; For Client mode
- --port (Echo server port); default: 8081; FLAGS_port
- --http_port (Http port.); default: 8080; FLAGS_http_port
- --tcp_nodelay (if true - use tcp_nodelay option for server sockets); default: false;
- --backlog (Accept queue length); default: 1024;
- --epoll (If true use epoll for server); default: false;
epoll이 있으면 Epoll()을 호출하고, 없으면 IOUring()을 호출합니다. - --max_clients default: 65536; For Server mode
- --write_num (); default: 1000; 미사용
- --max_pending_writes (); default: 300; 미사용
📦 성능 테스트
컴파일
- [helio] $ blaze.sh -release -> Release(최적화) 모드로 컴파일 준비
[build-opt] $ ninja -j1 echo_server -> 컴파일 by ninja, -j 스레드 옵션: 1로 지정 - [helio] $ blaze.sh -> 디버그 모드로 컴파일 준비: 옵션을 지정하지 않으면
[build-dbg] $ ninja -j1 echo_server -> 컴파일 by ninja, -j 스레드 옵션: 1로 지정 - Release(최적화) 모드로 컴파일해서 성능 테스트를 하면 디버그 모드 보다 좋은 결과를 기대했으나, 테스트 결과 차이가 없었다.
조건: raw=true
- Server: echo_server --logtostderr --proactor_threads=1 --max_clients=10000 --raw=true --size=10
- Client: echo_client --logtostderr --proactor_threads=1 --connect=localhost --raw=true --size=10 --c=10 --n=1000 --p=1
--> c=10 * n=1000 -> 1만번 실행
• Total time 550 ms, num reqs: 10000 qps: 18181 (query per second)
• Average:548(us) Min:218 Median:446 Max:3837
- Client: echo_client --logtostderr --proactor_threads=1 --connect=localhost --raw=true --size=10 --c=10 --n=10000 --p=1
--> c=10 * n=10000 -> 10만번 실행
• Total time 5015 ms, num reqs: 100000 qps: 19940
• Average:500(us) Min:132 Median:432 Max:3142
- Client: echo_client --logtostderr --proactor_threads=1 --connect=localhost --raw=true --size=10 --c=10 --n=10000 --p=10
--> --p(pipeline)=10으로 설정: Driver::Run()에서 보낼 때 n=1 마다 Write()회, 받을 때 n=1 마다 Recv()회 실행
• CPU 사용률: Server: 50%, Client: 30%
• Total time 94675 ms, num reqs: 100000 qps: 1056
• Average:9466(us) Min:3509 Median:9177 Max:90086
- Client: echo_client --logtostderr --proactor_threads=1 --connect=localhost --raw=true --size=10 --c=10 --n=100000 --p=1
--> c=10 * n=100000 -> 1백만번 실행
⦿ 1회
• Total time 50653 ms, num reqs: 1000000 qps: 19742
• Average:505(us) Min:104 Median:441 Max:27421
⦿ 2회
• Total time 50029 ms, num reqs: 1000000 qps: 19988
• Average:499(us) Min:112 Median:438 Max:108578
- Client: echo_client --logtostderr --proactor_threads=1 --connect=localhost --raw=true --size=10 --c=20 --n=100000 --p=1
--> c=20 * n=100000 -> 2백만번 실행
⦿ 1회
• Total time 92283 ms, num reqs: 2000000 qps: 21672
• Average:921(us) Min:218 Median:832 Max:28406
⦿ 2회: CPU Server: 37%, Client: 37%
• Total time 95041 ms, num reqs: 2000000 qps: 21043
• Average:949(us) Min:160 Median:878 Max:97207
조건: raw=false
- Server: echo_server --logtostderr --proactor_threads=1 --max_clients=10000 --raw=false --size=10
- Client: echo_client --logtostderr --proactor_threads=1 --connect=localhost --raw=true --size=10 --c=1 --n=1000000 --p=1
--> c=1 * n=1000000 -> 1백만번 실행
• CPU Server:42%, Client:36%
• Total time 73843 ms, num reqs: 1000000 qps: 13542
• Min:18 Median:67 Max:16629 - Client: echo_client --logtostderr --proactor_threads=1 --connect=localhost --raw=true --size=10 --c=100 --n=10000 --p=1
--> c=100 * n=10000 -> 1백만번 실행
• CPU Server:35%, Client:35%
• Total time 46184 ms, num reqs: 1000000 qps: 21652
• Min:983 Median:3756 Max:31693
- Client: echo_client --logtostderr --proactor_threads=1 --connect=localhost --raw=true --size=10 --c=10 --n=100000 --p=1
⦿ client 1개 실행
• 100만 33sec 30303/sec avg:325us median:278us
• 100만 32sec 31250/sec avg:324us median:279us
• 100만 31sec 32258/sec avg:312us median:276us
• 100만 57sec 17544/sec avg:575us median:551us CPU Server:41% Client:34%
• 100만 31sec 32258/sec avg:317us median:278us CPU Server:35% Client:34%
⦿ client 2개 실행 Server CPU:32%
• Client1: 100만 92sec avg:925us median:949us CPU:26%
• Client2: 100만 94sec avg:947us median:948us CPU:16%
⦿ client 3개 실행 Server CPU:35%
• Client1: 100만 186sec avg:1860us median:1920us CPU:14%
• Client2: 100만 191sec avg:1918us median:1921us CPU:14%
• Client3: 100만 190sec abg:1904us median:1920us CPU:14%
조건: threads=10 raw=true
- Server: echo_server --logtostderr --proactor_threads=10 --max_clients=10000 --raw=true --size=10
- 프로세스/스레드 확인
- Client: echo_client --logtostderr --proactor_threads=1 --connect=localhost --raw=true --size=10 --c=10 --n=100000 --p=1
⦿ client 스레드: 1개
• 1회) 100만 50sec qps:19926 avg:482(us) median:416(us)
• 2회) 100만 55sec qps:18128 avg:523(us) median:433(us)
- Client: echo_client --logtostderr --proactor_threads=2 --connect=localhost --raw=true --size=10 --c=10 --n=100000 --p=1
⦿ client 스레드: 2개
• 1회) 200만 71sec qps:28146 avg:675(us) min:31 median:496 max:193799
- Client: echo_client --logtostderr --proactor_threads=3 --connect=localhost --raw=true --size=10 --c=10 --n=100000 --p=1
⦿ client 스레드: 3개
• 1회) 300만 81sec qps:36828 avg:749(us) min:20 median:482 max:134040
• 2회) 300만 75sec qps:39946 avg:632(us) min:20 median:421 max:159217
• 3회) 300만 76sec qps:39020 avg:613(us) min:19 median:399 max:111468
• CPU Server 153%, Client: 114%
- Client: echo_client --logtostderr --proactor_threads=4 --connect=localhost --raw=true --size=10 --c=10 --n=100000 --p=1
⦿ Client 스레드: 4개
• 1회) 400만 91sec qps:43870 avg:825(us) min:28 median:521 max:159143
• 2회) 400만 80sec qps:49800 avg:707(us) min:31 median:494 max:128956
• 3회) 400만 77sec qps:51460 avg:686(us) min:27 median:501 max:120585
• CPU Server 151%, Client: 123%
- Client: echo_client --logtostderr --proactor_threads=5 --connect=localhost --raw=true --size=10 --c=10 --n=100000 --p=1
⦿ Client 스레드: 5개
• 1회) 500만 90sec qps:55152 avg:790(us) min:19 median:556 max:147255
• 2회) 500만 91sec qps:54511 avg:819(us) min:28 median:552 max:196281
• 3회) 500만 97sec qps:51082 avg:869(us) min:30 median:6230 max:117512
• 4회) 500만 64sec qps:77998 avg:590(us) min:16 median:469 max:75976
• 5회) 500만 63sec qps:78721 avg:594(us) min:15 median:468 max:52912
• 6회) 500만 171sec qps:29143 avg:1239(us) min:23 median:509 max:293549
• 7회) 500만 100sec qps:49920 avg:853(us) min:18 median:564 max:128095
• CPU Server 160%, Client: 139%
⦿ Client 스레드 5개 * Client 2개 실행
Client 2개를 동시에 실행하면 더 많이 처리할 것으로 기대했으나 2개 합계 47045로 client 1개 실행 최고치 78721보다 적다.
• Client1: 500만 214sec qps:23359 avg:1747 min:30 median:887 max:489451
• Client2: 500만 211sec qps:23686 avg:1755 min:19 median:903 max:597066
• CPU Server 127%, Client: 130%
- Client: echo_client --logtostderr --proactor_threads=10 --connect=localhost --raw=true --size=10 --c=10 --n=100000 --p=1
⦿ Client 스레드: 10개
최고치가 50185로 스레드 5개 일때 최고치 78721 보다 적다.
• 1회) 1000만 282sec qps:35456 avg:2232 min:23 median:843 max:370182
• 2회) 1000만 199sec qps:50185 avg:1663 min:26 median:951 max:383241
• 3회) 1000만 221sec qps:45187 avg:1785 min:18 median:941 max:427083
• CPU Server 131%, Client: 141%
- 요약
Server 스레드 10, Client 스레드 5 일 때 최고치 78721가 나왔다.
이는 스레드 5개 client 2개를 동시에 실행했을 때보다 높았고, 스레드 10개 client를 실행(50185)했을 때보다도 높았다.
스레드 10개 - CPU 5 core 설정
- Server: echo_server --logtostderr --proactor_threads=10 --max_clients=10000 --raw=true --size=10
2636 proactor_pool.cc:379] Setting affinity of thread 0 on cpu 0
2636 proactor_pool.cc:379] Setting affinity of thread 1 on cpu 1
2636 proactor_pool.cc:379] Setting affinity of thread 2 on cpu 2
2636 proactor_pool.cc:379] Setting affinity of thread 3 on cpu 3
2636 proactor_pool.cc:379] Setting affinity of thread 4 on cpu 4
2636 proactor_pool.cc:379] Setting affinity of thread 5 on cpu 0
2636 proactor_pool.cc:379] Setting affinity of thread 6 on cpu 1
2636 proactor_pool.cc:379] Setting affinity of thread 7 on cpu 2
2636 proactor_pool.cc:379] Setting affinity of thread 8 on cpu 3
2636 proactor_pool.cc:379] Setting affinity of thread 9 on cpu 4
- Client: echo_client --logtostderr --proactor_threads=10 --connect=localhost --raw=true --size=10 --c=10 --n=100000 --p=1
2703 proactor_pool.cc:379] Setting affinity of thread 0 on cpu 0
2703 proactor_pool.cc:379] Setting affinity of thread 1 on cpu 1
2703 proactor_pool.cc:379] Setting affinity of thread 2 on cpu 2
2703 proactor_pool.cc:379] Setting affinity of thread 3 on cpu 3
2703 proactor_pool.cc:379] Setting affinity of thread 4 on cpu 4
2703 proactor_pool.cc:379] Setting affinity of thread 5 on cpu 0
2703 proactor_pool.cc:379] Setting affinity of thread 6 on cpu 1
2703 proactor_pool.cc:379] Setting affinity of thread 7 on cpu 2
2703 proactor_pool.cc:379] Setting affinity of thread 8 on cpu 3
2703 proactor_pool.cc:379] Setting affinity of thread 9 on cpu 4
- CPU
CPU 사용률 총 500%에서 us+sy=(순수 사용률) 332%(67%)이고, hi+si=(인터럽트 사용률) 167%(33%)이다.
hi(Hardware Interrupt)와 si(Software Interrupt)의 비중이 생각보다 높다.
스레드 10개 - CPU 10 core 설정
- Server: echo_server --logtostderr --proactor_threads=10 --max_clients=10000 --raw=true --size=10
- CPU: 91.5us+391.7sy=(순수 사용률) 483.2%(48%) (server+client),
7.3wa+219.1hi+290.6si=(인터럽트 사용률) 517.0%(52%)
순수 사용률 48%보다 인터럽트 사용률 52%로 더 높다.
아래는 echo_server 스레드별 CPU 사용률이다. - Client: echo_client --logtostderr --proactor_threads=10 --connect=localhost --raw=true --size=10 --c=10 --n=100000 --p=1
⦿ 5core일때보다 오히려 성능이 떨어진다.
• 1회) 1000만 226sec qps:44189 avg:1360 min:16 median:598 max:1150557
• 2회) 1000만 232sec qps:43119 avg:1400 min:16 median:642 max:965261
- CPU: 92.9us+395.8sy=(순수 사용률) 488.7%(49%) (server+client),
8.5wa+203.0hi+299.8si=(인터럽트 사용률) 511.3%(51%)
아래는 echo_client 스레드별 CPU 사용률이다.
echo_server와 echo_client CPU 사용률
서버 스레드 10개 성능 테스트
- Client: echo_client --logtostderr --proactor_threads=1~6 --connect=localhost --raw=true --size=10 --c=10 --n=100000 --p=1
• threads=1) 100만 69sec qps:14413 avg:675 min:41 median:542 max:189282
• threads=2) 200만 88sec qps:22508 avg:739 min:27 median:436 max:155699
• threads=3) 300만 100sec qps:29744 avg:875 min:24 median:557 max: 82972
• threads=5) 500만 124sec qps:40210 avg:1053 min:22 median:641 max:139462
• threads=6) 600만 140sec qps:42852 avg:1146 min:23 median:601 max:241570
성능 테스트 요약
- VM 5core/10core
- Server thread=1/5/10
- Client thread=1/2/3/4/5/6/10
- 위 조건을 여러가지로 조합해서 테스트했습니다.
qps(Query Per Secend) 기준으로 (thread=1) 14413 ~ (thread=5) 78721까지 나왔다.
thread=10으로 해도 성능이 더 좋아지지 않는다. 오히려 thread=5 보다 성능이 떨어진다.
전체 실행 옵션 설명
실행 옵션 보기: build-opt$ echo_server --helpfull
- Flags from helio/examples/echo_server.cc:
- --backlog (Accept queue length); default: 1024;
- --c (Number of connections per thread); default: 10;
- --connect (hostname or ip address to connect to in client mode); default: "";
- --epoll (If true use epoll for server); default: false;
- --http_port (Http port.); default: 8080;
- --max_clients (); default: 65536;
- --max_pending_writes (); default: 300;
- --n (Number of requests per connection); default: 1000;
- --p (pipelining factor); default: 1;
- --port (Echo server port); default: 8081;
- --raw (If true, does not send/receive size parameter during the connection handshake); default: true;
- --size (Message size, 0 for hardcoded 4 byte pings); default: 1;
- --tcp_nodelay (if true - use tcp_nodelay option for server sockets); default: false;
- --write_num (); default: 1000;
- Flags from helio/build-dbg/_deps/abseil_cpp-src/absl/flags/parse.cc:
- --flagfile (comma-separated list of files to load flags from); default: ;
- --fromenv (comma-separated list of flags to set from the environment [use 'export FLAGS_flag1=value']); default: ;
- --tryfromenv (comma-separated list of flags to try to set from the environment if present); default: ;
- --undefok (comma-separated list of flag names that it is okay to specify on the command line even if the program does not define a flag with that name); default: ;
- Flags from helio/build-dbg/_deps/glog-src/src/logging.cc:
- --alsologtoemail (log messages go to these email addresses in addition to logfiles);
default: ""; - --alsologtostderr (log messages go to stderr in addition to logfiles); default: false;
- --colorlogtostderr (color messages logged to stderr (if supported by terminal)); default: false;
- --colorlogtostdout (color messages logged to stdout (if supported by terminal));
default: false; - --drop_log_memory (Drop in-memory buffers of log contents. Logs can grow very quickly and they are rarely read before they need to be evicted from memory. Instead, drop them from memory as soon as they are flushed to disk.); default: true;
- --log_backtrace_at (Emit a backtrace when logging at file:linenum.); default: "";
- --log_dir (If specified, logfiles are written into this directory instead of the default logging directory.); default: "";
- --log_link (Put additional links to the log files in this directory); default: "";
- --log_prefix (Prepend the log prefix to the start of each log line); default: true;
- --log_utc_time (Use UTC time for logging.); default: false;
- --log_year_in_prefix (Include the year in the log prefix); default: true;
- --logbuflevel (Buffer log messages logged at this level or lower (-1 means don't buffer; 0 means buffer INFO only; ...)); default: 0;
- --logbufsecs (Buffer log messages for at most this many seconds); default: 30;
- --logcleansecs (Clean overdue logs every this many seconds); default: 300;
- --logemaillevel (Email log messages logged at this level or higher (0 means email all; 3 means email FATAL only; ...)); default: 999;
- --logfile_mode (Log file mode/permissions.); default: 436;
- --logmailer (Mailer used to send logging email); default: "";
- --logtostderr (log messages go to stderr instead of logfiles); default: false;
- --logtostdout (log messages go to stdout instead of logfiles); default: false;
- --max_log_size (approx. maximum log file size (in MB). A value of 0 will be silently overridden to 1.); default: 1800;
- --minloglevel (Messages logged at a lower level than this don't actually get logged anywhere); default: 0;
- --stderrthreshold (log messages at or above this level are copied to stderr in addition to logfiles. This flag obsoletes --alsologtostderr.); default: 2;
- --stop_logging_if_full_disk (Stop attempting to log to disk if the disk is full.); default: false;
- --timestamp_in_logfile_name (put a timestamp at the end of the log file name); default: true;
- --alsologtoemail (log messages go to these email addresses in addition to logfiles);
- Flags from helio/build-dbg/_deps/glog-src/src/utilities.cc:
- --symbolize_stacktrace (Symbolize the stack trace in the tombstone); default: true;
- Flags from helio/build-dbg/_deps/glog-src/src/vlog_is_on.cc:
- --v (Show all VLOG(m) messages for m <= this. Overridable by --vmodule.); default: 0; ✅ 로그 레벨 설정
- --vmodule (per-module verbose level. Argument is a comma-separated list of <module name>=<log level>. <module name> is a glob pattern, matched against the filename base (that is, name ignoring .cc/.h./-inl.h). <log level> overrides any value given by --v.); default: "";
- Flags from helio/util/fibers/proactor_pool.cc:
- --proactor_affinity_mode (can be on, off or auto); default: "on";
- --proactor_threads (Number of io threads in the pool); default: 0; ✅ 스레드 개수 설정
- Flags from helio/util/fibers/uring_proactor.cc:
- --enable_direct_fd (If true tries to register file descriptors); default: false;
소스 설명
📦 echo_server.cc
주요 클래스와 함수에 대한 설명입니다.
클래스, 함수 목차
- class EchoConnection : public Connection
- class EchoListener : public ListenerInterface
- class Driver
- Driver(ProactorBase* p)
- void Connect(unsigned index, const tcp::endpoint& ep) 함수
- size_t Run(base::Histogram* dest) 함수
- class TLocalClient
- void RunServer(ProactorPool* pp) 함수
- AcceptServer acceptor(pp);
- acceptor.AddListener(GetFlag(FLAGS_port), new EchoListener);
- acceptor.Run(); ---> AcceptServer::Run()
- acceptor.Wait();
- base::Histogram send_res;
- int main(int argc, char* argv[])
- std::unique_ptr<ProactorPool> pp;
- pp.reset(fb2::Pool::IOUring(256)); ---> Linux IOUring
- pp->Run();
- RunServer(pp.get()); ---> FLAGS_connect.empty() RunServer()
--connect 옵션이 없을 경우 서버로 실행
- pthread_setaffinity_np() 함수
📦 class EchoConnection
'EchoConnection' 클래스는 'Connection' 클래스를 상속받아 에코 서버의 연결을 관리하는 역할을 수행합니다.
https://chatgpt.com/share/ab6e4c2f-7292-4df9-833f-a8bc698ca4c1
다음은 이 클래스의 주요 부분에 대한 설명입니다.
◼️ 클래스 멤버 설명
- public 섹션:
생성자 (Constructor)
EchoConnection() {
}
기본 생성자입니다. 생성자 내부에는 특별한 초기화 코드가 없습니다.
객체가 생성될 때 기본적으로 실행됩니다. - private 섹션:
- HandleRequests 메서드
void HandleRequests() final;
'final' 키워드는 이 메서드가 더 이상 오버라이드되지 않음을 의미합니다.
이 메서드는 에코 서버에서 들어오는 요청을 처리하는 역할을 합니다.
메서드의 구체적인 구현은 클래스 정의에는 포함되지 않았지만, 이 메서드가 주된 작업을 수행할 것으로 보입니다. - ReadMsg 메서드
std::error_code ReadMsg(size_t* sz);
메시지를 읽어오는 역할을 하는 메서드입니다.
반환 타입이 'std::error_code'이므로, 메시지를 읽는 과정에서 발생할 수 있는 오류를 처리합니다.
인자로 메시지의 크기를 가리키는 포인터 'sz'를 받습니다. - work_buf_ 멤버 변수
std::unique_ptr<uint8_t[]> work_buf_;
작업 버퍼를 가리키는 스마트 포인터입니다.
'uint8_t' 타입의 배열을 가리키며, 동적 할당된 메모리를 관리합니다.
스마트 포인터를 사용함으로써 메모리 관리가 자동으로 이루어집니다. - req_len_ 멤버 변수
size_t req_len_ = 0;
요청의 길이를 저장하는 멤버 변수입니다. 초기값은 0으로 설정되어 있습니다.
- HandleRequests 메서드
◼️ 주요 역할
'EchoConnection' 클래스는 에코 서버에서 클라이언트와의 연결을 처리하는 역할을 합니다.
- 생성자: 객체 생성 시 특별한 초기화 작업은 필요하지 않습니다.
- HandleRequests: 들어오는 요청을 처리하는 메서드입니다. 에코 서버에서 클라이언트의 요청을 받아서 응답을 되돌려주는 역할을 할 것입니다.
- ReadMsg: 요청 메시지를 읽어들이는 메서드입니다. 메시지 크기를 포인터로 받아서 그 크기만큼 메시지를 읽고, 그 과정에서 발생할 수 있는 오류를 반환합니다.
- work_buf_: 요청 메시지를 임시로 저장할 버퍼입니다. 동적 할당을 통해 메모리 관리가 자동으로 이루어집니다.
- req_len_: 현재 요청의 길이를 저장하는 변수입니다. 요청을 처리하는 데 사용될 것입니다.
이 클래스는 에코 서버의 핵심 기능을 담당하며, 클라이언트의 요청을 읽고 처리하고 응답을 돌려주는 일련의 과정을 수행합니다. 'Connection' 클래스를 상속받아 필요한 기능을 구현하고 있으며, 구체적인 동작은 HandleRequests(), ReadMsg() 메서드의 실제 구현 부분에서 정의됩니다.
📦 EchoConnection::ReadMsg() 함수
'EchoConnection' 클래스의 'ReadMsg' 메서드는 클라이언트로부터 메시지를 읽어오고,
그 과정에서 발생할 수 있는 오류를 처리하는 역할을 합니다.
https://chatgpt.com/share/ab6e4c2f-7292-4df9-833f-a8bc698ca4c1
다음은 코드의 각 부분에 대한 자세한 설명입니다.
◼️ 각 부분 설명
- 메서드 시그니처
std::error_code EchoConnection::ReadMsg(size_t* sz)- 'std::error_code': 이 메서드는 'std::error_code' 타입을 반환합니다. 이 타입은 오류 상태를 나타내는 데 사용됩니다.
- 'size_t* sz': 이 메서드는 메시지의 크기를 저장할 수 있는 포인터를 인자로 받습니다.
- 버퍼 설정
io::MutableBytes mb(work_buf_.get(), req_len_);- 'io::MutableBytes': 가변 길이의 바이트 배열을 나타내는 객체를 생성합니다.
- 'work_buf_.get()': 'work_buf_'는 'std::unique_ptr
' 타입의 멤버 변수로, 동적 배열을 관리합니다. 'get()' 메서드를 호출하여 실제 배열 포인터를 얻습니다. - 'req_len_': 요청 메시지의 길이를 나타내는 멤버 변수입니다.
- 결과적으로 'mb'는 'work_buf_'가 가리키는 배열을 'req_len_' 길이만큼 감싸는 객체가 됩니다.
- 데이터 수신
auto res = socket_->Recv(mb, 0);- 'socket_->Recv(mb, 0)': 소켓을 통해 데이터를 수신합니다. 'mb'는 수신한 데이터를 저장할 버퍼이며, '0'은 추가 옵션(비차단, 플래그 등)을 나타냅니다.
- 'auto res': 수신 결과를 저장하는 변수입니다. 이 변수는 결과와 상태를 모두 포함하는 객체로 예상됩니다.
- 수신 결과 처리
if (res) { *sz = *res; CHECK_EQ(*sz, req_len_); return {}; }- 'if (res)': 수신이 성공했는지 확인합니다. 성공 시 'res'는 참(즉, true) 값을 가집니다.
- '*sz = *res': 수신한 데이터의 크기를 'sz' 포인터가 가리키는 위치에 저장합니다.
- 'CHECK_EQ(*sz, req_len_)': 수신한 데이터의 크기가 예상한 'req_len_'과 같은지 확인합니다. 같지 않으면 오류를 발생시킬 수 있습니다.
- 'return {}': 빈 'std::error_code' 객체를 반환하여 성공을 나타냅니다.
- 오류 처리
return res.error();- 'if (res)' 조건이 거짓일 경우, 즉 수신이 실패했을 경우 실행됩니다.
- 'res.error()': 수신 결과에 포함된 오류 코드를 반환합니다.
◼️ 요약
'ReadMsg' 메서드는 클라이언트로부터 데이터를 읽어오는 과정에서 다음과 같은 단계를 수행합니다.
- 'work_buf_' 버퍼를 설정합니다.
- 소켓을 통해 데이터를 수신하고 결과를 확인합니다.
- 수신에 성공하면 수신한 데이터의 크기를 'sz'에 저장하고, 예상한 크기와 일치하는지 확인한 후 성공을 나타내는 빈 'std::error_code' 객체를 반환합니다.
- 수신에 실패하면 오류 코드를 반환합니다.
이 메서드는 데이터 수신의 성공 여부와 오류를 관리하여 안정적인 네트워크 통신을 지원합니다.
📦 EchoConnection::HandleRequests() 함수
'EchoConnection' 클래스의 'HandleRequests' 메서드는 클라이언트의 요청을 처리하는 주된 루틴을 정의합니다.
https://chatgpt.com/share/ab6e4c2f-7292-4df9-833f-a8bc698ca4c1
다음은 코드의 각 부분에 대한 자세한 설명입니다.
◼️ 각 부분 설명
- Fiber 이름 설정
ThisFiber::SetName("HandleRequests");
- 현재 실행 중인 fiber의 이름을 "HandleRequests"로 설정하여 디버깅이나 모니터링 시 유용하게 사용할 수 있습니다. - 변수 선언
- 'ec': 오류 코드를 저장할 변수.
- 'sz': 메시지의 크기를 저장할 변수.
- 'iovec vec[2]': 입출력 벡터 배열로, 여러 버퍼를 하나의 연속된 데이터 스트림처럼 취급할 수 있습니다.
- 'buf': 임시 버퍼. - TCP_NODELAY 설정
TCP_NODELAY 플래그가 설정된 경우, Nagle 알고리즘을 비활성화하여 작은 패킷이 즉시 전송되도록 합니다. - 연결 카운터 증가
connections.IncBy(1);
활성 연결 수를 1 증가시킵니다. - 원격 엔드포인트 가져오기
auto ep = socket_->RemoteEndpoint();
원격 클라이언트의 엔드포인트(주소와 포트)를 가져옵니다. - 새로운 연결 로그 기록
VLOG(1) << "New connection from " << ep; - 요청 길이 설정
- 'FLAGS_raw' 플래그에 따라 요청 메시지의 길이를 설정합니다.
- 'FLAGS_raw'가 설정되지 않은 경우, 먼저 8바이트 크기의 길이 정보를 수신합니다.
그런 다음 ACK(승인) 메시지를 보냅니다. - 요청 길이 유효성 검사 및 버퍼 할당
- 요청 길이가 유효한지 검사하고, 'work_buf_'에 해당 길이만큼 메모리를 할당합니다. - 요청 처리 루프
- 무한 루프를 돌며 요청을 처리합니다.
- 'ReadMsg' 메서드를 호출하여 메시지를 읽고, 오류를 확인합니다.
- 'ping_qps' 카운터를 증가시킵니다.
- 'is_raw' 플래그에 따라 적절한 방식으로 데이터를 클라이언트에 씁니다. - 연결 종료 로그 및 카운터 감소
◼️ 요약
'HandleRequests' 메서드는 클라이언트와의 연결을 설정하고,
클라이언트로부터 메시지를 수신하여 처리하고,
다시 클라이언트에 응답을 보내는 역할을 수행합니다.
이 과정에서 오류를 처리하고, 연결이 종료되면 적절히 정리합니다.
주요 작업은 다음과 같습니다:
1. TCP_NODELAY 설정.
2. 요청 길이 수신 (필요 시).
3. 메시지 수신 및 응답 처리.
4. 오류 처리 및 연결 종료.
이 메서드는 에코 서버의 핵심 기능을 구현하며, 클라이언트의 요청을 효과적으로 처리하기 위해 설계되었습니다.
📦 class EchoListener
'EchoListener' 클래스는 'ListenerInterface'를 상속받아 에코 서버에서 새로운 클라이언트 연결을
관리하는 역할을 수행합니다.
이 클래스는 새로운 연결을 생성하고 최대 연결 수에 도달했을 때의 처리를 정의합니다.
https://chatgpt.com/share/ab6e4c2f-7292-4df9-833f-a8bc698ca4c1
다음은 코드의 각 부분에 대한 자세한 설명입니다.
◼️ 각 부분 설명
- 클래스 선언
class EchoListener : public ListenerInterface {
'EchoListener' 클래스는 'ListenerInterface'를 상속받습니다. 'ListenerInterface'는 서버가 클라이언트 연결을 수신하고 관리하는 데 필요한 인터페이스를 정의합니다. - 생성자
- 기본 생성자입니다.
- 'SetMaxClients(GetFlag(FLAGS_max_clients))': 'FLAGS_max_clients' 플래그 값을 가져와 최대 클라이언트 수를 설정합니다. - NewConnection 메서드
- 'NewConnection' 메서드는 새로운 클라이언트 연결을 생성합니다.
- 'ProactorBase* context': 새로운 연결을 생성할 때 사용하는 프로액터(context)를 인자로 받습니다.
- 'VLOG(1) << "thread_id " << context->GetPoolIndex();': 연결을 처리하는 스레드 ID를 로그에 기록합니다.
- 'return new EchoConnection;': 새로운 'EchoConnection' 객체를 생성하여 반환합니다.
- 'final': 이 메서드는 더 이상 오버라이드되지 않음을 의미합니다. - OnMaxConnectionsReached 메서드
- 최대 연결 수에 도달했을 때 호출되는 메서드입니다.
- 'FiberSocketBase* sock': 최대 연결 수에 도달했을 때 클라이언트 소켓을 인자로 받습니다.
- 'sock->Write(io::Buffer(kMaxConnectionsError));': 클라이언트 소켓에 최대 연결 수 도달 메시지를 씁니다. 'kMaxConnectionsError'는 사전에 정의된 오류 메시지입니다.
◼️ 주요 역할
'EchoListener' 클래스는 다음과 같은 주요 역할을 합니다:
- 최대 클라이언트 수 설정
생성자에서 'FLAGS_max_clients' 값을 가져와 최대 클라이언트 수를 설정합니다.
이를 통해 서버가 동시에 처리할 수 있는 클라이언트 연결 수를 제한합니다. - 새로운 연결 생성
'NewConnection' 메서드는 새로운 클라이언트 연결을 생성하는 역할을 합니다.
이 메서드는 'ProactorBase' 컨텍스트를 사용하여 새로운 'EchoConnection' 객체를 생성하고 반환합니다.
또한, 어느 스레드에서 이 연결이 처리되고 있는지 로그에 기록합니다. - 최대 연결 수 도달 시 처리
'OnMaxConnectionsReached' 메서드는 최대 연결 수에 도달했을 때 호출되며, 클라이언트에게 최대 연결 수 도달 메시지를 전송합니다. 이를 통해 서버가 더 이상 새로운 연결을 수락할 수 없음을 클라이언트에게 알립니다.
◼️ 요약
'EchoListener' 클래스는 에코 서버에서 새로운 클라이언트 연결을 수락하고 관리하는 역할을 합니다. 최대 클라이언트 수를 설정하고, 새로운 연결을 생성하며, 최대 연결 수에 도달했을 때 적절히 처리하는 기능을 제공합니다. 이 클래스는 서버의 안정성과 효율성을 유지하는 데 중요한 역할을 합니다.
📦 class Driver
'Driver' 클래스는 서버와의 연결을 관리하고 클라이언트로서 서버에 요청을 보내는 역할을 합니다.
이 클래스는 주로 테스트나 벤치마크 목적으로 사용될 수 있습니다.
https://chatgpt.com/share/ab6e4c2f-7292-4df9-833f-a8bc698ca4c1
다음은 클래스의 각 부분에 대한 자세한 설명입니다.
◼️ 각 부분 설명
- 클래스 멤버 변수
- 'socket_': 'FiberSocketBase' 타입의 소켓을 가리키는 스마트 포인터입니다. 이 소켓을 통해 서버와 통신합니다.
- 'buf_': 서버와의 통신에 사용될 8바이트 크기의 버퍼입니다. - 복사 생성자 삭제
Driver(const Driver&) = delete;
- 'Driver' 객체의 복사 생성을 금지합니다. 이는 클래스 인스턴스의 유일성을 보장하기 위함입니다. - 생성자
- 'ProactorBase' 타입의 포인터 'p'를 인자로 받습니다.
- 'p'를 사용하여 소켓을 생성하고 'socket_' 멤버 변수에 할당합니다.
- 'reset' 함수는 'std::unique_ptr'이 관리하는 기존 객체를 삭제하고 새로운 객체를 설정합니다. - Connect 메서드
void Connect(unsigned index, const tcp::endpoint& ep);
- 주어진 인덱스와 TCP 엔드포인트('ep')를 사용하여 서버에 연결합니다.
- 'index'는 연결의 고유 식별자 역할을 할 수 있습니다.
- 'tcp::endpoint'는 서버의 IP 주소와 포트를 포함합니다. - Run 메서드
size_t Run(base::Histogram* dest);
- 서버와의 통신을 수행하고, 통신 결과를 히스토그램('dest')에 기록합니다.
- 반환 값은 처리된 데이터의 크기 또는 수행된 작업의 수를 나타낼 수 있습니다.
◼️ 'Driver' 클래스의 구현
- 생성자 구현
- 생성자에서 'ProactorBase' 포인터 'p'를 사용하여 소켓을 생성하고 초기화합니다.
◼️ 요약
'Driver' 클래스는 서버와의 연결을 설정하고 데이터를 주고받는 역할을 합니다.
주로 다음과 같은 기능을 제공합니다:
1. 소켓 생성: 'ProactorBase' 객체를 사용하여 소켓을 생성합니다.
2. 서버 연결: 지정된 TCP 엔드포인트를 통해 서버에 연결합니다.
3. 데이터 전송 및 수신: 서버와 통신을 수행하고 결과를 히스토그램에 기록합니다.
이 클래스는 서버와의 통신을 관리하며, 주로 테스트나 벤치마킹 목적으로 사용될 수 있습니다.
다음으로, 'Connect'와 'Run' 메서드의 구체적인 구현이 필요할 수 있습니다.
이 메서드들은 서버와의 실제 연결 및 통신 로직을 포함하게 됩니다.
📦 Driver::Connect() 함수
'Driver::Connect' 함수는 클라이언트 소켓을 사용하여 지정된 TCP 엔드포인트에 연결을 시도하고,
필요한 경우 재시도를 수행하며, 연결 설정 및 확인 작업을 처리합니다.
이 함수는 연결 설정 과정에서 다양한 조건을 점검하고 문제 발생 시 로그를 기록합니다.
https://chatgpt.com/share/ab6e4c2f-7292-4df9-833f-a8bc698ca4c1
다음은 함수의 각 부분에 대한 자세한 설명입니다.
◼️ 각 부분 설명
- 초기 설정
- 'iter'는 재시도 횟수를 추적합니다.
- 'kMaxIter'는 최대 재시도 횟수를 정의합니다.
- 'VLOG(1) << "Driver::Connect-Start " << index;'는 연결 시작을 로그에 기록합니다.
- 'is_raw'는 'FLAGS_raw' 플래그의 값을 저장하며, 원시 모드 여부를 결정합니다.
--raw (If true, does not send/receive size parameter during the connection handshake); default: true;
참(true)이면, 연결 핸드셰이크 중에 크기 매개변수를 보내거나 받지 않습니다. - 연결 시도 루프
- 최대 'kMaxIter' 횟수까지 연결을 시도합니다.
- 현재 시간을 기록하여 연결 소요 시간을 측정합니다.
- 'socket_->Connect(ep)'를 호출하여 서버에 연결을 시도합니다.
- 연결 실패 시 오류 메시지를 기록하고 프로그램을 중단합니다.
- 연결 성공 시 로그에 기록합니다. - 연결 시간 측정 및 확인
- 연결에 걸린 시간을 측정하고, 1초 이상 소요된 경우 오류 로그를 기록합니다. - 원시 모드 확인
- 'is_raw' 플래그가 설정된 경우, 추가 설정 없이 연결 루프를 빠져나갑니다. - 메시지 크기 전송
- 버퍼('buf_')에 메시지 크기를 저장하고, 서버에 전송합니다.
- 전송 결과를 확인하고 오류 발생 시 프로그램을 중단합니다. - 연결 시간 추가 측정 및 확인
- 메시지 크기 전송에 걸린 시간을 측정하고, 2초 이상 소요된 경우 오류 로그를 기록합니다. - ACK 대기 및 확인
- 서버로부터 ACK를 수신하고, 수신 시간에 대한 로그를 기록합니다.
- ACK 수신 성공 시 루프를 빠져나갑니다.
There could be scenario where tcp Connect succeeds, but socket in fact is not really connected (which I suspect happens due to small accept queue) and we discover this upon first Recv. I am not sure why it happens. Right now I retry.
'tcp Connect'가 성공하지만 실제로 소켓이 연결되지 않은 시나리오가 있을 수 있습니다 (수락 대기열이 작아서 그런 것 같습니다). 첫 번째 수신 시에 이를 발견합니다. 왜 그런지 잘 모르겠습니다. 지금은 다시 시도합니다. - 연결 재시도 처리
- TCP 연결이 성공했지만 실제로 연결되지 않았을 경우를 처리합니다.
- 소켓을 닫고 재시도를 로그에 기록합니다. - 소켓 버퍼 크기 설정
- 소켓의 송신(SO_SNDBUF) 및 수신(SO_RCVBUF) 버퍼 크기를 설정합니다. - 최대 재시도 확인 및 종료 로그
- 최대 재시도 횟수에 도달하지 않았는지 확인합니다.
- 연결 종료를 로그에 기록합니다.
◼️ 요약
'Driver::Connect' 함수는 클라이언트 소켓을 통해 서버와 연결을 시도하고, 연결 설정 및 확인 작업을 처리하는 기능을 수행합니다. 이 과정에서 연결 시간 측정, 오류 처리 및 재시도 로직을 포함하며, 로그를 통해 연결 상태를 추적합니다. 이 함수는 연결의 안정성과 성능을 보장하기 위한 다양한 검사를 수행하고, 필요한 경우 재시도를 통해 연결 성공을 보장합니다.
📦 Driver::Run() 함수
'Driver::Run' 함수는 'Driver' 클래스의 인스턴스를 사용하여 서버와의 데이터를 주고받는 작업을 수행하고,
해당 작업의 시간 측정을 통해 히스토그램에 기록합니다.
이 함수는 메시지 송수신과 관련된 여러 단계를 포함하며, 반복적인 작업을 수행합니다.
https://chatgpt.com/share/ab6e4c2f-7292-4df9-833f-a8bc698ca4c1
각 부분을 자세히 설명하겠습니다.
◼️ 각 부분 설명
- 초기 설정
- 'hist' 객체는 작업 시간 측정을 위한 히스토그램입니다.
- 'msg'는 전송할 메시지를 저장할 동적 배열입니다. 메시지 크기는 'FLAGS_size' 플래그 값을 사용합니다.
--size (Message size, 0 for hardcoded 4 byte pings); default: 1; - iovec 배열 설정
- 'iovec' 배열은 메시지 전송 및 수신을 위한 구조체입니다.
- 'vec[0]'는 메시지의 길이를 저장할 버퍼를 가리킵니다.
- 'vec[1]'는 실제 메시지 데이터를 가리킵니다. - 로컬 엔드포인트 및 소켓 확인
- 로컬 엔드포인트를 가져옵니다.
- 소켓이 열려 있는지 확인합니다. - 어댑터 및 초기 변수 설정
- 'AsioStreamAdapter'를 사용하여 소켓을 어댑터에 연결합니다.
- 다양한 플래그 값을 사용하여 필요한 변수를 초기화합니다.
--p (pipelining factor); default: 1; - 주 반복 루프
- 총 'FLAGS_n' 회 반복 작업을 수행합니다.
- 각 반복 작업 시작 시간을 기록합니다.
--n (Number of requests per connection); default: 1000; - 파이프라인 전송 루프
- 'pipeline_cnt'만큼 반복하여 메시지를 전송합니다.
- 전송 중 오류가 발생하거나 연결이 닫힌 경우 'conn_close'를 설정하고 루프를 종료합니다. - 파이프라인 수신 루프
- 'pipeline_cnt'만큼 반복하여 메시지를 수신합니다.
- 원시 모드가 아닌 경우, 먼저 'vec' 구조체를 사용하여 수신을 시도합니다.
- 각 수신 작업이 성공적으로 완료되었는지 확인합니다.
- 작업 시간(나노초)을 측정하고 히스토그램에 추가합니다.
- 연결이 닫힌 경우 루프를 종료합니다. - 소켓 종료 및 히스토그램 병합
- 모든 작업이 완료된 후 소켓을 종료합니다.
- 작업 시간 히스토그램을 외부 히스토그램 'dest'에 병합합니다.
- 총 수행된 반복 횟수를 반환합니다.
◼️ 요약
'Driver::Run' 함수는 메시지 송수신 작업을 반복 수행하며, 각 작업의 시간을 측정하여 히스토그램에 기록합니다. 메시지 전송과 수신 과정에서 오류가 발생하거나 연결이 닫힌 경우 이를 처리하고, 최종적으로 소켓을 종료하고 히스토그램 데이터를 병합합니다. 이를 통해 네트워크 성능을 분석하고, 메시지 송수신 작업의 효율성을 평가할 수 있습니다.
📦 class TLocalClient
'TLocalClient' 클래스는 Helio 프로젝트의 'echo_server.cc' 파일에 정의된 클래스입니다.
이 클래스는 로컬 클라이언트를 나타내며,
비동기 입출력 프로세스를 담당하는 'ProactorBase' 포인터와 'Driver' 객체의 고유 포인터 벡터를 멤버로 가집니다.
https://chatgpt.com/share/5d3fd819-dd11-4b83-bac4-a2a73f9fe335
다음은 'TLocalClient' 클래스 정의의 자세한 설명입니다.
◼️ 클래스 멤버 변수
- ProactorBase* p_;
- 'ProactorBase' 타입의 포인터로, 비동기 입출력 작업을 관리하는 객체를 가리킵니다.
'ProactorBase'는 이벤트를 처리하고, 입력 및 출력을 관리하는 역할을 합니다. - vector<std::unique_ptr<Driver>> drivers_;
- 'Driver' 객체의 고유 포인터를 저장하는 벡터입니다.
'Driver' 클래스는 실제 입출력 작업을 수행하는 역할을 할 가능성이 큽니다.
고유 포인터를 사용하여 'Driver' 객체의 소유권을 명확히 관리합니다.
◼️ 생성자
- TLocalClient(const TLocalClient&) = delete;
- 복사 생성자를 삭제합니다. 이는 'TLocalClient' 객체가 복사되지 않도록 하여, 포인터와 리소스 관리 문제를 방지합니다. - TLocalClient(ProactorBase* p) : p_(p)
- 매개변수로 'ProactorBase' 포인터를 받아 'p_' 멤버 변수를 초기화합니다.
- 'drivers_' 벡터의 크기를 'absl::GetFlag(FLAGS_c)' 함수 호출을 통해 설정합니다.
'FLAGS_c'는 아마도 실행 시 설정할 수 있는 플래그일 것입니다.
--c (Number of connections per thread); default: 10;
- 벡터의 각 요소를 새로운 'Driver' 객체로 초기화합니다.
각 'Driver' 객체는 'ProactorBase' 포인터를 인자로 받아 생성됩니다.
◼️ 멤버 함수
- void Connect(tcp::endpoint ep);
- 주어진 TCP 엔드포인트('tcp::endpoint')에 연결하는 기능을 합니다.
구체적인 구현은 소스 코드에서 확인할 수 있습니다. - size_t Run();
- 클라이언트를 실행시키고, 수행된 작업 수를 반환합니다.
구체적인 구현은 소스 코드에서 확인할 수 있습니다.
◼️ 종합 설명
'TLocalClient' 클래스는 비동기 입출력 작업을 수행하는 클라이언트로,
'ProactorBase'를 통해 입출력 이벤트를 관리하고,
'Driver' 객체를 통해 실제 입출력 작업을 수행합니다.
복사 생성자를 삭제하여 객체의 복사를 방지하고, 주어진 플래그에 따라 'Driver' 객체를 생성 및 관리합니다.
'Connect'와 'Run' 함수는 각각 클라이언트를 특정 엔드포인트에 연결하고, 클라이언트를 실행하는 기능을 제공합니다.
이 클래스는 주로 네트워크 서버에서 비동기 입출력 처리를 위해 사용될 가능성이 높으며,
'Proactor' 패턴을 활용하여 효율적인 이벤트 처리를 구현한 것으로 보입니다.
📦 TLocalClient::Connect() 함수
'TLocalClient::Connect' 함수는 'TLocalClient' 클래스의 멤버 함수로,
네트워크 연결을 설정하는 과정에서 비동기적으로 작업을 수행합니다.
https://chatgpt.com/share/5d3fd819-dd11-4b83-bac4-a2a73f9fe335
이 함수의 동작을 단계별로 설명하겠습니다.
◼️ 함수 정의와 설명
- 로그 메시지 출력: 'Connect' 함수의 시작을 로그로 기록합니다.
이는 디버깅 및 모니터링을 위한 정보 제공을 목적으로 합니다. - 'Fiber' 객체 벡터 생성: 'drivers_' 벡터의 크기와 동일한 크기의 'Fiber' 객체 벡터를 생성합니다.
'Fiber'는 경량 스레드 또는 비동기 작업을 처리하는 객체입니다. - ⦿ 'Fiber' 객체 생성 및 설정:
- 'fbs' 벡터의 각 'Fiber' 객체를 'MakeFiber' 함수를 통해 생성합니다.
- 각 'Fiber' 객체는 람다 함수를 실행하도록 설정됩니다. 이 람다 함수는 다음 작업을 수행합니다:
- 'ThisFiber::SetName': 현재 'Fiber'의 이름을 설정하여 디버깅 시 식별할 수 있도록 합니다.
이름은 'connect/{i}' 형태로 설정됩니다.
- 시간 측정: 'absl::GetCurrentTimeNanos()'를 사용하여 작업 시작 시간을 기록합니다.
- 연결 시도: 'drivers_[i]->Connect(i, ep)'를 호출하여 각 'Driver' 객체가 'ep'에 대해 연결을
시도하도록 합니다. 'i'는 드라이버의 인덱스를 나타냅니다.
- 소요 시간 계산: 연결 작업이 완료된 후, 경과 시간을 측정하고, 밀리초 단위로 변환합니다.
- 속도 로그: 연결 소요 시간이 4000ms를 초과하면 'ERROR' 로그를 기록하여 성능 문제를
식별합니다.
- 'Fiber' 객체의 완료 대기: 각 'Fiber' 객체의 'Join' 메서드를 호출하여 모든 'Fiber'가 완료될 때까지 기다립니다. 이는 비동기 작업이 모두 완료될 때까지 대기하는 과정입니다.
- 로그 메시지 출력: 'Connect' 함수의 종료를 로그로 기록합니다.
로그 플러시: 'base::FlushLogs()'를 호출하여 로그가 즉시 출력되도록 합니다.
◼️ 종합 설명
- 'TLocalClient::Connect' 함수는 주어진 TCP 엔드포인트에 대해 'drivers_' 벡터의 각 'Driver' 객체가 비동기적으로 연결을 시도하도록 합니다.
- 'Fiber'를 사용하여 각 연결 시도를 병렬로 수행하며, 'Fiber'의 완료를 기다리기 위해 'Join'을 호출합니다.
- 각 'Fiber'는 연결 시도와 소요 시간을 측정하고, 소요 시간이 특정 임계값을 초과할 경우 경고 로그를 기록합니다.
- 전체 연결 과정이 완료되면 종료 로그를 기록하고 로그를 플러시하여 즉시 출력되도록 합니다.
이 함수는 'Driver' 객체를 통한 네트워크 연결 작업을 비동기적으로 처리하며, 성능 모니터링 및 디버깅을 위한 로그를 적절히 기록하는 구조입니다.
📦 TLocalClient::Run() 함수
'TLocalClient::Run' 함수는 'TLocalClient' 클래스의 주요 메서드로, 클라이언트의 실행 로직을 담당합니다.
이 함수는 비동기적으로 여러 'Driver' 객체의 작업을 실행하고, 실행 결과를 통합하여 반환합니다.
https://chatgpt.com/share/5d3fd819-dd11-4b83-bac4-a2a73f9fe335
아래는 함수의 동작을 단계별로 설명한 것입니다.
◼️ 함수 정의와 설명
- 'ThisFiber::SetName("RunClient")': 현재 'Fiber'의 이름을 "RunClient"로 설정합니다.
이를 통해 디버깅 시 현재 'Fiber'의 역할을 쉽게 식별할 수 있습니다.
'base::Histogram hist': 'base::Histogram' 타입의 'hist' 객체를 생성합니다. 이 객체는 성능 또는 지연 시간 데이터를 수집하고 분석하는 데 사용됩니다. - 로그 메시지 출력: 'RunClient' 함수의 실행을 로그로 기록합니다.
'p_->GetPoolIndex()'는 현재 'ProactorBase' 객체의 풀 인덱스를 반환하며, 로그에 포함되어 현재 클라이언트의 위치를 식별합니다. - 'Fiber' 객체 벡터 생성: 'drivers_' 벡터의 크기와 동일한 크기의 'Fiber' 객체 벡터를 생성합니다.
각 'Fiber'는 비동기 작업을 처리합니다.
'size_t res = 0': 실행 결과를 저장할 변수 'res'를 초기화합니다. - 'Fiber' 객체 생성 및 설정:
- 'fbs' 벡터의 각 'Fiber' 객체를 'MakeFiber' 함수를 통해 생성합니다.
- 각 'Fiber'는 람다 함수를 실행하도록 설정됩니다. 이 람다 함수는 다음 작업을 수행합니다:
- 'ThisFiber::SetName': 현재 'Fiber'의 이름을 'run/{i}'로 설정하여 디버깅 시 식별할 수 있도록 합니다.
- 'res += drivers_[i]->Run(&hist)': 각 'Driver' 객체의 'Run' 메서드를 호출하여 작업을 실행하고, 'hist' 객체를 통해 성능 데이터를 수집합니다. 'Run' 메서드의 반환값을 'res'에 더합니다.
- - 'Fiber' 객체의 완료 대기: 각 'Fiber' 객체의 'Join' 메서드를 호출하여 모든 'Fiber'가 완료될 때까지 기다립니다. 이는 비동기 작업이 모두 끝날 때까지 대기하는 과정입니다.
- 뮤텍스 잠금 및 데이터 병합:
- 'unique_locklk(lat_mu)': 'lat_mu' 뮤텍스를 잠급니다.
이는 멀티스레딩 환경에서 데이터 경합을 방지하고 안전한 접근을 보장합니다.
- 'lat_hist.Merge(hist)': 'lat_hist' 객체와 'hist' 객체의 데이터를 병합합니다.
'lat_hist'는 클래스의 멤버로 정의되어 있을 가능성이 있으며, 전체 실행의 성능 데이터를 집계하는 역할을 합니다.- 결과 반환: 'res' 값을 반환합니다. 'res'는 모든 'Driver' 객체의 'Run' 메서드의 반환값을 합산한 결과입니다.
◼️ 종합 설명
- 'TLocalClient::Run' 함수는 'Fiber'를 사용하여 비동기적으로 여러 'Driver' 객체의 작업을 실행합니다.
- 각 'Fiber'는 'Driver' 객체의 'Run' 메서드를 호출하여 성능 데이터를 수집하고 결과를 합산합니다.
- 모든 'Fiber' 작업이 완료된 후, 성능 데이터('hist')를 클래스의 멤버 객체('lat_hist')와 병합하여 전체 성능 데이터를 업데이트합니다.
- 최종적으로, 모든 'Driver' 객체의 'Run' 메서드 호출 결과를 합산하여 반환합니다.
이 함수는 비동기 작업을 통해 클라이언트의 실행을 효율적으로 처리하며, 성능 데이터 수집 및 통합을 통해 시스템의 성능을 모니터링할 수 있도록 설계되어 있습니다.
📦 RunServer() 함수
'RunServer' 함수는 에코 서버를 초기화하고 실행하는 메인 함수입니다.
이 함수는 'main()' 함수에서 호출되며, 서버의 주요 설정과 실행 과정을 담당합니다.
https://chatgpt.com/share/ab6e4c2f-7292-4df9-833f-a8bc698ca4c1
다음은 코드의 각 부분에 대한 자세한 설명입니다.
◼️ 각 부분 설명
- 카운터 초기화
- 'ping_qps'와 'connections'는 각각 서버의 요청 처리 속도(QPS)와 활성 연결 수를 추적하는 데 사용되는 카운터입니다.
- 'Init(pp)': 'ProactorPool' 객체 'pp'를 사용하여 카운터를 초기화합니다. - AcceptServer 객체 생성 및 설정
- 'AcceptServer acceptor(pp)': 'ProactorPool'을 사용하여 'AcceptServer' 객체를 생성합니다. 이 객체는 클라이언트 연결을 수락하고 관리합니다.
- 'set_back_log(GetFlag(FLAGS_backlog))': 대기열의 크기를 설정합니다. 'FLAGS_backlog' 플래그 값을 사용하여 설정합니다.
--backlog (Accept queue length); default: 1024; - 리스너 추가
- 'AddListener(GetFlag(FLAGS_port), new EchoListener)': 지정된 포트에서 새로운 'EchoListener'를 추가하여 에코 서버를 설정합니다.
- HTTP 서버 포트가 지정된 경우, HTTP 리스너를 추가하고 서버 시작 메시지를 로그에 기록합니다.
--port (Echo server port); default: 8081;
--http_port (Http port.); default: 8080; - 서버 실행 및 대기
- 'acceptor.Run()': 'AcceptServer'를 실행하여 클라이언트 연결을 수락하기 시작합니다.
- 'acceptor.Wait()': 서버가 종료될 때까지 대기합니다. - 통계 수집 및 로그 기록
- 'base::Histogram send_res': 전송 시간에 대한 히스토그램을 초기화합니다.
- 'Mutex mu': 히스토그램 데이터를 병합할 때 사용할 뮤텍스입니다.
- 'pp->AwaitFiberOnAll': 모든 프로액터(fiber)에서 주어진 람다 함수가 실행될 때까지 대기합니다.
- 람다 함수는 뮤텍스를 잠그고('unique_lock lk(mu)'), 'send_hist'를 'send_res'에 병합합니다.
- 최종 히스토그램 데이터를 로그에 기록합니다.
◼️ 요약
'RunServer' 함수는 다음과 같은 단계를 통해 서버를 설정하고 실행합니다:
1. 요청 처리 속도와 활성 연결 수를 추적하는 카운터를 초기화합니다.
2. 'AcceptServer' 객체를 생성하고, 대기열 크기를 설정합니다.
3. 지정된 포트에서 'EchoListener'를 추가하고, 필요한 경우 HTTP 리스너도 추가합니다.
4. 서버를 실행하고 클라이언트 연결을 수락합니다.
5. 서버 실행 중 수집된 전송 시간 통계를 병합하여 로그에 기록합니다.
이 함수는 서버의 설정과 실행 과정을 종합적으로 처리하여 클라이언트 요청을 효율적으로 처리할 수 있도록 합니다.
📦 main() 함수
'main' 함수는 Helio 프로젝트의 'echo_server.cc' 파일에서 프로그램의 진입점을 정의합니다.
이 함수는 전체 서버 또는 클라이언트의 실행을 설정하고 관리하는 역할을 합니다.
https://chatgpt.com/share/5d3fd819-dd11-4b83-bac4-a2a73f9fe335
각 단계에 대해 자세히 설명하겠습니다.
◼️ 'main' 함수의 각 단계
- MainInitGuard guard(&argc, &argv)':
'MainInitGuard' 객체를 생성하여 프로그램 초기화 작업을 처리합니다. 이는 아마도 프로그램의 플래그나 인수 처리, 초기 설정 등을 수행할 것입니다. 'argc'와 'argv'는 명령줄 인수와 관련된 정보입니다.
- 포트 번호 확인:
'FLAGS_port' 플래그가 0보다 큰지 확인합니다. 이는 유효한 포트 번호가 설정되어 있는지 검증하는 단계입니다. - 'ProactorPool' 포인터 정의:
'ProactorPool'의 고유 포인터를 정의합니다. 'ProactorPool'은 비동기 작업을 처리하는 데 필요한 프로액터를 관리하는 객체입니다. - 프로액터 풀 생성:
- 리눅스에서의 설정:
- 'FLAGS_epoll' 플래그가 설정된 경우 'fb2::Pool::Epoll()'을 호출하여 'Epoll' 기반의 프로액터 풀을 생성합니다.
- 설정되지 않은 경우 'fb2::Pool::IOUring(256)'을 호출하여 'IOUring' 기반의 프로액터 풀을 생성합니다.
- epoll을 쓰겠다고 명시적으로 정하지 않으면 io_uring이 사용된다. - 리눅스 이외의 플랫폼:
- 'Epoll' 기반의 프로액터 풀을 생성합니다.
- 리눅스에서의 설정:
- 프로액터 풀 실행:
- 'ProactorPool'의 'Run' 메서드를 호출하여 비동기 작업을 처리할 준비를 합니다. - 서버 또는 클라이언트 모드 선택:
- 서버 모드:
'FLAGS_connect' 플래그가 비어 있으면, 'RunServer(pp.get())'를 호출하여 서버 모드로 실행합니다.
--connect (hostname or ip address to connect to in client mode); default: "";
--connect=localhost - 클라이언트 모드:
- 'FLAGS_size' 플래그가 0보다 큰지 확인합니다.
클라이언트 모드에서는 요청의 크기 또는 수를 설정할 필요가 있습니다.
--size (Message size, 0 for hardcoded 4 byte pings); default: 1;
- 서버 모드:
- DNS 해결:
- IP 주소 해결:
- 'proactor->Await'를 사용하여 비동기적으로 DNS를 해결하고, IP 주소를 'ip_addr' 배열에 저장합니다. 'absl::GetFlag(FLAGS_connect)'에서 가져온 호스트 이름을 DNS로 변환합니다. - 오류 처리:
- DNS 해결에 실패하면 오류 메시지를 기록하고 종료합니다.
- IP 주소 해결:
- 클라이언트 설정:
- 'TLocalClient' 객체 생성: 'thread_local'로 'TLocalClient'의 고유 포인터를 정의하여 스레드 로컬 클라이언트 객체를 생성합니다.
- IP 주소 및 포트 설정: 'boost::asio'를 사용하여 IP 주소를 생성하고, 'tcp::endpoint' 객체를 생성합니다.
- 클라이언트 연결: 'pp->AwaitFiberOnAll'를 통해 모든 프로액터에서 'TLocalClient' 객체를 생성하고, 'Connect' 메서드를 호출하여 연결을 설정합니다.
- 클라이언트 실행 및 성능 측정:
- 클라이언트 실행:
- 'start'에 현재 시간을 기록합니다.
- 'num_reqs'를 'atomic_uint64_t'로 정의하여 요청 수를 저장합니다.
- 'pp->AwaitFiberOnAll'을 통해 모든 프로액터에서 'client->Run()' 메서드를 호출하여 클라이언트를 실행하고, 요청 수를 집계합니다.
- 성능 결과 출력:
- 작업 완료 후 경과 시간을 측정하고 밀리초 단위로 변환합니다.
- 총 소요 시간, 요청 수, 초당 쿼리 수(QPS) 등의 성능 정보를 로그로 출력합니다.
- 전체 지연 시간(latency) 히스토그램을 출력합니다.
- 프로액터 풀 정지:
- 'pp->Stop()'을 호출하여 프로액터 풀을 정지합니다.
- 클라이언트 실행:
◼️ 종합 설명
- 'main' 함수는 서버 또는 클라이언트 모드에서 프로그램을 실행하기 위한 초기 설정 및 실행을 담당합니다.
- 서버 모드에서는 'RunServer' 함수를 호출하여 서버를 실행합니다.
- 클라이언트 모드에서는 DNS를 해결하고, 'TLocalClient'를 생성하여 비동기적으로 연결을 설정하고 요청을 실행합니다.
- 성능 측정을 통해 총 소요 시간, 요청 수, 초당 쿼리 수 등을 계산하고 로그로 출력합니다.
- 마지막으로, 프로액터 풀을 정지시켜 프로그램을 종료합니다.
이 함수는 Helio 프로젝트에서 서버 또는 클라이언트를 설정하고 실행하며, 성능 모니터링과 결과 출력을 통해 시스템의 동작을 평가할 수 있게 합니다.
📦 pthread_setaffinity_np() 함수
'pthread_setaffinity_np()' 함수는 특정 스레드가 실행될 CPU 코어를 설정하는 함수입니다.
CPU 코어를 지정하여 스레드의 CPU affinity(어피니티)를 설정하면,
그 스레드는 지정된 코어에서만 실행되거나 우선적으로 실행됩니다.
이는 CPU 캐시 효율을 높이거나 특정 하드웨어 자원을 최적화하기 위해 사용됩니다.
https://chatgpt.com/share/6728fe03-76bc-8004-ac0a-7dff0d941b20
◼️ 'pthread_setaffinity_np()' 함수 정의 및 사용법
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t *cpuset);
- 매개변수 설명
- thread: 어피니티를 설정할 대상 스레드입니다.
- cpusetsize: 'cpuset'의 크기(바이트)입니다. 보통 'sizeof(cpu_set_t)'를 사용합니다.
- cpuset: 스레드가 실행될 CPU의 집합을 나타내는 'cpu_set_t' 타입의 포인터입니다.
이 집합을 사용하여 어떤 CPU에서 스레드를 실행할지를 지정할 수 있습니다.
- 반환 값
- 0: 함수가 성공적으로 실행된 경우.
- 에러 코드: 실패한 경우 오류 번호가 반환됩니다.
◼️ 'cpu_set_t'와 CPU 셋 관리
'cpu_set_t'는 CPU 집합을 관리하는 자료형으로, 다음 함수들을 사용하여 각 CPU 코어의 설정을 관리할 수 있습니다.
- CPU_ZERO(cpu_set_t *set): 'set'을 초기화하여 모든 CPU를 제거합니다.
- CPU_SET(int cpu, cpu_set_t *set): 'set'에 'cpu' 코어를 추가합니다.
- CPU_CLR(int cpu, cpu_set_t *set): 'set'에서 'cpu' 코어를 제거합니다.
- CPU_ISSET(int cpu, cpu_set_t *set): 'set'에 'cpu'가 포함되어 있는지 검사합니다.
◼️ 사용 예시
아래는 'pthread_setaffinity_np()'를 사용하여 스레드를 특정 CPU 코어에 할당하는 예제입니다:
◼️ 'pthread_setaffinity_np()'의 활용
- 실시간 애플리케이션: 특정 코어에 스레드를 고정하여 응답 시간을 줄이고 일정한 성능을 유지할 수 있습니다.
- 멀티코어 시스템: 스레드가 특정 코어에서 실행되도록 하여 CPU 캐시 활용도를 높이고 스레드 간의 충돌을 줄일 수 있습니다.
- 하드웨어 리소스 최적화: 스레드를 특정 코어에 할당하여 CPU, GPU 등 하드웨어 자원의 활용을 극대화할 수 있습니다.
◼️ 주의사항
'pthread_setaffinity_np()'는 리눅스 고유 함수이며 POSIX 표준에는 포함되지 않습니다.
