Web Server 工作原理
Web开发一个永远绕不开的问题,Web Server工作原理。 从比较原始的TCP/IP服务器,到Apache、NGINX等等的出现,这些Web服务器的核心工作原理都离不开网络Socket接口。
比如Go,其库函数提供了大量的封装,我们只需要短短几行代码就可以实现一个服务器。这几行代码体现出的也正是网络套接字接口。
# TCP服务器 忽略了错误
func main(){
listen, _ := net.Listen("tcp", "localhost:8090")
for{
conn, _ := listen.Accept()
go handlefunc(conn)
}
}
在介绍其背后具体涉及哪些Socket接口前,给出一个基于套接字接口的网络应用图,更能一目了然。
套接字接口列表
套接字接口 | 函数调用 | 函数返回 | C/S使用 |
---|---|---|---|
socket | int socket(int domain, int type, int protocol) ; | 返回套接字描述符 | C/S |
connect | int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen); | 成功返回0 | C |
bind | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); | 成功返回0 | S |
listen | int listen(int sockfd, int backlog); | 成功返回0 | S |
accept | connfd = accept(int listenfd, struct sockaddr *addr, int *addrlen); | 返回连接描述符 | S |
socket 函数
clientfd = Socket(AF_INET, SOCK_STREAM, 0);
客户端和服务端均使用socket函数来创建套接字描述符(socket descriptor)。 socket返回的套接字描述符仅是部分打开的,还不能读写。如何打开它需要区分是客户端还是服务端。
connect 函数
int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen);
connect函数是给客户端用的。connect与套接字地址为 addr 的服务器建立一个网络连接。connect会阻塞调用,直到返回成功或者失败。如果成功,表示连接建立,clientfd 描述符也才准备好了读写。相应的客户端也确立了本端的IP、Port。
bind 函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind函数告诉内核,将服务端的addr 套接字地址和套接字描述符联系起来。
listen 函数
int listen(int sockfd, int backlog);
listen函数将套接字描述符sockfd 从一个主动套接字转化为监听套接字(listening socket)。 为什么说sockfd是主动套接字呢?因为内核默认为socket函数创建的描述符对应的就是主动套接字,说白了认为是客户端的(通常客户端认为是主动方,服务器是等待连接方)。 backlog是服务器在响应工作时,最多能排队的连接数。这在TCP服务器中是常见的用法。
backlog含义:
图片来自xiaolincoding
从bind和listen函数可以看出,它俩通常可以合成一个。
accept 函数
connfd = accept(int listenfd, struct sockaddr *addr, int *addrlen);
accept函数等待来自客户端的网络连接请求到达监听描述符listenfd,然后把addr填充成客户端套接字地址,并返回一个已连接描述符connfd(connected descriptor),这个描述符就是为了后续和客户端通信用的。
监听描述符listenfd 和 已连接描述符connfd的区别和联系
二者不能混淆。
监听描述符是作为客户端连接请求的一个端点,它被创建一次,并存在于服务器的整个生命周期。
已连接描述符是客户端和服务器之间已经建立起来了的连接的一个端点,服务器每次接受连接请求时都会创建一次,它只存在于服务器为一个客户端服务的过程中。
一图描述:
参考
- 经典宝藏: CSAPP