ไม่สามารถระบุการแมปพอร์ตไปยังพอร์ตของ IP ภายใน
แต่ฉันได้แมป IP ภายนอกกับเซิร์ฟเวอร์ Linux ภายในแล้ว
แต่ฉันก็ต้องการใช้ VNC เพื่อเชื่อมต่อกับคอมพิวเตอร์ Windows ภายในจากภายนอกด้วย
ผมจึงเขียนโปรแกรมนี้ขึ้นมาและมีหลักการดังนี้
โปรแกรมนี้จะเปิดพอร์ตบน Linux Server สำหรับการฟัง เมื่อการเชื่อมต่อภายนอกเชื่อมต่อกับพอร์ตนี้ โปรแกรมจะเปิดการเชื่อมต่ออื่นไปยัง Windows VNC ภายใน และส่งแพ็กเก็ตภายนอกที่สมบูรณ์ไปยังการเชื่อมต่อ VNC จากนั้น ส่งข้อมูลที่ส่งคืนโดยการเชื่อมต่อ VNC กลับไปที่พอร์ตภายนอก
รหัสโปรแกรม:
#!/usr/bin/php -q
<?php
$IP = '192.168.1.1' ; //IP ของคอมพิวเตอร์ Windows
$Port = '5900' ; //พอร์ตที่ใช้โดย VNC
$ServerPort = '9999' ; // พอร์ตที่ใช้โดย Linux Server ภายนอก
$RemoteSocket = false ; // เชื่อมต่อกับซ็อกเก็ตของ VNC
ฟังก์ชั่น SignalFunction & #40;$Signal)
& #123;
//นี่คือฟังก์ชันการประมวลผลข้อความของกระบวนการหลัก
$PID ทั่วโลก ; //PID ของกระบวนการลูก
สลับ & #40;$Signal)
& #123;
กรณี SIGTRAP & #58;
กรณี SIGTERM & #58;
//รับสัญญาณเพื่อจบโปรแกรม
ถ้า& #40;$PID)
& #123;
//ส่งสัญญาณ SIGTERM ให้เด็กบอกให้ยุติเรื่องโดยเร็ว
posix_kill & #40;$PID,SIGTERM);
//รอให้ Child Process สิ้นสุดเพื่อหลีกเลี่ยงซอมบี้
pcntl_wait & #40;$สถานะ);
& #125;
//ปิดซ็อกเก็ตที่เปิดโดยกระบวนการหลัก
DestroySocket & #40;);
exit& #40;0); exit& #40;0);
หยุดพัก;
กรณี SIGCHLD & #58;
-
เมื่อกระบวนการลูกสิ้นสุดลง เด็กจะส่งสัญญาณ SIGCHLD ไปยังผู้ปกครอง
เมื่อผู้ปกครองได้รับ SIGCHLD จะรู้ว่ากระบวนการลูกสิ้นสุดลงแล้ว และควรดำเนินการบางอย่างในการสิ้นสุด*/
unset& #40;$PID); //ล้าง $PID เพื่อระบุว่ากระบวนการย่อยสิ้นสุดลงแล้ว
pcntl_wait & #40;$Status); //หลีกเลี่ยงซอมบี้
หยุดพัก;
ค่าเริ่มต้น& #58;
& #125;
& #125;
ฟังก์ชั่น ChildSignalFunction & #40;$Signal)
& #123;
//นี่คือฟังก์ชันการประมวลผลข้อความของกระบวนการลูก
สลับ & #40;$Signal)
& #123;
กรณี SIGTRAP & #58;
กรณี SIGTERM & #58;
//กระบวนการลูกได้รับข้อความสิ้นสุด
DestroySocket & #40;); ปิดซ็อกเก็ต
exit& #40;0);
ค่าเริ่มต้น& #58;
& #125;
& #125;
ฟังก์ชั่น ProcessSocket & #40;$ConnectedServerSocket)
& #123;
//ฟังก์ชั่นการประมวลผลซ็อกเก็ตกระบวนการลูก
//$ConnectedServerSocket -> ซ็อกเก็ตที่เชื่อมต่อภายนอก
$ServerSocket ทั่วโลก , $RemoteSocket , $IP , $Port ;
$ServerSocket = $ConnectedServerSocket ;
ประกาศ & #40;เห็บ = 1); // ต้องเพิ่มบรรทัดนี้ มิฉะนั้นจะไม่สามารถตั้งค่าฟังก์ชันการประมวลผลข้อความได้
//ตั้งค่าฟังก์ชั่นการประมวลผลข้อความ
if& #40;!pcntl_signal(SIGTERM, "ChildSignalFunction")) กลับ;
if& #40;!pcntl_signal(SIGTRAP, "ChildSignalFunction")) กลับ;
//สร้าง Socket เชื่อมต่อกับ VNC
$RemoteSocket = socket_create & #40;AF_INET, SOCK_STREAM,SOL_TCP);
//เชื่อมต่อกับ VNC ภายใน
@ $RemoteConnected = socket_connect & #40;$RemoteSocket,$IP,$พอร์ต);
if& #40;!$RemoteConnected) return; // ไม่สามารถเชื่อมต่อกับจุดสิ้นสุด VNC ได้
//ตั้งค่าการประมวลผลซ็อกเก็ตเป็น Nonblock เพื่อป้องกันไม่ให้โปรแกรมถูกบล็อก
if& #40;!socket_set_nonblock($RemoteSocket)) กลับ;
if& #40;!socket_set_nonblock($ServerSocket)) กลับ;
ในขณะที่& #40;จริง)
& #123;
//ที่นี่เราใช้การรวมกลุ่มเพื่อรับข้อมูล
$NoRecvData = false ; //ตัวแปรนี้ใช้เพื่อตรวจสอบว่าการเชื่อมต่อภายนอกได้อ่านข้อมูลหรือไม่
$NoRemoteRecvData = false ; //ตัวแปรนี้ใช้เพื่อตรวจสอบว่าการเชื่อมต่อ VNC ได้อ่านข้อมูลหรือไม่
@ $RecvData = socket_read & #40;$ServerSocket,4096,PHP_BINARY_READ);
//อ่านข้อมูล 4096 ไบต์จากการเชื่อมต่อภายนอก
@ $RemoteRecvData = socket_read & #40;$RemoteSocket,4096,PHP_BINARY_READ);
//อ่านข้อมูล 4096 ไบต์จากการเชื่อมต่อ vnc
ถ้า & #40;$RemoteRecvData==='')
& #123;
//การเชื่อมต่อ VNC ถูกขัดจังหวะ ถึงเวลาสิ้นสุดแล้ว
echo "ปิดการเชื่อมต่อระยะไกลn" ;
กลับ;
& #125;
if& #40;$RemoteRecvData===false)
& #123;
-
เนื่องจากเราใช้โหมด nonblobk สถานการณ์ตรงนี้ก็คือการเชื่อมต่อ vnc ไม่มีข้อมูลให้อ่าน
-
$NoRemoteRecvData = จริง ;
//ล้างข้อผิดพลาดครั้งล่าสุด
socket_clear_error & #40;$RemoteSocket);
& #125;
ถ้า& #40;$RecvData==='')
& #123;
//การเชื่อมต่อภายนอกถูกขัดจังหวะ ถึงเวลาสิ้นสุดแล้ว
echo "ปิดการเชื่อมต่อไคลเอ็นต์n" ;
กลับ;
& #125;
if& #40;$RecvData===false)
& #123;
-
เนื่องจากเราใช้โหมด nonblobk สถานการณ์นี้ก็คือไม่มีข้อมูลให้อ่านจากการเชื่อมต่อภายนอก
-
$NoRecvData = จริง ;
//ล้างข้อผิดพลาดครั้งล่าสุด
socket_clear_error & #40;$ServerSocket);
& #125;
if& #40;$NoRecvData&&$NoRemoteRecvData)
& #123;
//ถ้าทั้งการเชื่อมต่อภายนอกหรือการเชื่อมต่อ VNC ไม่มีข้อมูลที่จะอ่าน
//ปล่อยให้โปรแกรมเข้าสู่โหมดสลีปเป็นเวลา 0.1 วินาทีเพื่อหลีกเลี่ยงการใช้ทรัพยากร CPU ในระยะยาว
ใช้งาน & #40;100000);
//หลังจากตื่นนอนแล้ว ให้ดำเนินการรวมกลุ่มต่อเพื่ออ่านซ็อกเก็ต
ดำเนินการต่อ;
& #125;
//รับข้อมูล
if& #40;!$NoRecvData)
& #123;
//การเชื่อมต่อภายนอกอ่านข้อมูล
ในขณะที่& #40;จริง)
& #123;
//ถ่ายโอนข้อมูลที่อ่านจากการเชื่อมต่อภายนอกไปยังการเชื่อมต่อ VNC
@ $WriteLen = socket_write & #40;$RemoteSocket,$RecvData);
if& #40;$WriteLen===false)
& #123;
//เนื่องจากปัญหาการส่งข้อมูลทางเครือข่าย จึงไม่สามารถเขียนข้อมูลได้ในขณะนี้
//สลีปเป็นเวลา 0.1 วินาทีก่อนลองอีกครั้ง
ใช้งาน & #40;100000);
ดำเนินการต่อ;
& #125;
ถ้า& #40;$WriteLen===0)
& #123;
//การเชื่อมต่อระยะไกลถูกขัดจังหวะ โปรแกรมควรจะสิ้นสุด
echo "ปิดการเชื่อมต่อการเขียนระยะไกลn" ;
กลับ;
& #125;
//เมื่อข้อมูลที่อ่านจากการเชื่อมต่อภายนอกถูกส่งไปยังการเชื่อมต่อ VNC อย่างสมบูรณ์ การวนซ้ำนี้จะถูกขัดจังหวะ
if& #40;$WriteLen==strlen($RecvData)) พัง;
//หากไม่สามารถส่งข้อมูลได้ในคราวเดียว จะต้องแบ่งออกเป็นหลาย ๆ การส่งข้อมูลจนกว่าข้อมูลจะถูกส่งทั้งหมด
$RecvData = substr & #40;$RecvData,$WriteLen);
& #125;
& #125;
if& #40;!$NoRemoteRecvData)
& #123;
//นี่คือข้อมูลที่อ่านจากการเชื่อมต่อ VNC แล้วโอนกลับไปยังการเชื่อมต่อภายนอก
//หลักการก็เกือบเหมือนข้างบนและจะไม่ลงรายละเอียดอีกครับ
ในขณะที่& #40;จริง)
& #123;
@ $WriteLen = socket_write & #40;$ServerSocket,$RemoteRecvData);
if& #40;$WriteLen===false)
& #123;
ใช้งาน & #40;100000);
ดำเนินการต่อ;
& #125;
ถ้า& #40;$WriteLen===0)
& #123;
echo "ปิดการเชื่อมต่อการเขียนระยะไกลn" ;
กลับ;
& #125;
if& #40;$WriteLen==strlen($RemoteRecvData)) พัง;
$RemoteRecvData = substr & #40;$RemoteRecvData,$WriteLen);
& #125;
& #125;
& #125;
& #125;
ฟังก์ชั่น DestroySocket & #40;)
& #123;
//ใช้สำหรับปิด Socket ที่เปิดอยู่
$ServerSocket ทั่วโลก , $RemoteSocket ;
ถ้า& #40;$RemoteSocket)
& #123;
//หากเปิดใช้งานการเชื่อมต่อ VNC แล้ว
// ต้องปิด Socket ก่อนปิด Socket ไม่เช่นนั้นอีกฝ่ายจะไม่รู้ว่าคุณได้ปิดการเชื่อมต่อแล้ว
@ socket_shutdown & #40;$RemoteSocket,2);
socket_clear_error & #40;$RemoteSocket);
//ปิดซ็อกเก็ต
socket_close & #40;$RemoteSocket);
& #125;
//ปิดการเชื่อมต่อภายนอก
@ socket_shutdown & #40;$ServerSocket,2);
socket_clear_error & #40;$ServerSocket);
socket_close & #40;$ServerSocket);
& #125;
//นี่คือจุดเริ่มต้นของโปรแกรมทั้งหมด และโปรแกรมจะเริ่มดำเนินการจากที่นี่
//ก่อนอื่นให้รัน fork ที่นี่
$PID = pcntl_fork & #40;);
if& #40;$PID==-1) die("ไม่สามารถแยก");
//ถ้า $PID ไม่ใช่ 0 แสดงว่านี่เป็นกระบวนการหลัก
//$PID คือกระบวนการลูก
//นี่คือกระบวนการของผู้ปกครอง ยุติมันด้วยตัวเองและปล่อยให้ Child กลายเป็น Daemon
ถ้า & #40;$PID) ตาย("Daemon PID:$PIDn");
//จากนี้ไป โหมด Daemon จะถูกดำเนินการ
//แยกกระบวนการปัจจุบันออกจากเทอร์มินัลแล้วเข้าสู่โหมด daemon
if& #40;!posix_setsid()) die("ไม่สามารถแยกออกจากเทอร์มินัลได้n");
//ตั้งค่าฟังก์ชันการประมวลผลข้อความของ daemon
ประกาศ & #40;ติ๊ก = 1);
if& #40;!pcntl_signal(SIGTERM, "SignalFunction")) die("ผิดพลาด!!!n");
if& #40;!pcntl_signal(SIGTRAP, "SignalFunction")) die("ข้อผิดพลาด!!!n");
if& #40;!pcntl_signal(SIGCHLD, "SignalFunction")) die("ข้อผิดพลาด!!!n");
//สร้าง Socket สำหรับการเชื่อมต่อภายนอก
$ServerSocket = socket_create & #40;AF_INET, SOCK_STREAM,SOL_TCP);
//ตั้งค่า IP และพอร์ตสำหรับการตรวจสอบการเชื่อมต่อภายนอก ตั้งค่าฟิลด์ IP เป็น 0 ซึ่งหมายถึงการฟัง IP ของอินเทอร์เฟซทั้งหมด
if& #40;!socket_bind($ServerSocket,0,$ServerPort)) die("ไม่สามารถผูกซ็อกเก็ตได้!n");
//เริ่มฟังพอร์ต
if& #40;!socket_listen($ServerSocket)) die("ฟังไม่ได้!n");
// ตั้งค่า Socket เป็นโหมดไม่บล็อค
if& #40;!socket_set_nonblock($ServerSocket)) die("ไม่สามารถตั้งค่าซ็อกเก็ตเซิร์ฟเวอร์ให้บล็อกได้!n");
//ล้างตัวแปร $PID เพื่อระบุว่าขณะนี้ไม่มี Child Process
ยกเลิกการตั้งค่า & #40;$PID);
ในขณะที่& #40;จริง)
& #123;
//เข้าสู่โหมดรวมกลุ่มและตรวจสอบว่ามีการเชื่อมต่อทุกๆ 1 วินาทีหรือไม่
นอน & #40;1);
//ตรวจสอบว่ามีการเชื่อมต่อเข้ามาหรือไม่
@ $ConnectedServerSocket = socket_accept & #40;$ServerSocket);
if& #40;$ConnectedServerSocket!==false)
& #123;
//มีคนเข้ามา.
//เริ่มกระบวนการลูกเพื่อจัดการการเชื่อมต่อ
$PID = pcntl_fork & #40;);
if& #40;$PID==-1) die("ไม่สามารถแยก");
if& #40;$PID) Continue;//นี่คือกระบวนการ daemon ให้ตรวจสอบต่อไป
//นี่คือจุดเริ่มต้นของกระบวนการย่อย
//ดำเนินการฟังก์ชั่นในซ็อกเก็ต
ProcessSocket & #40;$ConnectedServerSocket);
//หลังจากประมวลผล Socket แล้ว ให้จบ Socket
DestroySocket & #40;);
//สิ้นสุดกระบวนการย่อย
ออก& #40;0);
& #125;
& #125;
-