面试题答案
一键面试缓冲区管理
- 增大发送和接收缓冲区:
- 在套接字层面,使用
setsockopt
函数来增大缓冲区大小。例如,对于发送缓冲区:
- 在套接字层面,使用
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define BUFFER_SIZE 65536
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
int sndbuf = BUFFER_SIZE;
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0) {
perror("setsockopt SO_SNDBUF failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// 后续进行正常的网络连接等操作
close(sockfd);
return 0;
}
- 这样可以减少因缓冲区过小导致的频繁系统调用,提高数据传输效率。在接收端,类似地可以设置
SO_RCVBUF
选项来增大接收缓冲区。
- 使用零拷贝技术:
- 零拷贝技术可以避免数据在用户空间和内核空间之间不必要的拷贝。例如在Linux中,可以使用
sendfile
函数。假设已经打开了文件描述符fd
,并且有一个已连接的套接字client_socket
:
- 零拷贝技术可以避免数据在用户空间和内核空间之间不必要的拷贝。例如在Linux中,可以使用
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/sendfile.h>
int main() {
int client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 进行连接等操作
int fd = open("large_file.txt", O_RDONLY);
if (fd < 0) {
perror("File open failed");
close(client_socket);
exit(EXIT_FAILURE);
}
off_t offset = 0;
struct stat file_stat;
if (fstat(fd, &file_stat) < 0) {
perror("fstat failed");
close(fd);
close(client_socket);
exit(EXIT_FAILURE);
}
ssize_t sent_bytes = sendfile(client_socket, fd, &offset, file_stat.st_size);
if (sent_bytes < 0) {
perror("sendfile failed");
}
close(fd);
close(client_socket);
return 0;
}
多线程/多进程应用
- 多线程:
- 使用
pthread
库创建多个线程来处理数据传输。例如,一个线程负责从文件读取数据并发送,另一个线程负责接收确认信息(假设需要确认机制)。
- 使用
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#define BUFFER_SIZE 1024
void* send_data(void* arg) {
int sockfd = *((int*)arg);
int fd = open("data_file.txt", O_RDONLY);
if (fd < 0) {
perror("File open failed");
pthread_exit(NULL);
}
char buffer[BUFFER_SIZE];
ssize_t read_bytes;
while ((read_bytes = read(fd, buffer, BUFFER_SIZE)) > 0) {
if (send(sockfd, buffer, read_bytes, 0) != read_bytes) {
perror("Send failed");
break;
}
}
close(fd);
pthread_exit(NULL);
}
void* receive_ack(void* arg) {
int sockfd = *((int*)arg);
char ack[10];
if (recv(sockfd, ack, sizeof(ack), 0) < 0) {
perror("Receive ack failed");
}
pthread_exit(NULL);
}
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 进行连接等操作
pthread_t send_thread, recv_thread;
if (pthread_create(&send_thread, NULL, send_data, &sockfd) != 0) {
perror("pthread_create send_data failed");
close(sockfd);
exit(EXIT_FAILURE);
}
if (pthread_create(&recv_thread, NULL, receive_ack, &sockfd) != 0) {
perror("pthread_create receive_ack failed");
close(sockfd);
pthread_cancel(send_thread);
exit(EXIT_FAILURE);
}
pthread_join(send_thread, NULL);
pthread_join(recv_thread, NULL);
close(sockfd);
return 0;
}
- 多进程:
- 使用
fork
函数创建多个进程。例如,父进程负责监听连接,子进程负责数据传输。
- 使用
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#define BUFFER_SIZE 1024
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 进行绑定、监听等操作
int client_socket = accept(sockfd, NULL, NULL);
if (client_socket < 0) {
perror("Accept failed");
close(sockfd);
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid < 0) {
perror("Fork failed");
close(sockfd);
close(client_socket);
exit(EXIT_FAILURE);
} else if (pid == 0) { // 子进程
close(sockfd);
int fd = open("data_file.txt", O_RDONLY);
if (fd < 0) {
perror("File open failed");
close(client_socket);
exit(EXIT_FAILURE);
}
char buffer[BUFFER_SIZE];
ssize_t read_bytes;
while ((read_bytes = read(fd, buffer, BUFFER_SIZE)) > 0) {
if (send(client_socket, buffer, read_bytes, 0) != read_bytes) {
perror("Send failed");
break;
}
}
close(fd);
close(client_socket);
exit(EXIT_SUCCESS);
} else { // 父进程
close(client_socket);
// 父进程可以继续监听其他连接
}
close(sockfd);
return 0;
}
网络协议调优
- TCP协议参数调整:
- 例如调整TCP的拥塞控制算法。在Linux中,可以通过
setsockopt
设置TCP_CONGESTION
选项。假设使用cubic
拥塞控制算法:
- 例如调整TCP的拥塞控制算法。在Linux中,可以通过
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
const char* congestion = "cubic";
if (setsockopt(sockfd, IPPROTO_TCP, TCP_CONGESTION, congestion, strlen(congestion)) < 0) {
perror("setsockopt TCP_CONGESTION failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// 后续进行正常的网络连接等操作
close(sockfd);
return 0;
}
- 还可以调整
TCP_NODELAY
选项,禁用Nagle算法,减少延迟。
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
int flag = 1;
if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)) < 0) {
perror("setsockopt TCP_NODELAY failed");
close(sockfd);
exit(EXIT_FAILURE);
}
// 后续进行正常的网络连接等操作
close(sockfd);
return 0;
}
- 考虑使用UDP并实现可靠传输:
- 如果对延迟要求极高且可以自己实现可靠传输机制,可以使用UDP。例如实现一个简单的基于UDP的可靠传输协议,使用序列号和确认机制。
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 1024
#define SEQ_NUM_SIZE sizeof(unsigned int)
#define ACK_SIZE sizeof(unsigned int)
int main() {
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
struct sockaddr_in servaddr, cliaddr;
memset(&servaddr, 0, sizeof(servaddr));
memset(&cliaddr, 0, sizeof(cliaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(12345);
servaddr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("Bind failed");
close(sockfd);
exit(EXIT_FAILURE);
}
char buffer[BUFFER_SIZE];
unsigned int seq_num = 0;
while (1) {
socklen_t len = sizeof(cliaddr);
ssize_t recv_bytes = recvfrom(sockfd, buffer, BUFFER_SIZE, MSG_WAITALL, (struct sockaddr *) &cliaddr, &len);
if (recv_bytes < 0) {
perror("Receive failed");
continue;
}
// 假设这里处理接收到的数据
// 发送确认
unsigned int ack_num = seq_num;
if (sendto(sockfd, &ack_num, ACK_SIZE, MSG_CONFIRM, (const struct sockaddr *) &cliaddr, len) != ACK_SIZE) {
perror("Send ack failed");
}
seq_num++;
}
close(sockfd);
return 0;
}
在发送端,类似地需要给每个数据包加上序列号,发送后等待确认,超时则重发等操作。