1、创建套接
要创建一个可用的套接字,需要使用下面的函数:
int socket(int domain, int type, int protocol)
domain 就是指 PF_INET、PF_INET6 以及 PF_LOCAL 等,表示什么样的套接字。
type 可用的值是:
- SOCK_STREAM: 表示的是字节流,对应 TCP;
- SOCK_DGRAM: 表示的是数据报,对应 UDP;
- SOCK_RAW: 表示的是原始套接字。
参数 protocol 原本是用来指定通信协议的,但现在基本废弃。因为协议已经通过前面两个参数指定完成。protocol 目前一般写成 0 即可。
2、bind: 设定电话号码
创建出来的套接字如果需要被别人使用,就需要调用 bind 函数把套接字和套接字地址绑定,就像去电信局登记我们的电话号码一样。
调用 bind 函数的方式如下:
bind(int fd, sockaddr * addr, socklen_t len)
bind 函数后面的第二个参数是通用地址格式sockaddr * addr。这里有一个地方值得注意,那就是虽然接收的是通用地址格式,实际上传入的参数可能是 IPv4、IPv6 或者本地套接字格式。bind 函数会根据 len 字段判断传入的参数 addr 该怎么解析,len 字段表示的就是传入的地址长度,它是一个可变值。
设置 bind 的时候,对地址和端口可以有多种处理方式。
我们可以把地址设置成本机的 IP 地址,这相当告诉操作系统内核,仅仅对目标 IP 是本机 IP 地址的 IP 包进行处理。但是这样写的程序在部署时有一个问题,我们编写应用程序时并不清楚自己的应用程序将会被部署到哪台机器上。这个时候,可以利用通配地址的能力帮助我们解决这个问题。通配地址相当于告诉操作系统内核:“Hi,我可不挑活,只要目标地址是咱们的都可以。”比如一台机器有两块网卡,IP 地址分别是 202.61.22.55 和 192.168.1.11,那么向这两个 IP 请求的请求包都会被我们编写的应用程序处理。
对于 IPv4 的地址来说,使用 INADDR_ANY 来完成通配地址的设置;对于 IPv6 的地址来说,使用 IN6ADDR_ANY 来完成通配地址的设置。
struct sockaddr_in name; name.sin_addr.s_addr = htonl (INADDR_ANY); /* IPV4通配地址 */
除了地址,还有端口。如果把端口设置成 0,就相当于把端口的选择权交给操作系统内核来处理,操作系统内核会根据一定的算法选择一个空闲的端口,完成套接字的绑定。这在服务器端不常使用。
一般来说,服务器端的程序一定要绑定到一个众所周知的端口上。服务器端的 IP 地址和端口数据,相当于打电话拨号时需要知道的对方号码,如果没有电话号码,就没有办法和对方建立连接。
3、listen:接上电话线,一切准备就绪
bind 函数只是让我们的套接字和地址关联,如同登记了电话号码。如果要让别人打通电话,还需要我们把电话设备接入电话线,让服务器真正处于可接听的状态,这个过程需要依赖 listen 函数。
初始化创建的套接字,可以认为是一个"主动"套接字,其目的是之后主动发起请求(通过调用 connect 函数,后面会讲到)。通过 listen 函数,可以将原来的"主动"套接字转换为"被动"套接字,告诉操作系统内核:“我这个套接字是用来等待用户请求的。”当然,操作系统内核会为此做好接收用户请求的一切准备,比如完成连接队列。
listen 函数的原型是这样的:
int listen (int socketfd, int backlog)
第一个参数 socketdf 为套接字描述符,第二个参数 backlog,在 Linux 中表示已完成 (ESTABLISHED) 且未 accept 的队列大小,这个参数的大小决定了可以接收的并发数目。这个参数越大,并发数目理论上也会越大。但是参数过大也会占用过多的系统资源,一些系统,比如 Linux 并不允许对这个参数进行改变。
4、accept: 电话铃响起了
当客户端的连接请求到达时,服务器端应答成功,连接建立,这个时候操作系统内核需要把这个事件通知到应用程序,并让应用程序感知到这个连接。这个过程,就好比电信运营商完成了一次电话连接的建立, 应答方的电话铃声响起,通知有人拨打了号码,这个时候就需要拿起电话筒开始应答。
连接建立之后,你可以把 accept 这个函数看成是操作系统内核和应用程序之间的桥梁。它的原型是:
int accept(int listensockfd, struct sockaddr *cliaddr, socklen_t *addrlen)
函数的第一个参数 listensockfd 是套接字,可以叫它为 listen 套接字,因为这就是前面通过 bind,listen 一系列操作而得到的套接字。函数的返回值有两个部分,第一个部分 cliadd 是通过指针方式获取的客户端的地址,addrlen 告诉我们地址的大小,这可以理解成当我们拿起电话机时,看到了来电显示,知道了对方的号码;另一个部分是函数的返回值,这个返回值是一个全新的描述字,代表了与客户端的连接。
这里一定要注意有两个套接字描述字,第一个是监听套接字描述字 listensockfd,它是作为输入参数存在的;第二个是返回的已连接套接字描述字。