1. 单连接方式
单连接方式是最简单的方式,每个客户端连接都创建一个独立的线程或进程来处理数据传输。这种方式适用于连接数较少的情况,代码实现相对简单。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(server_socket);
exit(EXIT_FAILURE);
}
if (listen(server_socket, 5) == -1) {
perror("listen");
close(server_socket);
exit(EXIT_FAILURE);
}
while (1) {
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_socket == -1) {
perror("accept");
continue;
}
char buffer[1024];
int n = recv(client_socket, buffer, sizeof(buffer), 0);
if (n <= 0) {
perror("recv");
close(client_socket);
continue;
}
// 处理请求
char* response = "Hello, I am the server!";
send(client_socket, response, strlen(response), 0);
close(client_socket);
}
close(server_socket);
return 0;
}
2. Select方式
Select是最古老的I/O复用技术,它使用fd_set集合来监视文件描述符上的I/O事件。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define MAX_CLIENTS 5
int main() {
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(server_socket);
exit(EXIT_FAILURE);
}
if (listen(server_socket, 5) == -1) {
perror("listen");
close(server_socket);
exit(EXIT_FAILURE);
}
int client_sockets[MAX_CLIENTS] = {
0};
fd_set read_fds;
int max_fd;
while (1) {
FD_ZERO(&read_fds);
FD_SET(server_socket, &read_fds);
max_fd = server_socket;
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_sockets[i] > 0) {
FD_SET(client_sockets[i], &read_fds);
if (client_sockets[i] > max_fd) {
max_fd = client_sockets[i];
}
}
}
select(max_fd + 1, &read_fds, NULL, NULL, NULL);
if (FD_ISSET(server_socket, &read_fds)) {
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_socket == -1) {
perror("accept");
continue;
}
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_sockets[i] == 0) {
client_sockets[i] = client_socket;
break;
}
}
}
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_sockets[i] > 0 && FD_ISSET(client_sockets[i], &read_fds)) {
char buffer[1024];
int n = recv(client_sockets[i], buffer, sizeof(buffer), 0);
if (n <= 0) {
close(client_sockets[i]);
client_sockets[i] = 0;
} else {
// 处理请求
char* response = "Hello, I am the server!";
send(client_sockets[i], response, strlen(response), 0);
}
}
}
}
close(server_socket);
return 0;
}
3. Poll方式
Poll是改进的I/O复用技术,使用pollfd结构体数组来监视文件描述符上的I/O事件。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <poll.h>
#define MAX_CLIENTS 5
int main() {
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(server_socket);
exit(EXIT_FAILURE);
}
if (listen(server_socket, 5) == -1) {
perror("listen");
close(server_socket);
exit(EXIT_FAILURE);
}
struct pollfd fds[MAX_CLIENTS + 1];
memset(fds, 0, sizeof(fds));
fds[0].fd = server_socket;
fds[0].events = POLLIN;
while (1) {
int num_fds = poll(fds, MAX_CLIENTS + 1, -1);
if (
num_fds == -1) {
perror("poll");
continue;
}
if (fds[0].revents & POLLIN) {
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_socket == -1) {
perror("accept");
continue;
}
for (int i = 1; i < MAX_CLIENTS + 1; i++) {
if (fds[i].fd == 0) {
fds[i].fd = client_socket;
fds[i].events = POLLIN;
break;
}
}
}
for (int i = 1; i < MAX_CLIENTS + 1; i++) {
if (fds[i].fd > 0 && (fds[i].revents & POLLIN)) {
char buffer[1024];
int n = recv(fds[i].fd, buffer, sizeof(buffer), 0);
if (n <= 0) {
close(fds[i].fd);
fds[i].fd = 0;
} else {
// 处理请求
char* response = "Hello, I am the server!";
send(fds[i].fd, response, strlen(response), 0);
}
}
}
}
close(server_socket);
return 0;
}
4. Epoll方式
Epoll是Linux特有的高效I/O复用技术,使用事件驱动的方式来监视文件描述符上的I/O事件。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#define MAX_EVENTS 10
int main() {
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(server_socket);
exit(EXIT_FAILURE);
}
if (listen(server_socket, 5) == -1) {
perror("listen");
close(server_socket);
exit(EXIT_FAILURE);
}
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
close(server_socket);
exit(EXIT_FAILURE);
}
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = server_socket;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) == -1) {
perror("epoll_ctl");
close(server_socket);
close(epoll_fd);
exit(EXIT_FAILURE);
}
struct epoll_event events[MAX_EVENTS];
while (1) {
int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (num_events == -1) {
perror("epoll_wait");
continue;
}
for (int i = 0; i < num_events; i++) {
if (events[i].data.fd == server_socket) {
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_socket == -1) {
perror("accept");
continue;
}
event.events = EPOLLIN;
event.data.fd = client_socket;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event) == -1) {
perror("epoll_ctl");
close(client_socket);
}
} else {
int client_socket = events[i].data.fd;
char buffer[1024];
int n = recv(client_socket, buffer, sizeof(buffer), 0);
if (n <= 0) {
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_socket, NULL);
close(client_socket);
} else {
// 处理请求
char* response = "Hello, I am the server!";
send(client_socket, response, strlen(response), 0);
}
}
}
}
close(server_socket);
close(epoll_fd);
return 0;
}
5.选择适合的服务器连接方式
单连接方式适用于连接数较少的情况,服务器性能要求较低。
Select方式适用于连接数少于1000个的情况,服务器性能要求中等。
Poll方式适用于连接数在1000-10000个的情况,服务器性能要求较高。
Epoll方式适用于连接数超过10000个的情况,服务器性能要求非常高。
6. 结论
TCP作为服务器连接方式在Linux服务器开发中得到广泛应用。不同的连接方式,如单连接、Select、Poll和Epoll,各有优势,可以根据连接数和性能需求选择合适的方式。本文给出了C/C++代码例子,帮助读者更好地理解和使用这些方式。在实际的服务器开发中,选择合适的连接方式可以提高服务器的性能和可扩展性,确保服务器通信的稳定运行。