3ウェイハンドシェイクとは?SYN・SYN-ACK・ACKを実際のバイト列で読み解く
TCPの接続は、3回のやり取りで始まります。この記事では「なぜ3回なのか」を押さえたうえで、SYNとACKが実際にどんなビットとして送られているのかを1バイトずつ確認し、さらに自分のPCで接続の状態が変わる瞬間を観測します。コピーして実行できる手順にしました。
1. 3ウェイハンドシェイクは「お互いの番号を教え合う儀式」
Webページを開くとき、ブラウザとサーバーはいきなりデータを送り始めるわけではありません。まず3回のパケット交換をして、通信の準備を整えます。これが3ウェイハンドシェイク(スリーウェイハンドシェイク)です。
TCPの現行仕様はRFC 9293「Transmission Control Protocol (TCP)」(2022年8月)です。1981年のRFC 793をはじめ、7本のRFCをまとめて置き換えた文書で、ここに3ウェイハンドシェイクの定義があります。
3回の内訳はこうです。
| 順番 | 向き | 名前 | 意味 |
| 1 | クライアント → サーバー | SYN (シン) | 「接続したい。私の番号はこれです」 |
| 2 | サーバー → クライアント | SYN-ACK (シンアック) | 「受け取りました。私の番号はこれです」 |
| 3 | クライアント → サーバー | ACK (アック) | 「そちらの番号も受け取りました」 |
なぜ2回でも4回でもなく3回なのか
TCPは「送ったデータが確実に届いたか」を番号で管理します。そのため、双方が自分の開始番号を相手に伝え、それが届いたことを相手から確認してもらう必要があります。
この「伝える」「確認してもらう」を両方向ぶん行うと、本来は4回必要です。ところが真ん中の2つ、つまり「クライアントの番号を確認した」と「サーバーの番号を伝える」は1つのパケットにまとめられます。それがSYN-ACKです。だから3回で済みます。
SYN-ACKという名前は、SYNとACKという2つのフラグが同時に立っていることを表しています。新しいパケットの種類ではありません。「返事をしながら、同時に自分からも申し込む」という1枚のパケットです。この点は後ほど実際のビットで確認します。
この記事に出てくる用語の読み方
ネットワークの用語は英字の略語が多く、読み方が1つに決まっていないものもあります。ここでは実務で耳にすることの多い読み方を挙げます。別の読み方をする人もいるので、「こう読む人が多い」という程度に受け取ってください。
| 用語 | 読み方 | 由来・意味 |
| TCP | ティーシーピー | Transmission Control Protocol の略 |
| IP | アイピー | Internet Protocol の略 |
| UDP | ユーディーピー | User Datagram Protocol の略 |
| RFC | アールエフシー | インターネットの仕様書。Request for Comments の略 |
| SYN | シン | synchronize(同期する)の略。「サイン」と読む人もいます |
| ACK | アック | acknowledgment(確認応答)の略。「エーシーケー」とも読みます |
| SYN-ACK | シンアック | SYNとACKを続けて読みます |
| ISN | アイエスエヌ | 初期シーケンス番号。Initial Sequence Number の略 |
| シーケンス番号 | シーケンスばんごう | データの順番を表す通し番号 |
| オクテット | オクテット | 8ビットのこと。ほぼ「1バイト」と同じ意味で、仕様書でよく使われます |
| 用語 | 読み方 | 由来・意味 |
| CLOSED | クローズド | 接続がない状態 |
| LISTEN | リッスン | 接続を待ち受けている状態 |
| SYN-SENT | シンセント | SYNを送って返事を待っている状態 |
| SYN-RECEIVED | シンレシーブド | SYNを受け取って返事をした状態 |
| ESTABLISHED | エスタブリッシュト | 接続が確立した状態 |
| ソケット | ソケット | 通信の出入口にあたるプログラム上の部品 |
| pcap | ピーキャップ | 通信を記録したファイルの形式。packet capture の略 |
| lsof | エルエスオーエフ | list open files の略。「エルソフ」と読む人もいます |
| netstat | ネットスタット | network statistics の略 |
| TCB | ティーシービー | 接続の情報を保持する領域。Transmission Control Block の略 |
| バックログ | バックログ | 未完成の接続を保持できる数の上限 |
| SYNフラッド | シンフラッド | SYNを大量に送りつける攻撃 |
| SYNクッキー | シンクッキー | SYNフラッドへの対策のひとつ |
| TLS | ティーエルエス | 通信を暗号化する仕組み。Transport Layer Security の略 |
2. SYN・SYN-ACK・ACKの中身を1バイトずつ見る
説明だけではイメージしづらいので、3ウェイハンドシェイクだけを含む通信記録ファイル(pcap/ピーキャップ)を自分で組み立てて、中身を読み解きます。実際の通信は一切発生しないので、安全に何度でも試せます。
手順1:ハンドシェイクのpcapを作る
以下を handshake_pcap.py として保存します。Pythonの標準ライブラリだけで動きます。
#!/usr/bin/env python3
"""3ウェイハンドシェイクだけを含むpcapを組み立てる。実際の通信は行わない。"""
import struct
CLIENT, SERVER = "192.168.10.50", "203.0.113.10"
SPORT, DPORT = 51000, 80
ISN_C, ISN_S = 1_284_002_651, 3_907_441_180 # 初期シーケンス番号
FIN, SYN, RST, PSH, ACK = 0x01, 0x02, 0x04, 0x08, 0x10
def ip2b(s): return bytes(int(x) for x in s.split("."))
def csum(b):
if len(b) % 2: b += b"\x00"
s = sum(struct.unpack("!%dH" % (len(b) // 2), b))
while s >> 16: s = (s & 0xFFFF) + (s >> 16)
return (~s) & 0xFFFF
def tcp(src, dst, sport, dport, seq, ack, flags, win):
hdr = struct.pack("!HHIIHHHH", sport, dport, seq, ack, (5 << 12) | flags, win, 0, 0)
pseudo = ip2b(src) + ip2b(dst) + struct.pack("!BBH", 0, 6, len(hdr))
return hdr[:16] + struct.pack("!H", csum(pseudo + hdr)) + hdr[18:]
def ip(src, dst, payload, ident):
h = struct.pack("!BBHHHBBH", 0x45, 0, 20 + len(payload), ident, 0x4000, 64, 6, 0) \
+ ip2b(src) + ip2b(dst)
return h[:10] + struct.pack("!H", csum(h)) + h[12:] + payload
def eth(p):
return b"\x02\x00\x00\x00\x00\x01" + b"\x02\x00\x00\x00\x00\x02" + b"\x08\x00" + p
packets = [
(CLIENT, SERVER, SPORT, DPORT, ISN_C, 0, SYN, 65535),
(SERVER, CLIENT, DPORT, SPORT, ISN_S, ISN_C + 1, SYN | ACK, 65535),
(CLIENT, SERVER, SPORT, DPORT, ISN_C + 1, ISN_S + 1, ACK, 65535),
]
with open("handshake.pcap", "wb") as f:
f.write(struct.pack("!IHHiIII", 0xA1B2C3D4, 2, 4, 0, 0, 65535, 1))
for i, (s, d, sp, dp, seq, ack, fl, win) in enumerate(packets):
frame = eth(ip(s, d, tcp(s, d, sp, dp, seq, ack, fl, win), 200 + i))
f.write(struct.pack("!IIII", 1789984800, i * 200, len(frame), len(frame)) + frame)
print("handshake.pcap を作成しました(3パケット)")
手順2:中身を読み解く
次を decode_handshake.py として保存します。pcapを開いて、TCPヘッダーを項目ごとに表示するだけのプログラムです。
#!/usr/bin/env python3
"""pcapを読み、TCPヘッダーを1項目ずつ表示する。"""
import struct
NAMES = [(0x01, "FIN"), (0x02, "SYN"), (0x04, "RST"), (0x08, "PSH"),
(0x10, "ACK"), (0x20, "URG"), (0x40, "ECE"), (0x80, "CWR")]
data = open("handshake.pcap", "rb").read()
pos, n = 24, 0
while pos < len(data):
_, _, incl, _ = struct.unpack("!IIII", data[pos:pos + 16])
frame = data[pos + 16:pos + 16 + incl]
pos += 16 + incl
n += 1
ipv4 = frame[14:]
ihl = (ipv4[0] & 0x0F) * 4
src = ".".join(str(b) for b in ipv4[12:16])
dst = ".".join(str(b) for b in ipv4[16:20])
t = ipv4[ihl:]
sport, dport, seq, ack, off_flags, win, cks, _ = struct.unpack("!HHIIHHHH", t[:20])
flags = off_flags & 0x3F
on = [nm for bit, nm in NAMES if flags & bit]
print("── パケット%d %s:%d → %s:%d" % (n, src, sport, dst, dport))
print(" フラグ : %s (0x%03x)" % ("+".join(on), flags))
print(" シーケンス番号 : %d" % seq)
print(" 確認応答番号 : %s" % (ack if flags & 0x10 else "%d(ACKフラグなしのため無効)" % ack))
print(" ウィンドウサイズ: %d" % win)
print(" TCPヘッダー長 : %d バイト" % (((off_flags >> 12) & 0xF) * 4))
print(" 生バイト(先頭14): %s" % " ".join("%02x" % b for b in t[:14]))
print()
python3 handshake_pcap.py
python3 decode_handshake.py
実行するとこうなります。以下は実際の出力です。
ここで必ず見てほしい数字
3つの数字の関係が、3ウェイハンドシェイクの本質です。
| パケット | シーケンス番号 | 確認応答番号 | どう解釈するか |
| SYN | 1284002651 | (無効) | クライアントが自分の開始番号を宣言した |
| SYN-ACK | 3907441180 | 1284002652 | 「1284002651の次を待っています」=SYNを受け取った証明。同時に自分の開始番号を宣言 |
| ACK | 1284002652 | 3907441181 | 「3907441180の次を待っています」=SYN-ACKを受け取った証明 |
確認応答番号は「次に受け取りたい番号」です。「ここまで受け取った」ではなく「次はこれをください」と伝える形式になっています。だから相手のシーケンス番号より1つ大きい値になります。
SYNパケットはデータを1バイトも運んでいません。それなのに番号が1つ進みます。RFC 9293は、SYNは最初のデータより前に置かれるものとして扱われ、暗黙にシーケンス番号を1つ消費すると説明しています。番号を持たせることで、SYN自体を再送したり確認応答したりしても混乱しないようにするためです。
接続を閉じるときのFINも同じく1つ消費します。
3. SYNフラグの正体:TCPヘッダーの8つのビット
「SYNを送る」と言いますが、SYNという特別なパケットがあるわけではありません。TCPヘッダーの中の1ビットが立っているだけです。
RFC 9293は、制御ビット(フラグ)として8つを定義しています。
| フラグ | 読み方 | 正式名 | ビット値 | 意味 |
| FIN | フィン | Finish | 0x01 | 送信側からのデータはもうない |
| SYN | シン | Synchronize | 0x02 | シーケンス番号を同期する |
| RST | アールエスティー | Reset | 0x04 | 接続をリセットする |
| PSH | ピーエスエイチ | Push | 0x08 | データをすぐ上位へ渡す |
| ACK | アック | Acknowledgment | 0x10 | 確認応答番号のフィールドが有効である |
| URG | ユーアールジー | Urgent | 0x20 | 緊急ポインタのフィールドが有効である |
| ECE | イーシーイー | ECN-Echo | 0x40 | 輻輳通知に使う |
| CWR | シーダブリューアール | Congestion Window Reduced | 0x80 | 輻輳ウィンドウを減らしたことを示す |
先ほどの生バイト出力の末尾に注目してください。3つのパケットで、それぞれ 50 02、50 12、50 10 となっていました。ここがフラグの正体です。
| 生バイト | 上位4ビット | 下位のフラグ | 結果 |
50 02 | 5 → ヘッダー長 5×4 = 20バイト | 0x02 | SYN だけ |
50 12 | 同上 | 0x12 = 0x10 + 0x02 | ACK と SYN の両方 |
50 10 | 同上 | 0x10 | ACK だけ |
0x12 は 0x10(ACK)と 0x02(SYN)を足した値です。つまりSYN-ACKとは、同じ1バイトの中でACKのビットとSYNのビットが同時に1になっている状態にすぎません。特別なパケット種別ではなく、2つのスイッチが同時に入っているだけです。
ACKフラグには、もう1つ大事な役割があります。「確認応答番号のフィールドが有効かどうか」を示すスイッチだという点です。最初のSYNではACKフラグが立っていないため、確認応答番号の欄に何が入っていても意味を持ちません。先ほどのデコード結果で、パケット1だけ「ACKフラグなしのため無効」と表示していたのはこのためです。
4. シーケンス番号はなぜ0から始まらないのか
実験で使った開始番号は 1284002651 と 3907441180 でした。0ではありません。この最初の番号を初期シーケンス番号(ISN:Initial Sequence Number、アイエスエヌ)と呼びます。
ISNが0固定だと困ることが2つあります。
- 前の接続の残骸と混ざる:同じIPアドレスとポートの組み合わせで接続をやり直したとき、遅れて届いた古いパケットを新しい接続のものと取り違えるおそれがあります
- 第三者に推測される:次に使われる番号が分かると、接続に割り込んで偽のパケットを送り込みやすくなります
RFC 9293はこれを踏まえ、ISNを4マイクロ秒で進むタイマーの値と、接続の情報および秘密鍵から計算した値を組み合わせて決める方式を規定しています。単なる乱数でも連番でもなく、「時間とともに進み、かつ外部から推測しにくい」値になるように設計されています。
この記事のスクリプトはISNを固定値で書いています。中身を追いやすくするためです。実際に通信するプログラムを書く場合、ISNを自分で決める必要はありません。OSのTCP実装が適切に生成します。ソケットを使う通常のプログラミングでISNを意識する場面はほぼありません。
5. 状態はこう変わる:CLOSEDからESTABLISHEDまで
3ウェイハンドシェイクの最中、両側はそれぞれ内部の状態を変えていきます。RFC 9293が示す流れは次のとおりです。
| 段階 | クライアント側 | サーバー側 |
| 開始前 | CLOSED | LISTEN(待ち受け中) |
| SYN を送った / 受けた | SYN-SENT | SYN-RECEIVED |
| SYN-ACK を受けた / ACK を受けた | ESTABLISHED | ESTABLISHED |
両方がESTABLISHEDになって、ようやくデータを送れます。HTTPのリクエストが流れ始めるのはこの後です。
なお、両側が同時にSYNを送り合う同時オープン(simultaneous open)という場合もあります。このときはどちらもSYN-SENTからSYN-RECEIVEDを経てESTABLISHEDに至ります。RFC 9293は、TCP実装が同時オープンに対応しなければならないと定めています。日常的にはまず起きませんが、「クライアントとサーバーという役割は仕様上の前提ではない」ことを示す例です。
6. 自分のPCで状態が変わる瞬間を観測する
ここからは実機です。特別なツールは不要で、Pythonと lsof(エルエスオーエフ)コマンドだけで観測できます。通信はすべて自分のPCの中(127.0.0.1)で完結します。
以下を observe.py として保存して実行してください。
#!/usr/bin/env python3
"""3ウェイハンドシェイクを自分で起こし、ソケットの状態を観測する。"""
import socket, subprocess, threading, time
PORT = 18080
def show(label):
out = subprocess.run(["lsof", "-nP", "-iTCP:%d" % PORT],
capture_output=True, text=True).stdout
print("■ %s" % label)
lines = out.splitlines()[1:]
for line in lines:
print(" ", line.split("TCP ", 1)[1] if "TCP " in line else line)
if not lines:
print(" (該当なし)")
print()
srv = socket.socket()
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("127.0.0.1", PORT))
srv.listen(5)
show("1. サーバーが listen() を呼んだ直後")
held = {}
threading.Thread(target=lambda: held.setdefault("c", srv.accept()[0]), daemon=True).start()
cli = socket.socket()
t0 = time.time()
cli.connect(("127.0.0.1", PORT)) # ここで3ウェイハンドシェイクが完了する
print(" connect() の所要時間: %.2f ミリ秒\n" % ((time.time() - t0) * 1000))
time.sleep(0.3)
show("2. クライアントが connect() を呼んだ直後")
cli.close()
if "c" in held: held["c"].close()
srv.close()
実際の出力です。
ここから3つのことが読み取れます。
- LISTENのソケットは残ったままです。接続が成立すると、待ち受け用とは別に新しいソケットの組が作られます。だからサーバーは次の接続を受け付け続けられます
- ESTABLISHEDが2行あるのは、自分のPCの中で通信しているためです。クライアント側とサーバー側の両方が同じマシンに存在しています
- ハンドシェイクは0.15ミリ秒で完了しました。同じPC内なので一瞬です。相手が遠いほどこの時間は伸び、往復の遅延がそのまま接続開始の待ち時間になります
筆者のmacOS環境では、netstat -an で 127.0.0.1 のソケットもSYN_SENT状態も表示されませんでした。lsof -nP -iTCP では両方とも確認できたため、この記事では lsof を使っています。Linuxでは ss -tan が見やすく、こちらでも同じ状態名が確認できます。
7. 失敗するとどうなるか:即座に拒否と、返事なし
ハンドシェイクが成立しないパターンは、大きく2つあります。どちらも実際に測りました。
ケース1:ポートが閉じている → RSTで即座に拒否
誰も待ち受けていないポートにSYNを送ると、相手はRSTフラグを立てたパケットを返します。RFC 9293は、接続が存在しない状態に届いたセグメントに対しては、それ自体がRSTである場合を除いてリセットを返すと定めています。
import socket, time
s = socket.socket()
t0 = time.time()
try:
s.connect(("127.0.0.1", 18081)) # 誰も待っていないポート
except OSError as e:
print("%.2f ミリ秒で %s(errno=%s: %s)"
% ((time.time() - t0) * 1000, type(e).__name__, e.errno, e.strerror))
0.13ミリ秒で結果が返りました。「接続を拒否されました」というエラーは、相手がきちんと返事をしてくれた証拠です。プログラムから見ると失敗ですが、TCPとしては正常な動作です。
ケース2:返事がまったく来ない → 沈黙のまま再送を続ける
次に、応答しないアドレスへ接続してみます。192.0.2.1 はRFC 5737でドキュメント用に予約されたアドレスで、通常どこにも到達しません。
import socket, time
s = socket.socket() # タイムアウトを指定しない=OS任せ
t0 = time.time()
try:
s.connect(("192.0.2.1", 80))
except OSError as e:
print("%.1f 秒後に %s(errno=%s)" % (time.time() - t0, type(e).__name__, e.errno))
75秒かかりました。この間、OSはSYNを何度も再送しながら返事を待ち続けています。接続中に別のターミナルで lsof を実行すると、その状態が見えます。
SYN_SENT。第5章の表に出てきた状態が、そのまま現れています。SYNを送ったが、まだSYN-ACKを受け取っていない状態です。
| 状況 | 相手の反応 | 結果 | 実測 |
| 正常 | SYN-ACK を返す | ESTABLISHED | 0.15ミリ秒(自PC内) |
| ポートが閉じている | RST を返す | 接続拒否のエラー | 0.13ミリ秒 |
| 到達しない・遮断されている | 何も返さない | SYN_SENT のまま、やがてタイムアウト | 75.0秒 |
「すぐエラーになる」のか「長く固まる」のかで、原因の見当がつきます。即座に拒否されるなら相手まで届いており、サービスが動いていないだけ。長時間固まるならパケットが相手に届いていないか、届いても黙って捨てられている可能性が高い、という切り分けです。ファイアウォールの設定ミスを調べるときの最初の手がかりになります。
8. SYNフラッド攻撃とSYNクッキー
3ウェイハンドシェイクを理解すると、古典的な攻撃の仕組みもそのまま理解できます。
サーバーはSYNを受け取った時点でSYN-RECEIVED状態になり、接続の情報を記録する領域(TCB:Transmission Control Block、ティーシービー)を確保します。まだ3回目のACKは届いていないので、これはハーフオープン(半開き)の接続です。
この「まだ完成していない接続」を保持できる数には上限があります。これをバックログと呼びます。
SYNフラッド攻撃(シンフラッド)は、送信元アドレスを偽ったSYNを大量に送りつけ、このバックログを埋め尽くす攻撃です。RFC 4987「TCP SYN Flooding Attacks and Common Mitigations」(2007年8月、Informational)は、この攻撃がネットワークの帯域を潰すのではなく、ハーフオープン接続の枠を使い切らせることを狙うものだと説明しています。枠が尽きると、正規の利用者の接続が受け付けられなくなります。
同RFCが挙げている主な対策は次のとおりです。
| 対策 | 考え方 |
| SYNクッキー (シンクッキー) | 状態を保存せず、必要な情報をシーケンス番号に埋め込んで返す。3回目のACKが来た時点で復元するため、ハーフオープンの領域を使わない |
| SYNキャッシュ | 接続が完成するまでは最小限の情報だけを持つ |
| フィルタリング | 送信元アドレスの詐称を入口で防ぐ |
| バックログの拡大 | 枠を増やす。ただし限界がある |
| SYN-RECEIVEDタイマーの短縮 | 早く諦めて枠を空ける。本気の攻撃は防ぎきれない |
| 古いハーフオープンの再利用 | 枠が満杯なら古いものから捨てる |
| ファイアウォール・プロキシ | 接続の確立を手前の機器に肩代わりさせる |
ここで効いているのが、第4章で触れたISNの設計です。SYNクッキーは「サーバーが返すシーケンス番号」に情報を埋め込むという仕組みなので、そもそもISNの値がサーバーの裁量で決められることが前提になっています。ハンドシェイクの各フィールドが何のためにあるのかを知っていると、対策の理屈まで一本につながります。
通信を監視して不審な通信を見つける仕組みについては、IDSとIPSの違いをゼロから解説でSuricataを実際に動かしながら解説しています。
9. よくある質問
Q. 3ウェイハンドシェイクは毎回必要ですか?
TCP接続を新しく作るたびに必要です。ただし1本の接続を使い回せば、その回数は減らせます。HTTPで「Keep-Alive」や接続の再利用が重視されるのは、接続のたびに往復のやり取りが発生し、その分だけ待ち時間が増えるためです。実測のとおり同じPC内なら0.15ミリ秒ですが、遠いサーバーとの間では往復の遅延がそのまま上乗せされます。
Q. UDPにもハンドシェイクはありますか?
ありません。UDPは接続という概念を持たず、いきなりデータを送ります。そのため到達確認も順序の保証もない代わりに、開始時の往復が不要です。「TCPは3回の挨拶をしてから話し始める、UDPはいきなり話しかける」と考えると違いが整理しやすくなります。
Q. HTTPSだと、さらにやり取りが増えるのですか?
増えます。TCPの3ウェイハンドシェイクが終わってから、暗号化のための別のやり取り(TLSハンドシェイク/ティーエルエス)が始まります。「ハンドシェイク」という言葉は両方に使われるので、話題がTCPの接続確立なのか暗号化の準備なのかを区別して読むと混乱しません。この記事で扱っているのはTCPのほうです。
Q. 接続を閉じるときも3回ですか?
閉じるときは基本的に4回です。FINとACKをお互いに送り合うため、FIN → ACK → FIN → ACK という流れになります。開くときにSYNとACKを1枚にまとめられたのに対し、閉じるときは「こちらはもう送るものがないが、そちらはまだ送るかもしれない」という状態があり得るため、まとめられないのが理由です。
Q. 次は何を学べばいいですか?
この記事の内容が身についたら、① ウィンドウサイズと再送の仕組み(今回は65535という値を見ただけです)、② 接続を閉じる手順とTIME_WAIT状態、③ TLSハンドシェイクの流れ、の順が自然です。いずれも「番号で管理する」というTCPの考え方が土台になります。
10. まとめ
- 3ウェイハンドシェイクは番号の交換。双方が開始番号を宣言し、相手に受け取りを確認してもらう。真ん中の2つが1枚にまとまるので3回で済む
- SYN-ACKは特別なパケットではない。1バイトの中でSYN(0x02)とACK(0x10)のビットが同時に立ち、
0x12になっているだけ - 確認応答番号は「次に欲しい番号」。SYNはデータを運ばないのに番号を1つ消費するため、ぴったり+1になる
- 状態はCLOSED→SYN-SENT→ESTABLISHED(サーバー側はLISTEN→SYN-RECEIVED→ESTABLISHED)。
lsofで実際に目で見える - 失敗の仕方で原因が分かる。即座に拒否ならRSTが返っており相手まで届いている。長く固まるならパケットが黙って捨てられている
今日やれる最初の一歩は、第6章の observe.py を実行して LISTEN と ESTABLISHED を自分の目で見ることです。教科書の図が、自分のPCの中で本当に起きていることだと確認できます。
そのうえで第2章のデコードを動かせば、50 02 という2バイトがSYNパケットそのものだと分かります。そこまで来れば、3ウェイハンドシェイクはもう暗記する対象ではなくなります。


