Socket(套接字)被定义为在网络中传输信息的唯一标识。例如,我们通常将 Socket 定义为 IP 地址和端口的组合,因为一个 IP 地址和端口可以唯一确定一个网络通信端点。Socket 是一个较为抽象的概念,在 “一切皆文件” 的思想下,我们可以像操作一个 FD(文件描述符)一样操作一个 Socket,通过它来收发网络数据,实现网络通信。
Socket 网络编程是一种用于实现网络通信的编程技术。它基于 Socket API,通过创建 Socket 来实现不同计算机或进程之间的数据传输和通信。在 Socket 网络编程中,有两个关键的角色:Server(服务端)和 Client(客户端),Server 负责监听和接受 Client 的连接请求,而 Client 负责发起连接请求并与 Server 进行通信。Socket 网络编程使用 TCP/IP 协议(传输控制协议/因特网协议),TCP 协议提供可靠的、面向连接的通信,适用于要求数据传输的完整性和可靠性的场景。同时也可以使用 UDP 协议(用户数据报协议),UDP 协议提供无连接的通信,适用于对实时性要求较高的场景。
本文主要认识 TCP/IP 协议下的 Socket 网络编程。
在介绍具体的 Socket API 前我们需要分别了解在 Socket 网络编程中 Server 和 Client 分别需要哪些操作:
- Server 创建一个 Socket,并绑定到指定的 IP 和端口,开始监听 Client 的连接请求
- Client 创建一个 Socket,并指定 Server 的 IP 和端口,发起连接请求
- Server 接受客户端的连接请求,并创建一个新的 Socket 与 Client 进行通信
- Client 和 Server 通过收发数据进行通信
- 通信完成后,Client 和 Server 分别关闭 Socket
API 调用过程如下图所示:
socket()
用于创建一个新的 Socket,返回一个文件描述符,用于后续的操作。函数原型如下:
1
|
int socket(int domain, int type, int protocol);
|
domain
指定协议族,有 IPv4, IPv6, UNIX。
type
指定 Socket 类型,有面向连接的可靠字节流 Socket,无连接的不可靠数据报 Socket。
protocol
指定协议,一般为 0 即根据 domain
和 type
选择默认协议。
bind()
用于将一个 Socket 与特定的地址(IP + Port)进行绑定。函数原型如下:
1
|
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
listen()
用于使一个 Socket 进入监听状态。函数原型如下:
1
|
int listen(int sockfd, int backlog);
|
accept()
用于接收客户端连接请求。函数原型如下:
1
|
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
|
addr
用于存储接受连接请求的 Client 的地址信息
该函数调用会导致 Server 等待并接受来自 Client 的连接请求,当有新的连接请求到达后,accept()
会创建一个新的 Socket 并返回。通过这个新的 Socket 与 Client 通信。
connect()
用于在 Socket 上建立与 Server 的连接,其中涉及到的 TCP 三次握手暂且不表。函数原型如下:
1
|
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
send()
& recv()
分别用于发送与接收数据。函数原型如下:
1
2
3
|
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
|
buf
指向发送或接收缓冲区
len
希望发送或接收的数据字节数
flags
可选的标志参数,用于修改默认行为
返回值为实际发送或接收的数据字节数。
close()
用于关闭 Socket,释放相关资源。函数原型如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
// tcp-server-demo.cc
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(void)
{
// 创建IPv4 TCP Socket
int server_sock = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock == -1) {
std::cout << "Failed to create socket, error message: " << strerror(errno) << std::endl;
return -1;
}
sockaddr_in server_addr = {};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(10086); // 指定端口号为10086
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 接收任何地址的连接
if (bind(server_sock, (const sockaddr *)&server_addr, sizeof(sockaddr_in)) == -1) {
std::cout << "Failed to bind socket, error message: " << strerror(errno) << std::endl;
close(server_sock);
return -1;
}
if (listen(server_sock, 10) == -1) {
std::cout << "Failed to listen socket, error message: " << strerror(errno) << std::endl;
close(server_sock);
return -1;
}
sockaddr_in client_addr = {};
socklen_t client_addrlen = sizeof(client_addr);
// 接受Client连接并创建新Socket
int client_sock = accept(server_sock, (sockaddr *)&client_addr, &client_addrlen);
if (client_sock == -1) {
std::cout << "Failed to accept client, error message: " << strerror(errno) << std::endl;
close(server_sock);
return -1;
}
char buffer[1024] = {0};
// 接收Client发送的数据
ssize_t bytes_recv = recv(client_sock, buffer, sizeof(buffer) - 1, 0);
if (bytes_recv == -1) {
std::cout << "Failed to receive data from client, error message: " << strerror(errno) << std::endl;
close(client_sock);
close(server_sock);
return -1;
}
std::cout << "Received message from client: " << buffer << std::endl;
// 返回数据给Client
const char *respone_msg = "Hello, here is server.";
ssize_t bytes_sent = send(client_sock, respone_msg, strlen(respone_msg), 0);
if (bytes_sent <= 0) {
std::cout << "Failed to send data to client, error message: " << strerror(errno) << std::endl;
close(client_sock);
close(server_sock);
return -1;
}
std::cout << "Response hello to client" << std::endl;
close(client_sock);
close(server_sock);
return 0;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
// tcp-client-demo.cc
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(void)
{
int client_sock = socket(AF_INET, SOCK_STREAM, 0);
if (client_sock == -1) {
std::cout << "Failed to create socket, error message: " << strerror(errno) << std::endl;
return -1;
}
sockaddr_in server_addr = {};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(10086);
server_addr.sin_addr.s_addr = inet_addr("172.21.2.127"); // WSL中的地址
if (connect(client_sock, (const sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
std::cout << "Failed to connect to server, error message: " << strerror(errno) << std::endl;
close(client_sock);
return -1;
}
const char *hello_msg = "Hello, here is client";
ssize_t bytes_sent = send(client_sock, hello_msg, strlen(hello_msg), 0);
if (bytes_sent <= 0) {
std::cout << "Failed to send data to server, error message: " << strerror(errno) << std::endl;
close(client_sock);
return -1;
}
char buffer[1024] = {0};
ssize_t bytes_recv = recv(client_sock, buffer, sizeof(buffer), 0);
if (bytes_recv == -1) {
std::cout << "Failed to receive data from server, error message: " << strerror(errno) << std::endl;
close(client_sock);
return -1;
}
std::cout << "Received message from server: " << buffer << std::endl;
close(client_sock);
return 0;
}
|
1
2
3
4
|
# Server
➜ build git:(main) ✗ ./server
Received message from client: Hello, here is client
Response hello to client
|
1
2
3
|
# Client
➜ build git:(main) ✗ ./client
Received message from server: Hello, here is server.
|
本文粗略介绍了一下 Socket 网络编程的概念,TCP 协议下 Socket 编程的 API 以及调用过程,并没有进入到协议内部的具体细节,展示了一个简单到不能再简单的 demo 演示 Server 和 Client 代码实现,仅仅是对 Socket 网络编程有个初步的认知。
-
ChatGPT
-
RFC 147 - Definition of a socket