一、整体框架
在网络编程中,服务器的架构可以根据需求不同而有所不同。主要有以下几种框架:
1. 单循环服务器:同一时刻只处置惩罚一个客户端的哀求,通常使用传统的阻塞式编程模型。这种模型简单易实现,但处置惩罚能力有限,无法有效应对多个客户端的并发哀求。
2. 并发服务器:可以或许处置惩罚多个客户端的哀求。通过引入多线程或多历程等技能来实现并发处置惩罚,从而进步服务器的处置惩罚能力和响应速率。并发服务器可以分为以下几种实现方式:
- 多历程:每个客户端连接由一个独立的历程处置惩罚。常用于必要隔离处置惩罚情况的场景。
- 多线程:每个客户端连接由一个独立的线程处置惩罚。适用于必要较高资源共享的场景。
- IO多路复用:使用单个历程/线程处置惩罚多个连接,通过高效的IO多路复用机制来管理多个并发连接。适用于必要高并发处置惩罚的场景。
二、服务器
2.1 单循环服务器 vs 并发服务器
- 单循环服务器:
- 处置惩罚一个客户端的哀求时,其他客户端的哀求必须等待,导致处置惩罚效率低。
- 简单易实现,但不适合高并发场景。
- 并发服务器:
- 可以同时处置惩罚多个客户端的哀求。通过创建多个历程或线程来处置惩罚不同的客户端连接,从而进步服务器的并发处置惩罚能力。
- UDP协议由于是无连接的,天然支持并发处置惩罚。每个数据报独立处置惩罚,不必要建立持久连接。
- TCP协议是面向连接的,传统上一个TCP服务器只能处置惩罚一个客户端连接。但通过多历程或多线程的方式,可以实现TCP并发服务器。
三、TCP并发服务器
3.1 多历程
多历程模型的服务器在接收到连接哀求时,会创建一个新的历程来处置惩罚每一个客户端连接。以下是根本的实行流程:
1. socket():创建一个新的套接字。
2. bind():将套接字绑定到特定的IP地址和端标语。
3. listen():将套接字设置为监听模式,等待客户端的连接哀求。
4. accept():接受客户端的连接哀求,返回一个新的套接字用于与客户端通讯。
5. fork():创建一个子历程来处置惩罚新的客户端连接。父历程继续监听新的连接哀求。
3.2 多线程
多线程模型的服务器在接收到连接哀求时,会创建一个新的线程来处置惩罚每一个客户端连接。以下是一个示例代码:
/*************************************************************************
> File Name: pthread.c
> Author: yas
> Mail: rage_yas@hotmail.com
> Created Time: Tue 27 Aug 2024 02:48:41 PM
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
void *doSth(void *arg) {
int connfd = *((int *)arg);
while (1) {
char buff[1024] = {0};
ssize_t size = recv(connfd, buff, sizeof(buff), 0);
if (size <= 0) {
break;
}
printf("cli--------->%s\n", buff);
strcat(buff, "----->ok!");
send(connfd, buff, strlen(buff), 0);
}
close(connfd);
return NULL;
}
int conct(const char *ip, unsigned short port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
return -1;
}
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(port);
ser.sin_addr.s_addr = inet_addr(ip);
int ret = bind(sockfd, (struct sockaddr *)&ser, sizeof(ser));
if (ret == -1) {
perror("fail bind");
return -1;
}
ret = listen(sockfd, 120);
if (ret == -1) {
return -1;
}
return sockfd;
}
int main(int argc, char *argv[]) {
int connfd = 0;
pthread_t tid;
int sockfd = conct("192.168.1.112", 60000);
if (sockfd == -1) {
return -1;
}
while (1) {
connfd = accept(sockfd, NULL, NULL);
if (connfd == -1) {
return -1;
}
pthread_create(&tid, NULL, doSth, &connfd);
pthread_detach(tid);
}
return 0;
}
在这个示例中:
- `pthread_create` 用于创建新的线程来处置惩罚客户端哀求。
- `pthread_detach` 用于分离线程,使其在完成后主动回收资源。
3.3 IO多路复用
IO多路复用技能允许单个历程/线程处置惩罚多个IO操作,常用于高并发场景。主要实现方式有:
- select:查抄多个文件形貌符的状态,判定是否可以进行读写操作。
- poll:与`select`雷同,但支持更多的文件形貌符。
- epoll:高效的IO多路复用机制,适用于大规模文件形貌符的场景。
四、IO模型
4.1 阻塞IO
在阻塞IO模型中,系统调用会阻塞直到有数据可用或操作完成。比方,`fgets`、`scanf`、`read`、`recv`等。
特点:
- CPU占据率低:由于阻塞等待,CPU不会频繁进行上下文切换。
- 实行效率低:处置惩罚效率低下,特别是在处置惩罚大量连接时。
4.2 非阻塞IO
非阻塞IO模型允许系统调用立刻返回,无论是否有数据可用。必要通过轮询来查抄数据的到达。
特点:
- CPU占据率高:由于轮询机制,CPU会不停查抄IO状态,导致较高的占用率。
- 实现复杂:必要处置惩罚数据是否可用的逻辑。
实现步骤:
1. 获取文件形貌符的属性。
2. 增长非阻塞属性。
3. 设置新属性。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
void handle(int signo) {
char buf[1024] = {0};
fgets(buf, sizeof(buf), stdin);
printf("STDIN : %s\n", buf);
}
int main(int argc, char *argv[]) {
signal(SIGIO, handle);
char buf[1024] = {0};
mkfifo("./fifo", 0666);
int fd = open("./fifo", O_RDONLY);
int flag = fcntl(0, F_GETFL);
flag = flag | O_ASYNC;
fcntl(0, F_SETFL, flag);
fcntl(0, F_SETOWN, getpid());
while (1) {
memset(buf, 0, sizeof(buf));
read(fd, buf, sizeof(buf));
sleep(1);
printf("%s\n", buf);
}
close(fd);
return 0;
}
4.3 信号驱动IO
信号驱动IO模型通过信号机制来通知数据的到达。可以淘汰CPU的占用率,因为系统会在数据到达时发送信号。
特点:
- 异步通知:当IO操作准备好时,系统会发送信号通知历程。
- 效率高:适合处置惩罚少量IO操作的场景。
实现步骤:
1. 增长异步属性 `O_ASYNC`。
2. 关联信号和当进步程。
3. 注册信号处置惩罚函数。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |