一个简单的服务器和客户机程序

这个例子将以最简单的方式运用套接字对服务器和客户机进行操作。服务器的全部工作就是等候建立一个连接,然后用那个连接产生的Socket创建一个InputStream以及一个OutputStream。在这之后,它从InputStream读入的所有东西都会反馈给OutputStream,直到接收到行中止(END)为止,最后关闭连接。
客户机连接与服务器的连接,然后创建一个OutputStream。文本行通过OutputStream发送。客户机也会创建一个InputStream,用它收听服务器说些什么(本例只不过是反馈回来的同样的字句)。
服务器与客户机(程序)都使用同样的端口号,而且客户机利用本地主机地址连接位于同一台机器中的服务器(程序),所以不必在一个物理性的网络里完成测试(在某些配置环境中,可能需要同真正的网络建立连接,否则程序不能工作——尽管实际并不通过那个网络通信)。
下面是服务器程序:

 

//: JabberServer.java
// Very simple server that just
// echoes whatever the client sends.
import java.io.*;
import java.net.*;

public class JabberServer {
  // Choose a port outside of the range 1-1024:
  public static final int PORT = 8080;
  public static void main(String[] args)
      throws IOException {
    ServerSocket s = new ServerSocket(PORT);
    System.out.println("Started: " + s);
    try {
      // Blocks until a connection occurs:
      Socket socket = s.accept();
      try {
        System.out.println(
          "Connection accepted: "+ socket);
        BufferedReader in =
          new BufferedReader(
            new InputStreamReader(
              socket.getInputStream()));
        // Output is automatically flushed
        // by PrintWriter:
        PrintWriter out =
          new PrintWriter(
            new BufferedWriter(
              new OutputStreamWriter(
                socket.getOutputStream())),true);
        while (true) {
          String str = in.readLine();
          if (str.equals("END")) break;
          System.out.println("Echoing: " + str);
          out.println(str);
        }
      // Always close the two sockets...
      } finally {
        System.out.println("closing...");
        socket.close();
      }
    } finally {
      s.close();
    }
  }
} ///:~

可以看到,ServerSocket需要的只是一个端口编号,不需要IP地址(因为它就在这台机器上运行)。调用accept()时,方法会暂时陷入停顿状态(堵塞),直到某个客户尝试同它建立连接。换言之,尽管它在那里等候连接,但其他进程仍能正常运行(参考第14章)。建好一个连接以后,accept()就会返回一个Socket对象,它是那个连接的代表。
清除套接字的责任在这里得到了很艺术的处理。假如ServerSocket构建器失败,则程序简单地退出(注意必须保证ServerSocket的构建器在失败之后不会留下任何打开的网络套接字)。针对这种情况,main()会“掷”出一个IOException违例,所以不必使用一个try块。若ServerSocket构建器成功执行,则其他所有方法调用都必须到一个try-finally代码块里寻求保护,以确保无论块以什么方式留下,ServerSocket都能正确地关闭。
同样的道理也适用于由accept()返回的Socket。若accept()失败,那么我们必须保证Socket不再存在或者含有任何资源,以便不必清除它们。但假若执行成功,则后续的语句必须进入一个try-finally块内,以保障在它们失败的情况下,Socket仍能得到正确的清除。由于套接字使用了重要的非内存资源,所以在这里必须特别谨慎,必须自己动手将它们清除(Java中没有提供“破坏器”来帮助我们做这件事情)。
无论ServerSocket还是由accept()产生的Socket都打印到System.out里。这意味着它们的toString方法会得到自动调用。这样便产生了:

 

ServerSocket[addr=0.0.0.0,PORT=0,localport=8080]
Socket[addr=127.0.0.1,PORT=1077,localport=8080]

