计算机网络编程 | 并发服务器代码实现(多进程/多线程)

简介: 计算机网络编程 | 并发服务器代码实现(多进程/多线程)

什么是并发服务器

当涉及到构建高性能的服务器应用程序时,我们通常会考虑使用并发服务器来处理多个客户端请求。在并发服务器中,多进程和多线程是两种常见的并发模型,它们都有各自的优点和适用场景。本文将介绍多进程和多线程并发服务器的基础知识。

多进程并发服务器

多进程并发服务器通过创建多个子进程来处理客户端请求。每个子进程是操作系统中独立运行的单位,拥有自己的内存空间和资源。当有新的客户端连接请求到达时,服务器创建一个新的子进程来处理该请求。子进程负责与客户端通信并提供所需的服务。

多进程并发服务器的优点是稳定性高。由于每个子进程都是相互独立的,一个子进程的崩溃或错误不会影响其他子进程的执行。这种独立性使得多进程并发服务器能够有效地隔离错误,提高服务器的可靠性。

然而,多进程并发服务器也有一些缺点。创建和管理多个进程需要消耗更多的系统资源,包括内存和CPU时间。进程间的通信也需要特殊的机制,例如管道或共享内存,以便在不同进程之间传递数据。此外,由于每个进程都有自己的内存空间,进程间的数据共享和同步可能会变得复杂。

多线程并发服务器

多线程并发服务器通过创建多个线程来处理客户端请求。线程是在进程内部运行的独立执行流,共享同一个进程的内存空间和资源。与多进程不同,多线程服务器不需要创建新的进程来处理请求,而是在同一个进程中创建多个线程。

多线程并发服务器的优点是资源消耗较少。与进程相比,线程的创建和切换开销更小,因为它们共享进程的资源。这使得多线程并发服务器更加轻量级,能够更高效地利用系统资源。

然而,多线程并发服务器也存在一些问题。首先,线程共享进程的内存空间,因此在多线程环境中访问共享数据需要特殊的同步机制,以避免竞态条件和数据不一致。其次,由于线程共享相同的地址空间,一个线程的错误可能会影响整个进程,导致服务器崩溃或不稳定。

选择适合的并发模型

在选择多进程还是多线程并发服务器时,需要根据具体的应用需求和性能要求进行权衡。以下是一些建议:

  • 如果稳定性和容错性是首要考虑因素,多进程并发服务器可能是更好的选择。每个子进程的独立性可以有效地隔离错误,提高服务器的可靠性。
  • 如果服务器需要处理大量的并发连接并需要更高的性能和资源利用率,多线程并发服务器可能更适合。线程的创建和切换开销相对较小,可以更高效地处理并发请求。
  • 如果同时需要稳定性和性能,可以考虑使用混合模型,即在每个进程中创建多个线程,以实现更好的负载平衡和资源利用率。

无论选择多进程还是多线程并发服务器,都需要注意正确处理并发访问共享数据的问题,使用适当的同步机制(如锁、信号量)来保证数据的一致性和正确性。

总结起来,多进程和多线程并发服务器是实现高性能服务器的常见方式。它们各有优劣,选择合适的并发模型需要考虑应用需求和性能要求,并注意处理并发访问共享数据的问题。

多进程并发服务器代码实现

使用多进程并发服务器时要考虑以下几点:

  • 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符);
  • 系统内创建进程个数(与内存大小相关);
  • 进程创建过多是否降低整体服务性能(进程调度);

server

