一、通俗理解TCP三次握手
三次握手是TCP(传输控制协议)用于建立一个可靠连接的过程。这个协议确保了数据传输的可靠性,并且防止了旧的连接请求突然又传送到了服务器端,从而产生错误。下面我会用一个通俗易懂的例子来解释这个过程:
想象一下,你和你的朋友在两个不同的房间,你们之间有一道门,但门上没有窗户,你们不能直接看到对方。
-
第一次握手:
- 你(客户端)想要和你的朋友(服务器)开始聊天,所以你敲门(发送SYN,同步信号)并等待回应。
- 你的朋友听到了敲门声,但为了确认他听到的是你敲的门,他需要你再次敲门。
-
第二次握手:
- 你的朋友(服务器)回应你的敲门(发送SYN-ACK,同步确认信号),并告诉你他听到了你的敲门声。
- 你听到了朋友的回应,你知道他确实听到了你的敲门,现在你可以和他聊天了。
-
第三次握手:
- 你再次敲门(发送ACK,确认信号),告诉你的朋友你可以开始聊天了。
- 你的朋友听到了你的确认,现在他知道你们可以开始聊天了,门(连接)就打开了。
在这个过程中,你们双方都确认了两件事情:
- 你的朋友知道你在敲门(你的朋友准备好接收信息)。
- 你知道你的朋友听到了你的敲门(你准备好发送信息)。
这个过程就确保了你们可以开始一个可靠的对话,这就是TCP三次握手的基本原理。
在TCP/IP协议中,SYN和ACK是特殊的数据包,用于建立连接。SYN是同步序列编号,而ACK是确认收到对方的数据包。
- SYN:同步序列编号,用于建立连接时同步序号。
- ACK:确认收到对方的数据包。
三次握手完成后,TCP连接就建立起来了,数据就可以在客户端和服务器之间可靠地传输了。
二、Linux网络编程中的TCP socket连接步骤
在Linux网络编程中,使用TCP协议的socket建立连接的过程通常遵循三次握手协议。以下是TCP连接建立的基本步骤:
-
服务器监听:
- 服务器调用
socket()
函数创建一个socket。 - 服务器使用
bind()
函数将socket绑定到一个IP地址和端口上。 - 服务器调用
listen()
函数监听连接请求,准备接受客户端的连接。
- 服务器调用
-
客户端连接:
- 客户端调用
socket()
函数创建一个socket。 - 客户端使用
connect()
函数发起连接请求,指定服务器的IP地址和端口。
- 客户端调用
-
三次握手:
- 第一次握手:客户端发送一个TCP的SYN(同步序列编号)标志的数据包给服务器以发起一个新的连接,并指定客户端的初始序列号。
- 第二次握手:服务器接收到客户端的SYN请求后,需要确认客户端的SYN(即发送ACK),同时自己也发送一个SYN请求,即SYN+ACK包,此时服务器进入SYN_RECV状态。
- 第三次握手:客户端收到服务器的SYN+ACK包后,向服务器发送确认包ACK(acknowledgement),此时包的序列号为收到的序列号加1,同时客户端的TCP连接进入ESTABLISHED状态。服务器收到这个确认包后,也进入ESTABLISHED状态。
-
连接建立:
- 经过三次握手后,TCP连接建立成功,客户端和服务器可以开始数据传输。
-
数据传输:
- 客户端和服务器通过
send()
和recv()
(或write()
和read()
)函数进行数据的发送和接收。
- 客户端和服务器通过
-
连接终止:
- 数据传输完成后,任一方都可以调用
close()
函数来关闭连接,这会导致四次挥手过程的开始,最终断开连接。
- 数据传输完成后,任一方都可以调用
代码示例:
以下是服务器和客户端建立TCP连接的简单代码示例:
服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
int main() {
int server_fd, new_socket, valread;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
// 创建socket文件描述符
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 绑定socket地址
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
// 强制绑定socket
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听socket
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// 接受客户端连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {
perror("accept");
exit(EXIT_FAILURE);
}
// 读取客户端发送的数据
valread = read(new_socket, buffer, 1024);
printf("Client: %s\n", buffer);
// 关闭连接
close(new_socket);
close(server_fd);
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
struct sockaddr_in serv_addr;
int sock = 0;
char buffer[1024] = "Hello Server";
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("\n Socket creation error \n");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
// 将地址从字符串转换为AF_INET类型
if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) {
printf("\nInvalid address/ Address not supported \n");
return -1;
}
// 连接到服务器
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("\nConnection Failed \n");
return -1;
}
// 发送数据
send(sock, buffer, strlen(buffer), 0);
printf("Hello message sent\n");
// 关闭socket
close(sock);
return 0;
}
以上仅基本示例,在实际应用中,需考虑错误处理、资源管理、并发连接处理等复杂情况…