3.linux系统编程之网络
linux系统编程之网络
网络编程是Linux系统编程中最重要的部分之一,下面我将详细介绍Linux下的网络编程接口、核心概念和实际应用。
一、网络编程基础
1. 核心概念
- Socket:网络通信的基本端点,是操作系统提供的抽象接口
- IP地址:标识网络中的主机(IPv4 32位,IPv6 128位)
- 端口号:标识主机上的特定进程(16位整数,0-65535)
- 协议:TCP(可靠连接)和UDP(无连接不可靠)
2. 网络编程基本流程
TCP服务端流程:
- 创建socket
- 绑定(bind)地址和端口
- 监听(listen)连接
- 接受(accept)连接
- 读写数据
- 关闭连接
TCP客户端流程:
- 创建socket
- 连接(connect)服务器
- 读写数据
- 关闭连接
二、核心API详解
1. socket创建
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain:协议族- AF_INET:IPv4
- AF_INET6:IPv6
- AF_UNIX:本地通信
type:通信类型- SOCK_STREAM:TCP
- SOCK_DGRAM:UDP
protocol:通常为0
2. 地址绑定
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:socket文件描述符addr:地址结构体指针addrlen:地址结构体长度
3. 监听连接
#include <sys/socket.h>
int listen(int sockfd, int backlog);
backlog:等待连接队列的最大长度
4. 接受连接
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 返回新的socket描述符用于通信
5. 建立连接
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
三、完整代码示例
1. TCP服务端示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
char *response = "Hello from server";
// 创建socket文件描述符
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置socket选项
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);
// 绑定socket到端口
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);
}
printf("Connection accepted from %s:%d\n",
inet_ntoa(address.sin_addr), ntohs(address.sin_port));
// 读取客户端数据
read(new_socket, buffer, BUFFER_SIZE);
printf("Client says: %s\n", buffer);
// 发送响应
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 <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char *hello = "Hello from client";
char buffer[BUFFER_SIZE] = {0};
// 创建socket
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, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
perror("invalid address");
return -1;
}
// 连接服务器
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("connection failed");
return -1;
}
// 发送数据
send(sock, hello, strlen(hello), 0);
printf("Hello message sent\n");
// 读取响应
read(sock, buffer, BUFFER_SIZE);
printf("Server says: %s\n", buffer);
// 关闭socket
close(sock);
return 0;
}
3. UDP服务端示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int sockfd;
char buffer[BUFFER_SIZE];
struct sockaddr_in servaddr, cliaddr;
// 创建socket
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&servaddr, 0, sizeof(servaddr));
memset(&cliaddr, 0, sizeof(cliaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(PORT);
// 绑定socket
if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
printf("UDP server listening on port %d...\n", PORT);
socklen_t len;
int n;
len = sizeof(cliaddr);
// 接收数据
n = recvfrom(sockfd, (char *)buffer, BUFFER_SIZE,
MSG_WAITALL, (struct sockaddr *)&cliaddr, &len);
buffer[n] = '\0';
printf("Client : %s\n", buffer);
// 发送响应
sendto(sockfd, "Hello from UDP server", strlen("Hello from UDP server"),
MSG_CONFIRM, (const struct sockaddr *)&cliaddr, len);
printf("Response sent.\n");
close(sockfd);
return 0;
}
四、高级网络编程
1. 多客户端处理(fork方式)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#define PORT 8080
#define BUFFER_SIZE 1024
void handle_client(int client_socket) {
char buffer[BUFFER_SIZE] = {0};
char *response = "Hello from server";
read(client_socket, buffer, BUFFER_SIZE);
printf("Received: %s\n", buffer);
send(client_socket, response, strlen(response), 0);
printf("Response sent\n");
close(client_socket);
exit(0);
}
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
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);
while(1) {
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
continue;
}
printf("Connection accepted from %s:%d\n",
inet_ntoa(address.sin_addr), ntohs(address.sin_port));
pid_t pid = fork();
if (pid == 0) {
// 子进程处理客户端
close(server_fd);
handle_client(new_socket);
} else if (pid > 0) {
// 父进程继续监听
close(new_socket);
// 回收僵尸进程
while(waitpid(-1, NULL, WNOHANG) > 0);
} else {
perror("fork");
close(new_socket);
}
}
return 0;
}
2. I/O多路复用(select)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_CLIENTS 10
int main() {
int server_fd, new_socket, client_sockets[MAX_CLIENTS];
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
fd_set readfds;
int max_sd, activity, i, valread;
// 初始化客户端socket数组
for (i = 0; i < MAX_CLIENTS; i++) {
client_sockets[i] = 0;
}
// 创建socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置socket选项
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);
// 绑定socket
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);
while(1) {
// 清空socket集合
FD_ZERO(&readfds);
// 添加主socket到集合
FD_SET(server_fd, &readfds);
max_sd = server_fd;
// 添加子socket到集合
for (i = 0; i < MAX_CLIENTS; i++) {
int sd = client_sockets[i];
if (sd > 0) {
FD_SET(sd, &readfds);
}
if (sd > max_sd) {
max_sd = sd;
}
}
// 等待活动
activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);
if ((activity < 0) && (errno != EINTR)) {
perror("select error");
}
// 如果是主socket活动,表示有新连接
if (FD_ISSET(server_fd, &readfds)) {
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
printf("New connection, socket fd: %d, IP: %s, Port: %d\n",
new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));
// 添加新socket到数组
for (i = 0; i < MAX_CLIENTS; i++) {
if (client_sockets[i] == 0) {
client_sockets[i] = new_socket;
break;
}
}
}
// 处理客户端数据
for (i = 0; i < MAX_CLIENTS; i++) {
int sd = client_sockets[i];
if (FD_ISSET(sd, &readfds)) {
if ((valread = read(sd, buffer, BUFFER_SIZE)) == 0) {
// 客户端断开连接
getpeername(sd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
printf("Host disconnected, IP %s, Port %d\n",
inet_ntoa(address.sin_addr), ntohs(address.sin_port));
close(sd);
client_sockets[i] = 0;
} else {
// 处理客户端消息
buffer[valread] = '\0';
printf("Received: %s\n", buffer);
send(sd, "Hello from server", strlen("Hello from server"), 0);
}
}
}
}
return 0;
}
五、网络编程注意事项
- 错误处理:所有网络调用都应检查返回值并处理错误
- 字节序:使用
htons、htonl、ntohs、ntohl转换网络和主机字节序 - 地址重用:设置
SO_REUSEADDR选项避免地址占用问题 - 缓冲区管理:注意缓冲区溢出和边界检查
- 资源释放:确保所有socket都被正确关闭
- 并发控制:根据需求选择多进程、多线程或I/O多路复用
- 超时处理:设置适当的超时避免无限等待

浙公网安备 33010602011771号