客户/服务器模型(二):模块化

基于 socket 的客户/服务器系统大多是类似的。虽然电子邮件、文件传输、远程登录和分布式数据库,以及其他的 Internet 服务在屏幕上显示的内容相异,但是它们的运作原理是一致的。一旦理解了一个 socket 流的客户/服务器系统,就可以理解大多数其他的系统。
客户连接到服务器,然后发送、接受或者交换数据,最后退出。该交互过程中主要包含了以下 3 个操作:
(1)服务器设立服务。
(2) 客户连接到服务器。
(3) 服务器和客户处理事务。
建立连接
基于流的系统需要建立连接。
(1)建立服务器端 socket
设立一个服务一般需要如下3个步骤 :
a.创建一个 socket

socket = socket(PF_ INET , SOCK_STREAM , 0);

b.给 socket 绑定 一个 地址

bind(sock , ιaddr ,sizeof(addr));

c.监听接入请求

listen(sock , queue_size)

模块化:

#include<sys/socket.h>
#include<string.h>
#include<unistd.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<strings.h>
#define HOSTLEN 256
#define BACKLOG 1
int make_serv_socket_q(int portnum,int backlog);
int make_serv_socket(int portnum)
{
  return make_serv_socket_q(portnum,BACKLOG);
}
int make_serv_socket_q(int portnum,int backlog)
{   
  struct sockaddr_in saddr;
  struct hostent *hp;
  char hostname[HOSTLEN];
  int sock_id;

  sock_id = socket(PF_INET,SOCK_STREAM,0);
  if(sock_id == -1)
    return -1;

  memset((void*)&saddr,0x00,sizeof(saddr));
  gethostname(hostname,HOSTLEN);
  hp = gethostbyname(hostname);

      bcopy((void*)hp->h_addr_list[0],(void *)&saddr.sin_addr,(size_t)hp->h_length);
  saddr.sin_port = htons((uint16_t)portnum);
  saddr.sin_family = AF_INET;
  if(bind(sock_id,(struct sockaddr*)&saddr,sizeof(saddr))!= 0)
    return -1;
  if(listen(sock_id,backlog)!=0)
    return -1;
  return sock_id;
}   

(2)建立到服务器的连接
基于流的网络客户连接到服务器包含以下两个步骤 :
a.创建一个sock e t

socket = socket(PF_INET , SOCK_STREAM , 0 )

b.使用该 soc k et 连 接到服务器

connect(sock , &serv_addr , sizeof(serv_addr))

模块化:

int connect_to_serv(char *host,int portnum)
{
  int sock;
  struct sockaddr_in servadd;
  struct hostent *hp;

  sock = socket(AF_INET,SOCK_STREAM,0);

  if(sock == -1)
    return -1;

  memset(&servadd,0x00,sizeof(servadd));
  hp = gethostbyname(host);

  if(hp == NULL)
    return -1;
  bcopy(hp->h_addr_list[0],(struct sockaddr*)&servadd.sin_addr,(size_t)hp->h_length);
  servadd.sin_family = AF_INET;
  servadd.sin_port = htons((uint16_t)portnum);

  if(connect(sock,(struct sockaddr*)&servadd,sizeof(servadd))!=0)
    return -1;

  return sock;
}

(3)客户/服务器的会话
a.一般的客户端
网络客户通常调用服务器来获得服务

int fd;
fd = connect_to_server(host , port);
if(fd == 1)
    exit(1) ;
talk_with_server(fd);//会话 
close(fd) ;

b.一般的服务器端

int sock , fd;
    sock = make_serv_socket(port);
    if(sock == -1)
        exit (1);
    while (1)
    {
        fd = accept(sock ,NULL , NULL);
        if(fd == -1)
            break;
        process_request(fd);//处理请求
        close(fd) ;
    }

服务器的设计问题: DIY 或代理
这里使用了两种服务器的设计方法:
·自己做 (Do It Yourself .DIY)–服务器接收请求,自己处理工作。
.代理–服务器接收请求,然后创建一个新进程来处理工作。
优缺点:
a.自己做用于快速简单的任务,对于一些服务器,效率最高的方法是服务
器自己来完成工作并且在 listen 中限制连接队列的大小
b.代理用于慢速的更加复杂的任务,服务器处理耗时的任务或等待资源时,需要代理来完成其工作,服务器可以使用 fork 创建一个新进程来处理每个请求。通过这种方式,服务器可以同时处理多个任务。
c.使用 SIGCHLD 来阻止僵尸 (zombie) 问题,除了等待于进程死亡外,父进程可以设置为接收表示子进程死亡的信号,当子进程退出或被终止时,内核发SIGCHLD 给父进程。但它不同于其他信号,默认时 SIGCHLD 是被忽略的。父进程可以为 SIGCHLD 设置一个信号处理函数,它可以调用 wait 。
———>但是程序运行到信号处理函数跳转时会中断系统调用 accept 。当 accept 被信号中断时,返回 -1. 然后设置 errno 到 EINTR 。代码中把 accept 返回的-1 作为错误,然后从主循环中跳出来。因此需要更改 maln 函数来区分真正的错误和被打断的系统调用所产生的错误。

———->父进程在运行信号处理函数时,其他子进程退出发出的信号到达导致Unix阻塞,但是并不缓存信号。从而,第二个信号被阻塞,而第三个信号丢失了。此时,如果还有其他的子进程退出,来自于这些子进程的信号也将丢失。信号处理函数只调用了 wait 一次,所以每次丢失一个信号意味着少调用了一次 walt ,这将产生更多的僵尸进程 (zombie) 。解决方法是在处理函数中调用 wait 足够多的次数来去除所有的终止进程。
在wait中调用while(waitpid(1 , NULL , WNOHANG);
其第一个参数表示它所要等待的进程 lD号 。 值-1 表示等待所有的子进程。第二个参数是指 向 整型值的指针,用来获取状态。服务器并不关心子进程中发生了什么,不过一个健壮的服务器可能用该信息来跟踪错误。waitpid 的最后一个参数表示选项。WNOHANG参数告诉waitpid: 如果没有僵尸进程,则不必等待。该循环直到所有退由的子进程都被等待了才停止 。即使多个子进程同时退出并产生了多个SIGCHLD,所有的这些信号都会被处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值