1. 程式人生 > >Linux網路程式設計之高階併發伺服器

Linux網路程式設計之高階併發伺服器

在上一節,我們介紹了Linux簡單的併發伺服器,通過在伺服器端建立多個子程序,來接收客戶端的請求,實現併發處理,但這種方式明顯有缺陷,伺服器並不知道客戶端請求的數量,所以事先建立的程序數不好確定。所以,這裡介紹三種高階併發伺服器模式。第一種是伺服器端統一accept,接收客戶端的到來,然後為每個客戶端分配一個程序去處理. 第二種是統一accept接收請求,然後為每個客戶端分配一個執行緒去處理。第三種建立多個執行緒去處理客戶端請求,每個執行緒獨自監聽客戶端的請求。顯然,第一種方案解決了簡單伺服器的併發問題。第二種方案其實是對第一種方案的改進,因為執行緒切換的開銷明顯要小於程序切換的開銷。第三種方案就是原來用程序去處理每個請求,現在換成用執行緒去處理,個人認為改進不是很大.

2. 高階併發伺服器演算法流程

(1)統一accept,多程序

  socket(...);

  bind(...);

  listen(...);

  while(1){

  accept(...);

  fork(...);//子程序

 }

 close(...);//關閉伺服器套接字

子程序:

 recv(...);

 process(...);

 send(...);

 close(...);//關閉客戶端

(2)統一accept,多執行緒

  socket(...);

  bind(...);

  listen(...);

  while(1){

  accept(...);

  pthread_create(....);  

 }

close(...);//關閉伺服器

執行緒1:

recv(....);

process(....);

send(...);

close(...);//關閉客戶端

(3)accept放入每個執行緒

 socket(...);

 bind(...);

 listen(...);

pthread_create(...);

pthread_join(...);//等待執行緒結束

close(...);//關閉伺服器

執行緒1:

Mutex_lock//互斥鎖

accept(...);

Mutex_unlock(...);

recv(...);

process(...);

send(...);

close(...);//客戶端

3. 相關例子

TCP伺服器:

(1)統一accept多程序

伺服器;

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <time.h>
/**
高階併發伺服器
TCP統一accept
當有客戶端到來時,為每個客戶端建立程序,然後每個程序處理客戶端的請求,動態的建立程序

**/
#define PORT 8888
#define BUFFERSIZE 1024
#define BACKLOG 2

static void handle(int sc){//處理客戶端的請求
  char buffer[BUFFERSIZE];
  time_t now;
  int size;
  memset(buffer,0,BUFFERSIZE);
   size=recv(sc,buffer,BUFFERSIZE,0);
  if(size>0&&!strncmp(buffer,"TIME",4)){//時間伺服器,當客戶端請求時間就把時間傳送給客戶端
      memset(buffer,0,BUFFERSIZE);
      now=time(NULL);
      sprintf(buffer,"%24s\r\n",ctime(&now));
     send(sc,buffer,strlen(buffer),0);
}
 close(sc);

}
int main(int argc,char*argv[]){
  int ret;
  int s;
  int sc;//用於伺服器與客戶端進行資料傳輸的套接字
  struct sockaddr_in server_addr;
  struct sockaddr_in client_addr;
  int len;
  len=sizeof(client_addr);
  //建立流式套接字
  s=socket(AF_INET,SOCK_STREAM,0);
  if(s<0){
    perror("socket error");
   return -1;
  }
 //將地址結構繫結到套接字描述符上去
 memset(&server_addr,0,sizeof(server_addr));
 server_addr.sin_family=AF_INET;
 server_addr.sin_port=htons(PORT);
 server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
 ret=bind(s,(struct sockaddr*)&server_addr,sizeof(server_addr));
 if(ret==-1){
   perror("bind error");
   return -1;
 }
ret=listen(s,BACKLOG);
if(ret<0){
   perror("listen error");
  return -1;
 }

while(1){
   sc=accept(s,(struct sockaddr*)&client_addr,&len);
  if(sc<0){
    continue;
  }
  if(fork()==0){//子程序
  handle(sc);
  close(s);//子程序關閉用於監聽的套接字
  }else{
    close(sc);//父程序關閉客戶端套接字

 }

}

}