大家不久就会看到它们如何与客户程序做的事情配合。
程序的下一部分看来似乎仅仅是打开文件,以便读取和写入,只是InputStream和OutputStream是从Socket对象创建的。利用两个“转换器”类InputStreamReader和OutputStreamWriter,InputStream和OutputStream对象已经分别转换成为Java 1.1的Reader和Writer对象。也可以直接使用Java1.0的InputStream和OutputStream类,但对输出来说,使用Writer方式具有明显的优势。这一优势是通过PrintWriter表现出来的,它有一个过载的构建器,能获取第二个参数——一个布尔值标志,指向是否在每一次println()结束的时候自动刷新输出(但不适用于print()语句)。每次写入了输出内容后(写进out),它的缓冲区必须刷新,使信息能正式通过网络传递出去。对目前这个例子来说,刷新显得尤为重要,因为客户和服务器在采取下一步操作之前都要等待一行文本内容的到达。若刷新没有发生,那么信息不会进入网络,除非缓冲区满(溢出),这会为本例带来许多问题。
编写网络应用程序时,需要特别注意自动刷新机制的使用。每次刷新缓冲区时,必须创建和发出一个数据包(数据封)。就目前的情况来说,这正是我们所希望的,因为假如包内包含了还没有发出的文本行,服务器和客户机之间的相互“握手”就会停止。换句话说,一行的末尾就是一条消息的末尾。但在其他许多情况下,消息并不是用行分隔的,所以不如不用自动刷新机制,而用内建的缓冲区判决机制来决定何时发送一个数据包。这样一来,我们可以发出较大的数据包,而且处理进程也能加快。
注意和我们打开的几乎所有数据流一样,它们都要进行缓冲处理。本章末尾有一个练习,清楚展现了假如我们不对数据流进行缓冲,那么会得到什么样的后果(速度会变慢)。
无限while循环从BufferedReader in内读取文本行,并将信息写入System.out,然后写入PrintWriter.out。注意这可以是任何数据流,它们只是在表面上同网络连接。
客户程序发出包含了"END"的行后,程序会中止循环,并关闭Socket。
下面是客户程序的源码:

 

//: JabberClient.java
// Very simple client that just sends
// lines to the server and reads lines
// that the server sends.
import java.net.*;
import java.io.*;

public class JabberClient {
  public static void main(String[] args)
      throws IOException {
    // Passing null to getByName() produces the
    // special "Local Loopback" IP address, for
    // testing on one machine w/o a network:
    InetAddress addr =
      InetAddress.getByName(null);
    // Alternatively, you can use
    // the address or name:
    // InetAddress addr =
    //    InetAddress.getByName("127.0.0.1");
    // InetAddress addr =
    //    InetAddress.getByName("localhost");
    System.out.println("addr = " + addr);
    Socket socket =
      new Socket(addr, JabberServer.PORT);
    // Guard everything in a try-finally to make
    // sure that the socket is closed:
    try {
      System.out.println("socket = " + socket);
      BufferedReader in =
        new BufferedReader(
          new InputStreamReader(
            socket.getInputStream()));
      // Output is automatically flushed
      // by PrintWriter:
      PrintWriter out =
        new PrintWriter(
          new BufferedWriter(
            new OutputStreamWriter(
              socket.getOutputStream())),true);
      for(int i = 0; i < 10; i ++) {
        out.println("howdy " + i);
        String str = in.readLine();
        System.out.println(str);
      }
      out.println("END");
    } finally {
      System.out.println("closing...");
      socket.close();
    }
  }
} ///:~

