- はじめに
- Socketとは
- SocketとFile Descriptor
- Protocol FamilyとSocket Type
- Port Numberとは
- TCP ServerのSocket処理
- Listen SocketとConnected Socket
- Bind Addressの違い
- TCP ClientのSocket処理
- Listen Queueの仕組み
- Socket Send BufferとReceive Buffer
- BlockingとNon-blocking I/O
- Socketの終了とError
- LinuxでSocketを確認する
- Linux Socketでよくある障害と確認ポイント
- Socket処理の全体フロー
- まとめ
はじめに
Linux上のApplicationは、NICやTCP/IP Stackを直接操作するのではなく、Socketを通じてNetwork通信を行います。Web ServerはSocketを作成してTCP Port 80や443などで接続を待ち、ClientはSocketからServerへ接続してDataを送受信します。
Socketを理解するには、SocketとFile Descriptorの関係、IP AddressとPort、bind()、listen()、accept()、connect()、Listen SocketとConnected Socketの違いを整理する必要があります。
「Processは動いているのに接続できない」「特定のIP Addressからしか接続できない」「ESTABLISHEDやCLOSE-WAITが大量に残る」といった現象も、Socketの状態とSystem Callの流れを理解すると切り分けやすくなります。
本記事ではSocketの基本構造、TCP ServerとClientの処理、Listen Queue、送受信Buffer、BlockingとNon-blocking I/O、Linux上での確認方法を順番に解説します。
Socketとは
Socketは、ApplicationがKernelのNetwork機能を利用するための通信Endpointです。ApplicationはSocket APIを使ってSocketを作成し、接続、送信、受信、切断などの処理をKernelへ依頼します。
SocketにはProtocol Family、Socket Type、Local Address、Local Port、Remote Address、Remote Port、Protocol State、送受信Bufferなどが関連付けられます。これらすべての情報が作成直後から決まっているわけではなく、bind()やconnect()などの処理に伴って設定されます。

| 要素 | 例 | 役割 |
|---|---|---|
| Protocol Family | AF_INET、AF_INET6 | IPv4やIPv6などのAddress体系を指定 |
| Socket Type | SOCK_STREAM、SOCK_DGRAM | StreamやDatagramなどの通信方式を指定 |
| Local Address / Port | 192.0.2.10:443 | Local側のEndpointを示す |
| Peer Address / Port | 198.51.100.20:53000 | 通信相手のEndpointを示す |
SocketとFile Descriptor
Linuxでsocket()を実行すると、成功時には非負の整数であるFile Descriptorが返ります。Applicationは以後、このFile Descriptorを使ってSocketを指定します。

File DescriptorはProcess内で使用される番号であり、Socketそのものではありません。ProcessのFile Descriptor TableにあるEntryを通じてKernel内のFile ObjectやSocketへ到達します。同じSocketをdup()やfork()などによって複数のFile Descriptorが参照する場合もあります。
SocketもFile Descriptorとして扱われるため、read()、write()、close()、poll()、epoll()など、Fileと共通するSystem Callを利用できます。ただし、seekできないなど通常のDisk Fileとは異なる性質があります。
Protocol FamilyとSocket Type
socket()でSocketを作るときは、使用するProtocol FamilyやSocket Typeを指定します。
- Protocol Family:IPv4、IPv6、UNIX Domainなど、Socketが使用するAddress体系を指定
- Socket Type:Stream型、Datagram型などSocketの通信特性を指定
代表的な組み合わせは次のとおりです。
socket(AF_INET, SOCK_STREAM, 0) // IPv4 + TCP
socket(AF_INET6, SOCK_STREAM, 0) // IPv6 + TCP
socket(AF_INET, SOCK_DGRAM, 0) // IPv4 + UDP
socket(AF_UNIX, SOCK_STREAM, 0) // 同一Host内の通信
※厳密にはSOCK_STREAM = TCP、SOCK_DGRAM = UDPそのものではありませんが、Internet通信の代表的な組み合わせとしては、この対応で理解すると分かりやすいです。
Port Numberとは
Port Numberは、受信したTCP SegmentやUDP Datagramを適切なApplicationのSocketへ届けるための識別情報です。16 bitの値で、0から65535までの範囲があります。
| 範囲 | 名称 | 概要 |
|---|---|---|
| 0〜1023 | Well-known Ports | 代表的なServiceへ割り当てられる範囲 |
| 1024〜49151 | Registered Ports | ApplicationやServiceが登録して利用する範囲 |
| 49152〜65535 | Dynamic / Private Ports | 一時的なClient Portなどに使われる範囲 |
Linuxが自動選択するEphemeral Portの実際の範囲はSystem設定によって決まり、必ず上表のDynamic / Private Portsだけが使われるわけではありません。Linuxでは次の設定で確認できます。