客戶端:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFERSIZE 1024
int main(int argc,char*argv[]){
 int s;
 int ret;
 int size;
 struct sockaddr_in server_addr;
 char buffer[BUFFERSIZE];
 s=socket(AF_INET,SOCK_STREAM,0);
 if(s<0){
  perror("socket error");
  return -1;
}
bzero(&server_addr,sizeof(server_addr));
//將地址結構繫結到套接字
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(PORT);
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
//連線伺服器
 ret=connect(s,(struct sockaddr*)&server_addr,sizeof(server_addr));
 if(ret==-1){
  perror("connect error");
  return -1;
}
memset(buffer,0,BUFFERSIZE);
strcpy(buffer,"TIME");
size=send(s,buffer,strlen(buffer),0);
if(size<0){
  perror("send error");
  return -1;
}
memset(buffer,0,BUFFERSIZE);
size=recv(s,buffer,BUFFERSIZE,0);
if(size<0){
  perror("recv error");
  return;
}

printf("%s",buffer);
close(s);
return 0;
}

(2)統一accept多執行緒

伺服器:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <time.h>
/**
TCP併發伺服器,採用多執行緒,每次客戶端傳送請求,主執行緒建立一個子執行緒,用於處理客戶端的請求
執行緒具有速度快,佔用資源少,資料可以共享等優點 統一accept
**/
#define PORT 8888
#define BUFFERSIZE 1024
#define BACKLOG 2

static void handle(void* sc1){
 int sc;
 time_t now;
 char buffer[BUFFERSIZE]; 
 int size;
  sc=*((int*)sc1);//轉換成int指標,然後取值,sc1本身就是一個指標
 memset(buffer,0,BUFFERSIZE);
 size=recv(sc,buffer,BUFFERSIZE,0);
 if(size>0&&!strncmp(buffer,"TIME",4)){//請求伺服器的時間 
    memset(buffer,0,BUFFERSIZE);//清0
   now=time(NULL);
    sprintf(buffer,"%24s\r\n",ctime(&now));
    send(sc,buffer,strlen(buffer),0);//向客戶端傳送資料
}

close(sc);//關閉客戶端
}
int main(int argc,char*argv[]){
  int ret;
  int s;
  int sc;
  int len;
  pthread_t thread1;//定義執行緒名
   struct sockaddr_in server_addr,client_addr;
  len=sizeof(client_addr);
  //建立流式套接字
  s=socket(AF_INET,SOCK_STREAM,0);
  if(s<0){
    perror("socket error");
    return -1;
  }
  //將伺服器端的地址結構繫結到套接字描述符
  server_addr.sin_family=AF_INET;
  server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
  server_addr.sin_port=htons(PORT);
  ret=bind(s,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));
  if(ret<0){
    perror("bind error");
    return -1;
  }
//監聽
  ret=listen(s,BACKLOG);
  if(ret<0){
    perror("listen error");
    return -1;
  }

//接收客戶端的請求
for(;;){
    sc=accept(s,(struct sockaddr*)&client_addr,&len);
    if(sc<0){
     continue;
    } else {
   pthread_create(&thread1,NULL,handle,(void*)&sc);//建立執行緒,讓執行緒去處理,最後一個欄位是傳遞給執行緒處理函式handle的引數
   }
}

close(s);
}

