一.多进程并发服务器
多进程并发服务器是一种经典的服务器架构,它通过创建多个子进程来处理客户端连接,从而实现并发处理多个客户端请求的能力。
概念:
- 服务器启动时,创建主进程,并绑定监听端口。
- 当有客户端连接请求时,主进程接受连接,并创建一个子进程来处理该客户端连接。
- 子进程与客户端进行通信,处理请求和发送响应。
- 主进程继续监听新的连接请求。
- 子进程完成任务后,可以选择终止或继续处理其他连接,根据需求选择是否重复循环。
优点:
- 高并发处理能力:每个子进程都可以独立地处理一个客户端连接,利用多核处理器的优势,实现高并发处理能力,提高服务器的吞吐量。
- 稳定性:每个子进程都是独立的,一个进程出现问题不会影响其他进程,提高了服务器的稳定性和容错能力。
- 简单直观:采用多进程模型实现并发服务器相对简单,代码可读性高,易于理解和维护。
- 跨平台:多进程并发服务器概念适用于各种操作系统,不仅限于Linux平台。
缺点:
- 高资源消耗:每个子进程都需要独立的资源,包括内存、文件描述符等,当并发连接数较高时,会消耗大量的系统资源。
- 进程间通信:各个子进程之间的通信需要额外的机制,例如管道、共享内存等,增加了复杂性。
- 上下文切换开销:进程间切换的开销相对较大,会影响性能。
- 调试困难:由于每个子进程独立执行,调试和定位问题可能会变得复杂。
综上所述,多进程并发服务器能够满足高并发处理需求,并具有稳定性和简单性等优点。然而,它也存在资源消耗较高和调试困难等缺点,根据实际需求和应用场景选择合适的服务器架构是很重要的。
案例
利用多进程,在TCP通信的基础上,实现服务器可以同时连接多个客户端。实现大小写转换。
service.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void handler(int sig)
{
if (sig == SIGCHLD)
{
while(waitpid(0,NULL,WNOHANG)>0);
}
}
int main(int argc, char const *argv[])
{
//1.建立socket套接字
int socked = socket(AF_INET, SOCK_STREAM, 0);
if (socked < 0)
{
perror("socket is err");
return -1;
}
//2.bind绑定服务器ip和端口号
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
int len = sizeof(caddr);
int rev = bind(socked, (struct sockaddr *)(&saddr), sizeof(saddr));
if (rev < 0)
{
perror("bind is err");
return 0;
}
//3.listen设置最大同时链接数量
rev = listen(socked, 32);
if (rev < 0)
{
perror("rev is err");
return 0;
}
//4.建立子进程负责服务器给客户端发送消息,并且主进程不结束,此进程不结束
char buf[1024] = {0};
int size=0;
while (1)
{
int acceptfd = accept(socked, (struct sockaddr *)(&caddr), &len);
if (acceptfd < 0)
{
perror("accept is err");
return 0;
}
pid_t pid = fork();
if (pid < 0)
{
perror("pid is err");
return 0;
}
else if (pid == 0)
{
//子进程,每个子进程维护一个客户端
//不许要监听,直接关掉
close(socked);
while (1)
{
int flage = recv(acceptfd, buf, sizeof(buf), 0);
if (flage < 0)
{
perror("recv is err");
}
else if (flage == 0)
{
printf("ip:%s is close\n", inet_ntoa(caddr.sin_addr));
close(acceptfd);
break;
}
else
{
size = strlen(buf);
for (int i = 0; i < size; ++i)
{
if (buf[i] >= 'a' && buf[i] <= 'z')
buf[i] = buf[i] + ('A' - 'a');
else
buf[i] = buf[i] + ('a' - 'A');
}
printf("%d %s\n",getpid(),buf);
send(acceptfd, buf, sizeof(buf), 0);
}
}
}
else
{
//主进程回收子线程资源
close(acceptfd);
//异步节约资源
signal(SIGCHLD, handler);
}
}
return 0;
}
client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//1.socket建立文件描述符
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0)
{
perror("socket is err");
}
//2.connect连接服务器
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
int flage = connect(fd, (struct sockaddr *)(&saddr), sizeof(saddr));
if (flage < 0)
{
perror("connect is err");
}
//3.服务器端不断发送数据,接受服务器转化后的数据
char buf[1024] = {0};
while (1)
{
//memset(buf,0,sizeof(buf));
fgets(buf, sizeof(buf), stdin);
if (strncmp(buf,"quit#",5)==0)
{
break;
}
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
send(fd, buf, sizeof(buf), 0);
flage = recv(fd, buf, sizeof(buf), 0);
if (flage < 0)
{
perror("recv is err");
}
else
{
fprintf(stdout, "%s\n", buf);
}
}
close(fd);
return 0;
}
二.多线程并发服务器
多线程并发服务器是一种服务器架构,通过创建多个线程来处理客户端连接,实现并发处理多个客户端请求的能力。下面是多线程并发服务器的概念和一些优点:
概念:
- 服务器启动时,创建主线程,并绑定监听端口。
- 当有客户端连接请求时,主线程接受连接,并创建一个或多个线程来处理该客户端连接。
- 线程与客户端进行通信,处理请求和发送响应。
- 主线程继续监听新的连接请求。
- 线程完成任务后,选择终止或继续处理其他连接,根据需求选择是否重复循环。
优点:
- 资源效率:线程相比进程,创建和销毁的开销较小,占用的资源较少,特别是共享的资源,如文件描述符,可以在多个连接之间共享,提高了资源的利用效率。
- 响应速度:线程之间的切换开销较小,内存空间共享,可以快速响应客户端请求,减少了连接等待时间。
- 简单直观:与多进程相比,多线程实现并发服务器更简单直观,代码可读性高,易于理解和维护。
- 可扩展性:线程可轻松地添加和删除,适应不断变化的连接数需求,提供更好的可扩展性。
- 共享数据简便:线程之间共享的数据结构可以直接访问,不需要像进程间通信一样使用额外的机制。
缺点:
- 数据同步问题:多线程共享数据可能导致竞态条件和数据一致性问题,需要使用锁或其他同步机制来保证线程安全。
- 调试困难:由于线程之间共享同一进程的内存空间,调试和定位问题可能会变得复杂。
- 并行性限制:在某些情况下,多线程服务器的并行性可能受限于系统中可用的CPU核心数量。
- 需要考虑线程安全:编写线程安全的代码可能需要更多的开发工作和考虑,避免数据竞争和死锁等问题。
综上所述,多线程并发服务器能够满足高并发处理需求,并具有资源效率、响应速度、简单性和可扩展性等优点。然而,它也存在数据同步问题和调试困难等缺点,根据实际需求和应用场景选择合适的服务器架构是很重要的。
案例
利用多线程,在TCP通信的基础上,实现服务器可以同时连接多个客户端。实现大小写转换。
service.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
struct client
{
int acceptfd;
struct sockaddr_in caddr;
};
void *my_pthread(void *p)
{
char buf[1024] = {0};
int size = 0;
struct client *cl = (struct client *)p;
int acceptfd = cl->acceptfd;
struct sockaddr_in caddr = cl->caddr;
while (1)
{
int flage = recv(acceptfd, buf, sizeof(buf), 0);
if (flage < 0)
{
perror("recv is err");
}
else if (flage == 0)
{
printf("ip:%s is close\n", inet_ntoa(caddr.sin_addr));
close(acceptfd);
break;
}
else
{
size = strlen(buf);
for (int i = 0; i < size; ++i)
{
if (buf[i] >= 'a' && buf[i] <= 'z')
buf[i] = buf[i] + ('A' - 'a');
else
buf[i] = buf[i] + ('a' - 'A');
}
send(acceptfd, buf, sizeof(buf), 0);
}
}
close(acceptfd);
return 0;
}
int main(int argc, char const *argv[])
{
//1.建立socket套接字
int socked = socket(AF_INET, SOCK_STREAM, 0);
if (socked < 0)
{
perror("socket is err");
return -1;
}
//2.bind绑定服务器ip和端口号
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
int len = sizeof(caddr);
int rev = bind(socked, (struct sockaddr *)(&saddr), sizeof(saddr));
if (rev < 0)
{
perror("bind is err");
return 0;
}
//3.listen设置最大同时链接数量
rev = listen(socked, 32);
if (rev < 0)
{
perror("rev is err");
return 0;
}
//4.建立子进程负责服务器给客户端发送消息,并且主进程不结束,此进程不结束
int i = 0;
pthread_t tid;
struct client ti[1024] = {0};
while (1)
{
int acceptfd = accept(socked, (struct sockaddr *)(&caddr), &len);
if (acceptfd < 0)
{
perror("accept is err");
return 0;
}
ti[i].acceptfd = acceptfd;
ti[i].caddr = caddr;
pthread_create(&tid, NULL, my_pthread, (void *)&ti[i]);
pthread_detach(tid);
++i;
}
return 0;
}
client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//1.socket建立文件描述符
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0)
{
perror("socket is err");
}
//2.connect连接服务器
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
int flage = connect(fd, (struct sockaddr *)(&saddr), sizeof(saddr));
if (flage < 0)
{
perror("connect is err");
}
//3.服务器端不断发送数据,接受服务器转化后的数据
char buf[1024] = {0};
while (1)
{
//memset(buf,0,sizeof(buf));
fgets(buf, sizeof(buf), stdin);
if (strncmp(buf,"quit#",5)==0)
{
break;
}
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
send(fd, buf, sizeof(buf), 0);
flage = recv(fd, buf, sizeof(buf), 0);
if (flage < 0)
{
perror("recv is err");
}
else
{
fprintf(stdout, "%s\n", buf);
}
}
close(fd);
return 0;
}