Chương 56 · Sockets: Giới thiệu Chapter 56 · Sockets: Introduction
Socket: bộ máy chung để các ứng dụng trao đổi dữ liệu, trong cùng máy hay qua mạng Socket là gì, và vì sao hai chương trình nói chuyện được với nhau nhờ nó The socket: one apparatus for applications to exchange data, on one host or across a network
Kerrisk mở đầu nhóm chương về socket bằng một định nghĩa gọn: socket là một phương pháp IPC cho phép các ứng dụng trao đổi dữ liệu — hoặc trên cùng một máy, hoặc trên những máy khác nhau nối với nhau qua mạng.1 Hiện thực rộng rãi đầu tiên của sockets API xuất hiện cùng 4.2BSD năm 1983, rồi được chuyển sang gần như mọi hệ UNIX.1 Chương này đặt nền chung: miền giao tiếp (UNIX, IPv4, IPv6), kiểu socket (luồng vs datagram), cách tạo socket bằng socket() và gắn địa chỉ bằng bind(), cấu trúc địa chỉ tổng quát struct sockaddr, và chuỗi lời gọi client/server của stream socket lẫn datagram socket.1
Hãy coi socket như một chiếc điện thoại gắn vào mỗi chương trình. Muốn hai chương trình nói chuyện với nhau — dù ngồi chung một máy hay ở hai máy cách nhau nửa vòng Trái Đất — mỗi bên phải có một chiếc. Chương này dạy cách lắp chiếc điện thoại ấy, cách ghi số cho nó, và hai kiểu "gọi": gọi điện (giữ kết nối, nói qua nói lại) và gửi thư (mỗi lá một gói, không cần kết nối). Nhớ nhé: socket là chiếc điện thoại của chương trình.
Kerrisk opens the socket chapters with a tight definition: a socket is a method of IPC that lets applications exchange data — either on the same host, or on different hosts connected by a network.1 The first widespread implementation of the sockets API appeared with 4.2BSD in 1983, and was then ported to virtually every UNIX system.1 This chapter lays the shared groundwork: communication domains (UNIX, IPv4, IPv6), socket types (stream vs datagram), how to create a socket with socket() and bind an address with bind(), the generic struct sockaddr, and the client/server call sequences for both stream and datagram sockets.1
Vị trí trong sách: đây là chương dẫn nhập, không có mã ví dụ; các ví dụ ở miền UNIX và miền Internet được để dành cho các chương sau. Kerrisk vạch lộ trình: Chương 57 bàn socket miền UNIX (cùng máy); Chương 58 giới thiệu khái niệm mạng máy tính và giao thức TCP/IP; Chương 59 bàn socket miền Internet; Chương 60 bàn thiết kế server; Chương 61 bàn các chủ đề nâng cao. Sockets API cũng được đặc tả hình thức trong POSIX.1g — chuẩn được phê chuẩn năm 2000 sau khoảng một thập kỷ ở dạng dự thảo, rồi bị SUSv3 thay thế.1Where this sits in the book: it is an introductory chapter with no example code; code examples in the UNIX and Internet domains are deferred to later chapters. Kerrisk maps the route: Chapter 57 covers UNIX domain sockets (same host); Chapter 58 introduces computer-networking concepts and the TCP/IP protocols; Chapter 59 covers Internet domain sockets; Chapter 60 covers server design; Chapter 61 covers advanced topics. The sockets API is also formally specified in POSIX.1g — ratified in 2000 after roughly a decade as a draft standard, and later superseded by SUSv3.1
Luận đề của chương: mọi lập trình mạng trên UNIX đều quy về một bộ máy nhỏ và nhất quán — một socket() để tạo điểm cuối, một bind() để đặt tên, và hai mô hình trao đổi: stream (có kết nối, tin cậy, dòng byte, như cuộc gọi điện thoại) và datagram (không kết nối, giữ ranh giới thông điệp, không bảo đảm, như gửi thư). Nắm được bộ từ vựng chung này là nắm chìa khóa cho toàn bộ các chương socket về sau.1
Ý chính: dù là chương trình chat hay máy chủ web, tất cả đều dùng chung một bộ công cụ nhỏ để nói chuyện qua socket. Có hai kiểu nói: như gọi điện (giữ máy, nói qua lại) và như gửi thư (mỗi lần một gói). Nhớ nhé: hiểu socket, gọi điện và gửi thư là hiểu gốc của lập trình mạng.
The chapter's thesis: all UNIX network programming reduces to a small, consistent apparatus — a socket() to make an endpoint, a bind() to name it, and two exchange models: stream (connection-oriented, reliable, byte-stream, like a phone call) and datagram (connectionless, message-boundary-preserving, unreliable, like posting letters). Grasp this shared vocabulary and you hold the key to every later socket chapter.1
Cụm khái niệm 01 Concept cluster 01
Mỗi socket sống trong một miền giao tiếp, và có một trong hai kiểu Socket nói với ai (miền) và nói kiểu gì (kiểu socket) Every socket lives in a communication domain, and has one of two types
Mỗi socket tồn tại trong một miền giao tiếp (communication domain), và miền này quyết định hai điều: cách định danh một socket (tức định dạng "địa chỉ" của nó) và phạm vi giao tiếp (trong cùng máy hay giữa các máy nối qua mạng).1 Các hệ điều hành hiện đại hỗ trợ ít nhất ba miền: AF_UNIX (giao tiếp giữa các ứng dụng trên cùng máy, địa chỉ là một đường dẫn tệp), AF_INET (qua mạng IPv4, địa chỉ là IPv4 32-bit cộng số cổng 16-bit) và AF_INET6 (qua mạng IPv6, địa chỉ là IPv6 128-bit cộng số cổng 16-bit). Bảng 56-1 trong sách tóm tắt các miền này.13
Trước khi nói, chương trình phải chọn "kênh": nói trong cùng máy (miền UNIX, dùng tên tệp làm địa chỉ), hay nói qua mạng (miền IPv4/IPv6, dùng địa chỉ IP và số cổng). Sau đó chọn "kiểu nói": như gọi điện thoại, hay như gửi thư. Nhớ nhé: chọn kênh (miền) trước, rồi chọn cách nói (kiểu) sau.
Every socket exists in a communication domain, which determines two things: how a socket is identified (the format of its "address") and the range of communication (within one host, or between hosts connected by a network).1 Modern operating systems support at least three domains: AF_UNIX (communication between applications on the same host, address is a filesystem pathname), AF_INET (over an IPv4 network, address is a 32-bit IPv4 address plus a 16-bit port number), and AF_INET6 (over an IPv6 network, address is a 128-bit IPv6 address plus a 16-bit port). The book's Table 56-1 summarizes these domains.13
Định nghĩa. Có hai kiểu socket được mọi hiện thực cung cấp, dùng được ở cả miền UNIX lẫn miền Internet: stream (SOCK_STREAM) và datagram (SOCK_DGRAM). Bảng 56-2 tóm tắt ba tính chất phân biệt chúng: giao hàng tin cậy? giữ ranh giới thông điệp? có hướng kết nối? Stream trả lời Có–Không–Có; datagram trả lời Không–Có–Không.1
Nói đơn giản. Hai kiểu socket: kiểu "gọi điện" (stream) giữ kết nối, nói qua lại thành một dòng liên tục, bảo đảm tới nơi; kiểu "gửi thư" (datagram) mỗi lần một gói riêng, không cần kết nối, nhưng thư có thể lạc. Nhớ nhé: stream như gọi điện, datagram như gửi thư.
Definition. Every implementation provides two socket types, usable in both the UNIX and Internet domains: stream (SOCK_STREAM) and datagram (SOCK_DGRAM). Table 56-2 summarizes the three distinguishing properties: reliable delivery? message boundaries preserved? connection-oriented? Stream answers Yes–No–Yes; datagram answers No–Yes–No.1
Bảng 56-2 (tính chất kiểu socket)
Stream Datagram
tin cậy? Y N
giữ ranh giới? N Y
có kết nối? Y N
Stream: tin cậy · dòng byte · có kết nối
Datagram: có thể lạc · giữ ranh giới · không kết nối
Table 56-2 (socket type properties)
Stream Datagram
reliable? Y N
boundaries? N Y
connection? Y N
-
AF_UNIX / AF_INET / AF_INET6
Ba miền: cùng máy, IPv4, IPv6 Ba "kênh": trong máy, mạng IPv4, mạng IPv6 Three domains: same host, IPv4, IPv6
Miền AF_UNIX giao tiếp trong phạm vi nhân, giữa các ứng dụng trên cùng máy, dùng đường dẫn làm địa chỉ (cấu trúc
sockaddr_un). Miền AF_INET đi qua IPv4 giữa các máy nối mạng, địa chỉ gồm IPv4 32-bit cộng cổng 16-bit (sockaddr_in). Miền AF_INET6 đi qua IPv6, địa chỉ gồm IPv6 128-bit cộng cổng 16-bit (sockaddr_in6).110 Kerrisk lưu ý: dù IPv6 được thiết kế để kế vị IPv4, tại thời điểm viết sách IPv4 vẫn là giao thức phổ biến nhất.1Cùng máy thì nói qua "kênh UNIX", dùng một tên tệp làm địa chỉ. Khác máy thì nói qua mạng, dùng địa chỉ IP (kiểu IPv4 hoặc IPv6) kèm số cổng. Mỗi kênh có một kiểu cấu trúc địa chỉ riêng. Nhớ nhé: cùng máy dùng tên tệp, qua mạng dùng IP + cổng.
The AF_UNIX domain communicates within the kernel, between applications on the same host, using a pathname as the address (the
sockaddr_unstructure). The AF_INET domain goes over IPv4 between networked hosts, its address a 32-bit IPv4 address plus a 16-bit port (sockaddr_in). The AF_INET6 domain goes over IPv6, its address a 128-bit IPv6 address plus a 16-bit port (sockaddr_in6).110 Kerrisk notes: although IPv6 is designed as the successor to IPv4, at the time of writing IPv4 was still the most widely used protocol.1Bạn gặp nó ở đâuWhere you meet it Đối số đầu tiên củasocket()chính là miền: viếtAF_UNIX,AF_INEThayAF_INET6là bạn đang chọn kênh giao tiếp.2 Ngay khi gọisocket(), đối số đầu tiên là chọn kênh: UNIX, IPv4 hay IPv6. The first argument tosocket()is the domain: writingAF_UNIX,AF_INET, orAF_INET6is how you pick the channel.2AF_ hay PF_?AF_ or PF_?
Trong một số mã ta thấy hằng như
PF_UNIXthay vìAF_UNIX:AFlà "address family",PFlà "protocol family". Ban đầu người ta tưởng một họ giao thức có thể gánh nhiều họ địa chỉ, nhưng thực tế điều đó chưa từng xảy ra, nên mọi hiện thực đều định nghĩa các hằngPF_đồng nghĩa vớiAF_tương ứng. SUSv3 chỉ đặc tả các hằngAF_; sách luôn dùngAF_.1Some code uses constants likePF_UNIXinstead ofAF_UNIX:AFis "address family,"PFis "protocol family." It was originally imagined that one protocol family might support several address families, but in practice that never happened, so every implementation defines thePF_constants as synonyms for the correspondingAF_ones. SUSv3 specifies only theAF_constants; the book always usesAF_.1 -
SOCK_STREAM
Stream: tin cậy, hai chiều, dòng byte Stream: như một cuộc gọi điện thoại Stream: reliable, bidirectional, byte-stream
Stream socket (
SOCK_STREAM) cung cấp một kênh truyền tin cậy, hai chiều, dạng dòng byte. Tin cậy nghĩa là dữ liệu hoặc đến nơi nguyên vẹn đúng như lúc gửi (giả định mạng và bên nhận không sập), hoặc ta được báo về một thất bại có thể đã xảy ra. Hai chiều nghĩa là dữ liệu đi được theo cả hai hướng. Dòng byte nghĩa là — như với pipe — không có khái niệm ranh giới thông điệp.12 Stream socket hoạt động theo cặp đã nối, nên gọi là hướng kết nối (connection-oriented); một stream socket chỉ nối được với đúng một socket đối phương (peer).1Stream giống một cuộc gọi điện thoại: hai bên phải nối máy trước, rồi nói qua nói lại thành một dòng liên tục, và lời nói bảo đảm tới tai người kia đúng thứ tự. Nhưng vì là một dòng liền mạch, nó không tự chia câu — bên nhận phải tự biết đâu là hết một "câu". Nhớ nhé: stream nối máy trước, rồi chảy thành một dòng không ngắt.
A stream socket (
SOCK_STREAM) provides a reliable, bidirectional, byte-stream channel. Reliable means data either arrives intact exactly as sent (assuming neither the network nor the receiver crashes) or we are notified of a probable transmission failure. Bidirectional means data flows in either direction. Byte-stream means — as with pipes — there is no concept of message boundaries.12 Stream sockets operate in connected pairs, hence connection-oriented; a stream socket can be connected to only one peer.1Bạn gặp nó ở đâuWhere you meet it Ở miền Internet, stream socket (thường) dùng giao thức TCP — nên sách hay gọi tắt là "TCP socket".1 Trên Internet, stream socket thường chạy TCP — tên quen thuộc là "TCP socket". In the Internet domain, stream sockets (usually) use the TCP protocol — hence the shorthand "TCP socket."1 -
SOCK_DGRAM
Datagram: giữ ranh giới, nhưng không bảo đảm Datagram: như gửi thư qua bưu điện Datagram: boundaries kept, but not reliable
Datagram socket (
SOCK_DGRAM) trao đổi dữ liệu dưới dạng các thông điệp gọi là datagram. Với datagram socket, ranh giới thông điệp được giữ nguyên, nhưng việc truyền không tin cậy: thông điệp có thể đến sai thứ tự, bị nhân bản, hoặc không đến.1 Datagram socket là một ví dụ của khái niệm tổng quát hơn — socket không kết nối (connectionless): khác stream socket, một datagram socket không cần nối với socket khác mới dùng được.1Datagram giống gửi thư: mỗi lá thư là một gói riêng, bỏ vào thùng là đi, không cần "nối máy" trước. Bưu điện thường giao đúng, nhưng không hứa: thư có thể tới trễ, tới lộn thứ tự, hay lạc mất. Cái hay là mỗi lá thư vẫn nguyên vẹn thành một gói, không bị trộn lẫn. Nhớ nhé: datagram mỗi gói một lá thư, có thể lạc nhưng không bị trộn.
A datagram socket (
SOCK_DGRAM) exchanges data as messages called datagrams. With datagram sockets, message boundaries are preserved, but transmission is unreliable: messages may arrive out of order, be duplicated, or not arrive at all.1 A datagram socket is an example of the more general notion of a connectionless socket: unlike a stream socket, it need not be connected to another socket in order to be used.1Bạn gặp nó ở đâuWhere you meet it Ở miền Internet, datagram socket dùng giao thức UDP — nên sách hay gọi tắt là "UDP socket".1 Trên Internet, datagram socket chạy UDP — tên quen thuộc là "UDP socket". In the Internet domain, datagram sockets use the UDP protocol — hence the shorthand "UDP socket."1Một ngoại lệ về kết nốiA connection exception
Dù "không kết nối", datagram socket vẫn gọi
connect()được — việc này ghi nhớ một địa chỉ làm peer, đổi lại ta dùng được các lời gọi I/O đơn giản hơn. Phần này được bàn kỹ ở cụm 04.17Although "connectionless," a datagram socket can still callconnect()— this records an address as its peer, in return for which simpler I/O calls become available. This is covered in cluster 04.17Còn hơn cả bưu điệnWorse than the post
Datagram thêm một khả năng mà bưu điện không có: vì giao thức mạng bên dưới đôi khi gửi lại một gói, nên cùng một datagram có thể đến nhiều hơn một lần. Ứng dụng phải sẵn sàng cho cả mất, lặp lẫn đảo thứ tự.1Datagrams add a possibility the post lacks: because the underlying protocols may retransmit a packet, the same datagram can arrive more than once. An application must be ready for loss, duplication, and reordering alike.1
Cụm khái niệm 02 Concept cluster 02
Tạo một socket bằng socket(), gắn địa chỉ bằng bind(), qua cấu trúc sockaddr tổng quát Làm ra socket, ghi địa chỉ cho nó, và một cấu trúc địa chỉ dùng chung Create with socket(), name with bind(), via the generic sockaddr
Một socket được tạo bằng lời gọi socket(domain, type, protocol), trả về một bộ mô tả tệp (file descriptor) dùng để tham chiếu socket trong các lời gọi về sau. Đối số domain chọn miền giao tiếp; type thường là SOCK_STREAM hoặc SOCK_DGRAM; còn protocol luôn là 0 với các kiểu socket được mô tả trong sách.12 Thông thường, server dùng bind() để gắn socket của nó vào một địa chỉ cố định đã biết trước (well-known address), để client biết nơi tìm tới.14
Gọi socket() là bạn "lắp" một chiếc điện thoại; nó trả về một con số tay cầm (file descriptor) để lần sau gọi tới đúng chiếc đó. Rồi server gọi bind() để "ghi số" cho chiếc máy của mình — một số cố định mà ai cũng biết, để khách gọi đến. Nhớ nhé: socket() lắp máy, bind() ghi số.
A socket is created with socket(domain, type, protocol), returning a file descriptor used to refer to the socket in later calls. The domain argument chooses the communication domain; type is usually SOCK_STREAM or SOCK_DGRAM; and protocol is always 0 for the socket types described in the book.12 Typically, a server uses bind() to bind its socket to a well-known address — a fixed address known in advance — so clients can locate it.14
Định nghĩa. Mỗi miền dùng một định dạng địa chỉ khác nhau, nên mỗi miền có một kiểu cấu trúc riêng. Nhưng những lời gọi như bind() phải nhận được địa chỉ thuộc bất kỳ miền nào, nên sockets API định nghĩa một cấu trúc địa chỉ tổng quát — struct sockaddr — làm khuôn chung để ép (cast) mọi cấu trúc địa chỉ cụ thể về một kiểu duy nhất khi truyền vào lời gọi.19
Nói đơn giản. Mỗi kênh ghi địa chỉ một kiểu khác nhau (tên tệp, hay IP + cổng). Nhưng các lệnh như bind() chỉ có một ô nhận địa chỉ, nên cần một "phong bì chung" để nhét mọi loại địa chỉ vào. Phong bì chung đó là struct sockaddr. Nhớ nhé: sockaddr là phong bì chung cho mọi kiểu địa chỉ.
Definition. Each domain uses a different address format, so each has its own structure type. But calls like bind() must accept an address of any domain, so the sockets API defines a generic address structure — struct sockaddr — as a common template for casting any domain-specific address structure to a single type when passing it to socket calls.19
#include <sys/socket.h>
int fd = socket(domain, type, 0);
int bind(int sockfd,
const struct sockaddr *addr,
socklen_t addrlen);
/* trả 0 khi OK, −1 khi lỗi */
fd = socket(miền, kiểu, 0);
bind(fd, &địa_chỉ, cỡ_địa_chỉ);
/* OK --> 0, lỗi --> −1 */
#include <sys/socket.h>
int fd = socket(domain, type, 0);
int bind(int sockfd,
const struct sockaddr *addr,
socklen_t addrlen);
/* returns 0 on OK, −1 on error */
-
socket()
socket() trả về một file descriptor, và protocol luôn là 0 socket() cho ra một "tay cầm", đối số thứ ba luôn là 0 socket() returns a descriptor, and protocol is always 0
Vì
socket()trả về một file descriptor, một socket có thể được dùng với nhiều lời gọi I/O quen thuộc nhưread(),write()vàclose(), đúng như một tệp thường. Đối sốprotocolđể là 0 cho mọi kiểu trong sách; giá trị khác 0 chỉ dùng với vài kiểu socket đặc biệt không bàn tới (ví dụIPPROTO_RAWcho raw socket,SOCK_RAW).12Chiếc "tay cầm" mà
socket()trả về dùng y như tay cầm của một tệp: đọc, ghi, đóng đều được. Và trong hầu hết trường hợp, đối số thứ ba cứ để là 0. Nhớ nhé: socket dùng như tệp, protocol để 0.Because
socket()returns a file descriptor, a socket can be used with familiar I/O calls likeread(),write(), andclose(), just like an ordinary file. Theprotocolargument is 0 for all types in the book; nonzero values are used only with a few special socket types not covered (e.g.IPPROTO_RAWfor raw sockets,SOCK_RAW).12Bạn gặp nó ở đâuWhere you meet it Mọi chương trình mạng đều mở đầu bằngsocket()— đây là bước đầu tiên, dù bạn là client hay server.2 Chương trình mạng nào cũng bắt đầu bằng một lời gọisocket(). Every network program begins withsocket()— the first step, whether you are a client or a server.2Hai cờ từ nhân 2.6.27Two flags since kernel 2.6.27
Từ nhân 2.6.27, Linux cho phép OR thêm hai cờ phi chuẩn vào đối số
type:SOCK_CLOEXECbật cờ close-on-exec (FD_CLOEXEC) cho file descriptor mới, vàSOCK_NONBLOCKbật cờO_NONBLOCKđể I/O sau này không chặn — tiết kiệm các lời gọifcntl()phụ.12Since kernel 2.6.27, Linux lets two nonstandard flags be ORed into thetypeargument:SOCK_CLOEXECenables close-on-exec (FD_CLOEXEC) on the new descriptor, andSOCK_NONBLOCKsetsO_NONBLOCKso future I/O is nonblocking — saving extrafcntl()calls.12Một chi tiết của LinuxA Linux detail
Trên hầu hết kiến trúc Linux (trừ Alpha và IA-64), mọi lời gọi hệ thống về socket thực ra là các hàm thư viện được ghép qua một lời gọi duy nhất,
socketcall()— một di sản của cách Linux hiện thực socket thuở đầu. Sách vẫn gọi chúng là "system call".1On most Linux architectures (except Alpha and IA-64), all socket system calls are actually library functions multiplexed through a single call,socketcall()— an artifact of Linux's original socket implementation. The book still calls them "system calls."1 -
bind()
bind() gắn socket vào một địa chỉ đã biết trước bind() ghi "số" cố định cho socket của server bind() attaches a socket to a well-known address
bind(sockfd, addr, addrlen)gắn socketsockfd(lấy từsocket()) vào địa chỉ doaddrtrỏ tới; kiểu cấu trúc củaaddrtùy miền. Đối sốaddrlencho biết kích thước cấu trúc địa chỉ ấy, và có kiểusocklen_t— một kiểu số nguyên do SUSv3 quy định.149 Thông thường server gắn socket của nó vào một địa chỉ đã biết trước để client dễ tìm.1Nếu socket là chiếc điện thoại, thì
bind()là lúc bạn đăng ký một số máy cố định cho nó. Khách muốn gọi tới server phải biết số này trước. Vì vậy server luôn dùng một số "ai cũng biết". Nhớ nhé: bind() là đăng ký số cố định cho server.bind(sockfd, addr, addrlen)attaches socketsockfd(fromsocket()) to the addressaddrpoints to; the structure type ofaddrdepends on the domain. Theaddrlenargument gives that structure's size and has typesocklen_t— an integer type specified by SUSv3.149 Typically a server binds its socket to a well-known address so clients can find it.1Bạn gặp nó ở đâuWhere you meet it Không bắt buộc phảibind()vào địa chỉ đã biết: với socket miền Internet, server có thể bỏbind()và gọi thẳnglisten(), để nhân tự chọn một cổng tạm (ephemeral port), rồi dùnggetsockname()lấy lại địa chỉ.1 Không phải lúc nào cũng cần ghi số trước — đôi khi để hệ thống tự cấp một số tạm rồi hỏi lại. Binding to a well-known address is not mandatory: an Internet-domain server may skipbind()and calllisten()directly, letting the kernel pick an ephemeral port, then usegetsockname()to retrieve the address.1 -
struct sockaddr
Cấu trúc địa chỉ tổng quát: một khuôn cho mọi miền Một "phong bì chung" cho mọi kiểu địa chỉ The generic address structure: one template for all
struct sockaddrđiển hình gồm hai trường:sa_family(một hằngAF_*, kiểusa_family_tdo SUSv3 quy định) vàsa_data(mảng byte chứa địa chỉ, kích thước thay đổi theo miền). Mọi cấu trúc địa chỉ cụ thể đều bắt đầu bằng một trường family tương ứng — và chính giá trị trong trường này đủ để nhân xác định kích thước và định dạng của phần địa chỉ còn lại.19Phong bì chung này có hai phần: một ô ghi "đây là loại địa chỉ gì" (family), và một ô chứa địa chỉ thật. Nhìn ô đầu, hệ thống biết ngay cách đọc ô sau. Nhớ nhé: nhìn nhãn family là biết cách đọc phần địa chỉ còn lại.
A typical
struct sockaddrhas two fields:sa_family(anAF_*constant, of typesa_family_tspecified by SUSv3) andsa_data(a byte array holding the address, whose size varies by domain). Every domain-specific structure begins with a corresponding family field — and that value alone lets the kernel determine the size and format of the rest of the address.19Bạn gặp nó ở đâuWhere you meet it Mỗi khi bạn viết(struct sockaddr *) &addrđể ép mộtsockaddr_inhaysockaddr_unkhi gọibind()hayconnect()— đó chính là lý do cấu trúc tổng quát này tồn tại.1 Khi thấy đoạn mã ép kiểu(struct sockaddr *)trước khi gọi bind — đó là đang nhét địa chỉ vào phong bì chung. Each time you write(struct sockaddr *) &addrto cast asockaddr_inorsockaddr_unforbind()orconnect()— that is exactly why the generic structure exists.1Trường sa_len không có trên LinuxNo sa_len on Linux
Một số hiện thực UNIX còn có trường
sa_lenghi tổng kích thước cấu trúc. SUSv3 không bắt buộc trường này, và nó không có trong hiện thực sockets của Linux.19Some UNIX implementations also define ansa_lenfield giving the structure's total size. SUSv3 does not require it, and it is not present in Linux's sockets implementation.19Một lối tắt phi khả chuyểnA nonportable shortcut
Nếu định nghĩa macro
_GNU_SOURCE, glibc khai báo các lời gọi socket bằng một mở rộng gcc loại bỏ nhu cầu ép(struct sockaddr *). Nhưng dựa vào tính năng này là phi khả chuyển — sẽ sinh cảnh báo biên dịch trên hệ khác.1If you define_GNU_SOURCE, glibc prototypes the socket calls with a gcc extension that removes the need for the(struct sockaddr *)cast. But relying on it is nonportable — it causes compiler warnings on other systems.1
Cụm khái niệm 03 Concept cluster 03
Stream socket: chuỗi lời gọi client/server, như một cuộc gọi điện thoại Kiểu "gọi điện": ai nghe, ai gọi, rồi nói qua lại Stream sockets: the client/server call sequence, like a phone call
Kerrisk giải thích stream socket bằng phép ví với hệ thống điện thoại: socket() như lắp một chiếc điện thoại; muốn nói chuyện, một bên gọi bind() rồi listen() (có số và bật máy chờ), bên kia gọi connect() (quay số), và bên nghe gọi accept() (nhấc máy khi chuông reo); sau đó dữ liệu chảy cả hai chiều qua read()/write() cho tới khi một bên gọi close().1 Figure 56-1 trong sách minh họa đúng trình tự này: phía server đi socket → bind → listen → accept, phía client đi socket → connect.1
Hình dung một cuộc gọi điện: nhà A lắp máy, đăng ký số, rồi ngồi chờ. Nhà B lắp máy rồi quay số nhà A. Chuông reo, nhà A nhấc máy — thế là nối. Giờ hai bên tha hồ nói qua nói lại, tới khi một người gác máy. Nhớ nhé: server chờ nghe, client quay số, nối xong thì nói cả hai chiều.
Kerrisk explains stream sockets by analogy with the telephone system: socket() is like installing a phone; to talk, one side calls bind() then listen() (has a number and is turned on to wait), the other calls connect() (dials), and the listener calls accept() (picks up when it rings); data then flows both ways via read()/write() until one side calls close().1 The book's Figure 56-1 shows exactly this: the server does socket → bind → listen → accept, the client does socket → connect.1
Định nghĩa. Một stream socket được phân biệt là chủ động (active) hay bị động (passive). Socket vừa tạo bằng socket() mặc định là chủ động; nó dùng trong connect() để mở kết nối tới một socket bị động — gọi là active open. Một socket bị động (còn gọi là listening socket) là socket đã được đánh dấu cho phép kết nối đến bằng cách gọi listen(); chấp nhận một kết nối đến gọi là passive open.15
Nói đơn giản. Có máy "chờ nghe" (bị động, do gọi listen()) và máy "gọi đi" (chủ động, do gọi connect()). Thường thì server là máy chờ nghe, client là máy gọi đi. Nhớ nhé: server ngồi nghe, client chủ động gọi.
Definition. A stream socket is distinguished as active or passive. A socket freshly made by socket() is active by default; it is used in connect() to open a connection to a passive socket — an active open. A passive socket (also called a listening socket) is one marked to allow incoming connections by calling listen(); accepting one is a passive open.15
SERVER (bị động) CLIENT (chủ động)
socket()
bind()
listen()
accept() <───────── socket()
(chặn) <───────── connect()
read()/write() <──> write()/read()
close() close()
SERVER: socket → bind → listen → accept
CLIENT: socket → connect
rồi: read/write cả hai chiều → close
SERVER (passive) CLIENT (active)
socket()
bind()
listen()
accept() <───────── socket()
(blocks) <───────── connect()
read()/write() <──> write()/read()
close() close()
-
listen()
listen() đánh dấu socket là bị động, với một hàng chờ listen() bật chế độ "chờ nghe" và một hàng đợi listen() marks a socket passive, with a backlog queue
listen(sockfd, backlog)đánh dấu stream socketsockfdlà bị động, sau đó nó sẽ dùng để chấp nhận kết nối từ các socket chủ động khác. Không áp dụng đượclisten()cho một socket đã nối. Vì client có thể gọiconnect()trước khi server gọiaccept(), nhân phải ghi nhớ các yêu cầu kết nối đang chờ; đối sốbackloggiới hạn số kết nối chờ như vậy.15Gọi
listen()là server nói với hệ thống: "tôi sẵn sàng nhận cuộc gọi". Nếu nhiều khách gọi tới cùng lúc mà server chưa kịp nhấc, các cuộc gọi xếp hàng chờ — vàbackloglà độ dài tối đa của hàng chờ ấy. Nhớ nhé: listen() bật chế độ nhận và đặt độ dài hàng chờ.listen(sockfd, backlog)marks stream socketsockfdpassive; it will then be used to accept connections from other active sockets.listen()cannot be applied to a connected socket. Because a client may callconnect()before the server callsaccept(), the kernel must record pending connection requests; thebacklogargument limits how many such pending connections there may be.15Bạn gặp nó ở đâuWhere you meet it Khi một server bận xử lý client khác, các yêu cầu mới xếp vào hàng chờ cho tới khi đượcaccept()lấy ra; vượt quábacklogthì yêu cầu mới bị chặn.1 Khi server đang bận, cuộc gọi mới xếp hàng; hàng đầy thì khách phải chờ. When a server is busy with another client, new requests queue untilaccept()removes them; beyondbacklog, further requests block.1SOMAXCONN và trần backlogSOMAXCONN and the backlog cap
SUSv3 cho phép đặt trần cho
backlogqua hằngSOMAXCONNtrong<sys/socket.h>. Trên Linux hằng này là 128; từ nhân 2.4.25, giới hạn có thể chỉnh lúc chạy qua tệp/proc/sys/net/core/somaxconn.15SUSv3 lets an implementation capbacklogvia theSOMAXCONNconstant in<sys/socket.h>. On Linux it is 128; since kernel 2.4.25 the limit is adjustable at run time via/proc/sys/net/core/somaxconn.15Con số 5 trong mã cũThe old 5 in legacy code
Trong hiện thực sockets BSD gốc, trần
backloglà 5, và ta còn thấy con số này trong mã cũ. Mọi hiện thực hiện đại cho phép giá trị lớn hơn — cần thiết cho server TCP phục vụ nhiều client.1In the original BSD sockets implementation thebacklogcap was 5, and you may still see this in old code. All modern implementations allow higher values — necessary for TCP servers handling many clients.1 -
accept()
accept() tạo ra một socket MỚI cho mỗi kết nối accept() đẻ ra một socket mới mỗi lần nhấc máy accept() creates a NEW socket per connection
Điểm mấu chốt của
accept(): nó tạo một socket mới, và chính socket mới này mới là socket được nối với peer đã gọiconnect(). File descriptor của socket đã nối được trả về làm kết quả củaaccept(). Socket đang nghe (sockfd) vẫn mở và dùng tiếp để chấp nhận kết nối khác. Nếu không có kết nối đang chờ,accept()chặn cho tới khi có.16Đừng nhầm: khi server nhấc máy (
accept()), hệ thống không trao lại chiếc máy đang chờ, mà đưa cho server một chiếc máy mới đã nối sẵn với khách. Chiếc máy chờ vẫn để đó, sẵn sàng đón khách tiếp theo. Nhớ nhé: accept() đẻ socket mới, socket nghe vẫn ở lại.The key point about
accept(): it creates a new socket, and it is this new socket that is connected to the peer that calledconnect(). The connected socket's file descriptor is returned as the result ofaccept(). The listening socket (sockfd) stays open and is reused to accept further connections. If there are no pending connections,accept()blocks until one arrives.16Bạn gặp nó ở đâuWhere you meet it Server điển hình tạo một socket nghe,bind()nó vào địa chỉ đã biết, rồi phục vụ mọi client bằng cáchaccept()trên socket đó — mỗi client một socket mới.1 Một server dùng một máy chờ, mỗi khách gọi tới lại được một đường nối riêng. A typical server creates one listening socket,bind()s it to a well-known address, and serves all clients byaccept()ing on it — a new socket per client.1addrlen là đối số giá trị–kết quảaddrlen is a value-result argument
Các đối số còn lại của
accept()trả về địa chỉ của peer.addrlenlà đối số giá trị–kết quả: trước khi gọi phải đặt bằng kích thước vùng đệm; sau khi gọi, nó chứa số byte thực sự được chép vào. Không quan tâm địa chỉ peer thì truyềnNULLvà 0.16The remaining arguments toaccept()return the peer's address.addrlenis a value-result argument: before the call it must be set to the buffer size; after the call it holds the number of bytes actually copied. If you do not care about the peer's address, passNULLand 0.16accept4() từ nhân 2.6.28accept4() since kernel 2.6.28
Từ nhân 2.6.28, Linux có lời gọi phi chuẩn
accept4(), làm việc nhưaccept()nhưng thêm đối sốflagsnhậnSOCK_CLOEXECvàSOCK_NONBLOCK— tiết kiệm các lời gọifcntl()phụ.16Since kernel 2.6.28, Linux has a nonstandardaccept4()that works likeaccept()but adds aflagsargument takingSOCK_CLOEXECandSOCK_NONBLOCK— saving extrafcntl()calls.16 -
connect() · I/O · close()
connect() nối máy, rồi read/write, rồi close() Client quay số, nói chuyện, rồi gác máy connect() dials, then read/write, then close()
connect(sockfd, addr, addrlen)nối socket chủ độngsockfdtới socket đang nghe có địa chỉ cho trongaddr; hai đối số này dùng y như ởbind(). Nếuconnect()thất bại và muốn thử lại, SUSv3 quy định cách khả chuyển là đóng socket, tạo socket mới, rồi thử lại.17 Khi đã nối, một cặp stream socket cho kênh hai chiều: dùngread()/write()(haysend()/recv()); cuối cùngclose()để chấm dứt kết nối.1Client quay số bằng
connect(). Nối xong thì hai bên dùngread()vàwrite()nói qua lại tự nhiên. Nói xong, gọiclose()để gác máy. Nếu quay số không được và muốn thử lại, cách đúng là bỏ chiếc máy cũ, lấy chiếc mới. Nhớ nhé: quay số → nói → gác máy.connect(sockfd, addr, addrlen)connects the active socketsockfdto the listening socket whose address is inaddr; those two arguments work just as inbind(). Ifconnect()fails and you want to retry, SUSv3 specifies the portable method: close the socket, create a new one, and retry.17 Once connected, a pair of stream sockets gives a bidirectional channel: useread()/write()(orsend()/recv()); finallyclose()terminates the connection.1Bạn gặp nó ở đâuWhere you meet it Sau khi peer đóng, nếu ta cố đọc ta nhận end-of-file; nếu ta cố ghi, ta nhận tín hiệuSIGPIPEvà lời gọi hỏng với lỗiEPIPE— cách xử lý thường thấy là bỏ qua SIGPIPE và phát hiện qua EPIPE.1 Nếu bên kia gác máy mà ta còn ghi — hệ thống báo lỗi EPIPE; nếu ta đọc — ta thấy "hết dòng". After the peer closes, a read returns end-of-file; a write raisesSIGPIPEand the call fails withEPIPE— the usual handling is to ignore SIGPIPE and detect it via EPIPE.1
Cụm khái niệm 04 Concept cluster 04
Datagram socket: gửi và nhận không kết nối, như hệ thống bưu điện Kiểu "gửi thư": bỏ gói vào thùng, không cần nối máy Datagram sockets: connectionless send/receive, like the postal system
Kerrisk ví datagram socket với bưu điện: socket() như lập một hòm thư; để nhận được thư, ứng dụng (thường là server) dùng bind() gắn socket vào địa chỉ đã biết; gửi một datagram thì gọi sendto(), kèm địa chỉ nơi nhận (như ghi địa chỉ lên phong bì rồi bỏ vào thùng); nhận thì gọi recvfrom(), có thể chặn nếu chưa có thư, và cho ta biết luôn địa chỉ người gửi để trả lời; xong việc thì close().1 Figure 56-4 minh họa chuỗi lời gọi này — không có listen() hay accept().1
Hình dung gửi thư tay: bạn đặt một hòm thư (socket), ghi địa chỉ nhà bạn lên đó (bind) để người ta gửi thư tới. Muốn gửi, bạn viết địa chỉ người nhận lên phong bì rồi bỏ vào thùng (sendto). Muốn nhận, bạn ra hòm lấy thư (recvfrom) — trên thư có cả địa chỉ người gửi để bạn hồi âm. Nhớ nhé: mỗi gói là một lá thư có địa chỉ, không cần nối máy.
Kerrisk likens datagram sockets to the postal system: socket() is like setting up a mailbox; to receive letters, an application (usually a server) uses bind() to attach its socket to a well-known address; to send a datagram it calls sendto() with the recipient's address (like addressing a letter and posting it); to receive it calls recvfrom(), which may block if nothing has arrived and also tells you the sender's address so you can reply; and when done it calls close().1 Figure 56-4 illustrates this sequence — with no listen() or accept().1
Định nghĩa. recvfrom() và sendto() nhận và gửi datagram. Giá trị trả về và ba đối số đầu giống read()/write(); đối số thứ tư flags là mặt nạ bit điều khiển I/O đặc thù socket (để 0 nếu không cần). Cặp đối số src_addr/dest_addr cùng addrlen dùng để lấy hoặc chỉ định địa chỉ của peer — với recvfrom(), chúng trả về địa chỉ người gửi; với sendto(), chúng chỉ định socket đích.18
Nói đơn giản. sendto() là "gửi lá thư này tới địa chỉ kia". recvfrom() là "lấy lá thư mới nhất, và cho tôi biết ai gửi". Nhờ biết người gửi, ta mới hồi âm được. Nhớ nhé: sendto ghi địa chỉ người nhận, recvfrom cho biết địa chỉ người gửi.
Definition. recvfrom() and sendto() receive and send datagrams. Their return value and first three arguments match read()/write(); the fourth argument flags is a bit mask of socket-specific I/O features (0 if not needed). The src_addr/dest_addr and addrlen arguments obtain or specify the peer's address — with recvfrom() they return the sender's address; with sendto() they specify the destination socket.18
#include <sys/socket.h>
ssize_t recvfrom(fd, buf, len, flags,
src_addr, addrlen); /* ai gửi? */
ssize_t sendto(fd, buf, len, flags,
dest_addr, addrlen); /* gửi tới ai */
sendto(...) --> gửi gói tới một địa chỉ
recvfrom(...) --> nhận gói, biết người gửi
(không cần connect trước)
#include <sys/socket.h>
ssize_t recvfrom(fd, buf, len, flags,
src_addr, addrlen); /* who sent? */
ssize_t sendto(fd, buf, len, flags,
dest_addr, addrlen); /* send to whom */
-
one message per recvfrom()
Mỗi recvfrom() lấy đúng MỘT thông điệp Mỗi lần nhận là đúng một lá thư Each recvfrom() reads exactly ONE message
Dù chỉ định
lengthbằng bao nhiêu,recvfrom()cũng lấy đúng một thông điệp từ datagram socket. Nếu thông điệp dài hơnlengthbyte, phần dư bị cắt bỏ âm thầm (silently truncated).18 Đây là hệ quả trực tiếp của tính chất giữ ranh giới thông điệp: khác với dòng byte của stream, mỗi datagram là một đơn vị trọn vẹn, không gộp với datagram khác.1Mỗi lần ra hòm thư, bạn chỉ lấy đúng một lá thư — không phải một nắm chữ gộp lại. Nếu lá thư dài hơn chỗ bạn chuẩn bị để chứa, phần thừa bị cắt mất mà không báo. Nhớ nhé: một lần nhận là một lá thư, chuẩn bị đủ chỗ kẻo mất chữ.
Regardless of the
lengthspecified,recvfrom()retrieves exactly one message from a datagram socket. If the message is longer thanlengthbytes, the excess is silently truncated.18 This follows directly from the message-boundary-preserving property: unlike a stream's byte flow, each datagram is a whole unit, never merged with another.1Bạn gặp nó ở đâuWhere you meet it Nếu không cần biết người gửi, truyềnsrc_addrvàaddrlenlàNULL— khi đórecvfrom()tương đươngrecv(), và cũng có thể dùngread()để nhận một datagram.18 Nếu không cần biết ai gửi, cứ để trống ô địa chỉ — nhận thư vẫn được. If you do not need the sender, passsrc_addrandaddrlenasNULL— thenrecvfrom()is equivalent torecv(), and you can even useread()to receive a datagram.18Datagram độ dài 0Zero-length datagrams
Trên Linux, có thể dùng
sendto()để gửi datagram dài 0 byte; tuy nhiên không phải mọi hiện thực UNIX đều cho phép điều này.18On Linux,sendto()can send a zero-length datagram; however, not all UNIX implementations permit this.18Phát hiện cắt cụtDetecting truncation
Nếu dùng
recvmsg(), ta có thể biết một datagram đã bị cắt cụt qua cờMSG_TRUNCtrả về trong trườngmsg_flagscủa cấu trúcmsghdr.18Withrecvmsg(), you can learn that a datagram was truncated via theMSG_TRUNCflag returned in themsg_flagsfield of themsghdrstructure.18 -
connected datagram socket
connect() trên datagram: ghi nhớ một peer, I/O đơn giản hơn Có thể "khóa" một địa chỉ nhận/gửi quen connect() on a datagram: record a peer, simpler I/O
Dù datagram socket không kết nối, gọi
connect()trên nó vẫn có ích: nhân ghi nhớ một địa chỉ làm peer của socket (gọi là connected datagram socket). Sau đó, datagram gửi qua socket bằngwrite()(haysend()) tự động tới peer ấy, và chỉ datagram từ peer ấy mới đọc được trên socket.17 Lợi ích rõ nhất: dùng được các lời gọi I/O đơn giản hơn, không phải lặp lạidest_addrmỗi lần gửi.1Nếu cứ gửi thư đi gửi thư lại cho đúng một người, bạn có thể "lưu địa chỉ" người đó một lần (connect) rồi khỏi ghi lại mỗi lần gửi. Từ đó bạn chỉ gửi và chỉ nhận thư của đúng người ấy. Nhớ nhé: connect trên datagram là lưu sẵn một địa chỉ quen cho gọn.
Though a datagram socket is connectionless, calling
connect()on it is still useful: the kernel records an address as the socket's peer (a connected datagram socket). Thereafter, datagrams sent viawrite()(orsend()) go automatically to that peer, and only datagrams from that peer can be read on the socket.17 The obvious benefit: you can use simpler I/O calls, without repeatingdest_addron every send.1Bạn gặp nó ở đâuWhere you meet it Hiệu ứng củaconnect()là bất đối xứng: chỉ áp cho socket đã gọi, không áp cho socket phía kia (trừ khi bên kia cũng gọiconnect()).1 Chỉ bên nào "lưu địa chỉ" thì bên đó được gọn; bên kia vẫn làm theo cách thường. The effect ofconnect()is asymmetric: it applies only to the socket that called it, not to the remote socket (unless that side also callsconnect()).1Gỡ bỏ liên kết peerDissolving the peer association
Có thể đổi peer bằng một lời
connect()khác, hoặc gỡ hẳn bằng cách truyền cấu trúc địa chỉ có họ địa chỉ làAF_UNSPEC. SUSv3 khá mơ hồ về việc này (nói về "địa chỉ null" mà không định nghĩa); SUSv4 mới nêu rõ dùngAF_UNSPEC. Nhiều hiện thực UNIX khác không hỗ trợAF_UNSPECcho mục đích này.17You can change the peer with anotherconnect(), or dissolve it entirely by passing an address structure whose address family isAF_UNSPEC. SUSv3 was vague here (speaking of a "null address" without defining it); SUSv4 explicitly specifiesAF_UNSPEC. Many other UNIX implementations do not supportAF_UNSPECfor this.17 -
close() · shutdown()
Đóng kết nối, và giới hạn của close() Gác máy — nhưng gác máy không bảo đảm thư đã được đọc Closing, and the limit of close()
Với stream, cách thường để chấm dứt kết nối là gọi
close(); nếu nhiều file descriptor cùng trỏ tới một socket, kết nối chỉ chấm dứt khi tất cả được đóng.1 Nhưngclose()không cho biết peer đã đọc và xử lý đúng dữ liệu hay chưa — nếu cần bảo đảm, ứng dụng phải tự xây một giao thức xác nhận. Để kiểm soát mịn hơn cách đóng, có lời gọishutdown()(bàn ở Chương 61).1Gác máy là gọi
close(). Nhưng gác máy xong, ta không chắc bên kia đã nghe và hiểu hết lời ta nói — muốn chắc thì phải hẹn nhau một câu "đã nhận, rõ". Nhớ nhé: đóng socket không có nghĩa dữ liệu đã được đọc xong.For streams, the usual way to end a connection is
close(); if several descriptors refer to one socket, the connection ends only when all are closed.1 Butclose()does not tell you whether the peer read and correctly processed the data — if you need that guarantee, your application must build its own acknowledgement protocol. For finer control over closing, there isshutdown()(covered in Chapter 61).1Bạn gặp nó ở đâuWhere you meet it Datagram socket cũng đóng bằngclose()khi không còn cần — nhưng vì không kết nối, không có chuyện "chấm dứt kết nối" như stream.1 Hòm thư dùng xong cũng dẹp bằngclose(), đơn giản vậy thôi. A datagram socket is also closed withclose()when no longer needed — but being connectionless, there is no "connection termination" as with streams.1
Ba ngộ nhận thường gặp Three common misconceptions
“Socket chỉ để lập trình mạng, giữa các máy” “Sockets are only for networking, between hosts”
Ngộ nhận: socket bao giờ cũng đi qua mạng.
Thực tế: miền AF_UNIX cho phép hai ứng dụng trên cùng một máy giao tiếp mà không qua mạng, dùng đường dẫn làm địa chỉ.110
Myth: a socket always goes over a network.
Reality: the AF_UNIX domain lets two applications on the same host communicate without any network, using a pathname as the address.110
“Datagram chỉ là stream kém tin cậy hơn” “A datagram is just a less reliable stream”
Ngộ nhận: stream và datagram khác nhau mỗi ở độ tin cậy.
Thực tế: khác biệt cốt lõi là ranh giới thông điệp: stream là dòng byte không có ranh giới; datagram giữ ranh giới — mỗi recvfrom() lấy đúng một thông điệp.18
Myth: stream and datagram differ only in reliability.
Reality: the core difference is message boundaries: a stream is a byte flow with no boundaries; a datagram preserves them — each recvfrom() reads exactly one message.18
“Ta đọc/ghi ngay trên socket đang nghe” “You read/write on the listening socket”
Ngộ nhận: sau listen(), server trao đổi dữ liệu trực tiếp trên socket nghe.
Thực tế: accept() tạo một socket mới nối với client, và chính socket mới ấy mới dùng để đọc/ghi; socket nghe vẫn mở để đón client tiếp theo.16
Myth: after listen(), a server exchanges data directly on the listening socket.
Reality: accept() creates a new socket connected to the client, and it is that new socket you read/write on; the listening socket stays open for the next client.16
Tự kiểm Self-check
Năm câu hỏi để biết bạn đã nắm nền tảng về socket Năm câu hỏi kiểm tra nhanh Five questions to check you grasped the socket groundwork
Trả lời được những câu này nghĩa là bạn đã nắm bộ từ vựng chung của socket — miền, kiểu, và hai chuỗi lời gọi — nền cho các chương về socket miền UNIX và miền Internet.
Nếu trả lời được, nghĩa là bạn đã hiểu chương này.
Answering these means you grasp the shared vocabulary of sockets — domains, types, and the two call sequences — the groundwork for the UNIX-domain and Internet-domain socket chapters.
-
Một miền giao tiếp quyết định những gì? Kể ba miền và định dạng địa chỉ của từng miền.
Gợi ý sâu hơn: khác biệt giữa hằng
AF_vàPF_là gì, và vì sao trên thực tế chúng đồng nghĩa? -
So sánh stream và datagram theo ba tính chất: tin cậy, ranh giới thông điệp, hướng kết nối.
Gợi ý sâu hơn: vì sao "dòng byte" lại kéo theo việc không có ranh giới thông điệp?
-
socket()trả về gì, và vì sao có một cấu trúc địa chỉ tổng quátstruct sockaddr?Gợi ý sâu hơn: trường family trong cấu trúc địa chỉ giúp nhân làm gì?
-
Kể đúng thứ tự các lời gọi của server và client stream. Vì sao
accept()lại tạo một socket mới?Gợi ý sâu hơn: đối số
backlogcủalisten()dùng để làm gì, vàSOMAXCONNtrên Linux bằng bao nhiêu? -
Datagram socket gửi và nhận bằng lời gọi nào? Vì sao mỗi
recvfrom()chỉ lấy đúng một thông điệp?Gợi ý sâu hơn: gọi
connect()trên một datagram socket đem lại điều gì, và vì sao hiệu ứng là bất đối xứng?
Socket dùng để làm gì, và có nhất thiết phải qua mạng không?
Kiểu "gọi điện" và kiểu "gửi thư" khác nhau chỗ nào?
Lệnh nào tạo ra một socket, và lệnh nào ghi địa chỉ cho nó?
Bên server và bên client, ai chờ nghe, ai gọi tới?
Gửi và nhận một "lá thư" datagram bằng lệnh nào?
What does a communication domain determine? Name three domains and each one's address format.
Compare stream and datagram along three properties: reliability, message boundaries, connection orientation.
What does
socket()return, and why is there a genericstruct sockaddr?List, in order, the server's and client's stream calls. Why does
accept()create a new socket?Which calls send and receive on a datagram socket? Why does each
recvfrom()read exactly one message?
Khép lại chương Closing the chapter
Mọi ứng dụng mạng đều dựng trên một bộ máy nhỏ và nhất quán: một
socket()để tạo điểm cuối, mộtbind()để đặt tên, và hai mô hình — stream như cuộc gọi điện thoại (có kết nối, tin cậy, dòng byte) và datagram như gửi thư (không kết nối, giữ ranh giới, không bảo đảm). Nắm bộ từ vựng chung này, bạn đã sẵn sàng cho các chương đi sâu — socket miền UNIX, rồi socket miền Internet.1Lập trình mạng quy về một bộ công cụ nhỏ: tạo socket, ghi địa chỉ, rồi chọn gọi điện hay gửi thư. Hiểu được hai kiểu nói ấy là đủ để bước sang các chương thực hành. Nhớ nhé: socket là điện thoại, stream là gọi điện, datagram là gửi thư.
Every network application rests on a small, consistent apparatus: a
socket()to make an endpoint, abind()to name it, and two models — stream like a phone call (connection-oriented, reliable, byte-stream) and datagram like posting letters (connectionless, boundary-preserving, unreliable). Grasp this shared vocabulary and you are ready for the deep chapters — UNIX-domain sockets, then Internet-domain sockets.1