สวัสดี! วันก่อน เราได้พูดคุยเกี่ยวกับ สิ่งที่เกิดขึ้นเมื่อคุณกดปุ่มในเทอร์มินัลของคุณ
จากการติดตาม ฉันคิดว่าการใช้โปรแกรมที่เหมือนกับเซิร์ฟเวอร์ ssh ขนาดเล็กอาจเป็นเรื่องที่สนุก แต่ไม่มีความปลอดภัย คุณสามารถค้นหาได้ ใน github ที่นี่ และฉันจะอธิบายวิธีการทำงานในโพสต์บล็อกนี้
เป้าหมาย: “ssh” ไปยังคอมพิวเตอร์ระยะไกล
เป้าหมายของเราคือสามารถเข้าสู่ระบบคอมพิวเตอร์ระยะไกลและเรียกใช้คำสั่งต่างๆ ได้ เช่นเดียวกับที่คุณทำกับ SSH หรือ telnet
ความแตกต่างที่ใหญ่ที่สุดระหว่างโปรแกรมนี้กับ SSH ก็คือไม่มีการรักษาความปลอดภัยอย่างแท้จริง (ไม่ใช่แม้แต่รหัสผ่าน) – ใครก็ตามที่สามารถเชื่อมต่อ TCP กับเซิร์ฟเวอร์สามารถรับเชลล์และรันคำสั่งได้
เห็นได้ชัดว่านี่ไม่ใช่โปรแกรมที่มีประโยชน์ในชีวิตจริง แต่เป้าหมายของเราคือการเรียนรู้เพิ่มเติมเล็กน้อยเกี่ยวกับวิธีการทำงานของเทอร์มินัล ไม่ใช่เพื่อเขียนโปรแกรมที่มีประโยชน์
(ฉันจะเรียกใช้เวอร์ชันนี้บนอินเทอร์เน็ตสาธารณะในสัปดาห์หน้า แต่คุณสามารถดูวิธีเชื่อมต่อได้ในตอนท้ายของโพสต์บล็อกนี้)
มาเริ่มกันที่เซิร์ฟเวอร์กัน!
เราจะเขียนไคลเอนต์ด้วย แต่เซิร์ฟเวอร์เป็นส่วนที่น่าสนใจ ดังนั้นมาเริ่มกันที่ เราจะเขียนเซิร์ฟเวอร์ที่รับฟังพอร์ต TCP (ฉันเลือก 7777) และสร้างเทอร์มินัลระยะไกลสำหรับลูกค้าที่เชื่อมต่อเพื่อใช้งาน
เมื่อเซิร์ฟเวอร์ได้รับการเชื่อมต่อใหม่ จะต้อง:
- สร้าง pseudoterminal ให้ลูกค้าใช้
- เริ่มกระบวนการ
bash
shell เพื่อให้ไคลเอนต์ใช้ - เชื่อมต่อ
bash
กับ pseudoterminal - คัดลอกข้อมูลไปมาอย่างต่อเนื่องระหว่างการเชื่อมต่อ TCP และ pseudoterminal
ฉันเพิ่งพูดคำว่า “pseudoterminal” ไปบ่อย ๆ ดังนั้นเรามาพูดถึงความหมายกัน
เทอร์มินัลเทียมคืออะไร
เอาล่ะ เทอร์มินัลเทียมคืออะไร?
เทอร์มินอลเทียมเป็นเหมือนไปป์แบบสองทิศทางหรือเต้ารับ – คุณมีปลายทั้งสองข้าง และพวกเขาทั้งสองสามารถส่งและรับข้อมูลได้ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับข้อมูลที่ส่งและรับได้ใน สิ่งที่เกิดขึ้นหากคุณกดปุ่มในเทอร์มินัล
โดยพื้นฐานแล้วแนวคิดก็คือด้านหนึ่ง เรามีการเชื่อมต่อ TCP และอีกด้านหนึ่ง เรามี bash
shell ดังนั้นเราจึงต้องขอส่วนหนึ่งของ pseudoterminal กับการเชื่อมต่อ TCP และปลายอีกด้านหนึ่งเพื่อทุบตี
สองส่วนของ pseudoterminal เรียกว่า:
- “ต้นแบบเทอร์มินัลเทียม” นี่คือจุดสิ้นสุดที่เราจะเชื่อมต่อกับการเชื่อมต่อ TCP
- “อุปกรณ์เทอร์มินัลเทียมทาส” เราจะตั้งค่า
stdout
,stderr
และstdin
ของ bash shell เป็นสิ่งนี้
เมื่อเชื่อมต่อแล้ว เราสามารถสื่อสารกับ bash
ผ่านการเชื่อมต่อ TCP และเราจะมีรีโมตเชลล์!
ทำไมเราถึงต้องการสิ่ง “pseudoterminal” นี้อยู่แล้ว?
คุณอาจกำลังสงสัย – จูเลีย ถ้าเทอร์มินัลปลอมเป็นเหมือนซ็อกเก็ต ทำไมเราไม่สามารถตั้งค่า stdout
/ stderr
/ stdin
ของเชลล์ทุบตีเป็นซ็อกเก็ต TCP ได้
และคุณสามารถ! เราสามารถเขียนตัวจัดการการเชื่อมต่อ TCP แบบนี้ที่ทำอย่างนั้นได้ ไม่ใช่โค้ดจำนวนมาก ( server-notty.go )
func handle(conn net.Conn) { tty, _ := conn.(*net.TCPConn).File() // start bash with tcp connection as stdin/stdout/stderr cmd := exec.Command("bash") cmd.Stdin = tty cmd.Stdout = tty cmd.Stderr = tty cmd.Start() }
มันยังใช้งานได้ – ถ้าเราเชื่อมต่อกับ nc localhost 7778
เราสามารถเรียกใช้คำสั่งและดูผลลัพธ์ได้
แต่มีปัญหาเล็กน้อย ฉันจะไม่แสดงรายการทั้งหมด แค่สองรายการ
ปัญหาที่ 1: Ctrl + C ไม่ทำงาน
วิธีการทำงานของ Ctrl + C ในเซสชันการเข้าสู่ระบบระยะไกลคือ
- คุณกด ctrl + c
- ที่ได้รับการแปลเป็น
0x03
และส่งผ่านการเชื่อมต่อ TCP - เครื่องปลายทางรับ
- เคอร์เนลลินุกซ์ในส่วนอื่น ๆ “เฮ้นั่นคือ Ctrl + C!”
- Linux ส่ง
SIGINT
ไปยังกระบวนการที่เหมาะสม (เพิ่มเติมเกี่ยวกับ “กระบวนการที่เหมาะสม” ในภายหลัง)
หาก “เทอร์มินัล” เป็นเพียงการเชื่อมต่อ TCP สิ่งนี้จะไม่ทำงาน เพราะเมื่อคุณส่ง 0x04
ไปยังการเชื่อมต่อ TCP ลินุกซ์จะไม่ส่ง SIGINT
ไปยังกระบวนการใดๆ อย่างน่าอัศจรรย์
ปัญหาที่ 2: top
ไม่ทำงาน
เมื่อฉันพยายามเรียกใช้ top
ในเชลล์นี้ ฉันได้รับข้อความแสดงข้อผิดพลาด top: failed tty get
หากเราลากเส้น เราจะเห็นการเรียกระบบนี้:
ioctl(2, TCGETS, 0x7ffec4e68d60) = -1 ENOTTY (Inappropriate ioctl for device)
ดังนั้น top
กำลังเรียกใช้ ioctl
บนตัวอธิบายไฟล์เอาต์พุต (2) เพื่อรับข้อมูลบางอย่างเกี่ยวกับเทอร์มินัล แต่ลีนุกซ์ก็แบบว่า “เฮ้ นี่ไม่ใช่เทอร์มินัล!” และส่งคืนข้อผิดพลาด
มีหลายอย่างผิดพลาด แต่หวังว่า ณ จุดนี้คุณจะมั่นใจว่าเราจำเป็นต้องตั้งค่า stdout/stderr ของ bash ให้เป็นเทอร์มินัล ไม่ใช่อย่างอื่นเช่นซ็อกเก็ต
เรามาเริ่มดูที่รหัสเซิร์ฟเวอร์และดูว่าการสร้าง pseudoterminal เป็นอย่างไร
ขั้นตอนที่ 1: สร้าง pseudoterminal
นี่คือโค้ด Go บางส่วนเพื่อสร้าง pseudoterminal บน Linux สิ่งนี้ถูกคัดลอกจาก github.com/creack/pty แต่ฉันได้ลบการจัดการข้อผิดพลาดบางส่วนเพื่อให้ตรรกะง่ายต่อการติดตามเล็กน้อย:
pty, _ := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) sname := ptsname(p) unlockpt(p) tty, _ := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
ในภาษาอังกฤษ สิ่งที่เราทำคือ:
- เปิด
/dev/ptmx
เพื่อรับ “pseudoterminal master” อีกครั้งนั่นคือส่วนที่เราจะเชื่อมต่อกับการเชื่อมต่อ TCP - รับชื่อไฟล์ของ “slave pseudoterminal device” ซึ่งจะเป็น
/dev/pts/13
หรือบางอย่าง - “ปลดล็อค” pseudoterminal เพื่อให้เราใช้งานได้ ฉันไม่รู้ว่ามันคืออะไร (ทำไมมันถึงล็อคตั้งแต่เริ่มต้น?) แต่คุณต้องทำด้วยเหตุผลบางอย่าง
- open
/dev/pts/13
(หรือหมายเลขใดก็ตามที่เราได้รับจากptsname
) เพื่อรับ “slave pseudoterminal device”
ฟังก์ชั่น unlockpt
และ ptsname
เหล่านั้นทำอะไรได้บ้าง? พวกเขาเพียงแค่ทำการเรียกระบบ ioctl
ไปยังเคอร์เนลลินุกซ์ การสื่อสารทั้งหมดกับเคอร์เนล Linux เกี่ยวกับเทอร์มินัลดูเหมือนจะผ่านการเรียกระบบ ioctl
ต่างๆ
นี่คือรหัส มันค่อนข้างสั้น: (ฉันเพิ่งคัดลอกมาจาก creack/pty อีกครั้ง)
func ptsname(f *os.File) string { var n uint32 ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))) return "/dev/pts/" + strconv.Itoa(int(n)) } func unlockpt(f *os.File) { var u int32 // use TIOCSPTLCK with a pointer to zero to clear the lock ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) }
ขั้นตอนที่ 2: ขอ pseudoterminal ขึ้นไป bash
สิ่งต่อไปที่เราต้องทำคือเชื่อมต่อ pseudoterminal กับ bash
โชคดีที่ง่ายมาก นี่คือรหัส Go สำหรับมัน! เราแค่ต้องเริ่มกระบวนการใหม่และตั้งค่า stdin, stdout และ stderr เป็น tty
cmd := exec.Command("bash") cmd.Stdin = tty cmd.Stdout = tty cmd.Stderr = tty cmd.SysProcAttr = &syscall.SysProcAttr{ Setsid: true, } cmd.Start()
ง่าย! แม้ว่า – ทำไมเราต้องการ Setsid: true
คุณอาจถาม? ฉันพยายามแสดงความคิดเห็นโค้ดนั้นเพื่อดูว่ามีอะไรผิดพลาด ปรากฎว่าสิ่งที่ผิดพลาดคือ – Ctrl + C ใช้งานไม่ได้อีกต่อไป!
Setsid: true
สร้าง เซสชัน ใหม่สำหรับกระบวนการทุบตีใหม่ แต่ทำไมถึงทำให้ Ctrl + C
ใช้งานได้ Linux รู้ได้อย่างไรว่ากระบวนการใดที่จะส่ง SIGINT
ไปเมื่อคุณกด Ctrl + C
และเกี่ยวข้องกับเซสชันอย่างไร
Linux รู้ได้อย่างไรว่าจะส่ง Ctrl + C ไปที่กระบวนการใด
ฉันพบว่ามันค่อนข้างสับสน ดังนั้นฉันจึงไปหาหนังสือเล่มโปรดเพื่อเรียนรู้เกี่ยวกับสิ่งนี้: อินเทอร์เฟซการเขียนโปรแกรม linux โดยเฉพาะบทที่ 34 เกี่ยวกับกลุ่มกระบวนการและเซสชัน
บทนั้นมีข้อเท็จจริงสำคัญบางประการ: (#3, #4 และ #5 เป็นคำพูดโดยตรงจากหนังสือ)
- ทุกกระบวนการมี รหัสเซสชันและรหัส กลุ่มกระบวนการ (ซึ่งอาจหรืออาจไม่เหมือนกับ PID)
- เซสชันประกอบด้วยกลุ่มกระบวนการหลายกลุ่ม
- กระบวนการทั้งหมดในเซสชันใช้เทอร์มินัลควบคุมเดียว
- เทอร์มินัลอาจเป็นเทอร์มินัลควบคุมได้ไม่เกินหนึ่งเซสชัน
- ในช่วงเวลาใดๆ กลุ่มกระบวนการหนึ่งในเซสชันคือ กลุ่มกระบวนการเบื้องหน้า สำหรับเทอร์มินัล และกลุ่มอื่นคือกลุ่มกระบวนการเบื้องหลัง
- เมื่อคุณกด
Ctrl+C
ในเทอร์มินัล SIGINT จะถูกส่งไปยังกระบวนการทั้งหมดในกลุ่มกระบวนการเบื้องหน้า
กลุ่มกระบวนการคืออะไร? ความเข้าใจของฉันคือ:
- กระบวนการในไพพ์เดียวกัน
x | y | z
อยู่ในกลุ่มกระบวนการเดียวกัน - กระบวนการที่คุณเริ่มต้นบนเชลล์ไลน์เดียวกัน (
x && y && z
) อยู่ในกลุ่มกระบวนการเดียวกัน - กระบวนการลูกเป็นค่าเริ่มต้นในกลุ่มกระบวนการเดียวกัน เว้นแต่คุณจะตัดสินใจเป็นอย่างอื่นอย่างชัดเจน
ฉันไม่รู้สิ่งนี้ส่วนใหญ่ (ฉันไม่รู้ว่ากระบวนการมี ID เซสชัน!) ดังนั้นนี่เป็นเรื่องที่ต้องสนใจมาก ฉันพยายามวาดไดอะแกรมศิลปะ ASCII คร่าวๆ ของสถานการณ์
(maybe) terminal --- session --- process group --- process | |- process | |- process |- process group | |- process group
ดังนั้นเมื่อเรากด Ctrl+C ในเทอร์มินัล นี่คือสิ่งที่ฉันคิดว่าเกิดขึ้น:
-
\x04
ถูกเขียนไปยัง “pseudotermimal master” ของเทอร์มินัล - Linux ค้นหา เซสชัน สำหรับเทอร์มินัลนั้น (ถ้ามี)
- Linux ค้นหา กลุ่มกระบวนการเบื้องหน้า สำหรับเซสชันนั้น
- Linux ส่ง
SIGINT
หากเราไม่สร้างเซสชันใหม่สำหรับกระบวนการทุบตีใหม่ของเรา เทอร์มินัลเทียมใหม่ของเราจะ ไม่มี เซสชันที่เกี่ยวข้อง ดังนั้นจึงไม่มีอะไรเกิดขึ้นเมื่อเรากด Ctrl+C
แต่ถ้าเราสร้างเซสชันใหม่ pseudoterminal ใหม่จะมีเซสชันใหม่ที่เกี่ยวข้องด้วย
วิธีรับรายการเซสชันทั้งหมดของคุณ
นอกจากนี้ หากคุณต้องการรับรายการเซสชันทั้งหมดบนเครื่อง Linux ของคุณ โดยจัดกลุ่มตามเซสชัน คุณสามารถเรียกใช้:
$ ps -eo user,pid,pgid,sess,cmd | sort -k3
ซึ่งรวมถึง PID, ID กลุ่มกระบวนการ และ ID เซสชัน ตัวอย่างของผลลัพธ์ ต่อไปนี้คือสองกระบวนการในไปป์ไลน์:
bork 58080 58080 57922 ps -eo user,pid,pgid,sess,cmd bork 58081 58080 57922 sort -k3
คุณจะเห็นว่าพวกเขาแชร์ ID กลุ่มกระบวนการและ ID เซสชันเดียวกัน แต่แน่นอนว่าพวกเขามี PID ต่างกัน
นั่นเป็นจำนวนมาก แต่นั่นคือทั้งหมดที่เราจะพูดเกี่ยวกับเซสชันและกลุ่มกระบวนการในโพสต์นี้ ไปกันต่อเถอะ!
ขั้นตอนที่ 3: กำหนดขนาดหน้าต่าง
ต้องบอกเทอร์มินอลว่าใหญ่แค่ไหน!
อีกครั้งฉันเพิ่งคัดลอกมาจาก creack/pty
ฉันตัดสินใจฮาร์ดโค้ดขนาดเป็น 80×24
Setsize(tty, &Winsize{ Cols: 80, Rows: 24, })
เช่นเดียวกับการรับชื่อไฟล์ pts ของเทอร์มินัลและปลดล็อก การตั้งค่าขนาดเป็นเพียงการเรียกระบบ ioctl
หนึ่งครั้ง:
func Setsize(t *os.File, ws *Winsize) { ioctl(t.Fd(), syscall.TIOCSWINSZ, uintptr(unsafe.Pointer(ws))) }
ค่อนข้างง่าย! เราสามารถทำอะไรที่ฉลาดกว่านี้และได้ขนาดหน้าต่างจริง แต่ฉันขี้เกียจเกินไป
ขั้นตอนที่ 4: คัดลอกข้อมูลระหว่างการเชื่อมต่อ TCP และ pseudoterminal
เพื่อเป็นการเตือนความจำ ขั้นตอนคร่าวๆ ของเราในการตั้งค่าเซิร์ฟเวอร์การเข้าสู่ระบบระยะไกลนี้คือ:
- สร้าง pseudoterminal ให้ลูกค้าใช้
- เริ่มกระบวนการ
bash
shell - เชื่อมต่อ
bash
กับ pseudoterminal - คัดลอกข้อมูลไปมาอย่างต่อเนื่องระหว่างการเชื่อมต่อ TCP และ pseudoterminal
เราได้ทำ 1, 2 และ 3 แล้ว ตอนนี้เราแค่ต้องการส่งข้อมูลระหว่างการเชื่อมต่อ TCP และ pseudoterminal
มีการเรียก io.Copy
สองครั้ง การโทรหนึ่งครั้งเพื่อคัดลอกอินพุต จาก การเชื่อมต่อ tcp และอีกรายการหนึ่งเพื่อคัดลอกเอาต์พุต ไปยัง การเชื่อมต่อ TCP นี่คือลักษณะของรหัส:
go func() { io.Copy(pty, conn) }() io.Copy(conn, pty)
อันแรกอยู่ใน goroutine เพื่อให้ทั้งคู่วิ่งขนานกัน
ค่อนข้างง่าย!
ขั้นตอนที่ 5: ออกเมื่อเราทำเสร็จแล้ว
ฉันยังเพิ่มโค้ดเล็กน้อยเพื่อปิดการเชื่อมต่อ TCP เมื่อคำสั่งออก
go func() { cmd.Wait() conn.Close() }()
และนั่นคือทั้งหมดสำหรับเซิร์ฟเวอร์! คุณสามารถดูรหัส Go ทั้งหมดได้ที่นี่: server.go
ถัดไป: เขียนลูกค้า
ต่อไปเราต้องเขียนลูกค้า นี่เป็นมากกว่าเซิร์ฟเวอร์เพราะเราไม่ต้องตั้งค่าเทอร์มินัลมากนัก มีเพียง 3 ขั้นตอน:
- ใส่เทอร์มินัลลงในโหมดดิบ
- คัดลอก stdin/stdout ไปยังการเชื่อมต่อ TCP
- รีเซ็ตเทอร์มินัล
ไคลเอ็นต์ขั้นตอนที่ 1: วางเทอร์มินัลลงในโหมด “ดิบ”
เราจำเป็นต้องตั้งค่าเทอร์มินัลไคลเอ็นต์ให้อยู่ในโหมด “ดิบ” เพื่อให้ทุกครั้งที่คุณกดปุ่ม คีย์จะถูกส่งไปยังการเชื่อมต่อ TCP ทันที หากเราไม่ทำเช่นนี้ ทุกอย่างจะถูกส่งเมื่อคุณกด Enter
“โหมด Raw” ไม่ใช่สิ่งเดียว แต่เป็นแฟล็กจำนวนมากที่คุณต้องการปิด มีบทช่วยสอนที่ดีที่อธิบายแฟล็กทั้งหมดที่เราต้องปิดที่เรียกว่า Entering raw mode
เช่นเดียวกับทุกอย่างที่มีเทอร์มินัล สิ่งนี้ต้องการการเรียกระบบ ioctl
ในกรณีนี้ เราจะรับการตั้งค่าปัจจุบันของเทอร์มินัล แก้ไข และบันทึกการตั้งค่าเก่าเพื่อให้เราสามารถกู้คืนได้ในภายหลัง
ฉันหาวิธีทำสิ่งนี้ใน Go โดยไปที่ https://grep.app แล้วพิมพ์ syscall.TCSETS
เพื่อค้นหาโค้ด Go อื่นที่ทำสิ่งเดียวกัน
func MakeRaw(fd uintptr) syscall.Termios { // from https://github.com/getlantern/lantern/blob/devel/archive/src/golang.org/x/crypto/ssh/terminal/util.go var oldState syscall.Termios ioctl(fd, syscall.TCGETS, uintptr(unsafe.Pointer(&oldState))) newState := oldState newState.Iflag &^= syscall.ISTRIP | syscall.INLCR | syscall.ICRNL | syscall.IGNCR | syscall.IXON | syscall.IXOFF newState.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG ioctl(fd, syscall.TCSETS, uintptr(unsafe.Pointer(&newState))) return oldState }
ไคลเอ็นต์ขั้นตอนที่ 2: คัดลอก stdin/stdout ไปยังการเชื่อมต่อ TCP
นี้เหมือนกับสิ่งที่เราทำกับเซิร์ฟเวอร์ มันเป็นรหัสที่น้อยมาก:
go func() { io.Copy(conn, os.Stdin) }() io.Copy(os.Stdout, conn)
ไคลเอ็นต์ขั้นตอนที่ 3: คืนค่าสถานะของเทอร์มินัล
เราสามารถใส่เทอร์มินัลกลับเข้าสู่โหมดที่เริ่มต้นในลักษณะนี้ (อีก ioctl
!):
func Restore(fd uintptr, oldState syscall.Termios) { ioctl(fd, syscall.TCSETS, uintptr(unsafe.Pointer(&oldState))) }
เราทำได้!
เราได้เขียนเซิร์ฟเวอร์การเข้าสู่ระบบระยะไกลขนาดเล็กที่ช่วยให้ทุกคนเข้าสู่ระบบได้! ไชโย!
เห็นได้ชัดว่าสิ่งนี้ไม่มีความปลอดภัย ดังนั้นฉันจะไม่พูดถึงแง่มุมนั้น
มันทำงานบนอินเทอร์เน็ตสาธารณะ! คุณสามารถลอง!
ประมาณสัปดาห์หน้า ฉันจะสาธิตสิ่งนี้บนอินเทอร์เน็ตที่ tetris.jvns.ca
มันรัน tetris แทนที่จะเป็นเชลล์เพราะฉันต้องการหลีกเลี่ยงการละเมิด แต่ถ้าคุณต้องการลองใช้กับเชลล์ คุณสามารถรันบนคอมพิวเตอร์ของคุณเองได้ 🙂
หากคุณต้องการทดลองใช้ คุณสามารถใช้ netcat
เป็นไคลเอนต์แทนโปรแกรมไคลเอนต์ Go แบบกำหนดเองที่เราเขียน เนื่องจากการคัดลอกข้อมูลไปยัง/จากการเชื่อมต่อ TCP เป็นสิ่งที่ netcat ทำ นี่คือวิธี:
stty raw -echo && nc tetris.jvns.ca 7777 && stty sane
นี้จะช่วยให้คุณเล่นเกมเทตริสเทอร์มินัลที่เรียกว่า tint
คุณยังสามารถใช้ โปรแกรม client.go และเรียกใช้ go run client.go tetris.jvns.ca 7777
นี่ไม่ใช่โปรโตคอลที่ดี
โปรโตคอลนี้ที่เราเพิ่งคัดลอกไบต์จากการเชื่อมต่อ TCP ไปยังเทอร์มินัลและไม่มีอะไรอื่นไม่ดีเพราะไม่อนุญาตให้เราส่งข้อมูล เช่น เทอร์มินัลหรือขนาดหน้าต่างจริงของเทอร์มินัล
ฉันคิดเกี่ยวกับการใช้โปรโตคอลของ telnet เพื่อให้เราสามารถใช้ telnet เป็นไคลเอนต์ได้ แต่ฉันไม่รู้สึกว่าต้องการค้นหาวิธีการทำงานของ telnet ดังนั้นฉันจึงไม่ได้ทำ (เซิร์ฟเวอร์ 30% ใช้งานได้กับ telnet เหมือนเดิม แต่มีหลายสิ่งหลายอย่างที่พัง ฉันไม่ค่อยรู้สาเหตุ และฉันไม่รู้สึกว่าคิดออก)
มันจะทำให้เทอร์มินัลของคุณเลอะเล็กน้อย
คำเตือน: การใช้เซิร์ฟเวอร์นี้เพื่อเล่น tetris อาจทำให้เทอร์มินัลของคุณเสียหายเล็กน้อย เพราะมันกำหนดขนาดหน้าต่างเป็น 80×24 เพื่อแก้ไขว่าฉันเพิ่งปิดแท็บเทอร์มินัลหลังจากรันคำสั่งนั้น
หากเราต้องการแก้ไขปัญหานี้จริง เราจะต้องคืนค่าขนาดหน้าต่างหลังจากที่เราทำเสร็จแล้ว แต่เราจำเป็นต้องมีโปรโตคอลจริงมากกว่า “แค่คัดลอกไบต์ไปมาด้วย TCP สุ่มสี่สุ่มห้า” และฉันไม่ได้ ไม่รู้สึกอยากทำอย่างนั้น
นอกจากนี้ บางครั้งอาจใช้เวลาสักครู่ในการยกเลิกการเชื่อมต่อหลังจากโปรแกรมออกด้วยเหตุผลบางอย่าง ฉันไม่แน่ใจว่าทำไมถึงเป็นเช่นนั้น
โครงการเล็กๆ อื่นๆ
นั่นคือทั้งหมด! มีการใช้งานของเล่นที่คล้ายกันอื่น ๆ สองสามโปรแกรมที่ฉันเขียนไว้ที่นี่: