计算机网络编程-Socket通信以及实战
1 Socket基本原理
套接字(Socket)学习
2 代码实战
server.c
// 引入必要的头文件
#include <stdio.h> // 标准输入输出(printf、perror等)
#include <stdlib.h> // 标准库(exit函数)
#include <string.h> // 字符串处理(memset、strlen等)
#include <unistd.h> // 系统调用(close、read、write等)
#include <sys/socket.h> // socket核心函数(socket、bind、listen等)
#include <netinet/in.h> // 网络地址结构(sockaddr_in等)#define PORT 8080 // 服务器端口号(1024-65535之间,避免冲突)
#define BUFFER_SIZE 1024 // 数据缓冲区大小int main() {// 1. 定义变量int server_fd; // 服务器socket文件描述符(类似句柄)int new_socket; // 客户端连接的socket文件描述符struct sockaddr_in address; // 存储服务器和客户端的地址信息int opt = 1; // setsockopt的选项值int addrlen = sizeof(address); // 地址结构的长度char buffer[BUFFER_SIZE] = {0}; // 数据缓冲区,初始化全为0const char *hello = "Hello from server"; // 服务器发送的消息// 2. 创建socket// 参数1:AF_INET → 使用IPv4协议// 参数2:SOCK_STREAM → 面向连接的TCP协议// 参数3:0 → 自动选择协议(此处为IPPROTO_TCP)if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed"); // 出错时打印错误信息(perror会自动添加原因)exit(EXIT_FAILURE); // 退出程序,返回失败状态}// 3. 设置socket选项(可选但推荐)// SOL_SOCKET:设置socket层面的选项// SO_REUSEADDR:允许端口被重复使用(避免服务器重启时"地址已在使用"错误)// SO_REUSEPORT:允许多个socket绑定到同一端口(需系统支持)if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");exit(EXIT_FAILURE);}// 4. 初始化地址结构address.sin_family = AF_INET; // 使用IPv4address.sin_addr.s_addr = INADDR_ANY; // 绑定到所有可用的本地IP(0.0.0.0)address.sin_port = htons(PORT); // 端口号转换为网络字节序(大端序)// htons:host to network short(主机字节序→网络字节序)// 5. 绑定socket到指定地址和端口// 参数1:服务器socket的文件描述符// 参数2:通用地址结构指针(需强制转换)// 参数3:地址结构长度if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 6. 监听连接(使socket进入被动模式)// 参数2:backlog → 最大等待连接队列长度(超过则新连接被拒绝)if (listen(server_fd, 3) < 0) { // 允许最多3个连接在队列中等待perror("listen");exit(EXIT_FAILURE);}printf("Server listening on port %d...\n", PORT);// 7. 接受客户端连接(阻塞等待,直到有客户端连接)// 返回一个新的socket文件描述符(用于与该客户端通信)if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}printf("Client connected: %s:%d\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));// inet_ntoa:将网络字节序的IP地址转换为字符串(如192.168.1.1)// ntohs:network to host short(网络字节序→主机字节序)// 8. 读取客户端发送的数据ssize_t valread = read(new_socket, buffer, BUFFER_SIZE); // read返回实际读取的字节数(<= BUFFER_SIZE),0表示客户端关闭连接,-1表示错误printf("Received from client: %s\n", buffer);// 9. 向客户端发送响应send(new_socket, hello, strlen(hello), 0); // send参数:socket、数据、长度、标志(0表示默认)printf("Hello message sent\n");// 10. 关闭连接(释放资源)close(new_socket); // 关闭与客户端的连接close(server_fd); // 关闭服务器socketprintf("Server closed\n");return 0;
}
client.c
// 引入必要的头文件(与服务器相同)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> // 额外包含:inet_addr函数(IP地址转换)#define PORT 8080 // 服务器端口号(需与服务器一致)
#define BUFFER_SIZE 1024 // 数据缓冲区大小int main(int argc, char const *argv[]) {// 1. 定义变量int sock = 0; // 客户端socket文件描述符struct sockaddr_in serv_addr; // 服务器地址结构char buffer[BUFFER_SIZE] = {0}; // 数据缓冲区const char *hello = "Hello from client"; // 客户端发送的消息// 2. 创建socket(与服务器相同)if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("socket creation error");exit(EXIT_FAILURE);}// 3. 初始化服务器地址结构memset(&serv_addr, '0', sizeof(serv_addr)); // 清空地址结构serv_addr.sin_family = AF_INET; // IPv4serv_addr.sin_port = htons(PORT); // 服务器端口(网络字节序)// 4. 转换服务器IP地址(字符串→网络字节序)// 若服务器在本地,可使用"127.0.0.1";若在远程,替换为实际IPif (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {// inet_pton:将点分十进制IP转换为网络字节序(支持IPv6,比inet_addr更推荐)perror("invalid address/address not supported");exit(EXIT_FAILURE);}// 5. 连接服务器(触发TCP三次握手)if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {perror("connection failed");exit(EXIT_FAILURE);}printf("Connected to server\n");// 6. 向服务器发送数据send(sock, hello, strlen(hello), 0);printf("Hello message sent\n");// 7. 读取服务器的响应ssize_t valread = read(sock, buffer, BUFFER_SIZE);printf("Received from server: %s\n", buffer);// 8. 关闭socketclose(sock);printf("Client closed\n");return 0;
}