客戶端:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFERSIZE 1024
int main(int argc,char*argv[]){
 int s;
 int ret;
 int size;
 struct sockaddr_in server_addr;
 char buffer[BUFFERSIZE];
 s=socket(AF_INET,SOCK_STREAM,0);
 if(s<0){
  perror("socket error");
  return -1;
}
bzero(&server_addr,sizeof(server_addr));
//將地址結構繫結到套接字
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(PORT);
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
//連線伺服器
 ret=connect(s,(struct sockaddr*)&server_addr,sizeof(server_addr));
 if(ret==-1){
  perror("connect error");
  return -1;
}
memset(buffer,0,BUFFERSIZE);
strcpy(buffer,"TIME");
size=send(s,buffer,strlen(buffer),0);
if(size<0){
  perror("send error");
  return -1;
}
memset(buffer,0,BUFFERSIZE);
size=recv(s,buffer,BUFFERSIZE,0);
if(size<0){
  perror("recv error");
  return;
}

printf("%s",buffer);
close(s);
return 0;
}

(3)單獨執行緒accept

伺服器:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <time.h>
#include <pthread.h>
/**
多執行緒TCP併發伺服器
主執行緒建立多個執行緒,然後每個執行緒獨立的accept和進行資料的傳送與接收
多執行緒,獨立accept
**/
#define PORT 8888
#define BUFFERSIZE 1024
#define BACKLOG 2
#define CLIENTNUM 3
static void *handle(void* s1){
 int s;
 int len;
 int sc;
 pthread_mutex_t alock=PTHREAD_MUTEX_INITIALIZER;
 char buffer[BUFFERSIZE];
 int size;
 struct sockaddr_in client_addr;
 s=*((int*)s1);//得到伺服器端的套接字描述符
//等待客戶端連線
 len=sizeof(client_addr);
 for(;;){//不停的迴圈等待客戶端的連線
   time_t now;
   //進入互斥區,每次一個執行緒處理客戶端
pthread_mutex_lock(&alock); 
 sc=accept(s,(struct sockaddr*)&client_addr,&len);
pthread_mutex_unlock(&alock);
memset(buffer,0,BUFFERSIZE);
size=recv(sc,buffer,BUFFERSIZE,0);
if(size>0&&!strncmp(buffer,"TIME",4)){
  memset(buffer,0,BUFFERSIZE);
  now=time(NULL);
  sprintf(buffer,"%24s\r\n",ctime(&now));
  send(sc,buffer,strlen(buffer),0);
}
close(sc);//關閉客戶端





}
int main(int argc,char*argv[]){
   int ret;
   int s;
   int len;
   int i;
   pthread_t thread[CLIENTNUM];
   struct sockaddr_in server_addr;
   //建立流式套接字
   s=socket(AF_INET,SOCK_STREAM,0);
   if(s<0){
    perror("socket error");
    return -1;
  }
 //將地址結構繫結到套接字上
  server_addr.sin_family=AF_INET;
  server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
  server_addr.sin_port=htons(PORT);
  ret=bind(s,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));
  if(ret==-1){
   perror("bind error");
   return -1;
  }
//監聽
  ret=listen(s,BACKLOG);
  if(ret==-1){
    perror("listen error");
     return -1;
 }

//建立3個執行緒,每個執行緒獨立的accept
  for(i=0;i<CLIENTNUM;i++){
    pthread_create(&thread[i],NULL,handle,(void*)&s);//執行緒的處理函式為handle,傳遞的引數為套接字描述符s    

  }
//while(1);
//等待執行緒結束
 for(i=0;i<CLIENTNUM;i++){
   pthread_join(thread[i],NULL);
 
 }
//關閉套接字
close(s);

return 0;
}

客戶端:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFERSIZE 1024
int main(int argc,char*argv[]){
 int s;
 int ret;
 int size;
 struct sockaddr_in server_addr;
 char buffer[BUFFERSIZE];
 s=socket(AF_INET,SOCK_STREAM,0);
 if(s<0){
  perror("socket error");
  return -1;
}
bzero(&server_addr,sizeof(server_addr));
//將地址結構繫結到套接字
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(PORT);
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
//連線伺服器
 ret=connect(s,(struct sockaddr*)&server_addr,sizeof(server_addr));
 if(ret==-1){
  perror("connect error");
  return -1;
}
memset(buffer,0,BUFFERSIZE);
strcpy(buffer,"TIME");
size=send(s,buffer,strlen(buffer),0);
if(size<0){
  perror("send error");
  return -1;
}
memset(buffer,0,BUFFERSIZE);
size=recv(s,buffer,BUFFERSIZE,0);
if(size<0){
  perror("recv error");
  return;
}

printf("%s",buffer);
close(s);
return 0;
}