在main()中,大家可看到获得本地主机IP地址的InetAddress的三种途径:使用null,使用localhost,或者直接使用保留地址127.0.0.1。当然,如果想通过网络同一台远程主机连接,也可以换用那台机器的IP地址。打印出InetAddress addr后(通过对toString()方法的自动调用),结果如下:
localhost/127.0.0.1
通过向getByName()传递一个null,它会默认寻找localhost,并生成特殊的保留地址127.0.0.1。注意在名为socket的套接字创建时,同时使用了InetAddress以及端口号。打印这样的某个Socket对象时,为了真正理解它的含义,请记住一次独一无二的因特网连接是用下述四种数据标识的:clientHost(客户主机)、clientPortNumber(客户端口号)、serverHost(服务主机)以及serverPortNumber(服务端口号)。服务程序启动后,会在本地主机(127.0.0.1)上建立为它分配的端口(8080)。一旦客户程序发出请求,机器上下一个可用的端口就会分配给它(这种情况下是1077),这一行动也在与服务程序相同的机器(127.0.0.1)上进行。现在,为了使数据能在客户及服务程序之间来回传送,每一端都需要知道把数据发到哪里。所以在同一个“已知”服务程序连接的时候,客户会发出一个“返回地址”,使服务器程序知道将自己的数据发到哪儿。我们在服务器端的示范输出中可以体会到这一情况:
Socket[addr=127.0.0.1,port=1077,localport=8080]
这意味着服务器刚才已接受了来自127.0.0.1这台机器的端口1077的连接,同时监听自己的本地端口(8080)。而在客户端:
Socket[addr=localhost/127.0.0.1,PORT=8080,localport=1077]
这意味着客户已用自己的本地端口1077与127.0.0.1机器上的端口8080建立了 连接。
大家会注意到每次重新启动客户程序的时候,本地端口的编号都会增加。这个编号从1025(刚好在系统保留的1-1024之外)开始,并会一直增加下去,除非我们重启机器。若重新启动机器,端口号仍然会从1025开始增值(在Unix机器中,一旦超过保留的套按字范围,数字就会再次从最小的可用数字开始)。
创建好Socket对象后,将其转换成BufferedReader和PrintWriter的过程便与在服务器中相同(同样地,两种情况下都要从一个Socket开始)。在这里,客户通过发出字串"howdy",并在后面跟随一个数字,从而初始化通信。注意缓冲区必须再次刷新(这是自动发生的,通过传递给PrintWriter构建器的第二个参数)。若缓冲区没有刷新,那么整个会话(通信)都会被挂起,因为用于初始化的“howdy”永远不会发送出去(缓冲区不够满,不足以造成发送动作的自动进行)。从服务器返回的每一行都会写入System.out,以验证一切都在正常运转。为中止会话,需要发出一个"END"。若客户程序简单地挂起,那么服务器会“掷”出一个违例。
大家在这里可以看到我们采用了同样的措施来确保由Socket代表的网络资源得到正确的清除,这是用一个try-finally块实现的。
套接字建立了一个“专用”连接,它会一直持续到明确断开连接为止(专用连接也可能间接性地断开,前提是某一端或者中间的某条链路出现故障而崩溃)。这意味着参与连接的双方都被锁定在通信中,而且无论是否有数据传递,连接都会连续处于开放状态。从表面看,这似乎是一种合理的连网方式。然而,它也为网络带来了额外的开销。本章后面会介绍进行连网的另一种方式。采用那种方式,连接的建立只是暂时的。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索服务器
, socket
, 程序
, 子程序建立socket崩溃
, #try-finally
, PrintWriter
, 客户
一个
wincc客户机与服务器、瘦客户机服务器设置、客户机 服务器、客户机服务器模式、瘦客户机服务器搭建,以便于您获取更多的相关知识。

时间: 2024-11-03 21:15:50

一个简单的服务器和客户机程序的相关文章

MYSQL客户机程序4—在运行时获取连接参数

    6.5 客户机程序4-在运行时获取连接参数     现在我们有了容易修改的防止出现错误的连接代码,我们要了解一些如何做某些比使用NULL 连接参数更灵巧的事情,如在运行时允许用户指定一些值.客户机程序3由于固定连接参数方面的缺陷,要想更改那些值中的任何一个,都必须编辑源文件并重新编译.这十分不方便,特别是想使程序用于其他人时.在运行时指定连接参数的一个通用的方法是使用命令行选项.MySQL分发包中的程序接受两种形式的连接参数,如表6 - 1所示.    与标准的MySQL客户机程序一致,

客户机程序1—连接到MYSQL服务器

    6.2 客户机程序1-连接到服务器     我们的第一个MySQL客户机程序很简单:连接到服务器.断开,并退出.它本身并不是非常有用,但是必须知道如何做它,因为实际上用MySQL数据库做任何事情都必须与服务器连接.这是一个公用的操作,开发创建连接的代码是编写每个客户机程序都将使用的代码.除此之外,这项任务带给我们一些简单开始的事情.以后,我们可以增加这个客户机来做一些更有用的事情.我们第一个客户机程序的源代码突Щ绦?,包括一个单独的文件, client.c:    这个源文件以包括s

