C++ 之 SOCKET 通信详解
-
SOCKET中首先我们要理解如下几个定义概念:
- 一是IP地址:IP Address, 就是依照TCP/IP协议分配给本地主机的网络地址, 比如两个进程要通讯,任一进程要知道通讯对方的位置,就用对方的IP。
- 二是端口号: 用来标识本地通讯进程,方便OS提交数据.就是说进程指定了对方进程的网络IP,但这个IP只是用来标识进程所在的主机,如何来找到运行在这个主机的这个进程呢,就用端口号。
- 三是连接: 指两个进程间的通讯链路
- 四是半相关: 网络中用一个三元组可以在全局唯一标志一个进程:(协议,本地地址,本地端口号)
- 五是全相关:一个完整的网间进程通信需要由两个进程组成,并且只能使用同一种高层协议。也就是说,不可能通信的一端用TCP协议,而另一端用UDP协议。因此一个完整的网间通信需要一个五元组来标识:(协议,本地地址,本地端口号,远地地址,远地端口号)
-
运行模式:
- 在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器模式(Client/Server model),即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务。
- 客户/服务器模式的建立基于以下两点:首先,建立网络的起因是网络中软硬件资源、运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。其次,网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是客户/服务器模式的TCP/IP。
-
下面详细总结一下 SOCKET 相关原理
1. SOCKET 基础
-
Socket 是计算机网络中的端点,客户端和服务器通过Socket进行通信。每个Socket都是由操作系统提供的一个API,它封装了网络协议栈的各种细节,程序通过调用Socket相关函数来完成数据的发送和接收。
-
Socket的类型在网络编程中,主要有两种Socket类型:
- TCP Socket (SOCK_STREAM):面向连接的可靠通信。数据以流的形式传输,适用于要求可靠性、顺序传输的应用(如HTTP、FTP等)。
- UDP Socket (SOCK_DGRAM):无连接的、不可靠的通信方式。数据报文独立发送,不保证顺序、可靠性,适用于对实时性要求较高但对丢包可以容忍的场景(如DNS、视频流、实时游戏等)。
-
Socket是网络通信端点,TCP协议提供可靠连接(类似打电话),UDP协议提供无连接服务(类似发短信)。TCP服务端与客户端通信流程如下:
2. 编程步骤
1. 创建Socket
- 首先需要调用
socket()
函数来创建一个Socket。socket()
函数的原型如下:
int socket(int domain, int type, int protocol);
domain:指定协议族,通常是 AF_INET(IPv4)或者 AF_INET6(IPv6)。
type:指定Socket的类型,通常为 SOCK_STREAM(TCP)或者 SOCK_DGRAM(UDP)。
protocol:指定使用的协议,一般可以设置为0,操作系统会自动选择合适的协议。
- 例如,创建一个TCP Socket:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {perror("socket creation failed");exit(1);
}
2. 绑定Socket(bind)
- 创建Socket之后,需要将其与特定的地址(IP地址和端口)绑定。这一步骤通常在服务器端进行,以便监听某个特定的端口。使用
bind()
函数绑定Socket:
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有的网络接口
server_addr.sin_port = htons(8080); // 设置端口号if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {perror("bind failed");exit(1);
}
3. 监听Socket(listen) & 接受连接(accept)
- 对于TCP连接,服务器端需要监听并等待客户端的连接请求。可以使用
listen()
和accept()
函数来实现:
if (listen(sockfd, 5) < 0) {perror("listen failed");exit(1);
}int new_sock = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
if (new_sock < 0) {perror("accept failed");exit(1);
}
4. 连接到服务器(connect)
- 客户端通过
connect()
函数与服务器建立连接。它需要指定服务器的IP地址和端口:
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {perror("connect failed");exit(1);
}
5. 数据发送与接收(send/recv)
-
在连接建立之后,客户端和服务器就可以通过
send()
和recv()
函数进行数据传输。对于UDP,使用sendto()
和recvfrom()
。- send():将数据发送到Socket。
- recv():从Socket接收数据。
char message[] = "Hello, World!";
if (send(new_sock, message, sizeof(message), 0) < 0) {perror("send failed");exit(1);
}char buffer[1024];
int n = recv(new_sock, buffer, sizeof(buffer), 0);
if (n < 0) {perror("recv failed");exit(1);
}
buffer[n] = '\0';
printf("Received message: %s\n", buffer);
6. 关闭Socket(close)
- 通信完成后,需要关闭Socket以释放资源:
close(sockfd);
close(new_sock);
3. 一个简单的C++ TCP服务器和客户端
- 服务端
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <cstring>int main() {int server_fd, new_sock;struct sockaddr_in server_addr, client_addr;socklen_t addr_len = sizeof(client_addr);char buffer[1024];server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd < 0) {perror("Socket creation failed");exit(1);}server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(8080);if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {perror("Bind failed");exit(1);}if (listen(server_fd, 5) < 0) {perror("Listen failed");exit(1);}std::cout << "Waiting for client connection..." << std::endl;new_sock = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);if (new_sock < 0) {perror("Accept failed");exit(1);}std::cout << "Client connected!" << std::endl;int n = recv(new_sock, buffer, sizeof(buffer), 0);if (n < 0) {perror("Receive failed");exit(1);}buffer[n] = '\0';std::cout << "Received: " << buffer << std::endl;close(new_sock);close(server_fd);return 0;
}
- 客户端
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <cstring>int main() {int sockfd;struct sockaddr_in server_addr;char message[] = "Hello from client!";sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("Socket creation failed");exit(1);}server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {perror("Connection failed");exit(1);}if (send(sockfd, message, sizeof(message), 0) < 0) {perror("Send failed");exit(1);}close(sockfd);return 0;
}