หนังสือ “Linux API คู่มือฉบับสมบูรณ์"


หนังสือ “Linux API คู่มือฉบับสมบูรณ์"

สวัสดีตอนบ่าย ฉันขอนำเสนอหนังสือ "Linux API" ให้กับคุณ คู่มือฉบับสมบูรณ์" (แปลหนังสือ อินเทอร์เฟซการเขียนโปรแกรม Linux). สามารถสั่งซื้อได้จากเว็บไซต์ของผู้จัดพิมพ์และหากคุณใช้รหัสส่งเสริมการขาย LinuxAPI คุณจะได้รับส่วนลด 30%

ข้อความที่ตัดตอนมาจากหนังสือเพื่อการอ้างอิง:

ซ็อกเก็ต: สถาปัตยกรรมเซิร์ฟเวอร์

ในบทนี้ เราจะพูดถึงพื้นฐานของการออกแบบเซิร์ฟเวอร์แบบวนซ้ำและเซิร์ฟเวอร์แบบขนาน และยังดูที่ daemon พิเศษที่เรียกว่า inetd ซึ่งช่วยให้สร้างแอปพลิเคชันเซิร์ฟเวอร์อินเทอร์เน็ตได้ง่ายขึ้น

เซิร์ฟเวอร์แบบวนซ้ำและแบบขนาน

มีสถาปัตยกรรมเซิร์ฟเวอร์เครือข่ายที่ใช้ซ็อกเก็ตทั่วไปสองแบบ:

  • วนซ้ำ: เซิร์ฟเวอร์ให้บริการลูกค้าทีละราย ขั้นแรกให้ประมวลผลคำขอ (หรือหลายคำขอ) จากไคลเอนต์หนึ่งรายจากนั้นจึงย้ายไปยังไคลเอนต์ถัดไป

  • ขนาน: เซิร์ฟเวอร์ได้รับการออกแบบมาเพื่อให้บริการลูกค้าหลายรายพร้อมกัน

ตัวอย่างของเซิร์ฟเวอร์วนซ้ำตามคิว FIFO ได้ถูกนำเสนอแล้วในส่วน 44.8

โดยปกติเซิร์ฟเวอร์วนซ้ำจะเหมาะสมเฉพาะในสถานการณ์ที่สามารถประมวลผลคำขอของไคลเอนต์ได้อย่างรวดเร็ว เนื่องจากไคลเอนต์แต่ละรายถูกบังคับให้รอจนกว่าไคลเอนต์อื่น ๆ ที่อยู่ตรงหน้าจะได้รับบริการ กรณีการใช้งานทั่วไปสำหรับแนวทางนี้คือการแลกเปลี่ยนคำขอและการตอบกลับเดี่ยวระหว่างไคลเอนต์และเซิร์ฟเวอร์

เซิร์ฟเวอร์คู่ขนานมีความเหมาะสมในกรณีที่แต่ละคำขอใช้เวลาในการประมวลผลเป็นจำนวนมาก หรือในกรณีที่ไคลเอ็นต์และเซิร์ฟเวอร์มีส่วนร่วมในการแลกเปลี่ยนข้อความที่ยาวนาน ในบทนี้ เราจะเน้นไปที่วิธีดั้งเดิม (และง่ายที่สุด) ในการออกแบบเซิร์ฟเวอร์แบบขนานเป็นหลัก ซึ่งก็คือการสร้างกระบวนการลูกแยกต่างหากสำหรับไคลเอนต์ใหม่แต่ละราย กระบวนการนี้จะดำเนินการทั้งหมดเพื่อให้บริการลูกค้าและสิ้นสุดลง เนื่องจากแต่ละกระบวนการทำงานแยกจากกัน จึงเป็นไปได้ที่จะให้บริการลูกค้าหลายรายพร้อมกันได้ งานหลักของกระบวนการเซิร์ฟเวอร์หลัก (พาเรนต์) คือการสร้างลูกแยกต่างหากสำหรับไคลเอนต์ใหม่แต่ละราย (หรืออีกทางหนึ่ง สามารถสร้างเธรดการดำเนินการแทนกระบวนการได้)

ในส่วนต่อไปนี้ เราจะดูตัวอย่างของเซิร์ฟเวอร์ซ็อกเก็ตโดเมนอินเทอร์เน็ตแบบวนซ้ำและแบบขนาน เซิร์ฟเวอร์ทั้งสองนี้ใช้บริการ echo เวอร์ชันที่เรียบง่าย (RFC 862) ซึ่งจะส่งคืนสำเนาของข้อความใด ๆ ที่ไคลเอนต์ส่งถึงเซิร์ฟเวอร์