建立MYSQL客户机程序的一般过程

    MySQL提供用C编程语言编写的客户机库,可以用它编写访问MySQL的客户机程序.这个库定义了应用程序编程接口,包括下面的实用程序:    ■ 建立和终止与服务器会话的连接管理例程.    ■ 构造查询的例程,将例程发送到服务器,并处理结果.    ■ 当其他C API调用失败时,确定错误准确原因的状态和错误报告函数.    本章介绍如何用客户机库编写自己的程序.我们要记住的一些要点是,自己的程序与MySQL分发包中已有的客户机程序的一致性,代码的模块性和可重用性.本章假设您知道用C编程

Mysql入门系列:客户机程序5—MYSQL交互式查询程序

6.7 客户机程序5-交互式查询程序 让我们把迄今为止研究的诸多内容整理一下,编写一个简单的交互式客户机程序.它的功能包括可以进入查询,用通用目标查询处理程序process_query 执行查询,并用前面研究过的显示格式process_result_set() 显示查询结果. 客户机程序5在某些方面与mysql类似,虽然在几个特征上还是有所不同.客户机程序5在输入上有几个约束条件: ■ 每个输入行必须包括一个完整的查询. ■ 查询不会以分号或' g'为终止. ■ 不识别类似quit 的命令:而是

服务器和客户机是怎样连接的

2.3.1 服务器如何连接客户机    (1)出现如图2.34所示的[欢迎使用]界面.    (2)出现如图2.35所示的[监听程序配置,监听程序]界面.    (3)出现如图2.36所示的[监听程序配置,选择监听程序]界面.    (4)出现如图2.37所示的[监听程序配置,选择协议]界面.    (5)出现如图2.38所示的[监听程序配置,TCP/IP协议]界面.    (6)出现如图2.39所示的[监听程序配置,更多的监听程序]界面.2.3.2 客户机怎样连接服务器    管理客户机的工作

MYSQL客户机程序2—增加错误检查

    6.3 客户机程序2-增加错误检查    我们的第二个客户机程序将像第一个客户机程序一样,但是将修改它们,考虑错误出现的可能性."将错误检查作为读者的练习"这样的项目在编程文献中相当常见,这或许是因为检查错误相当令人讨厌.但是,我赞同这种观点,即MySQL客户机程序应该测试错误条件并适当地进行回应.由于某种原因,返回状态值的客户机库的调用做这些事情,而且您要承担忽略它们的后果.您最终还是要试图捕获由于没有错误检查而出现在程序中的错误,这些程序的用户会对程序运行如此不规律感到奇怪

MYSQL客户机程序3—产生连接代码模块

    6.4 客户机程序3-产生连接代码模块    对于我们的第三个客户机程序,即客户机程序3,通过将它封装到函数do_connect() 和do_disconnect() 中,将使连接和断开代码更加模块化,这样可以很容易地由多个客户机程序使用.这提供一种选择,可将连接代码精确地嵌入到main() 函数中.无论如何,对在应用程序过程中套用老调的任何代码都是一个好主意.将它放在可以通过多个程序访问的函数中,而不是在每个程序中都编写一遍.如果修正这个函数中的一个错误或对这个函数作了一些改进,则可只

客户机程序5—MYSQL交互式查询程序

    6.7 客户机程序5-交互式查询程序    让我们把迄今为止研究的诸多内容整理一下,编写一个简单的交互式客户机程序.它的功能包括可以进入查询,用通用目标查询处理程序process_query 执行查询,并用前面研究过的显示格式process_result_set() 显示查询结果.    客户机程序5在某些方面与mysql类似,虽然在几个特征上还是有所不同.客户机程序5在输入上有几个约束条件:    ■ 每个输入行必须包括一个完整的查询.    ■ 查询不会以分号或' \ g'为终止. 

服务器和客户机

网络最基本的精神就是让两台机器连接到一起,并相互"交谈"或者"沟通".一旦两台机器都发现了对方,就可以展开一次令人愉快的双向对话.但它们怎样才能"发现"对方呢?这就象在游乐园里那样:一台机器不得不停留在一个地方,侦听其他机器说:"嘿,你在哪里呢?" "停留在一个地方"的机器叫作"服务器"(Server):到处"找人"的机器则叫作"客户机"(Clie