1. socket数据报和流式套接字的工作过程
2.socket流程
2.1 创建socket
int socket (int domain, int type, int protocol);
-
domain:表示此socket的通信域(协议族),决定此socket到底是用于哪个域中的,网络PC间通信还是本地PC不同进程的通信等
比如,AF_UNIX,表示本地通信,其具体的绑定形式为文件路径;而对于AF_INET,表示网络通信,其具体绑定形式为ip+port。 -
type:套接字的类型,进一步确定通信特征。比如说报文、有连接、无连接等。不同的type对应的protocol也不同
-
protocol:表示为给定域和套接字类型选择默认协议,当对同一域和套接字类型支持多个协议时,可以通过该字段来选择一个特定协议,通常默认为0.上面设置的socket类型,在执行的时候也会有默认的协议类型提供,比如SOCK_STREAM就TCP协议
2.2 创建socket时的具体内核调用——socket()
其具体的系统调用为 int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
(这里以AF_INET域的TCP流式协议为例)。
首先它会在系统内部创建一个socket结构,然后将此socket结构与某个文件描述符fd绑定(Linux中一切皆文件),相当于是给这个socket起了一个名字,这样便可以通过fd来索引socket。
回想一下I/O模型中的I/O多路复用中的epoll不正是通过监听fd来响应连接请求的吗。
2.3 socket地址绑定——bind()
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
前面只是创建了socket并与fd绑定,但是具体的socket地址还不清楚,比如说如果domain选择了AF_INET域,那必然需要指定本机ip+port。相当于前面只是给socket起了名字,但这个socket到底是哪个市(port),哪个省(ip)的还不知道,所以要想找到这个人(socket),必须通过ip+port+fd来确定。
- sockfd:前一阶段生成的fd
- addr:指向sockaddr 的指针,而sockaddr就表示socket的地址结构。
对于AF_INET域来说,其地址结构就需要指定port,ip,比如:
对于AF_UNIX域来说,其地址结构就需要指定用于交互的文件绝对路径名addr.sin_family = AF_INET; addr.sin_port = htons(MYPORT) addr.sin_addr.s_addr = inet_addr("192.168.101.101")
addr.sin_family = AF_UNIX; addr.sun_path = "/tmp/path"
总结一下,bind()是系统函数暴露给用户的api,针对不同的协议族,其底层sys_bind()函数有多种形式,比如上面我们介绍了的AF_INET协议族和AF_UNIX协议族。内核的socket结构和fd是在调用socket函数时根据协议生成的,他绑定了不同的bind()函数形式。
2.4 socket监听——listen()
int listen(int sockfd, int backlog);
前面我们完成了socket的创建和绑定,但是如何让server直到是否有其它程序的socket连接请求呢,那就需要server监听socket队列,让其它程序发出的socket连接请求首先放入到一个可连接队列中,然后server监听这个队列,每当server空闲的时候,就可以accept一个socket。
2.5 server建立socket连接——accept()
client_fd = int accept(int sockfd, struct sockaddr *restric addr, socklen_t *restrict len);
- sockfd:原始侦听socket队列的fd
- addr:指向客户端sockaddr 信息的指针(客户端的sockaddr 由sys_accept()通过相应协议栈解析生成,比如ip等)
注意点是,accept返回的是客户端socket在server端分配的一个新的文件描述符fd,相当于server端是通过和这个新fd来与client通信。于是后面要传入到client的数据可以使用相应的I/O函数进行写入,比如write方式写到这个fd对应的文件中(socket本身都是文件描述符),然后由相应的系统调用去从这个fd中读取数据并返回给client。
注意这里只是建立连接,并未涉及到和客户端socket_fd的具体交互模式,比如当有多个客户端连接的时候,就会产生多个fd,而对于大量的fd,有些数据还没有传输完成,有些完成了,那server如何和这些fd进行交互即何种I/O模式(阻塞BIO、非阻塞NIO、异步AIO),其中最常见的就是I/O多路复用,这一块关于 服务器的I/O模式 后面会专门写一篇讲解。
2.6 client发起socket连接——connect()
int connect(int sockfd,struct sockaddr *,int addrlen)
- sockfd:客户端创建socket生成的fd
- sockaddr :指向server端的socket信息(比如IP等)的指针