このLinuxでは32768〜60999がLocal Portの自動割り当て範囲となります。
TCP Port 53とUDP Port 53は別のProtocol空間です。同じPort NumberでもTCPとUDPで別のSocketをBindできます。また、PortだけでなくLocal IP Addressとの組み合わせもBindの競合判定に関係します。
TCP ServerのSocket処理
TCP Serverはsocket()、bind()、listen()、accept()の順に処理し、ClientとのConnectionを受け付けます。Data送受信後はclose()でFile Descriptorを閉じます。

socket()でSocketを作る
socket()は指定されたProtocol FamilyとSocket Typeに対応するKernel Socketを作成し、File Descriptorを返します。この時点では通常、ServerのLocal Portはまだ割り当てられていません。
bind()でAddressとPortを割り当てる
bind()はSocketへLocal IP AddressとPortを割り当てます。Serverが192.0.2.10:8080へBindすると、そのAddressとPortを使用して接続を受け付ける準備ができます。
listen()で接続を待つ
listen()はTCP SocketをPassive Open側のListen Socketへ変更します。引数のbacklogは接続待ちQueueの上限に関係しますが、Applicationが指定した値だけで実際のQueue長が決まるわけではなく、Kernel設定による上限なども影響します。
Linuxではbacklogは主に3-way Handshake完了後、Applicationのaccept()を待つAccept Queueの上限に関係します。Handshake途中のConnectionを管理するSYN Queueには別のKernel設定なども関係します。
accept()でConnectionを取り出す
3-way Handshakeが完了したConnectionをaccept()すると、新しいConnected SocketのFile Descriptorが返ります。元のListen Socketはそのまま残り、新しい接続要求を待ち続けます。ServerはClientごとに作られたConnected Socketを使ってDataを送受信します。
Listen SocketとConnected Socket

Listen Socketは新しい接続要求を受け付けるためのSocketで、通常Application Dataの送受信には使用しません。一方、accept()で返されたConnected Socketは、特定のClientとのData送受信に使用します。
複数のConnected Socketが同じServer側Local AddressとPortを持てるのは、Remote AddressやRemote Portが異なるためです。TCP ConnectionはLocal Address、Local Port、Remote Address、Remote Portの組み合わせによって区別されます。
Bind Addressの違い
Bind Addressは、SocketがどのLocal IP AddressとPortで通信を受け付けるかを指定します。
| Bind Address | 主な意味 |
|---|---|
| 127.0.0.1 | 同一HostのIPv4 Loopbackからのみ接続できる |
| 192.0.2.10 | 指定したIPv4 Address宛ての接続を受け付ける |
| 0.0.0.0 | IPv4の利用可能なLocal Addressで接続を受け付ける |
| ::1 | IPv6 Loopbackからのみ接続できる |
| :: | IPv6のUnspecified Address。IPv4も受け付けるかは設定に依存する |
Serviceが127.0.0.1だけでListenしている場合、同一Hostからは接続できてもRemote Hostからは接続できません。一方、0.0.0.0でListenしていてもFirewall、Routing、Network Namespaceなどの影響によって外部から接続できない場合があります。
IPv6の::へBindしたSocketがIPv4-mapped Addressも受け付けるかは、IPV6_V6ONLY OptionやSystem設定に依存します。ssの表示だけで判断せず、Application設定と実際の接続結果を確認します。
TCP ClientのSocket処理
Clientがconnect()を実行すると、KernelはRouteなどに基づいてSource Addressを選び、Local Portが未指定であればEphemeral Portを割り当てます。その後、TCPはSYNを送信して3-way Handshakeを開始します。

