文章目录
- 1. socket常见API
- 2 sockaddr结构体及其子类
- 1. sockaddr结构体定义(基类)
- 2. 子类 sockaddr_in结构体用于(IPv4)
- 3 子类 sockaddr_un(Unix域套接字)
- 4. 总结画出其结构体
- 3.实现一个简单的tcp Echo 服务器和客户端(cpp)
- 3.1 客户端
- 3.2 服务器
- 3.3 测试结果
1. socket常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
domain
:指定协议族,对于IPv4通常使用AF_INET
。type
:指定套接字类型,对于UDP使用SOCK_DGRAM
。protocol
:通常设置为0,表示使用默认协议。return value
: 如果成功,则返回新套接字的文件描述符。如果出现错误,则返回-1
,并设置errno来指示错误。
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int sockfd, const struct sockaddr *address, socklen_t address_len);
sockfd
:套接字文件描述符。sockaddr
:指向sockaddr_in
结构的指针,包含要绑定的IP地址和端口号。address_len
:地址结构的长度。
// 开始监听socket (TCP, 服务器)
int listen(int sockfd, int backlog);
sockfd
:监听套接字的文件描述符。backlog
:连接队列的最大长度。
// 接收请求 (TCP, 服务器)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd
:监听套接字的文件描述符。addr
:用于存储接受到的连接请求的源地址(可以为NULL,如果不关心源地址)。addrlen
:指向源地址长度的指针(可以为NULL,如果不关心源地址长度)。return value
:成功时返回一个新的套接字文件描述符,用于与接受的连接进行通信;失败时返回-1
并设置errno。
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
:客户端套接字的文件描述符。addr
:指向服务器地址的指针。addrlen
:地址长度
//发送数据(TCP)
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd
:发送数据的套接字文件描述符。buf
:指向要发送的数据的指针。len
:数据长度。flags
:发送选项,通常设置为0。
//接收数据(TCP)
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd
:接收数据的套接字文件描述符。buf
:用于存储接收到的数据的缓冲区。len
:缓冲区长度。flags
:接收选项,通常设置为0。
//发送数据(UDP)
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd
:发送数据的套接字文件描述符。buf
:指向要发送的数据的指针。len
:数据长度。flags
:发送选项,通常设置为0。dest_addr
:目标地址,包含目标IP和端口号。addrlen
:目标地址的长度。
//接收数据(UDP)
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
sockfd
:接收数据的套接字文件描述符。buf
:用于存储接收到的数据的缓冲区。len
:缓冲区长度。flags
:接收选项,通常设置为0。src_addr
:用于存储源地址的指针(可以为NULL,如果不关心源地址)。addrlen
:指向源地址长度的指针(可以为NULL,如果不关心源地址长度)。
2 sockaddr结构体及其子类
1. sockaddr结构体定义(基类)
struct sockaddr
是一个通用的结构体,用于表示套接字地址。这个结构体是跨平台的,但它是抽象的,意味着它并不直接用于表示具体的地址类型(如IPv4或IPv6),而是作为一个基类,其他更具体的地址结构体(如 struct sockaddr_in
和 struct sockaddr_in6
)会基于它进行扩展。
下面是 struct sockaddr
的成员变量及其解释:
-
sa_family_t sa_family;
- 这是一个地址族字段,用来指示地址的类型。地址族决定了结构体中
sa_data
字段的解释方式。常见的地址族包括AF_INET
(用于IPv4地址)和AF_INET6
(用于IPv6地址)。其他可能的值还包括AF_UNIX
(用于本地套接字)等。
- 这是一个地址族字段,用来指示地址的类型。地址族决定了结构体中
-
char sa_data[14];
- 这是一个字符数组,用于存储协议地址。对于不同的地址族,这个字段的解释方式不同。例如,对于IPv4地址(
AF_INET
),这个字段的前4个字节通常会被解释为一个32位的无符号整数,表示IPv4地址。然而,由于struct sockaddr
是一个通用结构体,sa_data
字段的大小和布局可能不足以直接容纳所有类型的地址,因此在实际使用中,更具体的地址结构体(如struct sockaddr_in
)会提供额外的字段来正确存储和解释地址。
- 这是一个字符数组,用于存储协议地址。对于不同的地址族,这个字段的解释方式不同。例如,对于IPv4地址(
2. 子类 sockaddr_in结构体用于(IPv4)
struct sockaddr_in
是一个结构体,这个结构体是IPv4地址和端口号的封装。下面是它的成员变量及其解释:
-
__kernel_sa_family_t sin_family;
- 这是一个地址族字段,用来指示地址的类型。对于IPv4地址,这个字段的值通常是
AF_INET
。地址族决定了结构体中其他字段的解释方式。
- 这是一个地址族字段,用来指示地址的类型。对于IPv4地址,这个字段的值通常是
-
__be16 sin_port;
- 这个字段表示端口号,使用大端字节序(Big Endian)存储。端口号是一个16位的数字,用于区分同一台机器上的不同服务。
-
struct in_addr sin_addr;
- 这是一个结构体,包含了IPv4地址。
struct in_addr
结构体通常只包含一个32位的无符号整数,用于表示IP地址。这个整数通常使用点分十进制表示法(例如,192.168.1.1)转换为人类可读的格式。
- 这是一个结构体,包含了IPv4地址。
-
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)];
- 这是一个填充字段,用于确保
struct sockaddr_in
的大小与更通用的struct sockaddr
结构体相匹配。struct sockaddr
是一个更大的结构体,设计用于包含各种不同类型的地址。__SOCK_SIZE__
是一个宏,定义了struct sockaddr
的大小。填充字段的大小是根据struct sockaddr
的大小减去struct sockaddr_in
中已知字段的大小来计算的。这样做是为了确保struct sockaddr_in
可以被安全地转换为struct sockaddr
,或者相反,而不会出现内存对齐或大小不匹配的问题。
- 这是一个填充字段,用于确保
3 子类 sockaddr_un(Unix域套接字)
struct sockaddr_un
是一个用于表示Unix域套接字地址的结构体。Unix域套接字是一种在同一台机器上的不同进程间进行通信的机制。与基于网络的套接字不同,Unix域套接字不涉及网络协议栈,因此它们通常具有更低的延迟和更高的带宽。
下面是 struct sockaddr_un
的成员变量及其解释:
-
__kernel_sa_family_t sun_family;
- 这是一个地址族字段,用于指示地址的类型。对于Unix域套接字,这个字段的值应该被设置为
AF_UNIX
或其同义词AF_LOCAL
。地址族决定了结构体中其他字段的解释方式。
- 这是一个地址族字段,用于指示地址的类型。对于Unix域套接字,这个字段的值应该被设置为
-
char sun_path[UNIX_PATH_MAX];
- 这是一个字符数组,用于存储套接字文件的路径名。Unix域套接字可以通过文件系统路径名(也称为套接字文件)进行标识和访问。
UNIX_PATH_MAX
是一个宏,定义了sun_path
数组的最大长度,即套接字文件路径名的最大长度。这个长度在不同的系统和实现中可能有所不同,但通常足够长,可以容纳大多数文件系统路径名。
- 这是一个字符数组,用于存储套接字文件的路径名。Unix域套接字可以通过文件系统路径名(也称为套接字文件)进行标识和访问。
4. 总结画出其结构体
3.实现一个简单的tcp Echo 服务器和客户端(cpp)
3.1 客户端
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 12345
#define BUFFER_SIZE 1024
int main() {
int sock_fd;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
// 创建套接字
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("Socket creation failed");
return 1;
}
// 配置服务器地址结构
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) <= 0) {
perror("Invalid address");
close(sock_fd);
return 1;
}
// 连接服务器
if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Connection failed");
close(sock_fd);
return 1;
}
std::cout << "Connected to server." << std::endl;
// Echo 循环
while (true) {
std::cout << "Enter message: ";
std::cin.getline(buffer, BUFFER_SIZE);
if (std::strcmp(buffer, "exit") == 0) {
std::cout << "Exiting..." << std::endl;
break;
}
send(sock_fd, buffer, std::strlen(buffer), 0);
memset(buffer, 0, BUFFER_SIZE);
ssize_t bytes_received = recv(sock_fd, buffer, BUFFER_SIZE, 0);
if (bytes_received > 0) {
std::cout << "Echo from server: " << buffer << std::endl;
} else {
std::cout << "Server disconnected." << std::endl;
break;
}
}
close(sock_fd);
return 0;
}
3.2 服务器
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 12345
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
char buffer[BUFFER_SIZE];
socklen_t addr_len = sizeof(client_addr);
// 创建套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("Socket creation failed");
return 1;
}
// 配置服务器地址结构
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// 绑定套接字到端口
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Bind failed");
close(server_fd);
return 1;
}
// 监听连接
if (listen(server_fd, 5) == -1) {
perror("Listen failed");
close(server_fd);
return 1;
}
std::cout << "Server is listening on port " << PORT << "..." << std::endl;
// 接受客户端连接
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (client_fd == -1) {
perror("Accept failed");
close(server_fd);
return 1;
}
std::cout << "Client connected." << std::endl;
// Echo 循环
while (true) {
memset(buffer, 0, BUFFER_SIZE);
ssize_t bytes_received = recv(client_fd, buffer, BUFFER_SIZE, 0);
if (bytes_received <= 0) {
std::cout << "Client disconnected." << std::endl;
break;
}
std::cout << "Received: " << buffer << std::endl;
send(client_fd, buffer, bytes_received, 0);
}
close(client_fd);
close(server_fd);
return 0;
}