เสียงสะท้อนของเซิร์ฟเวอร์ UDP ซ้ำ

ในหัวข้อนี้และหัวข้อถัดไป เราจะแนะนำเซิร์ฟเวอร์สำหรับบริการเสียงก้อง พร้อมใช้งานบนพอร์ตหมายเลข 7 และทำงานได้ทั้ง UDP และ TCP (พอร์ตนี้ถูกสงวนไว้ ดังนั้นเซิร์ฟเวอร์ echo ต้องทำงานด้วยสิทธิ์ของผู้ดูแลระบบ)

เซิร์ฟเวอร์ echo UDP จะอ่านดาตาแกรมอย่างต่อเนื่องและส่งสำเนาข้อมูลเหล่านั้นไปยังผู้ส่ง เนื่องจากเซิร์ฟเวอร์ต้องการประมวลผลข้อความครั้งละหนึ่งข้อความเท่านั้น สถาปัตยกรรมแบบวนซ้ำก็เพียงพอแล้ว ไฟล์ส่วนหัวสำหรับเซิร์ฟเวอร์แสดงอยู่ในรายการ 56.1

รายการ 56.1. ไฟล์ส่วนหัวสำหรับโปรแกรม id_echo_sv.c และ id_echo_cl.c

#include "inet_sockets.h" /* ประกาศฟังก์ชันซ็อกเก็ตของเรา */
#รวม "tlpi_hdr.h"

#define SERVICE "echo" /* ชื่อบริการ UDP */

#define BUF_SIZE 500 /* ขนาดสูงสุดของดาตาแกรมนั้น
ลูกค้าและเซิร์ฟเวอร์สามารถอ่านได้ */
_____________________________________________________________________ซ็อกเก็ต/id_echo.h

รายการ 56.2 แสดงการใช้งานเซิร์ฟเวอร์ ประเด็นต่อไปนี้เป็นที่น่าสังเกต:

  • เพื่อให้เซิร์ฟเวอร์เข้าสู่โหมด daemon เราใช้ฟังก์ชันกลายเป็นDaemon() จากส่วนที่ 37.2

  • เพื่อให้โปรแกรมมีขนาดกะทัดรัดมากขึ้น เราใช้ไลบรารีสำหรับการทำงานกับซ็อกเก็ตโดเมนอินเทอร์เน็ต พัฒนาในส่วน 55.12

  • หากเซิร์ฟเวอร์ไม่สามารถตอบกลับไปยังไคลเอนต์ได้ เซิร์ฟเวอร์จะเขียนข้อความไปยังบันทึกโดยใช้การเรียก syslog()

ในแอปพลิเคชันจริง เราน่าจะกำหนดขีดจำกัดความถี่ในการบันทึกข้อความโดยใช้ syslog() วิธีนี้จะขจัดโอกาสที่ผู้โจมตีจะล้นบันทึกของระบบ นอกจากนี้ อย่าลืมว่าการเรียก syslog() แต่ละครั้งมีราคาค่อนข้างแพง เนื่องจากจะใช้ fsync() เป็นค่าเริ่มต้น

รายการ 56.2. เซิร์ฟเวอร์วนซ้ำที่ใช้บริการ UDP echo

_________________________________________________________________ซ็อกเก็ต/id_echo_sv.c
#รวม
#รวม "id_echo.h"
#รวม "become_daemon.h"

int
main(int argc, ถ่าน *argv[])
{
int sfd;
ssize_t numRead;
socklen_t เลน;
โครงสร้าง sockaddr_storage claddr;
ถ่าน buf [BUF_SIZE];
ถ่าน addrStr[IS_ADDR_STR_LEN];

ถ้า (กลายเป็นDaemon(0) == -1)
errExit("กลายเป็นปีศาจ");

sfd = inetBind (บริการ, SOCK_DGRAM, NULL);
ถ้า (sfd == -1) {
syslog(LOG_ERR, "ไม่สามารถสร้างซ็อกเก็ตเซิร์ฟเวอร์ (%s)",
strerror(เออร์โน));
ทางออก (EXIT_FAILURE);

/* รับดาต้าแกรมและส่งคืนสำเนาให้ผู้ส่ง */
}
สำหรับ (;;) {
len = ขนาดของ (struct sockaddr_storage);
numRead = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *) &claddr, &len);

