Linux:多进程、多线程服务器的实现解析(有图有代码有真相!!!)

一、问题引入

阻塞型的网络编程接口


几乎所有的程序员第一次接触到的网络编程都是从 listen()、send()、recv()等接口开始的。使用这些接口可以很方便的构建服务器 /客户机的模型。
我们假设希望建立一个简单的服务器程序,实现向单个客户机提供类似于“一问一答”的内容服务。


我们注意到,大部分的 socket接口都是阻塞型的。所谓阻塞型接口是指系统调用(一般是 IO接口)不返回调用结果并让当前线程一直阻塞,只有当该系统

调用获得结果或者超时出错时才返回。实际上,除非特别指定,几乎所有的 IO接口 (包括 socket 接口 )都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用 send()的同时,线程将被

阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。这给多客户机、多业务逻辑的网络编程带来了挑战。这时,很多程序员可能会选择多线程的方式来解决这个问题。


二、多进程多线程

应对多客户机的网络应用,最简单的解决方式是在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进

程),这样任何一个连接的阻塞都不会影响其他的连接。具体使用多进程还是多线程,并没有⼀一个特定的模式。传统意义上,进程的开

销要远远大于线程,所以,如果需要同时为较多的客户机提供服务,则不推荐使用多进程;如果单个服务执行体需要消耗较多的 CPU 资源,譬如需要进行

大规模或长时间的数据运算或文件访问,则进程较为安全。通常,使用pthread_create () 创建新线程,fork() 创建新进程。



我们假设对上述的服务器 / 客户机模型,提出更高的要求,即让服务器同时为多个客户机提供一问一答的服务。于是有了如上的模型。
在上述的线程 / 时间图例中,主线程持续等待客户端的连接请求,如果有连接,则创建新线程,并在新线程中提供为前例同样的问答服务。
很多初学者可能不明白为何一个 socket 可以 accept 多次。实际上,socket的设计者可能特意为多客户机的情况留下了伏笔,让 accept() 能够返回一个新
的 socket。下面是 accept 接口的原型:输入参数 sockfd 是从 socket(),bind() 和 listen() 中沿用下来的 socket 句柄


值。执行完 bind() 和 listen() 后,操作系统已经开始在指定的端口处监听所有的连接请求,如果有请求,则将该连接请求加入请求队列。调用 accept() 接口
正是从 socket s 的请求队列抽取第一个连接信息,创建⼀一个与 socked同类的新的 socket 返回句柄。新的 socket 句柄即是后续 read() 和 recv() 的输入参
数。如果请求队列当前没有请求,则 accept() 将进⼊入阻塞状态直到有请求进入队列。


1、多进程服务器、客户端实现简单通信

fork_server.c:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
static int startup(const char *_ip,int _port)
{
  int sock=socket(AF_INET,SOCK_STREAM,0);
  if(sock<0)
  {
   perror("socket");
   return 3; 
  }
  struct sockaddr_in local;
  local.sin_family=AF_INET;
  local.sin_port=htons(_port);
  local.sin_addr.s_addr=inet_addr(_ip);
  if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
  {
    perror("bind");
	return 4;
  }
  if(listen(sock,10)<0)
  {
    perror("listen");
	return 5;
  }
}
static void usage(const char *proc)
{
  printf("usage:[local_ip] [local_port]",proc);
}
int main(int argc,char *argv[])
{
	if(argc!=3)
	{
      usage(argv[0]);
	  printf("usage");
	  return 1;
	}
	int listen_sock=startup(argv[1],atoi(argv[2]));
	struct sockaddr_in peer;
	socklen_t len=sizeof(peer);
	while(1)
	{
	  int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
	  if(new_sock>0)
	  {
	   pid_t id=fork();
	   if(id==0)
	   {
	     //child
		 close(listen_sock);
		 char buf[1024];
         while(1)
		 {
		   ssize_t s=read(new_sock,buf,sizeof(buf)-1);
		   if(s>0)
		   {
		     	buf[s]=0;
			printf("client say#%s\n",buf);
			 write(new_sock,buf,strlen(buf));
		   }
		   else if(s==0)
		   {
		     printf("client quick\n");
		   }
		   else
		   {
		    break;
		   }
		 }
		 close(new_sock);
		 exit(1);
	   }
	   else
	   {
	     //father
		 close(new_sock);
		 if(fork()>0)
		 {
		   exit(0);
		 }
	   }
	  }
	  else
	  {
	    perror("new_sock");
		return 2;
	  }
	}
  return 0;
}

fork_client.c:



#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
static void usage(const char *proc)
{
  printf("usage:[server_ip] [server_port]",proc);
}
int main(int argc,char *argv[])
{
	int sock=socket(AF_INET,SOCK_STREAM,0);
	if(sock<0)
	{
	 perror("socket");
	 return 1;
	}
	struct sockaddr_in server;
	server.sin_family=AF_INET;
	server.sin_port=htons(atoi(argv[2]));
	server.sin_addr.s_addr=inet_addr(argv[1]);
	
	if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0)
	{
	 perror("connect");
	 return 2;
	}
	char buf[1024];
	while(1)
	{
		printf("please enter:");
		fflush(stdout);
	 ssize_t s=read(0,buf,sizeof(buf)-1);
	 if(s>0)
	 {
	   buf[s-1]=0;
	   write(sock,buf,strlen(buf));
	   ssize_t _s=read(sock,buf,sizeof(buf)-1);
	   if(_s>0)
	   {
	     buf[_s]=0;
		 printf("server echo:%s\n",buf);
	   }
	 }
	}
	close(sock);
    return 0;
}


2、多线程实现简单服务器(远程登陆:telnet)

thread_server.c

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
void* handleRequest(void* arg)
{
	char buf[10240];
  int sock=(int)arg;
  while(1)
  {
    ssize_t s=read(sock,buf,sizeof(buf)-1);
	//success
	if(s>0)
	{
	  buf[s]=0;
	  printf("%s\n",buf);
	  const char *msg= "HTTP/1.1 200 OK\r\n\r\n<html><h1>This is title</h1></html>\r\n";
	  write(sock,msg,strlen(msg));
	  break;
	}
	else if(s==0)   
	{
	printf("client is quit!\n");
	break;
	}
	else
	{
	  perror("read");
	  break;
	}
  }
  close(sock);
}
int startup(const char *_ip,int _port)
{
  //create socket
  int sock=socket(AF_INET,SOCK_STREAM,0);
  if(sock<0)
  {
   perror("socket");
   return 2;
  }
  int flag=1;
  setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));
  //bind
  struct sockaddr_in local;
  local.sin_family=AF_INET;
  local.sin_port=htons(_port);
  local.sin_addr.s_addr=inet_addr(_ip);
  if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
  {
    perror("bind");
	return 3;
  }
  //listen
  if(listen(sock,10)<0)
  {
    perror("listen");
	return 4;
  }
  return sock;
}
static void usage(char *proc)
{
  printf("usage:%s [server_ip] [server_port]",proc);
}
int main(int argc, char *argv[])
{
	if(argc!=3)
	{
	  usage(argv[0]);
	  return 1;
	}
	int listen_sock=startup(argv[1],atoi(argv[2]));
	struct sockaddr_in peer;
	socklen_t len=sizeof(peer);
	while(1)
	{
	  int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len); 
	  if(new_sock<0)
	  {
	    perror("accept");
		continue;
	  }
	  printf("client ip:%s,port:%d\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));
      pthread_t id;
	  pthread_create(&id,NULL,handleRequest,(void*)new_sock);
      pthread_detach(id);
	}
  return 0;
}






  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的多进程嵌套多线程的例子,其中父进程创建两个子进程,每个子进程再创建两个线程: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> void *thread_func(void *arg) { int tid = *(int *)arg; printf("Thread %d is running in Process %d\n", tid, getpid()); return NULL; } int main() { pid_t pid; int i, j, k; for (i = 0; i < 2; i++) { pid = fork(); if (pid < 0) { perror("fork error"); exit(EXIT_FAILURE); } else if (pid == 0) { printf("Child Process %d is running\n", getpid()); for (j = 0; j < 2; j++) { pthread_t tid; int ret; int *arg = (int *)malloc(sizeof(int)); *arg = j; ret = pthread_create(&tid, NULL, thread_func, arg); if (ret != 0) { perror("pthread_create error"); exit(EXIT_FAILURE); } } for (k = 0; k < 2; k++) { pthread_join(tid, NULL); } printf("Child Process %d is exiting\n", getpid()); exit(EXIT_SUCCESS); } } for (i = 0; i < 2; i++) { wait(NULL); } printf("Parent Process %d is exiting\n", getpid()); exit(EXIT_SUCCESS); } ``` 在这个例子中,父进程创建两个子进程,每个子进程再创建两个线程。每个线程打印出它所属的进程和线程编号。父进程在两个子进程都结束后退出。运行程序,可以看到输出结果类似于: ``` Child Process 13063 is running Thread 0 is running in Process 13063 Thread 1 is running in Process 13063 Child Process 13064 is running Thread 0 is running in Process 13064 Thread 1 is running in Process 13064 Child Process 13063 is exiting Thread 0 is running in Process 13064 Thread 1 is running in Process 13064 Child Process 13064 is exiting Parent Process 13062 is exiting ``` 可以看到,每个进程都创建了两个线程,并且线程运行的顺序是不确定的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值