Có khá nhiều chi tiết "thấp hơn".
Đầu tiên, hãy xem xét rằng kernel có một danh sách các quy trình và tại bất kỳ thời điểm nào, một số quy trình này đang chạy và một số thì không. Nhân cho phép mỗi tiến trình chạy một vài lát thời gian CPU, sau đó ngắt nó và chuyển sang phần tiếp theo. Nếu không có các tiến trình có thể chạy được, thì hạt nhân có thể sẽ đưa ra một lệnh như HLT cho CPU tạm dừng CPU cho đến khi có sự gián đoạn phần cứng.
Đâu đó trong máy chủ là một cuộc gọi hệ thống có nội dung "hãy cho tôi một việc phải làm". Có hai loại cách thức này có thể được thực hiện. Trong trường hợp của Apache, nó gọi accept
vào một ổ cắm mà Apache đã mở trước đó, có thể đang nghe trên cổng 80. Hạt nhân duy trì một hàng đợi các nỗ lực kết nối và thêm vào hàng đợi đó mỗi khi nhận được TCP TCP . Làm thế nào hạt nhân biết TCP TCP được nhận tùy thuộc vào trình điều khiển thiết bị; đối với nhiều NIC có thể bị gián đoạn phần cứng khi nhận được dữ liệu mạng.
accept
yêu cầu kernel trả lại cho tôi sự khởi đầu kết nối tiếp theo. Nếu hàng đợi không trống, thì accept
chỉ cần trả về ngay lập tức. Nếu hàng đợi trống, thì quy trình (Apache) sẽ bị xóa khỏi danh sách các quy trình đang chạy. Khi một kết nối được bắt đầu sau đó, quá trình được nối lại. Điều này được gọi là "chặn", bởi vì trong quá trình gọi nó, có accept()
vẻ như một hàm không quay trở lại cho đến khi nó có kết quả, có thể là một thời gian kể từ bây giờ. Trong thời gian đó quá trình không thể làm gì khác.
Khi accept
trở về, Apache biết rằng ai đó đang cố gắng bắt đầu một kết nối. Sau đó, nó gọi fork để phân chia quy trình Apache thành hai quy trình giống hệt nhau. Một trong những quy trình này tiếp tục xử lý yêu cầu HTTP, các cuộc gọi khác accept
lại nhận được kết nối tiếp theo. Do đó, luôn có một quy trình chính không có gì ngoài việc gọi accept
và sinh ra các quy trình con, và sau đó sẽ có một quy trình phụ cho mỗi yêu cầu.
Đây là một sự đơn giản hóa: có thể thực hiện việc này bằng các luồng thay vì các quy trình và cũng có thể thực hiện fork
trước để có một quy trình công nhân sẵn sàng thực hiện khi nhận được yêu cầu, do đó giảm chi phí khởi động. Tùy thuộc vào cách Apache được cấu hình, nó có thể thực hiện một trong những điều này.
Đó là loại rộng đầu tiên như thế nào để làm điều đó, và nó được gọi là chặn IO vì hệ thống gọi như accept
và read
và write
hoạt động trên socket sẽ ngừng quá trình này cho đến khi họ có một cái gì đó để quay trở lại.
Cách rộng khác để làm điều đó được gọi là IO không chặn hoặc dựa trên sự kiện hoặc IO không đồng bộ . Điều này được thực hiện với các cuộc gọi hệ thống như select
hoặc epoll
. Mỗi cái đều làm điều tương tự: bạn cung cấp cho chúng một danh sách các socket (hoặc nói chung, mô tả tệp) và những gì bạn muốn làm với chúng, và các khối kernel cho đến khi nó sẵn sàng thực hiện một trong những điều đó.
Với mô hình này, bạn có thể nói với kernel (với epoll
), "Hãy cho tôi biết khi nào có kết nối mới trên cổng 80 hoặc dữ liệu mới để đọc trên bất kỳ 9471 kết nối nào khác mà tôi đã mở". epoll
chặn cho đến khi một trong những điều đó sẵn sàng, sau đó bạn làm điều đó. Sau đó, bạn lặp lại. Hệ thống gọi như accept
và read
và write
không bao giờ khối, một phần vì bất cứ khi nào bạn gọi cho họ, epoll
chỉ nói với bạn rằng họ đã sẵn sàng để thì sẽ không có lý do gì để ngăn chặn, và cũng bởi vì khi bạn mở ổ cắm hoặc các tập tin bạn chỉ định rằng bạn muốn họ trong chế độ không chặn, vì vậy những cuộc gọi đó sẽ thất bại EWOULDBLOCK
thay vì chặn.
Ưu điểm của mô hình này là bạn chỉ cần một quy trình. Điều này có nghĩa là bạn không phải phân bổ cấu trúc ngăn xếp và kernel cho mỗi yêu cầu. Nginx và HAProxy sử dụng mô hình này và đó là lý do lớn để họ có thể xử lý nhiều kết nối hơn Apache trên phần cứng tương tự.