Blocking Socketでは、Handshakeが成功するかErrorになるまでconnect()が待つのが基本です。Non-blocking Socketでは接続処理中を示すEINPROGRESSが返ることがあり、poll()やepoll()などで完了を待った後、SO_ERRORを確認します。
Connection refusedは相手からRSTが返された場合などに発生し、Connection timed outは一定時間応答が得られない場合などに発生します。実際の原因を特定する際は、tcpdump、Server側のListen状態、Firewall、Routingなどを組み合わせて確認します。
Listen Queueの仕組み
LinuxのTCP Listen処理では、Handshake途中の接続要求と、Handshake完了後にApplicationのaccept()を待つConnectionが管理されます。一般にSYN QueueとAccept Queueという考え方で説明できます。

SYN Queueでは主に3-way Handshake途中のConnectionを管理します。Handshakeが完了するとConnectionはAccept Queue側で管理され、Applicationがaccept()を実行するのを待ちます。
Applicationのaccept()処理が追いつかずAccept Queueが満杯に近づくと、新しい接続の遅延や失敗につながる場合があります。原因はTCP設定だけでなく、Application Thread不足、CPU高負荷、Downstream待ち、File Descriptor上限なども考えられます。
また、SYN Cookieが使用される状況では、SYN Floodなどに対してHandshake途中の状態を通常とは異なる方法で扱う場合があります。Listen Queueを調査する際は、Kernel設定や負荷状況も含めて確認します。
Socket Send BufferとReceive Buffer
Socketには送信Dataを管理するSend Bufferと、受信DataをApplicationが読み取るまで保持するReceive Bufferがあります。TCPのSend Bufferでは、Applicationから受け取った送信Dataに加え、送信済みでもACKを受信していないDataが再送に備えて管理されます。

Applicationの送信速度がNetworkや受信側の処理速度より速いと、Send BufferへDataが溜まり、書き込みが待たされる場合があります。一方、受信ApplicationがDataを読み取らないとReceive Bufferが埋まり、TCPはReceive Windowを縮小して送信側の送信量を抑制します。
SO_SNDBUFとSO_RCVBUFでSocket単位のBuffer Sizeを指定できますが、Kernelの自動調整、System上限、内部Accountingなども関係します。Bufferを大きくすれば常に性能が向上するわけではなく、Memory使用量やQueueing Delayも含めて評価する必要があります。
BlockingとNon-blocking I/O
Blocking Socketでは、recv()を実行した時点でDataがなければ通常ProcessがSleepし、Data到着、Connection終了、Signal、Errorなどを待ちます。送信時もSend Bufferに十分な空きがなければ、書き込み可能になるまで待つことがあります。
Non-blocking Socketでは、すぐに処理できない場合にEAGAINまたはEWOULDBLOCKが返り、System Callは待たずに戻ります。Applicationはpoll()、select()、epoll()などを利用してFile Descriptorの準備状態を監視し、ReadyになったSocketを処理できます。
| 項目 | 特徴 | 主な用途 |
|---|---|---|
| Blocking Socket | 処理可能になるまでSystem Callが待つ | 単純なClient、Thread型Server |
| Non-blocking Socket | 処理できなければすぐに戻る | Event-driven処理 |
| epoll | 多数のFile DescriptorのReady状態を監視 | 高Connection数のLinux Server |
Readableという通知は、1回のread()やrecv()でApplication Message全体を取得できることを保証するものではありません。TCPはByte Streamなので、Application Protocolで定義された長さや区切りに従って、必要に応じて複数回読み取ります。また、Writableであっても任意量のDataを無制限に書き込めるわけではありません。
Socketの終了とError
close()はFile Descriptorを閉じるSystem Callです。最後の参照が閉じられたとき、TCPでは未送信DataやSocket Optionなどの状態に応じて、FINによる通常のConnection終了やRSTを伴う終了へ進みます。
shutdown()を使うと送信方向または受信方向を個別に閉じることができます。TCPはFull Duplex通信のため、送信を終了した後も相手からのDataを受信するHalf-closeが可能です。
recv()が0を返した場合、TCP PeerがFINを送信し、その前までのDataを読み終えたことを示します。Connection ResetではECONNRESET、切断後の書き込みではEPIPEやSIGPIPEが関係する場合があります。ApplicationではSystem Callの戻り値とerrnoを適切に処理する必要があります。
LinuxでSocketを確認する
Linuxでは、ssコマンドを使って現在のSocketやTCP Connectionを確認できます。
Server ApplicationがどのIP Address・Portで接続を待っているのかを調べる場合にもよく利用します。
TCPのListen Socketを確認する
TCPのListen Socketを確認するには、次のコマンドを実行します。
ss -lntp各Optionの意味は次のとおりです。
| Option | 意味 |
|---|---|
-l | Listen状態のSocketを表示 |
-n | AddressやPortを名前解決せず数値で表示 |
-t | TCP Socketを表示 |
-p | Socketを使用しているProcessを表示 |
たとえば、次のような結果が表示されます。

