TCP三次握手,如何使用套接字建立连接

服务端准备连接

socket:创建套接字

使用如下函数

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(int fd, sockaddr * addr, socklen_t len)
  • sockaddr 是通用地址格式,实际上传入的参数可能是 IPv4、IPv6 或者本地套接字格式
  • 根据地址结构的前两个字节判断出是哪种地址
  • 为了处理长度可变的结构,需要读取函数里的第三个参数,根据 len 字段(地址长度)判断传入的参数 addr 该怎么解析

使用如下

struct sockaddr_in name;
bind (sock, (struct sockaddr *) &name, sizeof (name)

地址选择

  • 设置成本机IP地址,但是不太好迁移,利用通配地址解决
 struct sockaddr_in name;
 name.sin_addr.s_addr = htonl (INADDR_ANY); /* IPV4 通配地址INADDR_ANY  IPV6通配地址IN6ADDR_ANY  */

端口选择

  • 客户端可以交给操作系统,操作系统内核会根据一定的算法给一个空闲端口
  • 服务端的程序要绑定在一个众所周知的端口上,完成套接字绑定

初始化Socket代码如下

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
 
 
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)
  • socketfd:套接字描述符
  • backlog:官方的解释为未完成连接队列的大小,这个参数的大小决定了可以接收的并发数目。
accept:电话铃响起

当客户端的连接请求到达时,服务器端应答成功,连接建立,这个时候操作系统内核需要把这个事件通知到应用程序,并让应用程序感知到这个连接

accept 这个函数的作用就是连接建立之后,操作系统内核和应用程序之间的桥梁

/**
  * 接受连接
  *
  * @param sockfd 上述listen函数指定的监听socket
  * @param addr   请求连接方(即客户端)地址
  * @param addrlen 客户端地址长度
  * @return 函数执行成功返回一个新的连接socket,失败返回-1
*/
int accept(int listensockfd, struct sockaddr *cliaddr, socklen_t *addrlen)

accept函数返回值:一个新的连接socket(因此也称为已连接套接字),该socket唯一标示了接受的新连接,后续双方利用已连接套接字进行通信。

为什么要把两个套接字分开呢?

  • 监听套接字一直都存在,它是要为成千上万的客户来服务的,直到这个监听套接字关闭
  • 一旦一个客户和服务器连接成功,完成了 TCP 三次握手,操作系统内核就为这个客户生成一个已连接套接字,让应用服务器使用这个已连接套接字和客户进行通信处理,处理完之后可以关闭这个这个已连接的连接。

客户端发起连接的过程

connect: 拨打电话
/**
  * 建立连接
  *
  * @param sockfd socket函数返回一个socket
  * @param server_addr  服务端地址(含有服务器的 IP 地址和端口号)-套接字地址结构的指针
  * @param addrlen 服务端地址长度
  * @return 函数执行成功返回0,失败返回-1
*/
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)

客户端利用connect函数连接服务端,如果连接成功后,其第一个参数sockfd就唯一标示这个连接。

客户在调用函数 connect 前不必非得调用 bind 函数,因为如果需要的话,内核会确定源 IP 地址,并按照一定的算法选择一个临时端口作为源端口。

TCP 三次握手

如果是 TCP 套接字,那么调用 connect 函数将激发 TCP 的三次握手过程,而且仅在连接建立成功或出错时才返回。

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

TCP三次握手过程如下:
在这里插入图片描述

  • 客户端的协议栈向服务器端发送了 SYN 包,并告诉服务器端当前发送序列号 j,客户端进入 SYNC_SENT 状态;
  • 服务器端的协议栈收到这个包之后,和客户端进行 ACK 应答,应答的值为 j+1,表示对 SYN 包 j 的确认,同时服务器也发送一个 SYN 包,告诉客户端当前我的发送序列号为 k,服务器端进入 SYNC_RCVD 状态;
  • 客户端协议栈收到 ACK 之后,使得应用程序从 connect 调用返回,表示客户端到服务器端的单向连接建立成功,客户端的状态为 ESTABLISHED,同时客户端协议栈也会对服务器端的 SYN 包进行应答,应答数据为 k+1;
  • 应答包到达服务器端后,服务器端协议栈使得 accept 阻塞调用返回,这个时候服务器端到客户端的单向连接也建立成功,服务器端也进入 ESTABLISHED 状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值