總結:

統一accept,多程序伺服器是對簡單併發伺服器的改進,而由於程序的切換開銷比較大,所以又有了統一accept,多執行緒的併發伺服器。而單獨執行緒的accept是完全用執行緒來處理請求。這些都是TCP伺服器,由於UDP是突發的資料流,沒有三次握手,所以伺服器不能檢測到客戶端什麼時候傳送資料。以上三種高階併發伺服器仍然存在著效能問題,下一節介紹的I/O複用的迴圈伺服器是對這三種高階併發伺服器的改進。

相關推薦

Linux網路程式設計高階併發伺服器(轉)

1. 介紹 在上一節,我們介紹了Linux簡單的併發伺服器,通過在伺服器端建立多個子程序,來接收客戶端的請求,實現併發處理,但這種方式明顯有缺陷,伺服器並不知道客戶端請求的數量,所以事先建立的程序數不好確定。所以,這裡介紹三種高階併發伺服器模式。第一種是伺服器端統一

Linux網路程式設計高階併發伺服器

在上一節,我們介紹了Linux簡單的併發伺服器,通過在伺服器端建立多個子程序,來接收客戶端的請求,實現併發處理,但這種方式明顯有缺陷,伺服器並不知道客戶端請求的數量,所以事先建立的程序數不好確定。所以,這裡介紹三種高階併發伺服器模式。第一種是伺服器端統一accept,接收

Linux網路程式設計》: 併發伺服器的三種實現模型

迴圈伺服器與併發伺服器模型 伺服器設計技術有很多,按使用的協議來分有 TCP 伺服器和 UDP 伺服器,按處理方式來分有迴圈伺服器和併發伺服器。 在網路程式裡面,一般來說都是許多客戶對應一個伺服器(多對一),為了處理客戶的請求,對服務端的程式就提出了特殊的要求。 目前最

linux網路程式設計多程序併發伺服器

1)使用多程序併發伺服器考慮的因素:       (1)父程序描述最大檔案描述符的個數(父程序需要關閉accept返回的新檔案描述符)       (2)系統內可建立程序的個數(與記憶體大小相關)       (3)程序建立過多是否降低整體服務效能 2)多程序建立併發

linux網路程式設計伺服器

基於tcp協議的網路程式 1.所用函式: socket函式 socket()開啟一個網路埠,如果成功,就像open()一樣返回一個檔案描述符,應用程式可以像讀寫檔案一樣用read/write在網路上首發資料,如果調用出錯返回-1 bind函式:

Linux網路程式設計I/O複用迴圈伺服器

原文:http://blog.csdn.net/chenjin_zhong/article/details/7270166 1.介紹 在前幾節,我們介紹了迴圈伺服器,併發伺服器. 簡單的迴圈伺服器每次只能處理一個請求,即處理的請求是序列的。而併發伺服器可以通過建立多

linux 網路程式設計伺服器多執行緒限制

本文討論伺服器端多執行緒併發的操作和限制: 基於實驗結果和百度結果: 實驗基礎:伺服器和客戶端,伺服器為每個客戶端連線開闢執行緒,驗證伺服器多執行緒的最大支援數目 實驗環境:ubuntu 12.04 實驗結果:   1、一切系統預設設定的情況下,最多接收了381個連結,也即

Linux網路程式設計:TCP客戶/伺服器模型及基本socket函式

TCP客戶/伺服器模型 TCP連線的分組交換 在使用socket API的時候應該清楚應用程式和TCP協議棧是如何互動的: 呼叫connect()會發出SYN段(SYN是TCP報文段頭部的一個標誌位,置為1) 阻塞的read()函式返回0就表明收到了FIN段 客戶端呼叫c

