一、网络协议栈基础
1. 分层模型
应用层(HTTP/FTP) ←→ 传输层(TCP/UDP) ←→ 网络层(IP) ←→ 链路层
-
Socket 定位:横跨传输层和应用层的编程接口
2. 关键协议特性
二、Socket 核心系统调用
1. 通用流程
graph TDA[创建socket] --> B[绑定地址 bind]B --> C[监听 listen (TCP Server)]C --> D[接受连接 accept]D --> E[读写数据 send/recv]E --> F[关闭连接 close]A --> G[连接 connect (TCP Client)]G --> E
2. 关键函数详解
// 创建套接字
int socket(int domain, int type, int protocol);
/*
domain: AF_INET(IPv4)/AF_INET6(IPv6)
type: SOCK_STREAM(TCP)/SOCK_DGRAM(UDP)
protocol: 通常填0自动选择
*/// 绑定地址
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/* 地址结构示例:
struct sockaddr_in {sa_family_t sin_family; // AF_INETin_port_t sin_port; // 端口(网络字节序)struct in_addr sin_addr; // IP地址
};
*/// TCP服务端监听
int listen(int sockfd, int backlog); // backlog为最大等待连接数// 接受连接(TCP专用)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);// 发起连接(TCP客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);// 数据收发
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);// 关闭连接
int close(int fd);
三、TCP 编程示例
1. TCP 服务端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>#define PORT 8080
#define BUFFER_SIZE 1024int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[BUFFER_SIZE] = {0};// 创建TCP socketif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 设置SO_REUSEADDR选项if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡address.sin_port = htons(PORT); // 端口转网络字节序// 绑定地址if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 监听if (listen(server_fd, 3) < 0) {perror("listen");exit(EXIT_FAILURE);}printf("Server listening on port %d...\n", PORT);// 接受连接if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}// 数据接收ssize_t valread = recv(new_socket, buffer, BUFFER_SIZE, 0);printf("Received: %s\n", buffer);// 数据发送char *response = "Hello from server";send(new_socket, response, strlen(response), 0);printf("Response sent\n");close(new_socket);close(server_fd);return 0;
}
2. TCP 客户端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>#define SERVER_IP "127.0.0.1"
#define PORT 8080
#define BUFFER_SIZE 1024int main() {int sock = 0;struct sockaddr_in serv_addr;char buffer[BUFFER_SIZE] = {0};if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("Socket creation error");return -1;}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// 转换IP地址if(inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {perror("Invalid address/ Address not supported");return -1;}// 连接服务器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {perror("Connection Failed");return -1;}// 发送数据char *hello = "Hello from client";send(sock, hello, strlen(hello), 0);printf("Hello message sent\n");// 接收响应ssize_t valread = recv(sock, buffer, BUFFER_SIZE, 0);printf("Server: %s\n", buffer);close(sock);return 0;
}
四、UDP 编程要点
1. 服务端核心流程
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&cliaddr, &len);
sendto(sockfd, response, strlen(response), 0, (struct sockaddr*)&cliaddr, len);
2. 客户端核心流程
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
sendto(sockfd, message, strlen(message), 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
recvfrom(sockfd, buffer, BUFFER_SIZE, 0, NULL, NULL);
五、关键概念解析
1. 字节序转换
uint32_t htonl(uint32_t hostlong); // 主机到网络(长整型)
uint16_t htons(uint16_t hostshort); // 主机到网络(短整型)
uint32_t ntohl(uint32_t netlong); // 网络到主机
uint16_t ntohs(uint16_t netshort);
2. 地址结构转换
// 字符串IP转二进制
int inet_pton(int af, const char *src, void *dst);// 二进制IP转字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
3. 多路复用技术
// select 示例
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);select(sockfd+1, &readfds, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &readfds)) {// 处理可读事件
}
六、常见问题排查
1、地址已在使用(Address already in use)
-
原因:SO_REUSEADDR未设置或端口未完全释放
-
解决:
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2、连接重置(Connection reset by peer)
-
可能原因:对端意外关闭连接
-
处理策略:检查recv返回值,0表示连接关闭
3、阻塞与非阻塞模式
// 设置非阻塞模式
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
七、性能优化方向
1、I/O 模型选择
-
阻塞模型(简单但并发差)
-
I/O多路复用(select/poll/epoll)
-
异步I/O(Windows IOCP / Linux io_uring)
2、缓冲区管理
// 设置发送缓冲区大小
int send_buf_size = 65536;
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &send_buf_size, sizeof(send_buf_size));
3、Nagle算法控制
int disable = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &disable, sizeof(disable));