TCP客戶端代碼如下:
程序啟動(dòng)后,逐個(gè)向服務(wù)器設(shè)備發(fā)送數(shù)據(jù)
問題:代碼中明明分開發(fā)的數(shù)據(jù),在抓包時(shí)發(fā)現(xiàn)內(nèi)容會(huì)自動(dòng)連接在一起后才發(fā)出去。
收數(shù)據(jù)也一樣,明明分開拿 數(shù)據(jù),會(huì)被合成一起才送給應(yīng)用程序。
查了PHP環(huán)境支持的
<?php
echo function_exists('socket_import_stream');
php test_socket_import_stream.php
1
[www@linux beckhoff_ads]$ php start_ads_client_wkm.php start
Workerman[start_ads_client_wkm.php] start in DEBUG mode
------------------------------------------- WORKERMAN --------------------------------------------
Workerman version:4.0.41 PHP version:7.4.28 Event-Loop:\Workerman\Events\Event
-------------------------------------------- WORKERS ---------------------------------------------
proto user worker listen processes status
tcp www none none 1 [OK]
--------------------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.
2022-08-01 06:15:28 Send 000020000000c0a8013401012103c0a801640101228004000400000000000000000000000000
2022-08-01 06:15:28 Send 00002c000000c0a801340101c800c0a8016401012180020004000c0000000000000001000000010000002100000010000000
2022-08-01 06:15:28 Send 000020000000c0a8013401012003c0a801640101218001000400000000000000000002000000
2022-08-01 06:15:28 Send 00002c000000c0a8013401012003c0a8016401012180010005000c0000000000000003000000010000002100000010000000
2022-08-01 06:15:28 Send 000021000000c0a8013401012103c0a80164010122800150000001000000000000000400000001
2022-08-01 06:15:28 Send 000021000000c0a8013401012103c0a80164010122800150000001000000000000000500000011
2022-08-01 06:15:28 Send 000022000000c0a8013401012103c0a8016401012280015000000200000000000000060000005112
2022-08-01 06:15:28 Send 000022000000c0a8013401012103c0a8016401012280015000000200000000000000070000005110
2022-08-01 06:15:28 Send 000022000000c0a8013401012103c0a8016401012280015000000200000000000000080000003800
2022-08-01 06:15:28 Send 0000a0000000c0a8013401012103c0a8016401012280015000008000000000000000090000005014050000000400480000000100000004004d00000006000000f9ff2200080000000100000000000400160000002800000004005100000002000000f9ff2200080000000100000000000400160000002800000004005100000002000000f9ff2200080000000100000000000400160000002800000004005100000002000000
2022-08-01 06:15:28 Connect Ok
2022-08-01 06:15:28 recv 000028000000c0a8016401012280c0a8013401012103040005000800000000000000000000000000000005000000000038000000c0a8016401012180c0a801340101c80002000500180000000000000001000000000000001000000050000000102700000100000000000000000038000000c0a8016401012180c0a80134010120030100050018000000000000000200000000000000020b3a08504c4320536572766572000000000000
2022-08-01 06:15:28 recv 0000ee000000c0a8016401012280c0a801340101210301500100ce0000000000000004000000e803e8030000000000003080000000ff00000000010000ff0000881300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000026000000c0a8016401012280c0a8013401012103015001000600000000000000050000000000c0990400000024000000c0a8016401012280c0a80134010121030150010004000000000000000600000000001200000061000000c0a8016401012280c0a80134010121030150010041000000000000000700000000001000000000000000000000000000000000cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd000000000000000000000000000000000000cdcdcdcd00000022000000c0a8016401012280c0a8013401012103015001000200000000000000080000000000
2022-08-01 06:15:28 recv 000024000000c0a8016401012280c0a80134010121030150010004000000000000000900000000001400
tcp協(xié)議是基于流的,不會(huì)自動(dòng)將請求分開。收到的數(shù)據(jù)可能是一個(gè)請求的部分?jǐn)?shù)據(jù),也可能是多請求數(shù)據(jù)連在一起。
使用tcp時(shí)需要用應(yīng)用層協(xié)議從數(shù)據(jù)流里區(qū)分請求邊界,這就是為什么有了tcp,還要有http ftp smtp這些應(yīng)用層協(xié)議。
tcp_nodelay 也不是用于分割請求的
根據(jù)網(wǎng)上的參考資料,開啟tcp_nodelay以后,執(zhí)行send指令數(shù)據(jù)會(huì)馬上送出去,收到數(shù)據(jù),程序會(huì)馬上產(chǎn)生onmessage事件。 ( https://www.cnblogs.com/zhangkele/p/9494280.html) 目前我們使用workerman做工業(yè)控制 ,連接倍福(beckhoff)PLC ,我再研究一下。感謝walkor.
beckhoff官方的C++驅(qū)動(dòng)程序默認(rèn)開啟了tcp_nodelay,可以做到數(shù)據(jù)即時(shí)收發(fā)的。 https://github.com/Beckhoff/ADS/blob/master/AdsLib/Sockets.cpp (line 229 setsockopt )。 而swoole也有這個(gè)特性 , workerman比swoole簡單,所以才選擇的這套方案。
我看了workerman的源碼,也有這個(gè)特性,worker.php line 2296,但是看樣子設(shè)置沒有生效,麻煩walkor再幫解答一下。
再此表達(dá)一下,workerman的tcp reconnect功能 很好使,網(wǎng)絡(luò)連接斷開以后會(huì)自動(dòng)重連,而swoole不一樣,連不上就直接報(bào)錯(cuò)了,connected狀態(tài)也依然為true,所以才選擇的workerman。
tcp_nodelay應(yīng)該是生效了的,只不過 tcp_nodelay 無法從根本上解決你的問題,tcp的特性就是這樣,數(shù)據(jù)沒有邊界,需要接收端從數(shù)據(jù)流里將完整的數(shù)據(jù)提取出來。
快速發(fā)送數(shù)據(jù)或者網(wǎng)絡(luò)延遲或者對端接收數(shù)據(jù)不夠快都會(huì)導(dǎo)致數(shù)據(jù)在socket緩沖區(qū)積壓,數(shù)據(jù)就連在一起了。
感謝walkor的分享,我已經(jīng)找到了原因:我在定時(shí)器里面執(zhí)行了一大堆send,就算操作系統(tǒng)的底層收到數(shù)據(jù)也沒有機(jī)會(huì)執(zhí)行onMessage回調(diào),因?yàn)槲叶〞r(shí)器里面的函數(shù)沒有執(zhí)行完。操作系統(tǒng)收到數(shù)據(jù)以后一直將數(shù)據(jù)放到了緩沖區(qū)里面。直到定時(shí)器里面的函數(shù)執(zhí)行完成以后,檢查baseRead時(shí),才會(huì)回調(diào)onMessage。
解決辦法:每次執(zhí)行完send指令以后,執(zhí)行這一句檢查是否收到了數(shù)據(jù),如果有回復(fù)數(shù)據(jù)就觸發(fā)OnMessage進(jìn)行檢查,否則繼續(xù)發(fā)送下一條數(shù)據(jù)。$conn->baseRead($conn->getSocket());
通過這一句處理問題,我認(rèn)真研究了workerman的底層源碼,之前只停留在會(huì)用,這次進(jìn)步了,特此將該問題分析共享出來。
發(fā)送一個(gè)我之前代碼的bug,之前我發(fā)送一次數(shù)據(jù),檢查一次接收,這樣是有問題的,因?yàn)橛锌赡軋?zhí)行一次baseRead不能一次性返回所有的接收數(shù)據(jù),因?yàn)檫€需要調(diào)用 getRecvBufferQueueSize() 做一個(gè)判斷,只有把數(shù)據(jù)取完以后再執(zhí)行其他內(nèi)容。
發(fā)送端和接收端都有socket緩沖區(qū),如果發(fā)送端發(fā)送多個(gè)數(shù)據(jù),數(shù)據(jù)有積壓在發(fā)送端socket緩沖區(qū)的情況,數(shù)據(jù)還是連在一起發(fā)送到對端的。除非是發(fā)送端只發(fā)送一個(gè)數(shù)據(jù)包,然后等對端響應(yīng)一個(gè)包。