/* server.c */
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 800
void do_sigchild(int num)
{
  while (waitpid(0, NULL, WNOHANG) > 0)
    ;
}
int main(void)
{
  struct sockaddr_in servaddr, cliaddr;
  socklen_t cliaddr_len;
  int listenfd, connfd;
  char buf[MAXLINE];
  char str[INET_ADDRSTRLEN];
  int i, n;
  pid_t pid;
  struct sigaction newact;
  newact.sa_handler = do_sigchild;
  sigemptyset(&newact.sa_mask);
  newact.sa_flags = 0;
  sigaction(SIGCHLD, &newact, NULL);
  listenfd = Socket(AF_INET, SOCK_STREAM, 0);
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(SERV_PORT);
  Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
  Listen(listenfd, 20);
  printf("Accepting connections ...\n");
  while (1) {
    cliaddr_len = sizeof(cliaddr);
    connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    pid = fork();
    if (pid == 0) {
      Close(listenfd);
      while (1) {
        n = Read(connfd, buf, MAXLINE);
        if (n == 0) {
          printf("the other side has been closed.\n");
          break;
        }
        printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
            ntohs(cliaddr.sin_port));
        for (i = 0; i < n; i++)
          buf[i] = toupper(buf[i]);
        Write(connfd, buf, n);
      }
      Close(connfd);
      return 0;
    } else if (pid > 0) {
      Close(connfd);
    } else
      perr_exit("fork");
  }
  Close(listenfd);
  return 0;
}

client

/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
  struct sockaddr_in servaddr;
  char buf[MAXLINE];
  int sockfd, n;
  sockfd = Socket(AF_INET, SOCK_STREAM, 0);
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
  servaddr.sin_port = htons(SERV_PORT);
  Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
  while (fgets(buf, MAXLINE, stdin) != NULL) {
    Write(sockfd, buf, strlen(buf));
    n = Read(sockfd, buf, MAXLINE);
    if (n == 0) {
      printf("the other side has been closed.\n");
      break;
    } else
      Write(STDOUT_FILENO, buf, n);
  }
  Close(sockfd);
  return 0;
}

多线程并发服务器代码实现

在使用线程模型开发服务器时需考虑以下问题:

  • 调整进程内最大文件描述符上限;
  • 线程如有共享数据,考虑线程同步;
  • 服务于客户端线程退出时,退出处理(退出值,分离态);
  • 系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU;

server

/* server.c */
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
struct s_info {
  struct sockaddr_in cliaddr;
  int connfd;
};
void *do_work(void *arg)
{
  int n,i;
  struct s_info *ts = (struct s_info*)arg;
  char buf[MAXLINE];
  char str[INET_ADDRSTRLEN];
  /* 可以在创建线程前设置线程创建属性,设为分离态,哪种效率高内? */
  pthread_detach(pthread_self());
  while (1) {
    n = Read(ts->connfd, buf, MAXLINE);
    if (n == 0) {
      printf("the other side has been closed.\n");
      break;
    }
    printf("received from %s at PORT %d\n",
        inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
        ntohs((*ts).cliaddr.sin_port));
    for (i = 0; i < n; i++)
      buf[i] = toupper(buf[i]);
    Write(ts->connfd, buf, n);
  }
  Close(ts->connfd);
}
int main(void)
{
  struct sockaddr_in servaddr, cliaddr;
  socklen_t cliaddr_len;
  int listenfd, connfd;
  int i = 0;
  pthread_t tid;
  struct s_info ts[256];
  listenfd = Socket(AF_INET, SOCK_STREAM, 0);
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(SERV_PORT);
  Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
  Listen(listenfd, 20);
  printf("Accepting connections ...\n");
  while (1) {
    cliaddr_len = sizeof(cliaddr);
    connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    ts[i].cliaddr = cliaddr;
    ts[i].connfd = connfd;
    /* 达到线程最大数时,pthread_create出错处理, 增加服务器稳定性 */
    pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
    i++;
  }
  return 0;
}

client

/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
  struct sockaddr_in servaddr;
  char buf[MAXLINE];
  int sockfd, n;
  sockfd = Socket(AF_INET, SOCK_STREAM, 0);
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
  servaddr.sin_port = htons(SERV_PORT);
  Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
  while (fgets(buf, MAXLINE, stdin) != NULL) {
    Write(sockfd, buf, strlen(buf));
    n = Read(sockfd, buf, MAXLINE);
    if (n == 0)
      printf("the other side has been closed.\n");
    else
      Write(STDOUT_FILENO, buf, n);
  }
  Close(sockfd);
  return 0;
}

