Linux并发服务器编程之多线程并发服务器

转载:http://blog.csdn.net/qq_29227939/article/details/53782198

上一篇文章使用fork函数实现了多进程并发服务器,但是也提到了一些问题:

  1. fork是昂贵的。fork时需要复制父进程的所有资源,包括内存映象、描述字等;
  2. 目前的实现使用了一种写时拷贝(copy-on-write)技术,可有效避免昂贵的复制问题,但fork仍然是昂贵的;
  3. fork子进程后,父子进程间、兄弟进程间的通信需要进程间通信IPC机制,给通信带来了困难;
  4. 多进程在一定程度上仍然不能有效地利用系统资源;
  5. 系统中进程个数也有限制。

  下面就介绍实现并发服务器的另外一种方式,使用多线程实现。多线程有助于解决以上问题。

线程基础

  关于线程的概念就不介绍了,先了解一下linux下线程的一些基本操作。

线程基础函数

  • pthread_create 创建线程

  pthread_create 函数用于创建新线程。当一个程序开始运行时,系统产生一个称为初始线 程或主线程的单个线程。额外的线程需要由 pthread_create 函数创建。 pthread_create 函数原型如下:

#include <pthread.h> 
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg); 
  • 1
  • 2

  如果新线程创建成功,参数 tid 返回新生成的线程 ID。一个进程中的每个线程都由一个 线程 ID 标识,其类型为 pthread_t。attr 指向线程属性的指针。每个线程有很多属性包括:优 先级、起始栈大小、是否是守护线程等等。通常将 attr 参数的值设为 NULL,这时使用系统 默认的属性。 
  但创建完一个新的线程后,需要说明它将执行的函数。函数的地址由参数 func 指定。该函数必须是一个静态函数,它只有一个通用指针作为参数,并返回一个通用指针。该执行函 数的调用参数是由 arg 指定,arg 是一个通用指针,用于往 func 函数中传递参数。如果需要传递多个参数时,必须将它们打包成一个结构,然后让 arg 指向该结构。线程以调用该执行 函数开始。 
  如果函数调用成功返回 0,出错则返回非 0。

  常见的返回错误值:

EAGAIN:超过了系统线程数目的限制。
ENOMEN:没有足够的内存产生新的线程。
EINVAL:无效的属性attr值。
  • 1
  • 2
  • 3

  示例代码:

