【网络编程】04-TCP三次握手

服务端准备连接的过程

创建套接字:
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原本用来指定通信协议的,现在基本已经废弃,一般写0即可。

Bind:绑定电话号码

创建出来的套接字如果需要被别人使用,就需要调用bind函数把套接字和套接字地址绑定。函数调用如下:

bind(int fd, sockaddr * addr, socklen_t len)

第二个参数sockaddr是通用地址格式,虽然是通用地址格式但是实际传入的可能是IPV4或者IPv6或者本地套接字格式。Bind会根据len字段的长度判断传入的addr参数如何解析,len就是传入的地址长度是一个可变的值。
如下是一段初始化IPv4 TCP套接字的例子。

#include 
#include 
#include 
#include 


int make_socket (uint16_t port)
{
  int sock;
  struct sockaddr_in name;


  /* 创建字节流类型的IPV4 socket. */
  sock = socket (PF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    {
      perror ("socket");
      exit (EXIT_FAILURE);
    }


  /* 绑定到port和ip. */
  name.sin_family = AF_INET; /* IPV4 */
  name.sin_port = htons (port);  /* 指定端口 */
  name.sin_addr.s_addr = htonl (INADDR_ANY); /* 通配地址 */
  /* 把IPV4地址转换成通用地址格式,同时传递长度 */
  if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
    {
      perror ("bind");
      exit (EXIT_FAILURE);
    }


  return sock
}

Listen:接上电话线,一切准备就绪

初始化创建的套接字可以理解为是一个主动套接字,其目的就是之后主动发起请求,而listen函数,可以将主动的套接字变为被动套接字,告诉操作系统内核,“我这个套接字是等待用户请求的”。Listen的函数原型如下:

int listen (int socketfd, int backlog)

这里的第二个参数为backlog,官方的解释是未完成连接队列的大小,这个参数决定了可以接收的并发连接的数目。

Accept:电话铃响起了

Accept这个函数的作用就是连接建立以后,操作系统内核和应用程序之间的桥梁。函数原型如下:

int accept(int listensockfd, struct sockaddr *cliaddr, socklen_t *addrlen)

cliadd 是通过指针方式获取的客户端的地址,addrlen 告诉我们地址的大小,函数的返回值是一个全新的描述字,代表了与客户端的连接。这里一定要注意有两个套接字描述字,第一个是监听套接字描述字 listensockfd,它是作为输入参数存在的;第二个是返回的已连接套接字描述字。

客户端发起连接的过程

前面讲述的 bind、listen 以及 accept 的过程,是典型的服务器端的过程。第一步还是和服务端一样,要建立一个套接字,方法和前面是一样的。不一样的是客户端需要调用 connect 向服务端发起请求。

connect: 拨打电话

客户端和服务器端的连接建立,是通过 connect 函数完成的。这是 connect 的构建函数:

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)

这个函数的第二个、第三个参数 servaddr 和 addrlen 分别代表指向套接字地址结构的指针和该结构的大小。套接字地址结构必须含有服务器的 IP 地址和端口号。
如果是 TCP 套接字,那么调用 connect 函数将激发 TCP 的三次握手过程,而且仅在连接建立成功或出错时才返回。其中出错返回可能有以下几种情况:

  • 三次握手无法建立,客户端发出的 SYN 包没有任何响应,于是返回 TIMEOUT 错误。这种情况比较常见的原因是对应的服务端 IP 写错。
  • 客户端收到了 RST(复位)回答,这时候客户端会立即返回 CONNECTION REFUSED 错误。这种情况比较常见于客户端发送连接请求时的请求端口写错,因为 RST 是 TCP 在发生错误时发送的一种 TCP 分节。产生 RST 的三个条件是:目的地为某端口的 SYN 到达,然而该端口上没有正在监听的服务器(如前所述);TCP 想取消一个已有连接;TCP 接收到一个根本不存在的连接上的分节。
  • 客户发出的 SYN 包在网络上引起了"destination unreachable",即目的不可达的错误。这种情况比较常见的原因是客户端和服务器端路由不通。
    对于无论是服务端还是客户端TCP连接建立过程的流程图如下所示:
    【网络编程】04-TCP三次握手_第1张图片

TCP三次握手

【网络编程】04-TCP三次握手_第2张图片
Tcp三次握手的流程如上图所示。具体过程如下:

  1. 客户端的协议栈向服务器发送了SYN包,并且告诉服务器当前客户端发送包的起始序号为j,客户端进入SYN_SEND状态
  2. 服务器端的协议栈收到该SYN包后,向客户端发送ACK应答,应答值为j+1,表示是对客户端SYN的应答,同时服务端发送自己的SYN包告诉当前服务端的报文初识序号为k。这个时候服务端进入SYN_RCVD状态
  3. 客户端协议栈收到自己的SYN包的ACK应答之后,便从connect函数的调用中返回,表示客户端到服务端的单向连接已经建立完成。客户端的状态变为ESTABLISHED,同时客户端协议栈也会对服务端发来的SYN进行应答,发送ACK应答值为k+1.
  4. 客户端发送的ACK应答达到服务端后服务端协议栈便从accept的函数调用中返回,这个时候服务端到客户端的单项连接也已经建立ok,服务端也进入ESTABLISHED状态。

这样连续的报文发送总共进行了三次,也就是著名的TCP三次握手,其实三次握手本质就是通信的双方互相确认我发送的你能收到,同时通信的双方告知对方自己报文的初识序号,为后面的报文发送做好准备。

你可能感兴趣的:(网络编程)