ถ้า (numRead == -1)
errExit("recvfrom");
ถ้า (sendto (sfd, buf, numRead, 0, (struct sockaddr *) &claddr, len)
!= numRead)
syslog(LOG_WARNING, "เกิดข้อผิดพลาดในการตอบสนองต่อ %s (%s)",
inetAddressStr((struct sockaddr *) &claddr, len,
addrStr, IS_ADDR_STR_LEN),
strerror(เออร์โน));
}
}
_________________________________________________________________ซ็อกเก็ต/id_echo_sv.c

เพื่อทดสอบการทำงานของเซิร์ฟเวอร์ เราใช้โปรแกรมจากรายการ 56.3 นอกจากนี้ยังใช้ไลบรารีสำหรับการทำงานกับซ็อกเก็ตโดเมนอินเทอร์เน็ตที่พัฒนาในส่วน 55.12 เนื่องจากอาร์กิวเมนต์บรรทัดคำสั่งแรก โปรแกรมไคลเอนต์ใช้ชื่อของโหนดเครือข่ายที่เซิร์ฟเวอร์ตั้งอยู่ ไคลเอนต์เข้าสู่ลูปโดยส่งแต่ละอาร์กิวเมนต์ที่เหลือไปยังเซิร์ฟเวอร์เป็นดาตาแกรมแยกกัน จากนั้นอ่านและพิมพ์ดาตาแกรมที่ได้รับจากเซิร์ฟเวอร์เพื่อตอบสนอง

รายการ 56.3. ไคลเอ็นต์สำหรับบริการ UDP echo

#รวม "id_echo.h"

int
main(int argc, ถ่าน *argv[])
{
int sfd, เจ;
size_t เลน;
ssize_t numRead;
ถ่าน buf [BUF_SIZE];

ถ้า (argc < 2 || strcmp(argv[1], "--help") == 0)
useErr("%s โฮสต์ msg...n", argv[0]);

/* สร้างที่อยู่เซิร์ฟเวอร์ตามอาร์กิวเมนต์บรรทัดคำสั่งแรก */
sfd = inetConnect (argv [1], บริการ, SOCK_DGRAM);
ถ้า (sfd == -1)
ร้ายแรง ("ไม่สามารถเชื่อมต่อกับซ็อกเก็ตเซิร์ฟเวอร์");

/* ส่งอาร์กิวเมนต์ที่เหลือไปยังเซิร์ฟเวอร์ในรูปแบบดาตาแกรมแยกกัน */
สำหรับ (j = 2; j <argc; j++) {
เลน = strlen(argv[j]);
ถ้า (เขียน (sfd, argv [j], len) != len)
ร้ายแรง ("เขียนบางส่วน/ล้มเหลว");

numRead = อ่าน (sfd, buf, BUF_SIZE);
ถ้า (numRead == -1)
errExit("อ่าน");
printf("[%ld ไบต์] %.*sn", (ยาว) numRead, (int) numRead, buf);
}
ทางออก(EXIT_SUCCESS);
}
_________________________________________________________________ซ็อกเก็ต/id_echo_cl.c

ด้านล่างนี้คือตัวอย่างของสิ่งที่เราจะเห็นเมื่อใช้งานเซิร์ฟเวอร์และอินสแตนซ์ไคลเอนต์สองรายการ:

$su // จำเป็นต้องมีสิทธิพิเศษในการผูกกับพอร์ตที่สงวนไว้
รหัสผ่าน:
# ./id_echo_sv // เซิร์ฟเวอร์เข้าสู่โหมดพื้นหลัง
# exit // สละสิทธิ์ผู้ดูแลระบบ
$ ./id_echo_cl localhost สวัสดีชาวโลก // ลูกค้ารายนี้ส่งสองดาตาแกรม
[5 ไบต์] สวัสดี // ลูกค้าแสดงการตอบกลับที่ได้รับจากเซิร์ฟเวอร์
[5 ไบต์] โลก
$ ./id_echo_cl localhost ลาก่อน // ลูกค้ารายนี้ส่งหนึ่งดาตาแกรม
[7 ไบต์] ลาก่อน

ฉันขอให้คุณอ่านที่น่าพอใจ)

ที่มา: linux.org.ru