#include <pthread.h>
#include <stdio.h>
pthread_t  tid;
void *ex()
{printf("this is a thread");
}
void main()
{pthread_create(&tid,NULL,ex,NULL);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  给线程传递参数:

void *function(void *arg); 
struct ARG { int connfd; int other;   //other data }; void main()  { struct ARG arg; int connfd,sockfd; pthread_t tid; //...While(1) {     if((connfd = accept(sockfd,NULL,NULL))== -1) { //handle exception                    } arg.connfd = connfd; if(pthread_create(&tid, NULL, funtion, (void *)&arg)) { // handle exception } } } void *funtion(void *arg) { struct  ARG info; info.connfd = ((struct ARG *)arg) -> connfd; info.other = ((struct ARG *)arg) -> other; //… close(info.connfd); pthread_exit(NULL); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • pthread_join 
      看这个函数首先提出一个概念,线程的类型。线程分为两类:可联合的和分离的。

    1. 默认情况下线程都是可联合的。可联合的线程终止 时,其线程 ID 和终止状态将保留,直到线程调用 pthread_join 函数。
    2. 而分离的线程退出后, 系统将释放其所有资源,其他线程不能等待其终止。如果一个线程需要知道另一个线程什么 时候终止,最好保留第二个线程的可联合性。

  pthread_join 函数与进程的 waitpid 函数功能类似,等待一个线程终止。 
pthread_join 函数原型如下:

#inlcude <pthread.h>
int pthread_join(pthread_t tid, void **status); 
  • 1
  • 2

  参数 tid 指定所等待的线程 ID。该函数必须指定要等待的线程,不能等待任一个线程结束。要求等待的线程必须是当前进程的成员,并且不是分离的线程或守护线程。 
  几个线程不 能同时等待一个线程完成,如果其中一个成功调用 pthread_join 函数,则其他线程将返回 ESRCH 错误。 
  如果等待的线程已经终止,则该函数立即返回。如果参数 status 指针非空,则 指向终止线程的退出状态值。 
  该函数如果调用成功则返回 0,出错时返回正的错误码。

  • pthread_detach 
      pthread_detach 函数将指定的线程变成分离的。 pthread_detach 函数原型如下:
#inlcude <pthread.h> 
int pthread_detach(pthread_t tid) ;
  • 1
  • 2

  参数 tid 指定要设置为分离的线程 ID。

  • pthread_self 
      每一个线程都有一个 ID,pthread_self 函数返回自己的线程 ID。 pthread_self 函数原型如下:
#inlcude <pthread.h> 
pthread_t pthread_self(void); 
  • 1
  • 2

  参数 tid 指定要设置为分离的线程 ID。 函数返回调用函数的线程 ID。 
  例如,线程可以通过如下语句,将自己设为可分离的:

pthread_detach(pthread_self()); 
  • 1
  • pthread_exit 
      函数 pthread_exit 用于终止当前线程,并返回状态值,如果当前线程是可联合的,则其 退出状态将保留。 pthread_exit函数原型如下:
#include <pthread.h> 
void pthread_exit(void *status);
  • 1
  • 2

  参数 status 指向函数的退出状态。这里的 status 不能指向一个局部变量,因为当前线程 终止后,其所有局部变量将被撤销。 
  该函数没有返回值。 
  还有两种方法可以使线程终止:

  1. 启动线程的函数 pthread_create 的第三个参数返回。该返回值就是线程的终止状态。
  2. 如果进程的 main 函数返回或者任何线程调用了 exit 函数,进程将终止,线程将随之 终止。

  下面可以看一下多线程并发服务器的实例了,需要注意的是,线程建立后,父、子线程不需要关闭任何的描述符,因为线程中使用的描述符是共享进程中的数据。

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <pthread.h>  #define PORT 1234  
#define BACKLOG 5  
#define MAXDATASIZE 1000  void process_cli(int connfd, struct sockaddr_in client);  
void *function(void* arg);  
struct ARG {  int connfd;  struct sockaddr_in client;  
};  void main()  
{  int listenfd,connfd;  pthread_t  tid;  struct ARG *arg;  struct sockaddr_in server;  struct sockaddr_in client;  socklen_t  len;  if ((listenfd =socket(AF_INET, SOCK_STREAM, 0)) == -1) {  perror("Creatingsocket failed.");  exit(1);  }  int opt =SO_REUSEADDR;  setsockopt(listenfd,SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));  bzero(&server,sizeof(server));  server.sin_family=AF_INET;  server.sin_port=htons(PORT);  server.sin_addr.s_addr= htonl (INADDR_ANY);  if (bind(listenfd,(struct sockaddr *)&server, sizeof(server)) == -1) {  perror("Bind()error.");  exit(1);  }  if(listen(listenfd,BACKLOG)== -1){  perror("listen()error\n");  exit(1);  }  len=sizeof(client);  while(1)  {  if ((connfd =accept(listenfd,(struct sockaddr *)&client,&len))==-1) {  perror("accept() error\n");  exit(1);  }  arg = (struct ARG *)malloc(sizeof(struct ARG));  arg->connfd =connfd;  memcpy((void*)&arg->client, &client, sizeof(client));  if(pthread_create(&tid, NULL, function, (void*)arg)) {  perror("Pthread_create() error");  exit(1);  }  }  close(listenfd);  
}  void process_cli(int connfd, struct sockaddr_in client)  
{  int num;  char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];  printf("Yougot a connection from %s. \n ",inet_ntoa(client.sin_addr) );  num = recv(connfd,cli_name, MAXDATASIZE,0);  if (num == 0) {  close(connfd);  printf("Clientdisconnected.\n");  return;  }  cli_name[num - 1] ='\0';  printf("Client'sname is %s.\n",cli_name);  while (num =recv(connfd, recvbuf, MAXDATASIZE,0)) {  recvbuf[num] ='\0';  printf("Receivedclient( %s ) message: %s",cli_name, recvbuf);  int i;  for (i = 0; i <num - 1; i++) {  if((recvbuf[i]>='a'&&recvbuf[i]<='z')||(recvbuf[i]>='A'&&recvbuf[i]<='Z'))  {  recvbuf[i]=recvbuf[i]+ 3;  if((recvbuf[i]>'Z'&&recvbuf[i]<='Z'+3)||(recvbuf[i]>'z'))  recvbuf[i]=recvbuf[i]- 26;  }  sendbuf[i] =recvbuf[i];  }  sendbuf[num -1] = '\0';  send(connfd,sendbuf,strlen(sendbuf),0);  }  close(connfd);  
}  void *function(void* arg)  
{  struct ARG *info;  info = (struct ARG*)arg;  process_cli(info->connfd,info->client);  free (arg);  pthread_exit(NULL);  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113

线程安全性

  上面的示例代码服务器端的业务逻辑都比较简单,没有涉及到共享数据产生的同步问题。在某些情况下,我们需要多个线程共享全局数据,在访问这些数据时就需要用到同步锁机制。而在共享线程内的全局数据时,可以使用Linux提供的线程特定数据TSD解决。

同步机制

  在linux系统中,提供一种基本的进程同步机制—互斥锁,可以用来保护线程代码中共享数据的完整性。 
  操作系统将保证同时只有一个线程能成功完成对一个互斥锁的加锁操作。 
  如果一个线程已经对某一互斥锁进行了加锁,其他线程只有等待该线程完成对这一互斥锁解锁后,才能完成加锁操作。

互斥锁函数

pthread_mutex_lock(pthread_mutex_t  *mptr)
  • 1

参数说明:

mptr:指向互斥锁的指针。
该函数接受一个指向互斥锁的指针作为参数并将其锁定。如果互斥锁已经被锁定,调用者将进入睡眠状态。函数返回时,将唤醒调用者。
如果互斥锁是静态分配的,就将mptr初始化为常值PTHREAD_MUTEX_INITIALIZER。 
  • 1
  • 2
  • 3

  锁定成功返回0,否则返回错误码。

pthread_mutex_unlock(pthread_mutex_t  *mptr);
  • 1

  用于互斥锁解锁操作。成功返回0,否则返回错误码。

示例代码:

#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int myglobal;
pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
void *thread_function(void *arg) {int i, j;for (i = 0; i < 5; i++) {pthread_mutex_lock(&mymutex);j = myglobal;j = j + 1;printf(".");fflush(stdout);sleep(1);myglobal = j;pthread_mutex_unlock(&mymutex);}return NULL;
}
int main(void) {pthread_t mythread;int i;if (pthread_create(&mythread, NULL, thread_function, NULL)) {printf("error creating thread.");abort();}for (i = 0; i < 5; i++) {pthread_mutex_lock(&mymutex);myglobal = myglobal + 1;pthread_mutex_unlock(&mymutex);printf("o");fflush(stdout);sleep(1);}if (pthread_join(mythread, NULL)) {printf("error joining thread.");abort();}printf("\nmyglobal equals %d\n", myglobal);exit(0);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

运行效果

线程私有数据

  在多线程环境里,应避免使用静态变量。在 Linux 系统中提供 了线程特定数据(TSD)来取代静态变量。它类似于全局变量,但是,是各个线程私有的, 它以线程为界限。TSD 是定义线程私有数据的惟一方法。同一进程中的所有线程,它们的同 一特定数据项都由一个进程内惟一的关键字 KEY 来标志。用这个关键字,线程可以存取线程私有数据。 在线程特定数据中通常使用四个函数。

  • pthread_key_create
#include <pthread.h> 
int pthread_key_create(pthread_key_t *key, void (* destructor)(void *value)); 
  • 1
  • 2

  pthread_key_create 函数在进程内部分配一个标志 TSD 的关键字。 
  参数 key 指向创建的关 键字,该关键字对于一个进程中的所有线程是惟一的。所以在创建 key 时,每个进程只能调 用一次创建函数 pthread_key_create。在 key 创建之前,所有线程的关键字值是 NULL。一旦 关键字被建立,每个线程可以为该关键字绑定一个值。这个绑定的值对于线程是惟一的,每 个线程独立维护。 
   参数 destructor 是一个可选的析构函数,可以和每个关键字联系起来。如果一个关键字 的 destructor 函数不为空,且线程为该关键字绑定了一个非空值,那么在线程退出时,析构函 数将会被调用。对于所有关键字的析构函数,执行顺序是不能指定的。 
  该函数正常执行后返回值为 0,否则返回错误码。

  • pthread_once
#include <pthread.h> 
int pthread_once(pthread_once_t *once, void (*init) (void)); 
  • 1
  • 2

  pthread_once 函数使用 once 参数所指的变量,保证每个进程只调用一次 init 函数。通常 once 参数取常量 PTHREAD_ONCE_INIT,它保证每个进程只调用一次 init 函数。 
  该函数正常执行后返回值为 0,否则返回错误码。

  • pthread_setspecific
#include <pthread.h> 
int pthread_setspecific(pthread_key_t key, const void *value);
  • 1
  • 2

  pthread_setspecific 函数为 TSD 关键字绑定一个与本线程相关的值。 
  参数 key 是 TSD 关 键字。 
  参数 value 是与本线程相关的值。value 通常指向动态分配的内存区域。 
  该函数正常执行后返回值为 0,否则返回错误码。

  • pthread_getspecific
#include <pthread.h> 
void * pthread_getspecific(pthread_key_t key); 
  • 1
  • 2

  pthread_getspecific 函数获取与调用线程相关的 TSD 关键字所绑定的值。 
  参数 key 是 TSD 关键字。 
  该函数正常执行后返回与调用线程相关的 TSD 关键字所绑定的值。否则返回 NULL。

  线程安全性代码示例:

#include <stdio.h>
#include <pthread.h>
pthread_key_t   key;
void echomsg(int t)
{printf("destructor excuted in thread %d,param=%d\n", pthread_self(), t);
}
void * child1(void *arg)
{int tid = pthread_self();printf("thread1 %d enter\n", tid);pthread_setspecific(key, (void *)tid);sleep(2);printf("thread1 %d key’s %d\n", tid, pthread_getspecific(key));sleep(5);
}
void * child2(void *arg)
{int tid = pthread_self();printf("thread2 %d enter\n", tid);pthread_setspecific(key, (void *)tid);sleep(1);printf("thread2 %d key’s %d\n", tid, pthread_getspecific(key));sleep(5);
}
int main(void)
{pthread_t tid1, tid2;printf("hello\n");pthread_key_create(&key, (void *)echomsg);pthread_create(&tid1, NULL, child1, NULL);pthread_create(&tid2, NULL, child2, NULL);sleep(10);pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_key_delete(key);printf("main thread exit\n");return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

运行结果

小结

  使用多线程实现并发服务器的优点是线程的开销小,切换容易。但是由于线程共享相同 的内存区域,所以在对共享数据的进行操作时,要注意同步问题。其中线程特定数据虽然实现起来比较烦琐,但是它是将一个非线程安全 函数转换成线程安全函数的常用方法。 
  除此之外,还可以通过改变调用函数参变量的方式实现线程的安全性,这里不作介绍。 
  下一篇文章将介绍另外一种实现并发服务器的方法:I/O 多路复用。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384354.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode(977)有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 示例 1&#xff1a; 输入&#xff1a;nums [-4,-1,0,3,10] 输出&#xff1a;[0,1,9,16,100] 解释&#xff1a;平方后&#xff0c;数组变为 […

IO多路复用之select全面总结(必看篇)

转载&#xff1a;http://www.jb51.net/article/101057.htm 1、基本概念 IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取&#xff0c;它就通知该进程。IO多路复用适用如下场合&#xff1a; &#xff08;1&#xff09;当客户处理多个描述字时&#xff08;一般…

leetcode(283)移动零

283. 移动零 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 示例: 输入: [0,1,0,3,12] 输出: [1,3,12,0,0] 说明: 必须在原数组上操作&#xff0c;不能拷贝额外的数组。 尽量减少操作次数。 方法一&#xff1…

exec函数族实例解析

转载&#xff1a;http://www.cnblogs.com/blankqdb/archive/2012/08/23/2652386.html fork()函数通过系统调用创建一个与原来进程(父进程)几乎完全相同的进程(子进程是父进程的副本&#xff0c;它将获得父进程数据空间、堆、栈等资源的副本。注意&#xff0c;子进程持有的是上述…

c/c++错题总结

1.类名 对象名 默认调用“对象名()”这个构造函数&#xff0c;在栈内存中存在对象名&#xff0c;在堆内存中存在实际对象&#xff1b; 2.类名 对象名(一个或以上个参数) 默认调用相应的构造函数&#xff0c;在栈内存中存在对象名&#xff0c;在堆内存中也是存在实际对象的&a…

c++程序编译过程

c程序编译分成四个过程&#xff1a;编译预处理&#xff0c;编译&#xff0c;汇编&#xff0c;链接 编译预处理&#xff1a;处理以#为开头 编译&#xff1a;将.cpp文件翻译成.s汇编文件 汇编&#xff1a;将.s汇编文件翻译成机器指令.o文件 链接&#xff1a;汇编生产的目标文件.o…

C++ template —— 动多态与静多态(六)

转载&#xff1a;http://www.cnblogs.com/yyxt/p/5157517.html 前面的几篇博文介绍了模板的基础知识&#xff0c;并且也深入的讲解了模板的特性。接下来的博文中&#xff0c;将会针对模板与设计进行相关的介绍。 ------------------------------------------------------------…

计算机的网络体系以及参考模型

计算机的网络体系以及参考模型一、OSI七层模型二、TCP/IP参考模型三、TCP/IP 五层参考模型四、OSI 模型和 TCP/IP 模型异同比较五、OSI 和 TCP/IP 协议之间的对应关系六、为什么 TCP/IP 去除了表示层和会话层&#xff1f;七、数据如何在各层之间传输&#xff08;数据的封装过程…

C++ 模板详解(二)

转载&#xff1a;http://www.cnblogs.com/gw811/archive/2012/10/25/2736224.html 四、类模板的默认模板类型形参 1、可以为类模板的类型形参提供默认值&#xff0c;但不能为函数模板的类型形参提供默认值。函数模板和类模板都可以为模板的非类型形参提供默认值。 2、类模板的类…

C++ 模板详解(一)

转载&#xff1a;http://www.cnblogs.com/gw811/archive/2012/10/25/2738929.html C模板 模板是C支持参数化多态的工具&#xff0c;使用模板可以使用户为类或者函数声明一种一般模式&#xff0c;使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。 模板是一种对类…

剑指Offer09. 用两个栈实现队列

class CQueue { public:stack<int> stack1,stack2;CQueue() {//初始化栈while(!stack1.empty()){stack1.pop();}while(!stack2.empty()){stack2.pop();}}void appendTail(int value) {stack1.push(value);}int deleteHead() {if(stack2.empty()){while(!stack1.empty()){…

rk3588 之启动

目录 uboot版本配置修改编译 linux版本配置修改编译 启动sd卡启动制作spi 烧录 参考 uboot 版本 v2024.01-rc2 https://github.com/u-boot/u-boot https://github.com/rockchip-linux/rkbin 配置修改 使用这两个配置即可&#xff1a; orangepi-5-plus-rk3588_defconfig r…

C++引用详解

转载&#xff1a;http://www.cnblogs.com/gw811/archive/2012/10/20/2732687.html 引用的概念 引用&#xff1a;就是某一变量&#xff08;目标&#xff09;的一个别名&#xff0c;对引用的操作与对变量直接操作完全一样。 引用的声明方法&#xff1a;类型标识符 &引用名目标…

Linux网络编程服务器模型选择之循环服务器

转载&#xff1a;http://www.cnblogs.com/lizhenghn/p/3617608.html 在网络程序里面&#xff0c;通常都是一个服务器处理多个客户机&#xff0c;为了出个多个客户机的请求&#xff0c;服务器端的程序有不同的处理方式。本节开始介绍Linux下套接字编程的服务器模型选择&#xff…

剑指Offer04. 二维数组中的查找

在一个 n * m 的二维数组中&#xff0c;每一行都按照从左到右递增的顺序排序&#xff0c;每一列都按照从上到下递增的顺序排序。请完成一个高效的函数&#xff0c;输入这样的一个二维数组和一个整数&#xff0c;判断数组中是否含有该整数。 相当于二叉搜索树,左孩子比根节点小&…

Linux网络编程服务器模型选择之并发服务器(上)

转载&#xff1a;http://www.cnblogs.com/lizhenghn/p/3617666.html 与循环服务器的串行处理不同&#xff0c;并发服务器对服务请求并发处理。循环服务器只能够一个一个的处理客户端的请求&#xff0c;显然效率很低。并发服务器通过建立多个子进程来实现对请求的并发处理。并发…

Linux网络编程服务器模型选择之并发服务器(下)

转载&#xff1a;http://www.cnblogs.com/lizhenghn/p/3618986.html 前面两篇文章&#xff08;参见&#xff09;分别介绍了循环服务器和简单的并发服务器网络模型&#xff0c;我们已经知道循环服务器模型效率较低&#xff0c;同一时刻只能为一个客户端提供服务&#xff0c;而且…

Linux网络编程服务器模型选择之IO复用循环并发服务器

转载&#xff1a;http://www.cnblogs.com/lizhenghn/p/3619091.html 在前面我们介绍了循环服务器&#xff0c;并发服务器模型。简单的循环服务器每次只能处理一个请求&#xff0c;即处理的请求是串行的&#xff0c;效率过低&#xff1b;并发服务器可以通过创建多个进程或者是线…

memcpy/memset函数的c语言实现

转载&#xff1a;http://blog.csdn.net/u011118276/article/details/46742341 1、memcpy 头文件&#xff1a;#include <string.h> 函数原型&#xff1a;void *memcpy(void *dest, const void *src, size_t n) 功能&#xff1a;将指针src指向的内存空间的n个字节复制到des…

计算机网络(一)计算机网络体系

计算机网络&#xff08;一&#xff09;计算机网络体系一、计算机网络概述概念功能组成分类二、体系结构和参考模型ISO/OSI模型物理层网络层传输层会话层表示层应用层OSI参考模型与TCP/IP参考模型OSI参考模型与TCP/IP参考模型不同5层参考模型一、计算机网络概述 概念 计算机网…