特に確認したいのがLocal Address:Portです。この例では127.0.0.1:8085でTCP Connectionを待ち受けています。
0.0.0.0:22のように表示されている場合は、IPv4では基本的に利用可能なすべてのLocal AddressのPort 22で待ち受けていることを意味します。
UDPの待受Socketを確認する
UDPの場合は-tの代わりに-uを指定します。
ss -lnup各Optionの意味は次のとおりです。
| Option | 意味 |
|---|---|
-l | 待受Socketを表示 |
-n | AddressやPortを名前解決せず数値で表示 |
-u | UDP Socketを表示 |
-p | Socketを使用しているProcessを表示 |
たとえば、次のような結果が表示されます。

UDPはTCPのようにConnectionを確立しないため、TCPのLISTENに相当するConnection Stateはありません。
ss -lntpはTCPのListen Socket、ss -lnupはUDPの待受Socketを確認するコマンド、と整理すると分かりやすいです。
特定のPortだけ確認する
たとえばPort 8080をListenしているTCP Socketを確認する場合は、対象Portに絞り込んで確認できます。

Server Applicationが起動しているのに接続できない場合は、まずssで「想定したAddressとPortで本当にListenしているか」を確認すると、切り分けに役立ちます。
TCP Connectionと内部状態を確認する
| コマンド | 何を見るか | 用途 |
|---|---|---|
ss -ntp | TCP Connection一覧 | 基本的なConnection確認 |
ss -ntp state established | ESTABLISHEDのみ | 通信中Connectionの確認 |
ss -ntp state close-wait | CLOSE-WAITのみ | Peer切断後のclose処理などを調査 |
ss -ntmi | TCP内部情報・Socket Memory | RTT、cwnd、Queue、Memoryなどの詳細調査 |
ss -ntpで基本的なTCP Connectionを確認し、より詳しく調査したい場合はss -ntmiを使うと、TCP内部情報やSocket Memoryまで確認できます。
ProcessのFile Descriptorを確認する
/proc/<PID>/fdをlsコマンドで確認すると、特定Processが現在開いているFile Descriptorの一覧を表示できます。

