Linux Socket 网络编程 (IBM网站)第二章

第二章
编写套接字服务器的步骤
套接字服务器比客户机稍微复杂一点,这主要是因为服务器通常需要能够处理多个客户机请求。服务器基本上包括两个方面:处理每一个已建立的连接,以及要建立的连接。

在我们的例子中,以及在大多数情况下,都可以将特定连接的处理划分为支持函数,这看起来有点像 TCP 客户机所做的事情。我们将这个函数命名为 HandleClient()。

对新连接的监听与客户机有一点不同,其诀窍在于,最初创建并绑定到某个地址或端口的套接字并不是实际连接的套接字。这个最初的套接字的作用更像一个套接字工厂,它根据需要产生新的已连接的套接字。这种安排在支持派生的、线程化的或异步的分派处理程序(使用 select())函数)方面具有优势;不过对于这个入门级的教程,我们将仅按同步的顺序处理未决的已连接套接字。

TCP 回显服务器(应用程序设置)
我们的回显服务器与客户机非常类似,都以几个 #include 语句开始,并且定义了一些常量和错误处理函数:


          #include <stdio.h>
          #include <sys/socket.h>
          #include <arpa/inet.h>
          #include <stdlib.h>
          #include <string.h>
          #include <unistd.h>
          #include <netinet/in.h>

          #define MAXPENDING 5    /* Max connection requests */
          #define BUFFSIZE 32
          void Die(char *mess) { perror(mess); exit(1); }
         

常量 BUFFSIZE 限定了每次循环所发送的数据量。常量 MAXPENDING 限定了在某一时间将要排队等候的连接的数量(在我们的简单的服务器中,一次仅提供一个连接服务)。函数 Die() 与客户机中的相同。

TCP 回显服务器(连接处理程序)
用于回显连接的处理器程序很简单。它所做的工作就是接收任何可用的初始字节,然后循环发回数据并接收更多的数据。对于短的(特别是小于 BUFFSIZE) 的)回显字符串和典型的连接,while 循环只会执行一次。但是底层的套接字接口 (以及 TCP/IP) 不对字节流将如何在 recv() 调用之间划分做任何保证。


          void HandleClient(int sock) {
            char buffer[BUFFSIZE];
            int received = -1;
            /* Receive message */
            if ((received = recv(sock, buffer, BUFFSIZE, 0)) < 0) {
              Die("Failed to receive initial bytes from client");
            }
            /* Send bytes and check for more incoming data in loop */
            while (received > 0) {
              /* Send back received data */
              if (send(sock, buffer, received, 0) != received) {
                Die("Failed to send bytes to client");
              }
              /* Check for more data */
              if ((received = recv(sock, buffer, BUFFSIZE, 0)) < 0) {
                Die("Failed to receive additional bytes from client");
              }
            }
            close(sock);
          }
         

传入处理函数的套接字是已经连接到发出请求的客户机的套接字。一旦完成所有数据的回显,就应该关闭这个套接字。父服务器套接字被保留下来,以便产生新的子套接字,就像刚刚被关闭那个套接字一样。

TCP 回显服务器(配置服务器套接字)
就像前面所介绍的,创建套接字的目的对服务器和对客户机稍有不同。服务器创建套接字的语法与客户机相同,但结构 echoserver 是用服务器自己的信息而不是用它想与之连接的对等方的信息来建立的。您通常需要使用特殊常量 INADDR_ANY ,以支持接收服务器提供的任何 IP 地址上的请求;原则上,在诸如这样的多重主机服务器中,您可以相应地指定一个特定的 IP 地址。


          int main(int argc, char *argv[]) {
            int serversock, clientsock;
            struct sockaddr_in echoserver, echoclient;

            if (argc != 2) {
              fprintf(stderr, "USAGE: echoserver <port>\n");
              exit(1);
            }
            /* Create the TCP socket */
            if ((serversock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
              Die("Failed to create socket");
            }
            /* Construct the server sockaddr_in structure */
            memset(&echoserver, 0, sizeof(echoserver));       /* Clear struct */
            echoserver.sin_family = AF_INET;                  /* Internet/IP */
            echoserver.sin_addr.s_addr = htonl(INADDR_ANY);   /* Incoming addr */
            echoserver.sin_port = htons(atoi(argv[1]));       /* server port */
         

注意,无论是IP地址还是端口,它们都要被转换为用于 sockaddr_in 结构的网络字节顺序。转换回本机字节顺序的逆向函数是 ntohs() 和 ntohl()。这些函数在某些平台上不可用,但是为跨平台兼容性而使用它们是明智的。

TCP 回显服务器(绑定和监听)
虽然客户机应用程序 connect() 到某个服务器的 IP 地址和端口,但是服务器却 bind() 到它自己的地址和端口。


          /* Bind the server socket */
          if (bind(serversock, (struct sockaddr *) &echoserver,
                                       sizeof(echoserver)) < 0) {
            Die("Failed to bind the server socket");
          }
          /* Listen on the server socket */
          if (listen(serversock, MAXPENDING) < 0) {
            Die("Failed to listen on server socket");
          }
         

一旦帮定了服务器套接字,它就准备好可以 listen() 了。与大多数套接字函数一样,如果出现问题,bind() 和 listen() 函数都返回 -1。一旦服务器套接字开始监听,它就准备 accept() 客户机连接,充当每个连接上的套接字的工厂。

TCP 回显服务器(套接字工厂)
为客户机连接创建新的套接字是服务器的一个难题。函数 accept() 做两件重要的事情:返回新的套接字的套接字指针;填充指向echoclient(在我们的例子中) 的 sockaddr_in 结构。


            /* Run until cancelled */
            while (1) {
              unsigned int clientlen = sizeof(echoclient);
              /* Wait for client connection */
              if ((clientsock =
                   accept(serversock, (struct sockaddr *) &echoclient,
                          &clientlen)) < 0) {
                Die("Failed to accept client connection");
              }
              fprintf(stdout, "Client connected: %s\n",
                              inet_ntoa(echoclient.sin_addr));
              HandleClient(clientsock);
            }
          }
         

我们可以看到 echoclient 中已填充的结构,它调用访问客户机 IP 地址的 fprintf()。客户机套接字指针被传递给 HandleClient(),我们在本节的开头看到了这点。



结束语
在本教程中介绍的服务器和客户机很简单,但是它们展示了编写 TCP 套接字应用程序的每个基本要素。如果所传输的数据更复杂,或者应用程序中的对等方(客户机和服务器)之间的交互更高深,那就是另外的应用程序编程问题了。即使这样,所交换的数据仍然遵循 connect() 和 bind() 然后再 send() 和 recv() 的模式。

本教程没有谈及的一件事情是 UDP 套接字的使用,虽然我们在本教程开头的摘要中提到了。比起 UDP,TCP 使用得更普遍,不过同时理解 UDP 套接字以作为你编写应用程序的选择也是很重要的。

转载于:https://www.cnblogs.com/shelvenn/archive/2007/11/22/969077.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值