发出Web请求到服务器都经历了什么(六)TCP连接与socket接口关系

106 阅读4分钟

应用程序是怎么把消息给操作系统的?socket就是操作系统提供给应用程序的编程接口、通信接口。socket也叫套接字。支持两种类型TCP和UDP。

image.png

套接字是特殊的文件描述符

一个套接字是一种特殊的文件描述符,特殊是因为一般的文件描述符只有一个缓冲区,但套接字有两个缓冲区,一个缓冲区是发送端,一个缓冲区是接收端。因为是文件,因此socket支持的操作其实就是读写IO和打开关闭。

套接字分类

TCP可靠传输,保证数据准确性,因此效率相对没那么高了。UDP保证时效,在规定时间内需要发送多少就发送多少报文,相对快。原始套接字只考虑IP或只考虑MAC地址。

image.png

流式套接字实现

大致流程首先服务器会创建初始化socket,与本地IP、端口号进行绑定,对端口进行监听,因为服务器和客户端是一对多的关系,因此需要队列来存放客户端连接。之后调用accept阻塞,如果拿到了客户端连接,就会再次创建一个socket和客户端进行连接,之后就可以和客户端进行数据收发。关闭连接时,关闭的是accept这里创建的socket,一开始创建的socket还在,会继续等待下一个客户端连接。

服务端调用socket()告诉通信方怎么传输,调用bind()告诉通信方往哪里传,调用listen()监听有没有客户端来连接进来,调用accept()从内核获取客户端连接,或者如果没有连接就阻塞等待客户端连接。

流程图

image.png

注意图中fd为套接字,服务端中最初创建的socket可以理解为监听套接字,即图中红色fd。acctpe在接收到客户端连接请求后,会创建一个新的socket,这个套接字可以称为已连接套接字也称为通信套接字,即图中绿色的fd。连接套接字可以有多个。

socket 相当于打开文件操作

指明网络层和传输层用什么协议,如果网络层使用IPv4就将domain设置为AF_INET,如果传输层设置为TCP就将type设置为SOCK_STREAM。

int socket(int domain, int type, int protocol);
  • domain

    协议域决定了socket的协议类型,常见有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

  • type

    常用的socket类型有,SOCK_STREAM、SOCK_DGRAM->UDP、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等

    • SOCK_STREAM->TCP

    流式套接字,面向连接有保障的传输数据。

    • SOCK_DGRAM->UDP

    无保障的面向消息的socket。

  • protocol

    常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等。一般设置为0,与type进行协议默认匹配。

bind函数 绑定地址

服务器的IP和端口提供服务,一般都是固定的,因此需要绑定这个操作。客户端一般都是随机生成的端口号+客户端IP,因此没有bind()这步操作。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd

    socket描述,由上面socket()创建的

  • addr

    不同的协议有不同的结构,常用的IPV4协议的结构中存放了端口、IP。

  • addrlen

    地址长度

listen函数

listen监听客户端请求。因为服务端和客户端是一对多的关系,因此服务端需要有存客户端的区域——队列。socket会维护2个队列,未完成连接队列(syns queue)/半连接和已完成连接(accept queue)队列。listen函数这里涉及到的是未完成连接队列。backlog的值为已完成连接队列中数量之和。为什么不把未完成连接的队列放进去考虑呢?因为防止syn攻击,只发了SYN没有后续ACK的客户端。

int listen(int sockfd, int backlog);
  • sockfd

    要监听的socket描述符。

  • backlog

    可以排队的最大连接个数。

结合TCP连接中的三次握手流程,当客户端发送SYN第一次握手后,服务端会在该未完成连接队列中创建一个与SYN包对应的项。

connect函数

客户端通过调用connect与TCP服务器进行连接。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd

    客户端的socket描述符。

  • addr

    服务器socket地址。

  • addrlen

    socket地址长度。

accept函数

accept接收客户端请求。提取出所监听套接字的等待连接队列中第一个连接请求,创建一个新的套接字,并返回指向该套接字的文件描述符。这里会涉及到已完成连接队列。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd 开始时创建的socket

3次握手完成之后,对应的在未完成队列中的连接就会放到该完成连接队列中。 image.png