linux 網路程式設計廣播

linux 網路程式設計之廣播 轉載:https://blog.csdn.net/qdlovecsj/article/details/8805483 廣播方式主要是指使用UDP套介面傳送資料,傳送資料的目標地址不是普通的地址,而是所指定網路的廣播地址。 什麼是廣播地址?是指IP地

Linux網路程式設計TCP程式設計

直接上程式碼如下所示: 1、標頭檔案定義 #ifndef __HEAD_NET_H__ #define __HEAD_NET_H__ #include <stdio.h> #include <string.h> #include <stdlib.h>

Linux網路程式設計IO模型

本文基於IO訪問中存在的兩個階段詳細介紹了Linux產生的五種IO模型。 上篇文章回顧: 小米開源監控Open-Falcon收錄汽車之家貢獻的Win版Agent 同步與非同步 同步是指一個任務的完成需要依賴另外一個任務時,只有等待被依賴的任務完成後

linux網路程式設計TCP狀態轉換及埠複用

(1)TCP狀態轉換圖               其中圖中分為三種狀態:實線代表的主動發起連線,虛線代表的被動發起連線,細實線代表的可以雙向發起連線的狀態。 主動發起連線方狀態變化:1)主動發起連線的一方傳送SYN標誌位,進入SYN_SENT狀態,等待接收被髮起連線方

嵌入式Linux網路程式設計,UDP迴圈伺服器,sendto(),recvfrom()

文章目錄 1,UDP迴圈伺服器模型: 2,UDP的使用場景 3,UDP資料傳送和接受sendto()、recvfrom() 4,UDP迴圈伺服器示例(可同時連線多個客戶端) 4.1,標頭檔案 net.h 4.2,客戶端程式碼cl

linux網路程式設計TCP介面詳解

socket int socket(int domain, int type, intprotocol);     監聽套接字描述符由socket建立,隨後用作bind和listen的第一個引數。一個伺服器通常僅建立一個監聽套接字,他在該伺服器的生命週期內一直存在。 c

linux網路程式設計用socket實現簡單客戶端和服務端的通訊(基於TCP)

一、介紹基於TCP協議通過socket實現網路程式設計常用API 1、讀者如果不是很熟悉,可以先看我之前寫的幾篇部落格,有socket,地址結構的理解,更加方便讀者理解 地址分別是: 2、socket(TCP)程式設計API簡介 1)、socket int s

linux網路程式設計RTP協議

以下內容取自: 本機通訊:https://www.cnblogs.com/lidabo/p/4160138.html(RTP協議傳輸)https://www.cnblogs.com/lidabo/p/4160145.html(RTP協議傳輸) 非本機:http://velep.com/arc

linux網路程式設計用socket實現簡單客戶端和服務端的通訊(基於UDP)

1、sendto和recvfrom函式介紹 sendto(經socket傳送資料) 相關函式 send , sendmsg,recv , recvfrom , socket 表頭檔案 #include < sys/types.h >#includ

Linux網路程式設計 大小端初探

    首先解釋一下大小端的概念。    大端(Big Endian),同時也是網路序,是資料在網路上傳輸的一種資料組織格式,其儲存的方式比較符合人們讀寫的習慣。    小端(Little Endian

網路程式設計:TCP伺服器的簡單實現

說到TCP伺服器,就不得不提socket程式設計,我們知道,在TCP/IP協議中,“IP地址+TCP或UDP埠號”唯一標識⽹絡通訊中的唯一一個程序,“IP地址+埠號”就稱為socket。 在TCP協

網路程式設計中設計併發伺服器,使用多程序與多執行緒有什麼區別?

網路程式設計中設計併發伺服器,使用多程序與多執行緒,請問有什麼區別? 答案一: 1,程序:子程序是父程序的複製品。子程序獲得父程序資料空間、堆和棧的複製品。 2,執行緒:相對與程序而言,執行緒是一個更加接近與執行體的概念,它可以與同進程的其他執行緒共享資料,但擁有自己的棧空