上記の実行結果における14や15はTomcat Process内のFile Descriptor番号で、socket:[2799238332]の数値部分はKernel側でSocketに関連付けられたinode番号です。
実際の調査では、File DescriptorだけでなくAddressやPortなども確認できるlsofを利用すると分かりやすい場合があります。
lsof -nP -p <PID>| Option | 意味 | 説明 |
|---|---|---|
-n | No hostname resolution | IP AddressをHost名へ名前解決しない |
-P | No port name conversion | Port番号をService名へ変換しない |
-p <PID> | Process ID | 指定したPIDのProcessだけを表示 |
コマンド実行例を記載します。

指定したProcessが開いているFile、Library、Device、Socketなどを一覧で確認できます。TCP/UDP Socketであれば、File Descriptorに加えてLocal / Remote Address、Port、Connection Stateなどを確認できるため、ProcessとNetwork Connectionの対応を調査する際に便利です。
Linux Socketでよくある障害と確認ポイント
| 現象 | 主な確認ポイント |
|---|---|
| Connection refused | 対象PortのLISTEN、Bind Address、RST送信元 |
| Connection timed out | Routing、Firewall、SYN / SYN-ACKの到達 |
| Address already in use | 同じAddress / Portを使用するSocket、Socket Option、残存状態 |
| Too many open files | Process / SystemのFile Descriptor上限、FD Leak |
| Acceptが遅い | Listen Queue、Application Thread、CPU、Downstream |
| Send-Qが増える | Peerの受信状況、Network Loss、Window、Application Protocol |
| Recv-Qが増える | Local ApplicationがDataを十分な速度で読み取れているか |
| CLOSE-WAITが増える | Peer切断後にLocal ApplicationがSocketをcloseしているか |
Processが起動しているだけではServiceが利用可能とは限りません。まず期待するNetwork Namespace内で、正しいAddressとPortへBindし、LISTENしているかを確認します。その後、Firewall、Routing、Packetの到達状況などを順番に調査します。
大量のSocketやQueue滞留が発生している場合、症状だけを解消する目的でTimeoutや上限値を変更すると、根本原因を隠してしまう可能性があります。ApplicationのConnection Lifecycle、Error処理、Queue、File Descriptor使用量などを先に確認することが重要です。
Socket処理の全体フロー

ServerではListen SocketとConnected Socketが別に存在します。Clientのconnect()によってTCP 3-way Handshakeが開始され、Handshakeが完了したConnectionをServer Applicationがaccept()で取り出します。
ApplicationはConnected SocketのFile Descriptorを使ってDataを送受信します。その裏側ではKernelがSocket BufferやTCPのProtocol Stateを管理し、TCP/IP Stackを通じてNetworkとのData送受信を行います。
まとめ
LinuxのNetwork通信では、ApplicationはSocketを通じてKernelのTCP/IP Stackを利用します。Socketは単なるIP AddressとPortの組み合わせではなく、File Descriptor、Protocol State、Send / Receive Bufferなどと関連するKernelの通信Endpointです。
- SocketはApplicationとKernelのNetwork機能をつなぐ通信Endpoint
- ApplicationはFile Descriptorを使ってSocketを操作する
- TCP Serverはsocket() → bind() → listen() → accept()の流れでConnectionを受け付ける
- Listen SocketとConnected Socketは役割が異なる
- Listen QueueやSocket Bufferの状態は接続性能や通信性能に影響する
- Blocking / Non-blockingによってApplicationのSocket処理方式が異なる
- Linuxではss、lsof、/proc/<PID>/fdなどを使ってSocketの状態を確認できる
Socketの仕組みを理解しておくと、「接続できない」「Connectionが大量に残る」「Send-QやRecv-Qが増える」といったNetwork障害を、Application、Socket、TCP/IP、Networkの各Layerに分けて切り分けやすくなります。
Linuxにおけるネットワークの仕組みについては、以下の書籍にまとめています。興味がありましたら、ぜひ読んでみてください。
Linuxのネットワークの仕組みを
図解で理解したい方へ
Socket・TCP・ルーティング・NIC 現場で役立つ通信処理の基礎知識など Linuxにおけるネットワークの仕組みを図解を中心にわかりやすく解説しています。
Kindleで書籍を見る →


コメント