pipe 提供单向字节流,socketpair 创建一对已经连接的本地套接字,适合双向通信。两者都通过文件描述符表达引用和生命周期,也都需要协议来区分正常结束、部分传输与错误。
管道:容量和原子写入是两个概念
pipefd[0] 是读端,pipefd[1] 是写端。Linux 的 pipe2 可以在创建时原子设置 O_CLOEXEC 和 O_NONBLOCK,避免先创建再设置标志的竞争窗口。
PIPE_BUF 是多写者之间不交错写入的保证边界,不是管道总容量。长度不超过该界限的一次写入可获得相应原子性;超过时可能与别的写者交错。非阻塞模式下,小于等于 PIPE_BUF 的写入若空间不足会返回 EAGAIN,不能把它理解为必定部分成功。pipe(7)
读取没有消息边界:一次 write 不保证对应一次 read,即使写入本身不与其他写者交错。应用可采用固定长度、长度前缀或转义分隔符协议。
容量影响能缓冲多少数据。常见 Linux 配置使用多页容量,但页大小、用户配额和内核限制会改变结果;用 F_GETPIPE_SZ 查询,F_SETPIPE_SZ 请求调整,并检查实际返回值。增大缓冲可平滑突发,也可能增加排队延迟,不能消除持续生产快于消费的失衡。
读取批次边界与协议消息边界独立
协议消息
长度 3 + abc
消息 1
长度 5 + hello
消息 2
可能的 read
头部前 2 字节
还不能解码长度
头部尾部 + ab
消息 1 尚不完整
c + 整条消息 2
一次可完成两条消息
接收端保留未完成前缀,验证长度上限,完整消息才交给业务;EOF 时检查残留。
长度前缀需要一个跨读取批次的解析状态
下面约定每条消息先有 4 字节大端无符号长度,再跟相应 payload;允许空消息,单条最多 1024 字节。解析器接收的是读取到的字节块,块边界不进入协议。示例人为拆分头部和 payload,验证它仍能得到两条完整消息。
class Decoder :
def __init__ ( self ) :
self . pending = bytearray ( )
self . expected = None
def feed ( self , chunk ) :
self . pending . extend ( chunk )
messages = [ ]
while True :
if self . expected is None :
if len ( self . pending ) < 4 :
break
self . expected = int . from_bytes ( self . pending [ : 4 ] , " big" )
del self . pending [ : 4 ]
if self . expected > 1024 :
raise ValueError ( " message too large" )
if len ( self . pending ) < self . expected :
break
messages . append ( bytes ( self . pending [ : self . expected ] ) )
del self . pending [ : self . expected ]
self . expected = None
return messages
def finish ( self ) :
if self . expected is not None or self . pending :
raise ValueError ( " truncated message at EOF" )
wire = ( 3 ) . to_bytes ( 4 , " big" ) + b " abc" + ( 5 ) . to_bytes ( 4 , " big" ) + b " hello"
decoder = Decoder ( )
messages = [ ]
for chunk in [ wire [ : 2 ] , wire [ 2 : 6 ] , wire [ 6 : ] ] :
messages . extend ( decoder . feed ( chunk ) )
assert messages == [ b " abc" , b " hello" ]
decoder . finish ( )
assert Decoder ( ) . feed ( bytes ( 4 ) ) == [ b " " ]
for bad in [ ( 1025 ) . to_bytes ( 4 , " big" ) , b " \0 \0 " , ( 3 ) . to_bytes ( 4 , " big" ) + b " ab" ] :
try :
decoder = Decoder ( )
decoder . feed ( bad )
decoder . finish ( )
except ValueError :
pass
else :
raise AssertionError ( " malformed stream accepted" )
解析错误后本例放弃这条流,不尝试猜测下一个消息头;应用若需要重新同步,协议必须设计明确的恢复规则。feed 把输入块与本批完整消息暂存在内存中,所以实际接入还应限制单次 read 大小和待处理消息队列,不能认为“单条 1024 字节”已经限制整个进程的内存。
收到 EOF 只说明字节流结束。若还差两个 payload 字节,应该报告截断而不是把半条消息当成功;已经交付的早期完整消息是否需要撤销,是上层事务协议的问题。
EOF 与 SIGPIPE 来自引用状态
EOF 取决于所有写端引用:fork 后两侧都要关闭多余端
父进程
保留写端,关闭读端
子进程
保留读端,关闭写端
字节流
父进程完成写入并关闭
最后一个写端引用消失
子进程读尽缓冲后
read 返回 0(EOF)
若子进程仍保留自己的写端,即使父进程已经关闭,读端也可能一直等不到 EOF。
所有写端都关闭且缓冲读完,read 才返回 0。写入时若所有读端都已关闭,通常产生 SIGPIPE;若该信号被忽略或适当处理,write 以 EPIPE 失败。空缓冲但仍有写者,与真正 EOF 是不同状态。
fork、dup 和继承都会增加引用。O_CLOEXEC 在成功 exec 时关闭相应描述符,不会替你在 fork 后立即关闭不使用的一端。socket 的 shutdown(SHUT_WR) 还可以明确半关闭一个方向,与简单减少某个 fd 引用不同。
关闭一个写端,为什么还不是 EOF?
先复制写端,再关闭原引用,空读仍是 EAGAIN。随后写入并关闭最后一个写端,第一次读取仍应返回缓冲中的数据,下一次才是 EOF。关闭的是引用,读取的是字节;这两条状态必须一起判断。
正在呈现知识画面 重新加载
关闭一个写端,为什么还不是 EOF? EOF 要同时满足缓冲耗尽与所有写端关闭。仍有写引用时,空缓冲非阻塞读取是 EAGAIN。
{"id":"pipe-reference-eof","title":"关闭一个写端,为什么还不是 EOF?","summary":"EOF 要同时满足缓冲耗尽与所有写端关闭。仍有写引用时,空缓冲非阻塞读取是 EAGAIN。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"write\" data-i18n=\"write\"></button><button id=\"dup\" data-i18n=\"dup\"></button><button id=\"read\" data-i18n=\"read\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><p class=\"hint\" data-i18n=\"refs\"></p><div id=\"refs\" class=\"choices\"></div><div class=\"track-row\"><span data-i18n=\"buffer\"></span><div id=\"buffer\" class=\"track\"></div></div><div class=\"metrics\"><div><span data-i18n=\"writers\"></span><strong id=\"writers\"></strong></div><div><span data-i18n=\"result\"></span><strong id=\"result\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.choices{grid-template-columns:repeat(3,minmax(0,1fr))}#buffer{display:grid;grid-template-columns:repeat(8,minmax(0,1fr))}.metrics strong{font-size:17px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let refs=[true,false,false],q=[],result=null;for(let i=0;i<3;i++){const e=document.createElement('button');e.dataset.ref=i;e.onclick=()=>{refs[i]=false;draw()};$('#refs').append(e)}function draw(){const n=refs.filter(Boolean).length;document.querySelectorAll('[data-ref]').forEach(e=>{const i=+e.dataset.ref;e.disabled=!refs[i];e.textContent='fd '+(4+i)+' · '+t(refs[i]?'open':'closed')});cells('buffer',Array.from({length:8},(_,i)=>({text:q[i]??'—',cls:q[i]?'on':'empty'})));set('writers',n);set('result',result??t('none'));$('#dup').disabled=n===0||n===3;$('#write').disabled=n===0||q.length>6;}$('#write').onclick=()=>{q.push('A','B');draw()};$('#dup').onclick=()=>{refs[refs.indexOf(false)]=true;draw()};$('#read').onclick=()=>{result=q.length?q.splice(0,3).join(''):refs.some(Boolean)?'EAGAIN':'0 · EOF';draw()};$('#reset').onclick=()=>{refs=[true,false,false];q=[];result=null;draw()};draw();","audio":false,"strings":{"buffer":"管道缓冲","closed":"已关闭","dup":"复制写端","heading":"关闭一个写端,为什么还不是 EOF?","intro":"写入字节、复制写端、关闭引用,再尝试非阻塞读取。","none":"尚未读取","open":"打开","read":"读取最多3字节","refs":"点击关闭写端引用","reset":"重置","result":"最近 read 结果","scope":"单个读端保持打开,容量8字节,写入两字节,读取最多3字节。最多三个写引用;不模拟 SIGPIPE 与多写者原子性。","scopeLabel":"模型范围与约定","write":"写入 AB","writers":"存活写引用"}}
一个验证 EOF 的父子进程例子
下面仅发送固定的五字节 payload,处理短写与 EINTR,并让子进程读到 EOF 后核对总长度。
#define _GNU_SOURCE
#include < errno.h>
#include < fcntl.h>
#include < stddef.h>
#include < sys/wait.h>
#include < unistd.h>
int main ( void ) {
int fd[ 2 ] ;
if ( pipe2 ( fd, O_CLOEXEC ) == - 1 ) return 1 ;
pid_t child = fork ( ) ;
if ( child == - 1 ) { close ( fd[ 0 ] ) ; close ( fd[ 1 ] ) ; return 1 ; }
if ( child == 0 ) {
close ( fd[ 1 ] ) ;
size_t total = 0 ;
char buf[ 32 ] ;
for ( ; ; ) {
ssize_t n = read ( fd[ 0 ] , buf, sizeof buf ) ;
if ( n > 0 ) total += ( size_t ) n;
else if ( n == 0 ) break ;
else if ( errno != EINTR) _exit ( 2 ) ;
}
close ( fd[ 0 ] ) ;
_exit ( total == 5 ? 0 : 3 ) ;
}
close ( fd[ 0 ] ) ;
const char payload[ ] = " hello" ;
size_t done = 0 ;
int failed = 0 ;
while ( done < 5 ) {
ssize_t n = write ( fd[ 1 ] , payload + done, 5 - done ) ;
if ( n > 0 ) done += ( size_t ) n;
else if ( n < 0 && errno == EINTR) continue ;
else { failed = 1 ; break ; }
}
close ( fd[ 1 ] ) ;
int status;
while ( waitpid ( child, & status, 0 ) == - 1 ) {
if ( errno != EINTR) return 1 ;
}
return failed || ! WIFEXITED ( status ) || WEXITSTATUS ( status ) != 0 ;
}
用 cc -Wall -Wextra pipe_demo.c -o pipe_demo 编译。真实协议还要处理对端提前退出、超时、SIGPIPE 策略和输入大小限制;这里的长度核对只用于说明生命周期,不是通用消息完整性校验。
不用等待超时,也能验证多余写端导致无 EOF
以下单进程例子复制一个写端,关闭原写端后对空管道非阻塞读取。由于复制的写端仍存在,结果是 EAGAIN;关闭最后一个写端后,同一次读取才返回 0。
#define _GNU_SOURCE
#include < errno.h>
#include < fcntl.h>
#include < stdio.h>
#include < unistd.h>
int main ( void ) {
int fd[ 2 ] ;
if ( pipe2 ( fd, O_NONBLOCK | O_CLOEXEC ) == - 1 ) return 1 ;
int extra = fcntl ( fd[ 1 ] , F_DUPFD_CLOEXEC, 0 ) ;
if ( extra == - 1 ) { close ( fd[ 0 ] ) ; close ( fd[ 1 ] ) ; return 1 ; }
if ( close ( fd[ 1 ] ) == - 1 ) { close ( extra ) ; close ( fd[ 0 ] ) ; return 1 ; }
char byte;
errno = 0 ;
ssize_t before = read ( fd[ 0 ] , & byte, 1 ) ;
int saved = errno;
int failed = ! ( before == - 1 && ( saved == EAGAIN || saved == EWOULDBLOCK) ) ;
if ( close ( extra ) == - 1 ) failed = 1 ;
if ( read ( fd[ 0 ] , & byte, 1 ) != 0 ) failed = 1 ;
if ( close ( fd[ 0 ] ) == - 1 ) failed = 1 ;
if ( ! failed) puts ( " writer remains: EAGAIN; last writer closed: EOF" ) ;
return failed;
}
保存为 pipe-refs.c,用 cc -Wall -Wextra pipe-refs.c -o pipe-refs 编译运行。本例没有写入数据,因此最后一次关闭后立即可见 EOF;若已有数据,还必须先读尽它。F_DUPFD_CLOEXEC 为新描述符也设置关闭执行标志,避免误以为所有复制接口都会自动继承 FD_CLOEXEC。F_DUPFD_CLOEXEC
socketpair:类型决定边界
int sv[ 2 ] ;
int rc = socketpair ( AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0 , sv ) ;
这是一段需要 <sys/socket.h> 的 API 片段。socketpair 的两端创建后已互相连接,不必再做 bind、listen、accept。socketpair(2)
AF_UNIX 类型 数据模型 接收时需要注意
SOCK_STREAM 可靠、有序字节流 自行分帧,处理短读短写
SOCK_DGRAM 本地可靠、有序数据报 保留边界,缓冲太小可能截断消息
SOCK_SEQPACKET 面向连接、可靠、有序记录 保留边界,检查截断及记录大小
Linux AF_UNIX 数据报不等于 UDP 的交付语义,不能写成“不可靠”。SOCK_SEQPACKET 也不是到 Linux 5.3 才出现。可靠性表示接口范围内的传输语义,并不保证接收进程读到后一定完成业务处理;崩溃恢复仍需要上层协议。unix(7)
AF_UNIX 避开 IP/TCP 路径,但仍有自己的协议实现、缓冲和背压;“不走任何协议栈、没有流控”不准确。延迟与吞吐应按消息大小、并发、复制路径和调度测量。
凭证与文件描述符传递
SCM_RIGHTS 传递的是对打开文件描述的引用,接收进程取得自己的 fd 数字,不是把发送者那个整数原样变成可用句柄。共享的文件偏移和状态也可能随该打开文件描述关联,需要理解所有权与关闭责任。
SO_PEERCRED 可用于查询连接对端的凭证,SCM_CREDENTIALS 则是消息辅助数据机制,接收策略和内核校验依接口规定。内核提供的身份信息应再结合应用授权规则,不能看到 UID 就无条件信任任何操作。
recvmsg 要检查 MSG_CTRUNC、控制缓冲大小和接收 fd 数量。拒绝一条消息时也必须关闭已接收但不再使用的 fd;Linux 的 MSG_CMSG_CLOEXEC 可避免收到的描述符意外跨 exec 泄露。unix(7) 辅助数据
splice:减少用户缓冲往返,不承诺绝对不复制
splice 要求至少一端是管道,可将数据在支持的 fd 与 pipe 之间传递。某些路径能移动页引用,避免先复制到用户缓冲再写回内核,但实际是否复制取决于端点与内核实现,不能统一保证物理数据一次都不复制。splice(2)
一次 splice 也可能短传输、阻塞或返回 EAGAIN。文件到管道再到套接字的循环要记录已进入管道但尚未发出的字节,先处理背压,避免重复读取或丢弃尾部。它与 sendfile 有相关优化用途,但不能把 sendfile 的所有实现细节固定成某两次 splice 调用。
eventfd 与选型
eventfd 是内核维护的 64 位计数器接口:普通读取返回累计值并清零,EFD_SEMAPHORE 模式每次读取递减一。它适合通知或工作计数,不承载任意字节消息,也不是只能沿某个单向端点传输的管道。eventfd(2)
单向流选 pipe;双向字节协议选 socketpair STREAM;需要记录边界可考虑 SEQPACKET;传递 fd 或身份信息用 AF_UNIX 辅助数据;只需通知则考虑 eventfd。大块共享数据的生命周期与同步见 共享内存与信号量 。
关键词:pipe, PIPE_BUF, EOF, SIGPIPE, socketpair, AF_UNIX, SOCK_SEQPACKET, SCM_RIGHTS, credentials, splice, eventfd, backpressure