618图书推荐

书籍是知识的海洋,计算机好书推荐

🔥🔥🔥618,清华社 IT BOOK 多得图书活动开始啦!活动时间为2023 年6 月7 日至6 月18 日,清华社为您精选多款高分好书,涵盖了 C++、Java、Python、前端、后端、数据库、算法与机器学习等多个IT 开发领域,适合不同层次的读者。全场5 折,扫码领券更有优惠哦!快来京东点击链接 IT BOOK多得查看详情吧!



相关文章
|
18天前
|
消息中间件 并行计算 安全
进程、线程、协程
【10月更文挑战第16天】进程、线程和协程是计算机程序执行的三种基本形式。进程是操作系统资源分配和调度的基本单位,具有独立的内存空间,稳定性高但资源消耗大。线程是进程内的执行单元,共享内存,轻量级且并发性好,但同步复杂。协程是用户态的轻量级调度单位,适用于高并发和IO密集型任务,资源消耗最小,但不支持多核并行。
35 1
|
1天前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
3天前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
13 2
|
25天前
|
机器学习/深度学习 网络架构 计算机视觉
目标检测笔记(一):不同模型的网络架构介绍和代码
这篇文章介绍了ShuffleNetV2网络架构及其代码实现,包括模型结构、代码细节和不同版本的模型。ShuffleNetV2是一个高效的卷积神经网络,适用于深度学习中的目标检测任务。
61 1
目标检测笔记(一):不同模型的网络架构介绍和代码
|
5天前
|
调度 Python
深入浅出操作系统:进程与线程的奥秘
【10月更文挑战第28天】在数字世界的幕后,操作系统悄无声息地扮演着关键角色。本文将拨开迷雾,深入探讨操作系统中的两个基本概念——进程和线程。我们将通过生动的比喻和直观的解释,揭示它们之间的差异与联系,并展示如何在实际应用中灵活运用这些知识。准备好了吗?让我们开始这段揭秘之旅!
|
15天前
|
Python
Python中的多线程与多进程
本文将探讨Python中多线程和多进程的基本概念、使用场景以及实现方式。通过对比分析,我们将了解何时使用多线程或多进程更为合适,并提供一些实用的代码示例来帮助读者更好地理解这两种并发编程技术。
|
18天前
|
消息中间件 并行计算 安全
进程、线程、协程
【10月更文挑战第15天】进程、线程和协程是操作系统中三种不同的执行单元。进程是资源分配和调度的基本单位,每个进程有独立的内存空间;线程是进程内的执行路径,共享进程资源,切换成本较低;协程则更轻量,由用户态调度,适合处理高并发和IO密集型任务。进程提供高隔离性和安全性,线程支持高并发,协程则在资源消耗和调度灵活性方面表现优异。
42 2
|
24天前
|
算法 安全 调度
深入理解操作系统:进程与线程的管理
【10月更文挑战第9天】在数字世界的心脏跳动着的,不是别的,正是操作系统。它如同一位无形的指挥家,协调着硬件与软件的和谐合作。本文将揭开操作系统中进程与线程管理的神秘面纱,通过浅显易懂的语言和生动的比喻,带你走进这一复杂而又精妙的世界。我们将从进程的诞生讲起,探索线程的微妙关系,直至深入内核,理解调度算法的智慧。让我们一起跟随代码的脚步,解锁操作系统的更多秘密。
33 1
|
29天前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
29 3
|
10天前
|
Linux 调度
探索操作系统核心:进程与线程管理
【10月更文挑战第24天】在数字世界的心脏,操作系统扮演着至关重要的角色。它不仅是计算机硬件与软件之间的桥梁,更是管理和调度资源的大管家。本文将深入探讨操作系统的两大基石——进程与线程,揭示它们如何协同工作以确保系统运行得井井有条。通过深入浅出的解释和直观的代码示例,我们将一起解锁操作系统的管理奥秘,理解其对计算任务高效执行的影响。