@@ -1301,6 +1301,229 @@ void main (){
1301
1301
// 文件大小: 18 bytes
1302
1302
```
1303
1303
1304
+ ## C 网络编程
1305
+
1306
+ ### 网络编程介绍
1307
+
1308
+ C使用sockets进行网络通信。包含头文件:
1309
+
1310
+ - ` #include <sys/socket.h> ` : 套接字操作,如创建、绑定和监听套接字
1311
+ - ` #include <arpa/inet.h> ` : IP 地址转换
1312
+ - ` #include <unistd.h> ` : 关闭套接字等
1313
+ - ` #include <netinet/in.h> ` : 网络地址结构定义和相关敞亮
1314
+
1315
+ ### 创建套接字
1316
+
1317
+ 网络通信的第一步是创建套接字。套接字是网络通信的基础,通过它可以与远程主机进行数据交换。
1318
+
1319
+ #### 服务端
1320
+
1321
+ ``` cpp
1322
+ int server_fd, new_socket; // 定义服务器文件描述符和新连接的套接字
1323
+ int port = 8080 ; // 服务器使用的端口号
1324
+
1325
+ // 创建套接字文件描述符
1326
+ // AF_INET 表示使用 IPv4 协议,SOCK_STREAM 表示使用 TCP 协议,协议参数通常为 0(默认 TCP)
1327
+ if ((server_fd = socket(AF_INET, SOCK_STREAM, 0 )) == 0 ) {
1328
+ perror ("socket failed");
1329
+ exit(EXIT_FAILURE);
1330
+ }
1331
+ ```
1332
+
1333
+ #### 客户端
1334
+
1335
+ ``` cpp
1336
+ int sock = 0 ; // 客户端的套接字描述符
1337
+ struct sockaddr_in serv_addr; // 定义服务器地址结构体
1338
+
1339
+ // 创建套接字
1340
+ if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
1341
+ perror("Socket creation failed");
1342
+ exit(EXIT_FAILURE);
1343
+ }
1344
+ ```
1345
+
1346
+ ### 绑定套接字
1347
+
1348
+ 服务端创建套接字后,需要将其绑定到特定的 IP 地址和端口,以便客户端能够连接。
1349
+
1350
+ #### 服务端
1351
+
1352
+ ```cpp
1353
+ struct sockaddr_in address; // 定义存储地址信息的结构体
1354
+ address.sin_family = AF_INET; // 设置地址族为 IPv4
1355
+ address.sin_addr.s_addr = INADDR_ANY; // 将服务器绑定到所有可用的网络接口(即本机的所有 IP 地址)
1356
+ address.sin_port = htons(port); // 将端口号转换为网络字节序,大端模式
1357
+
1358
+ // 将套接字绑定到指定的地址和端口上
1359
+ // bind() 将服务器的文件描述符与 IP 地址和端口号进行绑定,以便客户端能够通过该地址和端口访问服务器
1360
+ if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
1361
+ perror("bind failed");
1362
+ exit(EXIT_FAILURE);
1363
+ }
1364
+ ```
1365
+
1366
+ ### 监听和接收连接
1367
+
1368
+ 服务端在绑定套接字之后,需要进入监听状态,以等待客户端的连接请求。
1369
+
1370
+ #### 服务端
1371
+
1372
+ ``` cpp
1373
+ // 开始监听客户端连接
1374
+ // 监听连接请求
1375
+ // listen() 函数将套接字设置为被动模式,准备接收来自客户端的连接请求
1376
+ if (listen(server_fd, 3 ) < 0 ) { // 第二个参数 3 表示连接请求的队列大小
1377
+ perror("listen failed");
1378
+ exit(EXIT_FAILURE);
1379
+ }
1380
+
1381
+ int addrlen = sizeof (address); // 获取地址结构体的大小
1382
+ // accept() 函数会阻塞等待客户端的连接请求,一旦连接请求到来,创建一个新的套接字 new_socket 用于数据传输
1383
+ if ((new_socket = accept(server_fd, (struct sockaddr * )&address, (socklen_t* )&addrlen)) < 0) {
1384
+ perror("accept failed");
1385
+ exit(EXIT_FAILURE);
1386
+ }
1387
+ ```
1388
+
1389
+ ### 连接到服务端
1390
+
1391
+ 客户端使用 `connect()` 函数连接到服务器的 IP 地址和端口。
1392
+
1393
+ #### 客户端
1394
+
1395
+ ```cpp
1396
+ // 设置服务器地址
1397
+ serv_addr.sin_family = AF_INET; // 设置地址族为 IPv4
1398
+ serv_addr.sin_port = htons(port); // 将端口号转换为网络字节序
1399
+
1400
+ // 将 IP 地址转换为二进制并存储在 serv_addr 结构体中
1401
+ if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
1402
+ perror("Invalid address/ Address not supported");
1403
+ exit(EXIT_FAILURE);
1404
+ }
1405
+
1406
+ // 连接服务器
1407
+ // connect() 函数将客户端的套接字与服务器的地址绑定,从而建立连接
1408
+ if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
1409
+ perror("Connection Failed");
1410
+ exit(EXIT_FAILURE);
1411
+ }
1412
+ ```
1413
+
1414
+ ### 发送和接收数据
1415
+
1416
+ 一旦连接建立,服务端和客户端可以通过套接字发送和接收数据。
1417
+
1418
+ #### 服务端
1419
+
1420
+ ``` cpp
1421
+ // 服务端从客户端接收数据
1422
+ char buffer[1024 ] = {0}; // 缓冲区,用于存储接收的数据
1423
+ int valread = read(new_socket, buffer, 1024); // 从客户端读取数据
1424
+ printf ("Client: %s\n", buffer); // 打印接收到的客户端数据
1425
+
1426
+ // 服务端发送响应数据给客户端
1427
+ const char * response = "Hello from server"; // 响应消息
1428
+ send (new_socket, response, strlen(response), 0); // 发送数据到客户端
1429
+ printf ("Server message sent\n");
1430
+ ```
1431
+
1432
+ #### 客户端
1433
+
1434
+ ```cpp
1435
+ // 客户端发送数据给服务端
1436
+ const char *message = "Hello from client"; // 要发送的消息
1437
+ send(sock, message, strlen(message), 0); // 发送数据到服务端
1438
+ printf("Client message sent\n");
1439
+
1440
+ // 客户端从服务端接收响应数据
1441
+ char buffer[1024] = {0}; // 缓冲区,用于存储接收到的数据
1442
+ int valread = read(sock, buffer, 1024); // 读取服务端的响应数据
1443
+ printf("Server: %s\n", buffer); // 打印接收到的服务端数据
1444
+ ```
1445
+
1446
+ ### 关闭套接字
1447
+
1448
+ 完成通信后,双方都应关闭各自的套接字以释放资源。
1449
+
1450
+ #### 服务端
1451
+
1452
+ ``` cpp
1453
+ // 关闭服务端套接字
1454
+ close (new_socket); // 关闭用于数据传输的客户端套接字
1455
+ close(server_fd); // 关闭服务器的监听套接字
1456
+
1457
+ ```
1458
+
1459
+ #### 客户端
1460
+
1461
+ ```cpp
1462
+ // 关闭客户端套接字
1463
+ close(sock); // 关闭客户端的套接字
1464
+ ```
1465
+
1466
+ ## I/O多路复用
1467
+
1468
+ ### 多路复用介绍
1469
+
1470
+ 在网络编程中,服务端可以使用 I/O 多路复用 技术,如 ` select ` 、` poll ` 或 ` epoll ` 。这些技术允许服务端同时监听多个文件描述符(如套接字),并在其中一个发生事件时进行处理,提升系统效率。包含头文件:
1471
+
1472
+ - ` #include <sys/select.h> ` : 提供 ` select `
1473
+ - ` #include <poll.h> ` : 提供 ` poll `
1474
+ - ` #include <sys/epoll.h> ` : 提供` epoll `
1475
+
1476
+ ### 使用select
1477
+
1478
+ ``` c
1479
+ fd_set read_fds; // 定义文件描述符集合
1480
+ FD_ZERO (&read_fds); // 清空集合
1481
+ FD_SET(server_socket, &read_fds); // 将服务端套接字加入集合
1482
+
1483
+ int max_fd = server_socket;
1484
+ int activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL); // 等待事件发生
1485
+
1486
+ if (activity < 0 && errno != EINTR) {
1487
+ perror("select error");
1488
+ }
1489
+ ```
1490
+
1491
+ ### 使用poll
1492
+
1493
+ ```c
1494
+ struct pollfd fds[2]; // 定义文件描述符数组
1495
+ fds[0].fd = server_socket;
1496
+ fds[0].events = POLLIN; // 监听读事件
1497
+
1498
+ int poll_count = poll(fds, 2, -1); // 等待事件
1499
+
1500
+ if (poll_count < 0) {
1501
+ perror("poll error");
1502
+ }
1503
+ ```
1504
+
1505
+ ### 使用epoll
1506
+
1507
+ ``` c
1508
+ int epoll_fd = epoll_create1(0 ); // 创建 epoll 文件描述符
1509
+ struct epoll_event event;
1510
+ event.events = EPOLLIN;
1511
+ event.data.fd = server_socket;
1512
+
1513
+ if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) == -1 ) {
1514
+ perror ("epoll_ctl failed");
1515
+ }
1516
+
1517
+ struct epoll_event events[10 ]; // 事件数组
1518
+ int event_count = epoll_wait(epoll_fd, events, 10 , -1 ); // 等待事件发生
1519
+
1520
+ for (int i = 0 ; i < event_count; i++) {
1521
+ if (events[i].data.fd == server_socket) {
1522
+ // 处理服务端套接字上的事件
1523
+ }
1524
+ }
1525
+ ```
1526
+
1304
1527
杂项
1305
1528
---
1306
1529
0 commit comments