套接字学习教程之三


7.6 辅助函数

  FreeBSD C 语言库包含了许多套接字编程的辅助函数。例如,在样例客户端中,我们硬性指定了 time.nist.govIP地址。但是我们并非总是知道 IP地址。甚至即使我们知道, 允许用户输入IP地址甚至域名 将使用我们的软件更有弹性。

7.6.1 gethostbyname

  域名是不能直接传送给任何套接字函数的, FreeBSD C 语言库携带了函数 gethostbyname(3)gethostbyname2(3),声明在netdb.h中。

struct hostent * gethostbyname(const char *name);
struct hostent * gethostbyname2(const char *name, int af);

  这两个函数都返回hostent结构指针,内含有关域的许多信息。对于我们的情况,结构体中的域 h_addr_list[0]指向长度 h_length字节的地址, 也按网络字节顺序存储。

  这允许我们建立一个要有弹性得多的──也要有用得多的 ──版本的daytime程序:

/*
 * daytime.c
 *
 * G. Adam Stanislav 编程
 * 2001年6月19日
 */
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

int main(int argc, char *argv[]) {
  register int s;
  register int bytes;
  struct sockaddr_in sa;
  struct hostent *he;
  char buf[BUFSIZ+1];
  char *host;

  if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket");
    return 1;
  }

  bzero(&sa, sizeof sa);

  sa.sin_family = AF_INET;
  sa.sin_port = htons(13);

  host = (argc > 1) ? (char *)argv[1] : "time.nist.gov";

  if ((he = gethostbyname(host)) == NULL) {
    perror(host);
    return 2;
  }

  bcopy(he->h_addr_list[0],&sa.sin_addr, he->h_length);

  if (connect(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
    perror("connect");
    return 3;
  }

  while ((bytes = read(s, buf, BUFSIZ)) > 0)
    write(1, buf, bytes);

  close(s);
  return 0;
}

  现在我们可以在命令行打一个域名(或者一个IP地址,两种方式都可以),程序将尝试连接daytime服务器。 否则,将仍然缺省为time.nist.gov。然后, 即使在使用缺省值的情形中我们将使用gethostbyname 而不是硬性指定192.43.244.18。这样,即使将来 IP地址变更,我们也能找到。

  由于从我们的本地计算机获取时间几乎不需要时间, 你可以一并运行daytime两次: 第一次从time.nist.gov取得时间,第二次从你自己的系统取得时间。然后你可以比较结果, 看看你的系统时钟到底怎么样:

% daytime ; daytime localhost


52080 01-06-20 04:02:33 50 0 0 390.2 UTC(NIST) * 
2001-06-20T04:02:35Z
%

  正如你看见的,我的系统比NIST时间快两秒钟。

7.6.2 getservbyname

  有时你不能确定某种服务该用什么端口。 函数getservbyname(3),也声明在 netdb.h中,此时就很上手:

struct servent * getservbyname(const char *name, const char *proto);

  结构体servent包含 s_port,这是正确的端口号, 已经按照网络字节顺序存储。

  假如我们不知道daytime服务的正确端口, 我们可以这样找到:

  struct servent *se;
  ...
  if ((se = getservbyname("daytime", "tcp")) == NULL {
    fprintf(stderr, "Cannot determine which port to use./n");
    return 7;
  }
  sa.sin_port = se->s_port;

  你通常知道端口。但是如何你正开发一个新协议, 你可能正在一个非正式端口上测试。有一天,你要注册那个协议和端口 (如果不在别处,至少要在你的 /etc/services里,那是 getservbyname查找的地方)。上面的代码就不再会返回错误,你就可以使用临时端口号。 一旦你已经将协议列入/etc/services, 你的软件不必重写代码也可以找到端口。


7.7 并发服务器

  不同于顺序服务器,并发服务器 就要能在一个时间为多个客户端提供服务。 例如,一个聊天服务器可能服务一个特定的客户端数小时 ──在停止为这个客户端服务之前服务器不能等待,除非是在等待一下个客户端到来之前的间隙才能等待。

  这需要在我们的流程图中做一个重要的更改:

  我们将提供服务守护进程移至它自己的服务进程。然而,因为每个子进程都继承所有打开的文件(套接字被像文件一样处理), 新进程不仅继承“accept()返回的句柄,” 那是指调用accept返回的套接字;新进程也继承 顶级套接字,这是顶级进程一开始打开的套接字。

  然而,服务进程不需要这个套接字, 应该立即关闭(close)它。同样的, 守护进程不再需要 accept()返回的套接字, 不仅应该,还必须 关闭(close)它──否则,那迟早会耗尽可用的文件描述符

  在服务进程完成服务之后,它将关闭accept()返回的套接字。它不会返回到accept,而是退出进程。

  在UNIX®上,一个进程并不真正的退出, 而是返回至父进程。典型情况中, 父进程等待(wait)子进程, 并取得一个返回值。但是,我们的守护进程 不能简单的停止或等待,那有违建立其它进程的整个目的。 但是如果从不使用wait, 它的子进程可能会成为僵尸── 不再有功用可仍然徘徊着。

  出于那样的原因,守护进程 需要在初始化守护进程阶段设置 信号处理程序。 至少要处理信号SIGCHLD,这样守护进程可以从系统清除僵尸返回值并释放僵尸占用的系统资源。

  这是现在我们的流程图包含一个进程信号框的原因,它不与任何其它框相连接。顺便说一句,许多服务器程序也处理SIGHUP, 作为超级用户发出的要求重读配置文件的信号。这允许我们不必终止或重启服务器程序就改变设置。

 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Delphi中,套(Socket)编程用到的基本类是TServerSocket与TClientSocket。这两个类全部位于ScktComp单元中。其类型定义如下: type TServerSocket = class (ScktComp.TCustomServerSocket); TClientSocket = class (ScktComp.TCustomSocket)。      在编写程序时,首先要对TServerSocket(在服务器端)与TClientSocket(在客户端)进行实例化。对于TServerSocket的对象,主要设置其服务类型(ServerType)与端口(Port)的属性,然后编写“OnClientRead”事件处理程序的代码,处理来自客户机的请求。如要启动服务器,设置TServerSocket对象的Active属性为真(即Active := True),如要停止服务器,则设置TServerSocket对象的Active属性为假(即Active := False)。      对于TClientSocket的对象,主要设置对方服务器的服务类型(ServerType)、端口(Port)以及IP地址(Address)的属性,之后编写“OnConnect与OnRead”事件处理程序的代码“OnConnect”事件处理程序用来检查与服务器连成功与否(必须在这里进行检查才有效),“OnRead”事件处理程序用来读取服务器发来的信息。如要连服务器,设置TClientSocket对象的Active属性为真(即Active := True;注意:检查连是否成功,必须在“OnConnect”事件处理程序中进行),如要断开与服务器的连,则设置TClientSocket对象的Active属性为假(即Active := False)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值