Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] main from jaywcjlove:main #78

Merged
merged 1 commit into from
Sep 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 223 additions & 0 deletions docs/c.md
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,229 @@ void main (){
// 文件大小: 18 bytes
```

## C 网络编程

### 网络编程介绍

C使用sockets进行网络通信。包含头文件:

- `#include <sys/socket.h>`: 套接字操作,如创建、绑定和监听套接字
- `#include <arpa/inet.h>`: IP 地址转换
- `#include <unistd.h>`: 关闭套接字等
- `#include <netinet/in.h>`: 网络地址结构定义和相关敞亮

### 创建套接字

网络通信的第一步是创建套接字。套接字是网络通信的基础,通过它可以与远程主机进行数据交换。

#### 服务端

```cpp
int server_fd, new_socket; // 定义服务器文件描述符和新连接的套接字
int port = 8080; // 服务器使用的端口号

// 创建套接字文件描述符
// AF_INET 表示使用 IPv4 协议,SOCK_STREAM 表示使用 TCP 协议,协议参数通常为 0(默认 TCP)
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
```

#### 客户端

```cpp
int sock = 0; // 客户端的套接字描述符
struct sockaddr_in serv_addr; // 定义服务器地址结构体

// 创建套接字
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
```

### 绑定套接字

服务端创建套接字后,需要将其绑定到特定的 IP 地址和端口,以便客户端能够连接。

#### 服务端

```cpp
struct sockaddr_in address; // 定义存储地址信息的结构体
address.sin_family = AF_INET; // 设置地址族为 IPv4
address.sin_addr.s_addr = INADDR_ANY; // 将服务器绑定到所有可用的网络接口(即本机的所有 IP 地址)
address.sin_port = htons(port); // 将端口号转换为网络字节序,大端模式

// 将套接字绑定到指定的地址和端口上
// bind() 将服务器的文件描述符与 IP 地址和端口号进行绑定,以便客户端能够通过该地址和端口访问服务器
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
```

### 监听和接收连接

服务端在绑定套接字之后,需要进入监听状态,以等待客户端的连接请求。

#### 服务端

```cpp
// 开始监听客户端连接
// 监听连接请求
// listen() 函数将套接字设置为被动模式,准备接收来自客户端的连接请求
if (listen(server_fd, 3) < 0) { // 第二个参数 3 表示连接请求的队列大小
perror("listen failed");
exit(EXIT_FAILURE);
}

int addrlen = sizeof(address); // 获取地址结构体的大小
// accept() 函数会阻塞等待客户端的连接请求,一旦连接请求到来,创建一个新的套接字 new_socket 用于数据传输
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept failed");
exit(EXIT_FAILURE);
}
```

### 连接到服务端

客户端使用 `connect()` 函数连接到服务器的 IP 地址和端口。

#### 客户端

```cpp
// 设置服务器地址
serv_addr.sin_family = AF_INET; // 设置地址族为 IPv4
serv_addr.sin_port = htons(port); // 将端口号转换为网络字节序

// 将 IP 地址转换为二进制并存储在 serv_addr 结构体中
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
perror("Invalid address/ Address not supported");
exit(EXIT_FAILURE);
}

// 连接服务器
// connect() 函数将客户端的套接字与服务器的地址绑定,从而建立连接
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("Connection Failed");
exit(EXIT_FAILURE);
}
```

### 发送和接收数据

一旦连接建立,服务端和客户端可以通过套接字发送和接收数据。

#### 服务端

```cpp
// 服务端从客户端接收数据
char buffer[1024] = {0}; // 缓冲区,用于存储接收的数据
int valread = read(new_socket, buffer, 1024); // 从客户端读取数据
printf("Client: %s\n", buffer); // 打印接收到的客户端数据

// 服务端发送响应数据给客户端
const char *response = "Hello from server"; // 响应消息
send(new_socket, response, strlen(response), 0); // 发送数据到客户端
printf("Server message sent\n");
```

#### 客户端

```cpp
// 客户端发送数据给服务端
const char *message = "Hello from client"; // 要发送的消息
send(sock, message, strlen(message), 0); // 发送数据到服务端
printf("Client message sent\n");

// 客户端从服务端接收响应数据
char buffer[1024] = {0}; // 缓冲区,用于存储接收到的数据
int valread = read(sock, buffer, 1024); // 读取服务端的响应数据
printf("Server: %s\n", buffer); // 打印接收到的服务端数据
```

### 关闭套接字

完成通信后,双方都应关闭各自的套接字以释放资源。

#### 服务端

```cpp
// 关闭服务端套接字
close(new_socket); // 关闭用于数据传输的客户端套接字
close(server_fd); // 关闭服务器的监听套接字

```

#### 客户端

```cpp
// 关闭客户端套接字
close(sock); // 关闭客户端的套接字
```

## I/O多路复用

### 多路复用介绍

在网络编程中,服务端可以使用 I/O 多路复用 技术,如 `select`、`poll` 或 `epoll`。这些技术允许服务端同时监听多个文件描述符(如套接字),并在其中一个发生事件时进行处理,提升系统效率。包含头文件:

- `#include <sys/select.h>`: 提供 `select`
- `#include <poll.h>`: 提供 `poll`
- `#include <sys/epoll.h>`: 提供`epoll`

### 使用select

```c
fd_set read_fds; // 定义文件描述符集合
FD_ZERO(&read_fds); // 清空集合
FD_SET(server_socket, &read_fds); // 将服务端套接字加入集合

int max_fd = server_socket;
int activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL); // 等待事件发生

if (activity < 0 && errno != EINTR) {
perror("select error");
}
```

### 使用poll

```c
struct pollfd fds[2]; // 定义文件描述符数组
fds[0].fd = server_socket;
fds[0].events = POLLIN; // 监听读事件

int poll_count = poll(fds, 2, -1); // 等待事件

if (poll_count < 0) {
perror("poll error");
}
```

### 使用epoll

```c
int epoll_fd = epoll_create1(0); // 创建 epoll 文件描述符
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = server_socket;

if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) == -1) {
perror("epoll_ctl failed");
}

struct epoll_event events[10]; // 事件数组
int event_count = epoll_wait(epoll_fd, events, 10, -1); // 等待事件发生

for (int i = 0; i < event_count; i++) {
if (events[i].data.fd == server_socket) {
// 处理服务端套接字上的事件
}
}
```

杂项
---

Expand Down