精华内容
下载资源
问答
  • Java实现socket连接技巧 Socket通信几乎无时不在, 当然能够搜集到的信息也大量存在, 为了避免重复的劳作, 抽取了关于客户端和服务端的Socket, 并将其应用到适合JVM(LInux/Windows)或者DVM(Android)平台. 这个封装好...

    Java实现socket连接技巧

    Socket通信几乎无时不在, 当然能够搜集到的信息也大量存在, 为了避免重复的劳作, 抽取了关于客户端和服务端的Socket, 并将其应用到适合JVM(LInux/Windows)或者DVM(Android)平台. 这个封装好的API具有以下优势:
    1.满足具有Socket客户端需求的基本应用。
    2.满足具有Socket服务端的基本应用。具备并发能力, 能满足可设定个数客户端连接。
    本文的目的就是为了对Socket做一个封装, 方便客户端和服务端能直接使用Socket.封装好的API可以从下面获取
    Java Socket的封装
    其中src/中的是API源码; usage/目录是使用例程
    1客户端Socket API要点:
    1)客户端和指定的服务端相连, 因此客户端需要指明服务端对应的IP地址和端口号
    2)需要设置超时返回
    3)需要设置循环等待, 因为基本的Socket通信都是一来一回, 这种来回是通过阻塞来完成的。
    4)每个客户端连入服务端的时候, 都具备本身的ID, 类似于HTTP的Session, 这点容易被忽视。在多客户端连接中, 可以重点关注。本文提供的代码也有所提及, 但没有深入, 这点留给读者进一步发掘。
    代码参照/usage目录下的客户端测试代码, 注意, 先启动服务端,或者你拿着NetAssis 来测试也不错.
    2 服务端Socket API要点:
    1)服务端一般是被多个客户端连接的, 并且这些连接要求服务端做相似的处理, 因此这里就将这些相似处理, 抽象成一个SingleTask.java 接口, 具体的业务只需要实现这样的接口, 就可以并行的处理这些Task.
    2)不能无限制的让客户端连入Server, 因此需要设置上限值
    3)启动线程池, 每个线程针对一个具体的客户端连接
    4)注意接收阻塞位置, 需要设置死循环, 读不到数据将死守着等待(但别耽误其它线程处理事情)
    5)注意服务端要在死循环中侦听, 这样保证不错过任何来自客户端的请求。
    代码参照:/usage目录下的Server端测试代码。
    代码中注释很多,因此这里就不详细述说。
    我这儿整理了比较全面的JAVA相关的面试资料,需要领取面试资料的同学,请加群:473984645
    常见问题:

    1. 客户端Client的时候, 如果存在网络问题, 为了避免网络问题,造成客户端长时间等待, 此时要设置一个TimeOut
    clientSocket = new Socket(); 
    //这个TimeOut是连接等待时间
    clientSocket.connect(tcpAddress, timeOut);
    1. 当客户端已经连接, 每次收到一个数据, 客户端将启动处理, 假如服务器长久不发数据, 此时客户端会阻塞等待, 为了避免这个时候的等待, 可以设置一个超时
    clientSocket.setSoTimeout(timeOut);

    我这儿整理了比较全面的JAVA相关的面试资料,需要领取面试资料的同学,请加群:473984645

    Java使用socket实现一个多线程web服务器的方法

    除了服务器类,还包括请求类和响应类
    请求类:获取客户的HTTP请求,分析客户所需要的文件响应类:获得用户请求后将用户需要的文件读出,添加上HTTP应答头。发送给客户端。
    服务器处理类

    package com.lp.app.webserver;
    import java.io.*;
    import java.net.*;
    //使用Socket创建一个WEB服务器,本程序是多线程系统以提高反应速度。
    class WebServer
    { 
    public static String WEBROOT = "";//默认目录
     public static String defaultPage = "index.htm";//默认文件
     public static void main (String [] args) throws IOException
     {
     System.out.println ("服务器启动...\n");
      //使用8080端口提供服务
     ServerSocket server = new ServerSocket (8080);
     while (true)
     {
      //阻塞,直到有客户连接
      Socket sk = server.accept ();
      System.out.println ("Accepting Connection...\n");
      //启动服务线程
      new WebThread (sk).start ();
     }
     }
    }
    //使用线程,为多个客户端服务
    class WebThread extends Thread
    {
     private Socket sk;
     WebThread (Socket sk)
     {
      this.sk = sk;
     }
     //线程体
     public void run ()
     {
      InputStream in = null;
      OutputStream out = null;
      try{
      in = sk.getInputStream();
      out = sk.getOutputStream();
      //接收来自客户端的请求。
      Request rq = new Request(in);
      //解析客户请求
      String sURL = rq.parse();
      System.out.println("sURL="+sURL);
      if(sURL.equals("/"))
        sURL = WebServer.defaultPage;
      Response rp = new Response(out);
      rp.Send(sURL);
       }
      catch (IOException e)
      {
      System.out.println (e.toString ());
      }
      finally
      {
      System.out.println ("关闭连接...\n");
      //最后释放资源
      try{
       if (in != null)
       in.close ();
       if (out != null)
       out.close ();
       if (sk != null)
       sk.close ();
      }
      catch (IOException e)
      {
      }
      }
     }
    }

    请求类

    package com.lp.app.webserver;
    import java.io.*;
    import java.net.*;
    //获取客户的HTTP请求,分析客户所需要的文件
    public class Request{
     InputStream in = null;
     //获得输入流。这是客户的请求数据。
     public Request(InputStream input){
     this.in = input;
     }
     //解析客户的请求
     public String parse() {
     //从Socket读取一组数据
     StringBuffer requestStr = new StringBuffer(2048);
     int i;
     byte[] buffer = new byte[2048];
     try {
     i = in.read(buffer);
     }
     catch (IOException e) {
     e.printStackTrace();
     i = -1;
     }
     for (int j=0; j<i; j++) {
     requestStr.append((char) buffer[j]);
     }
     System.out.print(requestStr.toString());
     return getUri(requestStr.toString());
     }
     //获取URI信息字符
     private String getUri(String requestString) {
     int index1, index2;
     index1 = requestString.indexOf(' ');
     if (index1 != -1) {
     index2 = requestString.indexOf(' ', index1 + 1);
     if (index2 > index1)
      return requestString.substring(index1 + 1, index2);
     }
     return null;
     }
    }

    响应类

    package com.lp.app.webserver;
    import java.io.*;
    import java.net.*;
    //获得用户请求后将用户需要的文件读出,添加上HTTP应答头。发送给客户端。
    public class Response{
     OutputStream out = null;
     //发送请求的文件 public void Send(String ref) throws IOException {
     byte[] bytes = new byte[2048];
     FileInputStream fis = null;
     try {
     //构造文件
     File file = new File(WebServer.WEBROOT, ref);
     if (file.exists()) {
      //构造输入文件流
      fis = new FileInputStream(file);
      int ch = fis.read(bytes, 0, 2048);
      //读取文件
      String sBody = new String(bytes,0);
      //构造输出信息
      String sendMessage = "HTTP/1.1 200 OK\r\n" +
      "Content-Type: text/html\r\n" +
      "Content-Length: "+ch+"\r\n" +
      "\r\n" +sBody;
      //输出文件
      out.write(sendMessage.getBytes());
     }else {
      // 找不到文件
      String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
      "Content-Type: text/html\r\n" +
      "Content-Length: 23\r\n" +
      "\r\n" +
      "<h1>File Not Found</h1>";
      out.write(errorMessage.getBytes());
     }
     }
     catch (Exception e) {
     // 如不能实例化File对象,抛出异常。
     System.out.println(e.toString() );
     }
     finally {
     if (fis != null)
      fis.close();
     }
     }
     //获取输出流
     public Response(OutputStream output) {
     this.out = output;
    }
    }

    我这儿整理了比较全面的JAVA相关的面试资料,需要领取面试资料的同学,请加群:473984645
    在这里插入图片描述
    获取更多学习资料,可以加群:473984645或扫描下方二维码
    在这里插入图片描述

    展开全文
  • 或许有点长但是一步步教你我想你也愿意看7.2面向套接字编程我们已经通过了解Socket的接口,知其所以然,下面我们就将通过具体的案例,来熟悉Socket的具体工作方式7.2.1使用套接字实现基于TCP协议的服务器和客户机...

    或许有点长

    但是一步步教你

    我想你也愿意看7.2面向套接字编程

    我们已经通过了解Socket的接口,知其所以然,下面我们就将通过具体的案例,来熟悉Socket的具体工作方式7.2.1使用套接字实现基于TCP协议的服务器和客户机程序

    依据TCP协议,在C/S架构的通讯过程中,客户端和服务器的Socket动作如下:

    客户端:1.用服务器的IP地址和端口号实例化Socket对象。2.调用connect方法,连接到服务器上。3.将发送到服务器的IO流填充到IO对象里,比如BufferedReader/PrintWriter。4.利用Socket提供的getInputStream和getOutputStream方法,通过IO流对象,向服务器发送数据流。5. 通讯完成后,关闭打开的IO对象和Socket。

    服务器:1. 在服务器,用一个端口来实例化一个 ServerSocket对象。此时,服务器就可以这个端口时刻监听从客户端发来的连接请求。2.调用ServerSocket的accept方法,开始监听连接从端口上发来的连接请求。3.利用accept方法返回的客户端的Socket对象,进行读写IO的操作

    通讯完成后,关闭打开的流和Socket对象。7.2.1.1开发客户端代码

    根据上面描述的通讯流程,我们可以按如下的步骤设计服务器端的代码。

    第一步,依次点击Eclipse环境里的“文件”|“新建”|“项目”选项,进入“新建项目”的向导对话框,在其中选中“Java项目”,点击“下一步”按钮,在随后弹出的对话框里,在其中的“项目名”一栏里,输入项目名“TCPSocket”,其它的选项目

    选择系统默认值,再按“完成”按钮,结束创建Java项目的动作。

    第二步,完成创建项目后,选中集成开发环境左侧的项目名“TCPSocket”,点击右键,在随后弹出的菜单里依次选择“新建”!“类”的选项,创建服务器类的代码。

    在随后弹出的“新建Java类”的对话框里,输入包名“tcp”,输入文件名“ServerCode”,请注意大小写,在“修饰符”里选中“公用”,在“想要创建哪些方法存根”下,选中“publicstaticvoidmain(String[] args )”单选框,同时把其它两项目取消掉,再按“完成”按钮,可以生成代码。

    第三步,在生成的代码里,编写引入Java包的代码,只有当我们引入这些包后,我们才能调用这些包里提供的IO和Socket类的方法。packagetcp;importjava.io.BufferedReader;importjava.io.BufferedWriter;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.net.ServerSocket;importjava.net.Socket;

    第四步,编写服务器端的主体代码,如下所示。publicclassServerCode

    {//设置端口号publicstaticintportNo=3333;publicstaticvoidmain(String[] args)throwsIOException

    {

    ServerSocket s=newServerSocket(portNo);

    System.out.println("The Server is start:"+s);//阻塞,直到有客户端连接Socket socket=s.accept();try{

    System.out.println("Accept the Client:"+socket);//设置IO句柄BufferedReader in=newBufferedReader(newInputStreamReader(socket

    .getInputStream()));

    PrintWriter out=newPrintWriter(newBufferedWriter(newOutputStreamWriter(socket.getOutputStream())),true);while(true)

    {

    String str=in.readLine();if(str.equals("byebye"))

    {break;

    }

    System.out.println("In Server reveived the info:"+str);

    out.println(str);

    }

    }finally{

    System.out.println("close the Server socket and the io.");

    socket.close();

    s.close();

    }

    }

    }

    这段代码的主要业务逻辑是:1.         在上述代码里的main函数前,我们设置了通讯所用到的端口号,为3333。2.         在main函数里,根据给定3333端口号,初始化一个ServerSocket对象s,该对象用来承担服务器端监听连接和提供通讯服务的功能。3.         调用ServerSocket对象的accept方法,监听从客户端的连接请求。当完成调用accept方法后,整段服务器端代码将回阻塞在这里,直到客户端发来connect请求。4.         当客户端发来connect请求,或是通过构造函数直接把客户端的Socket对象连接到服务器端后,阻塞于此的代码将会继续运行。此时服务器端将会根据accept方法的执行结果,用一个Socket对象来描述客户端的连接句柄。5.         创建两个名为in和out的对象,用来传输和接收通讯时的数据流。6.         创建一个while(true)的死循环,在这个循环里,通过in.readLine()方法,读取从客户端发送来的IO流(字符串),并打印出来。如果读到的字符串是“byebye”,那么退出while循环。7.         在try…catch…finally语句段里,不论在try语句段里是否发生异常,并且不论这些异常的种类,finally从句都将会被执行到。在finally从句里,将关闭描述客户端的连接句柄socket对象和ServerSocket类型的s对象。7.2.1.2开发客户端代码

    我们可以按以下的步骤,开发客户端的代码。

    第一,在TCPSocket项目下的tcp包下,创建一个名为ClientCode.java的文件。在其中编写引入Java包的代码,如下所示:packagetcp;importjava.io.BufferedReader;importjava.io.BufferedWriter;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.net.InetAddress;importjava.net.Socket;

    第二,编写客户端的主体代码,如下所示:publicclassClientCode

    {staticString clientName="Mike";//端口号publicstaticintportNo=3333;publicstaticvoidmain(String[] args)throwsIOException

    {//设置连接地址类,连接本地InetAddress addr=InetAddress.getByName("localhost");//要对应服务器端的3333端口号Socket socket=newSocket(addr, portNo);try{

    System.out.println("socket ="+socket);//设置IO句柄BufferedReader in=newBufferedReader(newInputStreamReader(socket

    .getInputStream()));

    PrintWriter out=newPrintWriter(newBufferedWriter(newOutputStreamWriter(socket.getOutputStream())),true);

    out.println("Hello Server,I am"+clientName);

    String str=in.readLine();

    System.out.println(str);

    out.println("byebye");

    }finally{

    System.out.println("close the Client socket and the io.");

    socket.close();

    }

    }

    }

    上述客户端代码的主要业务逻辑是:1.         同样定义了通讯端口号,这里给出的端口号必须要和服务器端的一致。2.         在main函数里,根据地址信息“localhost”,创建一个InetAddress类型的对象addr。这里,因为我们把客户端和服务器端的代码都放在本机运行,所以同样可以用“127.0.0.1”字符串,来创建InetAddress对象。3.         根据addr和端口号信息,创建一个Socket类型对象,该对象用来同服务器端的ServerSocket类型对象交互,共同完成C/S通讯流程。4.         同样地创建in和out两类IO句柄,用来向服务器端发送和接收数据流。5.         通过out对象,向服务器端发送"Hello Server,I am …"的字符串。发送后,同样可以用in句柄,接收从服务器端的消息。6.         利用out对象,发送”byebye”字符串,用以告之服务器端,本次通讯结束。7.         在finally从句里,关闭Socket对象,断开同服务器端的连接。7.2.1.3运行效果演示

    在上述两部分里,我们分别讲述了C/S通讯过程中服务器端和客户端代码的业务逻辑,下面我们将在集成开发环境里,演示这里通讯流程。

    第一步,选中ServerCode.java代码,在eclipse的“运行”菜单里,选中“运行方式”|“1Java应用程序”的菜单,开启服务器端的程序。

    开启服务端程序后,会在eclipse环境下方的控制台里显示如下的内容:

    The Server is start: ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=3333]

    在这里,由于ServerSocket对象并没监听到客户端的请求,所以addr和后面的port值都是初始值。

    第二步,按同样的方法,打开ClientCode.java程序,启动客户端。启动以后,将在客户端的控制台里看到如下的信息:

    socket=Socket[addr=localhost/127.0.0.1,port=3333,localport=1326]

    Hello Server,I am Mike

    close the Client socket and the io.

    从中可以看到,在第一行里,显示客户端Socket对象连接的IP地址和端口号,在第二行里,可以到到客户端向服务器端发送的字符串,而在第三行里,可以看到通讯结束后,客户端关闭连接Socket和IO对象的提示语句。

    第三步,在eclipse下方的控制台里,切换到ServerCode服务端的控制台提示信息里,我们可以看到服务器端在接收到客户端连接请求后的响应信息。

    响应的信息如下所示:

    The Server is start: ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=3333]

    Accept the Client: Socket[addr=/127.0.0.1,port=1327,localport=3333]

    In Server reveived the info: Hello Server,I am Mike

    close the Server socket and the io.

    其中,第一行是启动服务器程序后显示的信息。在第二行里,显示从客户端发送的连接请求的各项参数。在第三行里,显示了从客户端发送过来的字符串。在第四行里,显示了关闭服务器端ServerSocket和IO对象的提示信息。从中我们可以看出在服务器端里accept阻塞和继续运行的这个过程。

    通过上述的操作,我们可以详细地观察到C/S通讯的全部流程,请大家务必要注意:一定要先开启服务器端的程序再开启客户端,如果这个步骤做反的话,客户端程序会应找不到服务器端而报异常。7.2.2使用套接字连接多个客户机

    在7.1的代码里,客户端和服务器之间只有一个通讯线程,所以它们之间只有一条Socket信道。

    如果我们在通过程序里引入多线程的机制,可让一个服务器端同时监听并接收多个客户端的请求,并同步地为它们提供通讯服务。

    基于多线程的通讯方式,将大大地提高服务器端的利用效率,并能使服务器端能具备完善的服务功能。7.2.2.1开发客户端代码

    我们可以按以下的步骤开发基于多线程的服务器端的代码。

    第一步,在3.2里创建的“TCPSocket”项目里,新建一个名为ThreadServer.java的代码文件,创建文件的方式大家可以参照3.2部分的描述。首先编写package和import部分的代码,用来打包和引入包文件,如下所示:packagetcp;importjava.io.*;importjava.net.*;

    第二步,由于我们在服务器端引入线程机制,所以我们要编写线程代码的主体执行类ServerThreadCode,这个类的代码如下所示:classServerThreadCodeextendsThread

    {//客户端的socketprivateSocket clientSocket;//IO句柄privateBufferedReader sin;privatePrintWriter sout;//默认的构造函数publicServerThreadCode()

    {}publicServerThreadCode(Socket s)throwsIOException

    {

    clientSocket=s;//初始化sin和sout的句柄sin=newBufferedReader(newInputStreamReader(clientSocket

    .getInputStream()));

    sout=newPrintWriter(newBufferedWriter(newOutputStreamWriter(

    clientSocket.getOutputStream())),true);//开启线程start();

    }//线程执行的主体函数publicvoidrun()

    {try{//用循环来监听通讯内容for(;;)

    {

    String str=sin.readLine();//如果接收到的是byebye,退出本次通讯if(str.equals("byebye"))

    {break;

    }

    System.out.println("In Server reveived the info:"+str);

    sout.println(str);

    }

    System.out.println("closing the server socket!");

    }catch(IOException e)

    {

    e.printStackTrace();

    }finally{

    System.out.println("close the Server socket and the io.");try{

    clientSocket.close();

    }catch(IOException e)

    {

    e.printStackTrace();

    }

    }

    }

    }

    这个类的业务逻辑说明如下:1.         这个类通过继承Thread类来实现线程的功能,也就是说,在其中的run方法里,定义了该线程启动后要执行的业务动作。2.         这个类提供了两种类型的重载函数。在参数类型为Socket的构造函数里, 通过参数,初始化了本类里的Socket对象,同时实例化了两类IO对象。在此基础上,通过start方法,启动定义在run方法内的本线程的业务逻辑。3.         在定义线程主体动作的run方法里,通过一个for(;;)类型的循环,根据IO句柄,读取从Socket信道上传输过来的客户端发送的通讯信息。如果得到的信息为“byebye”,则表明本次通讯结束,退出for循环。4.         catch从句将处理在try语句里遇到的IO错误等异常,而在finally从句里,将在通讯结束后关闭客户端的Socket句柄。

    上述的线程主体代码将会在ThreadServer类里被调用。

    第三步,编写服务器端的主体类ThreadServer,代码如下所示:publicclassThreadServer

    {//端口号staticfinalintportNo=3333;publicstaticvoidmain(String[] args)throwsIOException

    {//服务器端的socketServerSocket s=newServerSocket(portNo);

    System.out.println("The Server is start:"+s);try{for(;;)

    {//阻塞,直到有客户端连接Socket socket=s.accept();//通过构造函数,启动线程newServerThreadCode(socket);

    }

    }finally{

    s.close();

    }

    }

    }

    这段代码的主要业务逻辑说明如下:1.         首先定义了通讯所用的端口号,为3333。2.         在main函数里,根据端口号,创建一个ServerSocket类型的服务器端的Socket,用来同客户端通讯。3.         在for(;;)的循环里,调用accept方法,监听从客户端请求过来的socket,请注意这里又是一个阻塞。当客户端有请求过来时,将通过ServerThreadCode的构造函数,创建一个线程类,用来接收客户端发送来的字符串。在这里我们可以再一次观察ServerThreadCode类,在其中,这个类通过构造函数里的start方法,开启run方法,而在run方法里,是通过sin对象来接收字符串,通过sout对象来输出。4.         在finally从句里,关闭服务器端的Socket,从而结束本次通讯。7.2.2.2开发客户端代码

    我们可以按以下的步骤,编写的基于多线程的客户端代码。

    第一步,在 “TCPSocket”项目里,新建一个名为ThreadClient.java的代码文件。同样是编写package和import部分的代码,用来打包和引入包文件,如下所示:packagetcp;importjava.net.*;importjava.io.*;

    第二步,编写线程执行主体的ClientThreadCode类,同样,这个类通过继承Thread来实现线程的功能。classClientThreadCodeextendsThread

    {//客户端的socketprivateSocket socket;//线程统计数,用来给线程编号privatestaticintcnt=0;privateintclientId=cnt++;privateBufferedReader in;privatePrintWriter out;//构造函数publicClientThreadCode(InetAddress addr)

    {try{

    socket=newSocket(addr,3333);

    }catch(IOException e)

    {

    e.printStackTrace();

    }//实例化IO对象try{

    in=newBufferedReader(newInputStreamReader(socket.getInputStream()));

    out=newPrintWriter(newBufferedWriter(newOutputStreamWriter(socket.getOutputStream())),true);//开启线程start();

    }catch(IOException e)

    {//出现异常,关闭sockettry{

    socket.close();

    }catch(IOException e2)

    {

    e2.printStackTrace();

    }

    }

    }//线程主体方法publicvoidrun()

    {try{

    out.println("Hello Server,My id is"+clientId );

    String str=in.readLine();

    System.out.println(str);

    out.println("byebye");

    }catch(IOException e)

    {

    e.printStackTrace();

    }finally{try{

    socket.close();

    }catch(IOException e)

    {

    e.printStackTrace();

    }

    }

    }

    }

    这个类的主要业务逻辑是:1.         在构造函数里, 通过参数类型为InetAddress类型参数和3333,初始化了本类里的Socket对象,随后实例化了两类IO对象,并通过start方法,启动定义在run方法内的本线程的业务逻辑。2.         在定义线程主体动作的run方法里,通过IO句柄,向Socket信道上传输本客户端的ID号,发送完毕后,传输”byebye”字符串,向服务器端表示本线程的通讯结束。3.         同样地,catch从句将处理在try语句里遇到的IO错误等异常,而在finally从句里,将在通讯结束后关闭客户端的Socket句柄。

    第三步,编写客户端的主体代码,在这段代码里,将通过for循环,根据指定的待创建的线程数量,通过ClientThreadCode的构造函数,创建若干个客户端线程,同步地和服务器端通讯。publicclassThreadClient

    {publicstaticvoidmain(String[] args)throwsIOException, InterruptedException

    {intthreadNo=0;

    InetAddress addr=InetAddress.getByName("localhost");for(threadNo=0;threadNo<3;threadNo++)

    {newClientThreadCode(addr);

    }

    }

    }

    这段代码执行以后,在客户端将会有3个通讯线程,每个线程首先将先向服务器端发送"Hello Server,My id is"的字符串,然后发送”byebye”,终止该线程的通讯。7.2.2.3运行效果演示

    接下来,我们来观察一下基于多线程的C/S架构的运行效果。

    第一步,我们先要启动服务器端的ThreadServer代码,启动后,在控制台里会出现如下的提示信息:

    The Server is start: ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=3333]

    上述的提示信息里,我们同样可以看到,服务器在开启服务后,会阻塞在accept这里,直到有客户端请求过来。

    第二步,我们在启动完服务器后,运行客户端的ThreadClient.java代码,运行后,我们观察服务器端的控制台,会出现如下的信息:

    The Server is start: ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=3333]

    In Server reveived the info: Hello Server,My id is0In Server reveived the info: Hello Server,My id is1In Server reveived the info: Hello Server,My id is2closing the server socket!close the Server socket and the io.

    closing the server socket!close the Server socket and the io.

    closing the server socket!close the Server socket and the io.

    其中,第一行是原来就有,在后面的几行里,首先将会输出了从客户端过来的线程请求信息,比如

    In Server reveived the info: Hello Server,My id is0接下来则会显示关闭Server端的IO和Socket的提示信息。

    这里,请大家注意,由于线程运行的不确定性,从第二行开始的打印输出语句的次序是不确定的。但是,不论输出语句的次序如何变化,我们都可以从中看到,客户端有三个线程请求过来,并且,服务器端在处理完请求后,会关闭Socker和IO。

    第三步,当我们运行完ThreadClient.java的代码后,并切换到ThreadClient.java的控制台,我们可以看到如下的输出:

    Hello Server,My id is0Hello Server,My id is2Hello Server,My id is1这说明在客户端开启了3个线程,并利用这3个线程,向服务器端发送字符串。

    而在服务器端,用accept方法分别监听到了这3个线程,并与之对应地也开了3个线程与之通讯。7.2.3UDP协议与传输数据报文

    UDP协议一般应用在 “群发信息”的场合,所以它更可以利用多线程的机制,实现多信息的同步发送。

    为了改善代码的架构,我们更可以把一些业务逻辑的动作抽象成方法,并封装成类,这样,基于UDP功能的类就可以在其它应用项目里被轻易地重用。7.2.3.1开发客户端代码

    如果我们把客户端的所有代码都写在一个文件中,那么代码的功能很有可能都聚集在一个方法力,代码的可维护性将会变得很差。

    所以我们专门设计了ClientBean类,在其中封装了客户端通讯的一些功能方法,在此基础上,通过UDPClient.java文件,实现UDP客户端的功能。

    另外,在这里以及以后的代码里,我们不再详细讲述用Eclipse开发和运行Java程序的方法,而是重点讲述Java代码的业务逻辑和主要工作流程。

    首先,我们可以按如下的步骤,设计ClientBean这个类。通过import语句,引入所用到的类库,代码如下所示。importjava.io.IOException;importjava.net.DatagramPacket;importjava.net.DatagramSocket;importjava.net.InetAddress;importjava.net.SocketException;importjava.net.UnknownHostException;

    第二,定义ClientBean所用到的变量,并给出针对这些变量操作的get和set类型的方法,代码如下所示。//描述UDP通讯的DatagramSocket对象privateDatagramSocket ds;//用来封装通讯字符串privatebytebuffer[];//客户端的端口号privateintclientport ;//服务器端的端口号privateintserverport;//通讯内容privateString content;//描述通讯地址privateInetAddress ia;//以下是各属性的Get和Set类型方法publicbyte[] getBuffer()

    {returnbuffer;

    }publicvoidsetBuffer(byte[] buffer)

    {this.buffer=buffer;

    }publicintgetClientport()

    {returnclientport;

    }publicvoidsetClientport(intclientport)

    {this.clientport=clientport;

    }publicString getContent()

    {returncontent;

    }publicvoidsetContent(String content)

    {this.content=content;

    }publicDatagramSocket getDs()

    {returnds;

    }publicvoidsetDs(DatagramSocket ds)

    {this.ds=ds;

    }publicInetAddress getIa()

    {returnia;

    }publicvoidsetIa(InetAddress ia)

    {this.ia=ia;

    }publicintgetServerport()

    {returnserverport;

    }publicvoidsetServerport(intserverport)

    {this.serverport=serverport;

    }

    在上述的代码里,我们定义了描述用来实现UDP通讯的DatagramSocket类型对象ds,描述客户端和服务器端的端口号clientport和serverport,用于描述通讯信息的buffer和content对象,其中,buffer对象是byte数组类型的,可通过UDP的数据报文传输,而content是String类型的,在应用层面表示用户之间的通讯内容,另外还定义了InetAddress类型的ia变量,用来封装通讯地址信息。

    在随后定义的一系列get和set方法里,给出了设置和获取上述变量的方法。

    第三,编写该类的构造函数,代码如下所示。publicClientBean()throwsSocketException, UnknownHostException

    {

    buffer=newbyte[1024];

    clientport=1985;

    serverport=1986;

    content="";

    ds=newDatagramSocket(clientport);

    ia=InetAddress.getByName("localhost");

    }

    在这个构造函数里,我们给各变量赋予了初始值,其中分别设置了客户端和服务器端的端口号分别为1985和1985,设置了通讯连接地址为本地,并根据客户端的端口号初始化了DatagramSocket对象。

    当程序员初始化ClientBean类时,这段构造函数会自动执行,完成设置通讯各参数等工作。

    第四,编写向服务器端发送消息的sendToServer方法,代码如下所示。publicvoidsendToServer()throwsIOException

    {

    buffer=content.getBytes();

    ds.send(newDatagramPacket(buffer,content.length(),ia,serverport));

    }

    在这段代码里,根据String类型的表示通讯信息的content变量,初始化UDP数据报文,即DatagramPacket对象,并通过调用DatagramSocket类型对象的send方法,发送该UDP报文。

    纵观ClientBean类,我们可以发现在其中封装了诸如通讯端口、通讯内容和通讯报文等对象以及以UDP方式发送信息的sendToServer方法。所以,在UDPClient类里,可以直接调用其中的接口,方便地实现通讯功能。

    其次,我们可以按如下的步骤,设计UDPClient这个类。

    第一步,通过import语句,引入所用到的类库,代码如下所示。importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;

    第二步,编写线程相关的代码。

    由于我们要在UDP客户端里通过多线程的机制,同时开多个客户端,向服务器端发送通讯内容,所以我们的UDPClient类必须要实现Runnable接口,并在其中覆盖掉Runnable接口里的run方法。定义类和实现run方法的代码如下所示。publicclassUDPClientimplementsRunnable

    {publicstaticString content;publicstaticClientBean client;publicvoidrun()

    {try{

    client.setContent(content);

    client.sendToServer();

    }catch(Exception ex)

    {

    System.err.println(ex.getMessage());

    }

    }//end of run//main 方法//…}

    在上述代码的run方法里,我们主要通过了ClientBean类里封装的方法,设置了content内容,并通过了sentToServer方法,将content内容以数据报文的形式发送到服务器端。

    一旦线程被开启,系统会自动执行定义在run方法里的动作。

    第三步,编写主方法。在步骤(2)里的//main方法注释的位置,我们可以插入UDPClient类的main方法代码,具体如下所示。publicstaticvoidmain(String args[])throwsIOException

    {

    BufferedReader br=newBufferedReader(newInputStreamReader(System.in));

    client=newClientBean();

    System.out.println("客户端启动

    9b8a8a44dd1c74ae49c20a7cd451974e.png");while(true)

    {//接收用户输入content=br.readLine();//如果是end或空,退出循环if(content==null||content.equalsIgnoreCase("end")||content.equalsIgnoreCase(""))

    {break;

    }//开启新线程,发送消息newThread(newUDPClient()).start();

    }

    }

    这段代码的主要业务逻辑是,首先初始化了BufferedReader类型的br对象,该对象可以接收从键盘输入的字符串。随后启动一个while(true)的循环,在这个循环体里,接收用户从键盘的输入,如果用户输入的字符串不是“end”,或不是为空,则开启一个UDPClient类型的线程,并通过定义在run方法里的线程主体动作,发送接收到的消息。如果在循环体里,接收到“end”或空字符,则通过break语句,退出循环。

    从上述代码里,我们可以看出,对于每次UDP发送请求,UDPClient类都将会启动一个线程来发送消息。7.2.3.2开发客户端代码

    同样,我们把服务器端所需要的一些通用方法以类的形式封装,而在UDP的服务器端,通过调用封装在ServerBean类里的方法来完成信息的接收工作。

    首先,我们可以按如下的步骤,设计ServerBean类的代码。

    第一步,通过import语句,引入所用到的类库,代码如下所示。importjava.io.IOException;importjava.net.DatagramPacket;importjava.net.DatagramSocket;importjava.net.InetAddress;importjava.net.SocketException;importjava.net.UnknownHostException;

    第二步,同样定义ServerBean类里用到的变量,并给出针对这些变量操作的get和set类型的方法。由于这里的代码和ClientBean类里的非常相似,所以不再赘述,代码部分大家可以参考光盘上。

    第三步,编写该类的构造函数,在这个构造函数里,给该类里的一些重要属性赋了初值,代码如下所示。publicServerBean()throwsSocketException, UnknownHostException

    {

    buffer=newbyte[1024];

    clientport=1985;

    serverport=1986;

    content="";

    ds=newDatagramSocket(serverport);

    ia=InetAddress.getByName("localhost");

    }

    从中我们可以看到,在UDP的服务端里,为了同客户端对应,所以同样把clientport和serverport值设置为1985和1986,同时初始化了DatagramSocket对象,并把服务器的地址也设置成本地。

    第四,编写实现监听客户端请求的listenClient方法,代码如下所示。publicvoidlistenClient()throwsIOException

    {//在循环体里接收消息while(true)

    {//初始化DatagramPacket类型的变量DatagramPacket dp=newDatagramPacket(buffer,buffer.length);//接收消息,并把消息通过dp参数返回ds.receive(dp);

    content=newString(dp.getData(),0,dp.getLength());//打印消息print();

    }

    }

    在这个方法里,构造了一个while(true)的循环,在这个循环体内部,调用了封装在DatagramSocket类型里的receive方法,接收客户端发送过来的UDP报文,并通过print方法,把报文内容打印出来。

    而print方法的代码比较简单,只是通过输出语句,打印报文里的字符串。publicvoidprint()

    {

    System.out.println(content);

    }

    而UDP通讯的服务器端代码相对简单,以下是UDPServer类的全部代码。importjava.io.IOException;publicclassUDPServer

    {publicstaticvoidmain(String args[])throwsIOException

    {

    System.out.println("服务器端启动

    9b8a8a44dd1c74ae49c20a7cd451974e.png");//初始化ServerBean对象ServerBean server=newServerBean();//开启监听程序server.listenClient();

    }

    }

    从上述代码里,我们可以看到,在UDP的服务器端里,主要通过ServerBean类里提供的listenClient方法,监听从客户端发送过来的UDP报文,并通过解析得到其中包含的字符串,随后输出。7.3.2.3开发客户端代码

    由于我们已经讲述过通过Eclipse查看代码运行结果的详细步骤,所以这里我们将直接通过命令行的方式,通过javac和java等命令,查看基于多线程UDP通讯的演示效果。1.         首先我们把刚才编写好的四段java代码(即ClientBean.java、UDPClient.java、ServerBean.java和UDPServer.java)放到D盘下的work目录下(如果没有则新建)。2.         点击“开始菜单”|“运行”选项,并在“运行程序”的对话框里输入”cmd”命令,进入DOS命令界面,并进入到D:\work这个目录里。3.         如果大家已经按照第一章的说明,成功地配置好关于java的path和classpath环境变量,在这里可以直接运行javac*.java命令,编译这四个.java文件,编译后,会在D:\work目录下产生同四个java文件相对应的.class文件。4.         在这个命令窗口里运行java UDPServer命令,通过运行UDPServer代码,开启UDP服务器端程序,开启后,会出现如图7-3所示的信息。

    图7-3启动UDP服务端后的效果5.         在出现上图的效果后,别关闭这个命令窗口,按步骤(2)里说明的流程,新开启一个DOS命令窗口,并同样进入到D:\work这个目录下。6.         在新窗口里输入java UDPClient,开启UDP客户端程序。开启后,可通过键盘向服务器端输入通讯字符串,这些字符串将会以数据报文的形式发送到服务器端。

    在图7-4里,演示了UDP客户端向服务器端发送消息的效果。

    图7-4UDP客户端发送消息的效果

    每当我们在客户端发送一条消息,服务器端会收到并输出这条消息,从代码里我们可以得知,每条消息是通过为之新开启的线程发送到服务器端的。

    如果我们在客户端输入”end”或空字符串,客户端的UDPClient代码会退出。在图7-5里演示了UDP服务器端接收并输出通讯字符串的效果。

    图7-5UDP服务器端接收到消息的效果7.         由于UDPServer.java代码里,我们通过一个while(true)的循环来监听客户端的请求,所以当程序运行结束后,可通过Ctrl+C的快捷键的方式退出这段程序。

    展开全文
  • 7.2.1使用套接字实现基于TCP协议的服务器和客户机程序依据TCP协议,在C/S架构的通讯过程中,客户端和服务器的Socket动作如下:客户端:1.用服务器的IP地址和端口号实例化Socket对象。2.调用connect方法,连接到...

    7.2.1使用套接字实现基于TCP协议的服务器和客户机程序

    依据TCP协议,在C/S架构的通讯过程中,客户端和服务器的Socket动作如下:

    客户端:1.用服务器的IP地址和端口号实例化Socket对象。2.调用connect方法,连接到服务器上。3.将发送到服务器的IO流填充到IO对象里,比如BufferedReader/PrintWriter。4.利用Socket提供的getInputStream和getOutputStream方法,通过IO流对象,向服务器发送数据流。5. 通讯完成后,关闭打开的IO对象和Socket。

    服务器:1. 在服务器,用一个端口来实例化一个 ServerSocket对象。此时,服务器就可以这个端口时刻监听从客户端发来的连接请求。2.调用ServerSocket的accept方法,开始监听连接从端口上发来的连接请求。3.利用accept方法返回的客户端的Socket对象,进行读写IO的操作

    通讯完成后,关闭打开的流和Socket对象。7.2.1.1开发客户端代码

    根据上面描述的通讯流程,我们可以按如下的步骤设计服务器端的代码。

    第一步,依次点击Eclipse环境里的“文件”|“新建”|“项目”选项,进入“新建项目”的向导对话框,在其中选中“Java项目”,点击“下一步”按钮,在随后弹出的对话框里,在其中的“项目名”一栏里,输入项目名“TCPSocket”,其它的选项目

    选择系统默认值,再按“完成”按钮,结束创建Java项目的动作。

    第二步,完成创建项目后,选中集成开发环境左侧的项目名“TCPSocket”,点击右键,在随后弹出的菜单里依次选择“新建”!“类”的选项,创建服务器类的代码。

    在随后弹出的“新建Java类”的对话框里,输入包名“tcp”,输入文件名“ServerCode”,请注意大小写,在“修饰符”里选中“公用”,在“想要创建哪些方法存根”下,选中“publicstaticvoidmain(String[] args )”单选框,同时把其它两项目取消掉,再按“完成”按钮,可以生成代码。

    第三步,在生成的代码里,编写引入Java包的代码,只有当我们引入这些包后,我们才能调用这些包里提供的IO和Socket类的方法。packagetcp;importjava.io.BufferedReader;importjava.io.BufferedWriter;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.net.ServerSocket;importjava.net.Socket;

    第四步,编写服务器端的主体代码,如下所示。publicclassServerCode

    {//设置端口号publicstaticintportNo=3333;publicstaticvoidmain(String[] args)throwsIOException

    {

    ServerSocket s=newServerSocket(portNo);

    System.out.println("The Server is start:"+s);//阻塞,直到有客户端连接Socket socket=s.accept();try{

    System.out.println("Accept the Client:"+socket);//设置IO句柄BufferedReader in=newBufferedReader(newInputStreamReader(socket

    .getInputStream()));

    PrintWriter out=newPrintWriter(newBufferedWriter(newOutputStreamWriter(socket.getOutputStream())),true);while(true)

    {

    String str=in.readLine();if(str.equals("byebye"))

    {break;

    }

    System.out.println("In Server reveived the info:"+str);

    out.println(str);

    }

    }finally{

    System.out.println("close the Server socket and the io.");

    socket.close();

    s.close();

    }

    }

    }

    这段代码的主要业务逻辑是:1.         在上述代码里的main函数前,我们设置了通讯所用到的端口号,为3333。2.         在main函数里,根据给定3333端口号,初始化一个ServerSocket对象s,该对象用来承担服务器端监听连接和提供通讯服务的功能。3.         调用ServerSocket对象的accept方法,监听从客户端的连接请求。当完成调用accept方法后,整段服务器端代码将回阻塞在这里,直到客户端发来connect请求。4.         当客户端发来connect请求,或是通过构造函数直接把客户端的Socket对象连接到服务器端后,阻塞于此的代码将会继续运行。此时服务器端将会根据accept方法的执行结果,用一个Socket对象来描述客户端的连接句柄。5.         创建两个名为in和out的对象,用来传输和接收通讯时的数据流。6.         创建一个while(true)的死循环,在这个循环里,通过in.readLine()方法,读取从客户端发送来的IO流(字符串),并打印出来。如果读到的字符串是“byebye”,那么退出while循环。7.         在try…catch…finally语句段里,不论在try语句段里是否发生异常,并且不论这些异常的种类,finally从句都将会被执行到。在finally从句里,将关闭描述客户端的连接句柄socket对象和ServerSocket类型的s对象。7.2.1.2开发客户端代码

    我们可以按以下的步骤,开发客户端的代码。

    第一,在TCPSocket项目下的tcp包下,创建一个名为ClientCode.java的文件。在其中编写引入Java包的代码,如下所示:packagetcp;importjava.io.BufferedReader;importjava.io.BufferedWriter;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.OutputStreamWriter;importjava.io.PrintWriter;importjava.net.InetAddress;importjava.net.Socket;

    第二,编写客户端的主体代码,如下所示:publicclassClientCode

    {staticString clientName="Mike";//端口号publicstaticintportNo=3333;publicstaticvoidmain(String[] args)throwsIOException

    {//设置连接地址类,连接本地InetAddress addr=InetAddress.getByName("localhost");//要对应服务器端的3333端口号Socket socket=newSocket(addr, portNo);try{

    System.out.println("socket ="+socket);//设置IO句柄BufferedReader in=newBufferedReader(newInputStreamReader(socket

    .getInputStream()));

    PrintWriter out=newPrintWriter(newBufferedWriter(newOutputStreamWriter(socket.getOutputStream())),true);

    out.println("Hello Server,I am"+clientName);

    String str=in.readLine();

    System.out.println(str);

    out.println("byebye");

    }finally{

    System.out.println("close the Client socket and the io.");

    socket.close();

    }

    }

    }

    上述客户端代码的主要业务逻辑是:1.         同样定义了通讯端口号,这里给出的端口号必须要和服务器端的一致。2.         在main函数里,根据地址信息“localhost”,创建一个InetAddress类型的对象addr。这里,因为我们把客户端和服务器端的代码都放在本机运行,所以同样可以用“127.0.0.1”字符串,来创建InetAddress对象。3.         根据addr和端口号信息,创建一个Socket类型对象,该对象用来同服务器端的ServerSocket类型对象交互,共同完成C/S通讯流程。4.         同样地创建in和out两类IO句柄,用来向服务器端发送和接收数据流。5.         通过out对象,向服务器端发送"Hello Server,I am …"的字符串。发送后,同样可以用in句柄,接收从服务器端的消息。6.         利用out对象,发送”byebye”字符串,用以告之服务器端,本次通讯结束。7.         在finally从句里,关闭Socket对象,断开同服务器端的连接。7.2.1.3运行效果演示

    在上述两部分里,我们分别讲述了C/S通讯过程中服务器端和客户端代码的业务逻辑,下面我们将在集成开发环境里,演示这里通讯流程。

    第一步,选中ServerCode.java代码,在eclipse的“运行”菜单里,选中“运行方式”|“1Java应用程序”的菜单,开启服务器端的程序。

    开启服务端程序后,会在eclipse环境下方的控制台里显示如下的内容:

    The Server is start: ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=3333]

    在这里,由于ServerSocket对象并没监听到客户端的请求,所以addr和后面的port值都是初始值。

    第二步,按同样的方法,打开ClientCode.java程序,启动客户端。启动以后,将在客户端的控制台里看到如下的信息:

    socket=Socket[addr=localhost/127.0.0.1,port=3333,localport=1326]

    Hello Server,I am Mike

    close the Client socket and the io.

    从中可以看到,在第一行里,显示客户端Socket对象连接的IP地址和端口号,在第二行里,可以到到客户端向服务器端发送的字符串,而在第三行里,可以看到通讯结束后,客户端关闭连接Socket和IO对象的提示语句。

    第三步,在eclipse下方的控制台里,切换到ServerCode服务端的控制台提示信息里,我们可以看到服务器端在接收到客户端连接请求后的响应信息。

    响应的信息如下所示:

    The Server is start: ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=3333]

    Accept the Client: Socket[addr=/127.0.0.1,port=1327,localport=3333]

    In Server reveived the info: Hello Server,I am Mike

    close the Server socket and the io.

    其中,第一行是启动服务器程序后显示的信息。在第二行里,显示从客户端发送的连接请求的各项参数。在第三行里,显示了从客户端发送过来的字符串。在第四行

    里,显示了关闭服务器端ServerSocket和IO对象的提示信息。从中我们可以看出在服务器端里accept阻塞和继续运行的这个过程。

    通过上述的操作,我们可以详细地观察到C/S通讯的全部流程,请大家务必要注意:一定要先开启服务器端的程序再开启客户端,如果这个步骤做反的话,客户端程序会应找不到服务器端而报异常。7.2.2使用套接字连接多个客户机

    在7.1的代码里,客户端和服务器之间只有一个通讯线程,所以它们之间只有一条Socket信道。

    如果我们在通过程序里引入多线程的机制,可让一个服务器端同时监听并接收多个客户端的请求,并同步地为它们提供通讯服务。

    基于多线程的通讯方式,将大大地提高服务器端的利用效率,并能使服务器端能具备完善的服务功能。7.2.2.1开发客户端代码

    我们可以按以下的步骤开发基于多线程的服务器端的代码。

    第一步,在3.2里创建的“TCPSocket”项目里,新建一个名为ThreadServer.java的代码文件,创建文件的方式大家可以参照3.2部分的描述。首先编写package和import部分的代码,用来打包和引入包文件,如下所示:packagetcp;importjava.io.*;importjava.net.*;

    第二步,由于我们在服务器端引入线程机制,所以我们要编写线程代码的主体执行类ServerThreadCode,这个类的代码如下所示:classServerThreadCodeextendsThread

    {//客户端的socketprivateSocket clientSocket;//IO句柄privateBufferedReader sin;privatePrintWriter sout;//默认的构造函数publicServerThreadCode()

    {}publicServerThreadCode(Socket s)throwsIOException

    {

    clientSocket=s;//初始化sin和sout的句柄sin=newBufferedReader(newInputStreamReader(clientSocket

    .getInputStream()));

    sout=newPrintWriter(newBufferedWriter(newOutputStreamWriter(

    clientSocket.getOutputStream())),true);//开启线程start();

    }//线程执行的主体函数publicvoidrun()

    {try{//用循环来监听通讯内容for(;;)

    {

    String str=sin.readLine();//如果接收到的是byebye,退出本次通讯if(str.equals("byebye"))

    {break;

    }

    System.out.println("In Server reveived the info:"+str);

    sout.println(str);

    }

    System.out.println("closing the server socket!");

    }catch(IOException e)

    {

    e.printStackTrace();

    }finally{

    System.out.println("close the Server socket and the io.");try{

    clientSocket.close();

    }catch(IOException e)

    {

    e.printStackTrace();

    }

    }

    }

    }

    这个类的业务逻辑说明如下:1.         这个类通过继承Thread类来实现线程的功能,也就是说,在其中的run方法里,定义了该线程启动后要执行的业务动作。2.         这个类提供了两种类型的重载函数。在参数类型为Socket的构造函数里, 通过参数,初始化了本类里的Socket对象,同时实例化了两类IO对象。在此基础上,通过start方法,启动定义在run方法内的本线程的业务逻辑。3.         在定义线程主体动作的run方法里,通过一个for(;;)类型的循环,根据IO句柄,读取从Socket信道上传输过来的客户端发送的通讯信息。如果得到的信息为“byebye”,则表明本次通讯结束,退出for循环。4.         catch从句将处理在try语句里遇到的IO错误等异常,而在finally从句里,将在通讯结束后关闭客户端的Socket句柄。

    上述的线程主体代码将会在ThreadServer类里被调用。

    第三步,编写服务器端的主体类ThreadServer,代码如下所示:publicclassThreadServer

    {//端口号staticfinalintportNo=3333;publicstaticvoidmain(String[] args)throwsIOException

    {//服务器端的socketServerSocket s=newServerSocket(portNo);

    System.out.println("The Server is start:"+s);try{for(;;)

    {//阻塞,直到有客户端连接Socket socket=s.accept();//通过构造函数,启动线程newServerThreadCode(socket);

    }

    }finally{

    s.close();

    }

    }

    }

    这段代码的主要业务逻辑说明如下:1.         首先定义了通讯所用的端口号,为3333。2.         在main函数里,根据端口号,创建一个ServerSocket类型的服务器端的Socket,用来同客户端通讯。3.         在

    for(;;)的循环里,调用accept方法,监听从客户端请求过来的socket,请注意这里又是一个阻塞。当客户端有请求过来时,将通过

    ServerThreadCode的构造函数,创建一个线程类,用来接收客户端发送来的字符串。在这里我们可以再一次观察

    ServerThreadCode类,在其中,这个类通过构造函数里的start方法,开启run方法,而在run方法里,是通过sin对象来接收字符

    串,通过sout对象来输出。4.         在finally从句里,关闭服务器端的Socket,从而结束本次通讯。7.2.2.2开发客户端代码

    我们可以按以下的步骤,编写的基于多线程的客户端代码。

    第一步,在 “TCPSocket”项目里,新建一个名为ThreadClient.java的代码文件。同样是编写package和import部分的代码,用来打包和引入包文件,如下所示:packagetcp;importjava.net.*;importjava.io.*;

    第二步,编写线程执行主体的ClientThreadCode类,同样,这个类通过继承Thread来实现线程的功能。classClientThreadCodeextendsThread

    {//客户端的socketprivateSocket socket;//线程统计数,用来给线程编号privatestaticintcnt=0;privateintclientId=cnt++;privateBufferedReader in;privatePrintWriter out;//构造函数publicClientThreadCode(InetAddress addr)

    {try{

    socket=newSocket(addr,3333);

    }catch(IOException e)

    {

    e.printStackTrace();

    }//实例化IO对象try{

    in=newBufferedReader(newInputStreamReader(socket.getInputStream()));

    out=newPrintWriter(newBufferedWriter(newOutputStreamWriter(socket.getOutputStream())),true);//开启线程start();

    }catch(IOException e)

    {//出现异常,关闭sockettry{

    socket.close();

    }catch(IOException e2)

    {

    e2.printStackTrace();

    }

    }

    }//线程主体方法publicvoidrun()

    {try{

    out.println("Hello Server,My id is"+clientId );

    String str=in.readLine();

    System.out.println(str);

    out.println("byebye");

    }catch(IOException e)

    {

    e.printStackTrace();

    }finally{try{

    socket.close();

    }catch(IOException e)

    {

    e.printStackTrace();

    }

    }

    }

    }

    这个类的主要业务逻辑是:1.         在构造函数里, 通过参数类型为InetAddress类型参数和3333,初始化了本类里的Socket对象,随后实例化了两类IO对象,并通过start方法,启动定义在run方法内的本线程的业务逻辑。2.         在定义线程主体动作的run方法里,通过IO句柄,向Socket信道上传输本客户端的ID号,发送完毕后,传输”byebye”字符串,向服务器端表示本线程的通讯结束。3.         同样地,catch从句将处理在try语句里遇到的IO错误等异常,而在finally从句里,将在通讯结束后关闭客户端的Socket句柄。

    第三步,编写客户端的主体代码,在这段代码里,将通过for循环,根据指定的待创建的线程数量,通过ClientThreadCode的构造函数,创建若干个客户端线程,同步地和服务器端通讯。publicclassThreadClient

    {publicstaticvoidmain(String[] args)throwsIOException, InterruptedException

    {intthreadNo=0;

    InetAddress addr=InetAddress.getByName("localhost");for(threadNo=0;threadNo<3;threadNo++)

    {newClientThreadCode(addr);

    }

    }

    }

    这段代码执行以后,在客户端将会有3个通讯线程,每个线程首先将先向服务器端发送"Hello Server,My id is"的字符串,然后发送”byebye”,终止该线程的通讯。7.2.2.3运行效果演示

    接下来,我们来观察一下基于多线程的C/S架构的运行效果。

    第一步,我们先要启动服务器端的ThreadServer代码,启动后,在控制台里会出现如下的提示信息:

    The Server is start: ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=3333]

    上述的提示信息里,我们同样可以看到,服务器在开启服务后,会阻塞在accept这里,直到有客户端请求过来。

    第二步,我们在启动完服务器后,运行客户端的ThreadClient.java代码,运行后,我们观察服务器端的控制台,会出现如下的信息:

    The Server is start: ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=3333]

    In Server reveived the info: Hello Server,My id is0In Server reveived the info: Hello Server,My id is1In Server reveived the info: Hello Server,My id is2closing the server socket!close the Server socket and the io.

    closing the server socket!close the Server socket and the io.

    closing the server socket!close the Server socket and the io.

    其中,第一行是原来就有,在后面的几行里,首先将会输出了从客户端过来的线程请求信息,比如

    In Server reveived the info: Hello Server,My id is0接下来则会显示关闭Server端的IO和Socket的提示信息。

    这里,请大家注意,由于线程运行的不确定性,从第二行开始的打印输出语句的次序是不确定的。但是,不论输出语句的次序如何变化,我们都可以从中看到,客户端有三个线程请求过来,并且,服务器端在处理完请求后,会关闭Socker和IO。

    第三步,当我们运行完ThreadClient.java的代码后,并切换到ThreadClient.java的控制台,我们可以看到如下的输出:

    Hello Server,My id is0Hello Server,My id is2Hello Server,My id is1这说明在客户端开启了3个线程,并利用这3个线程,向服务器端发送字符串。

    而在服务器端,用accept方法分别监听到了这3个线程,并与之对应地也开了3个线程与之通讯。7.2.3UDP协议与传输数据报文

    UDP协议一般应用在 “群发信息”的场合,所以它更可以利用多线程的机制,实现多信息的同步发送。

    为了改善代码的架构,我们更可以把一些业务逻辑的动作抽象成方法,并封装成类,这样,基于UDP功能的类就可以在其它应用项目里被轻易地重用。7.2.3.1开发客户端代码

    如果我们把客户端的所有代码都写在一个文件中,那么代码的功能很有可能都聚集在一个方法力,代码的可维护性将会变得很差。

    所以我们专门设计了ClientBean类,在其中封装了客户端通讯的一些功能方法,在此基础上,通过UDPClient.java文件,实现UDP客户端的功能。

    另外,在这里以及以后的代码里,我们不再详细讲述用Eclipse开发和运行Java程序的方法,而是重点讲述Java代码的业务逻辑和主要工作流程。

    首先,我们可以按如下的步骤,设计ClientBean这个类。通过import语句,引入所用到的类库,代码如下所示。importjava.io.IOException;importjava.net.DatagramPacket;importjava.net.DatagramSocket;importjava.net.InetAddress;importjava.net.SocketException;importjava.net.UnknownHostException;

    第二,定义ClientBean所用到的变量,并给出针对这些变量操作的get和set类型的方法,代码如下所示。//描述UDP通讯的DatagramSocket对象privateDatagramSocket ds;//用来封装通讯字符串privatebytebuffer[];//客户端的端口号privateintclientport ;//服务器端的端口号privateintserverport;//通讯内容privateString content;//描述通讯地址privateInetAddress ia;//以下是各属性的Get和Set类型方法publicbyte[] getBuffer()

    {returnbuffer;

    }publicvoidsetBuffer(byte[] buffer)

    {this.buffer=buffer;

    }publicintgetClientport()

    {returnclientport;

    }publicvoidsetClientport(intclientport)

    {this.clientport=clientport;

    }publicString getContent()

    {returncontent;

    }publicvoidsetContent(String content)

    {this.content=content;

    }publicDatagramSocket getDs()

    {returnds;

    }publicvoidsetDs(DatagramSocket ds)

    {this.ds=ds;

    }publicInetAddress getIa()

    {returnia;

    }publicvoidsetIa(InetAddress ia)

    {this.ia=ia;

    }publicintgetServerport()

    {returnserverport;

    }publicvoidsetServerport(intserverport)

    {this.serverport=serverport;

    }

    在上述的代码里,我们定义了描述用来实现UDP通讯的DatagramSocket类型对象ds,描述客户端和服务器端的端口号clientport和

    serverport,用于描述通讯信息的buffer和content对象,其中,buffer对象是byte数组类型的,可通过UDP的数据报文传

    输,而content是String类型的,在应用层面表示用户之间的通讯内容,另外还定义了InetAddress类型的ia变量,用来封装通讯地址信

    息。

    在随后定义的一系列get和set方法里,给出了设置和获取上述变量的方法。

    第三,编写该类的构造函数,代码如下所示。publicClientBean()throwsSocketException, UnknownHostException

    {

    buffer=newbyte[1024];

    clientport=1985;

    serverport=1986;

    content="";

    ds=newDatagramSocket(clientport);

    ia=InetAddress.getByName("localhost");

    }

    在这个构造函数里,我们给各变量赋予了初始值,其中分别设置了客户端和服务器端的端口号分别为1985和1985,设置了通讯连接地址为本地,并根据客户端的端口号初始化了DatagramSocket对象。

    当程序员初始化ClientBean类时,这段构造函数会自动执行,完成设置通讯各参数等工作。

    第四,编写向服务器端发送消息的sendToServer方法,代码如下所示。publicvoidsendToServer()throwsIOException

    {

    buffer=content.getBytes();

    ds.send(newDatagramPacket(buffer,content.length(),ia,serverport));

    }

    在这段代码里,根据String类型的表示通讯信息的content变量,初始化UDP数据报文,即DatagramPacket对象,并通过调用DatagramSocket类型对象的send方法,发送该UDP报文。

    纵观ClientBean类,我们可以发现在其中封装了诸如通讯端口、通讯内容和通讯报文等对象以及以UDP方式发送信息的sendToServer方法。所以,在UDPClient类里,可以直接调用其中的接口,方便地实现通讯功能。

    其次,我们可以按如下的步骤,设计UDPClient这个类。

    第一步,通过import语句,引入所用到的类库,代码如下所示。importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;

    第二步,编写线程相关的代码。

    由于我们要在UDP客户端里通过多线程的机制,同时开多个客户端,向服务器端发送通讯内容,所以我们的UDPClient类必须要实现Runnable接口,并在其中覆盖掉Runnable接口里的run方法。定义类和实现run方法的代码如下所示。publicclassUDPClientimplementsRunnable

    {publicstaticString content;publicstaticClientBean client;publicvoidrun()

    {try{

    client.setContent(content);

    client.sendToServer();

    }catch(Exception ex)

    {

    System.err.println(ex.getMessage());

    }

    }//end of run//main 方法//…}

    在上述代码的run方法里,我们主要通过了ClientBean类里封装的方法,设置了content内容,并通过了sentToServer方法,将content内容以数据报文的形式发送到服务器端。

    一旦线程被开启,系统会自动执行定义在run方法里的动作。

    第三步,编写主方法。在步骤(2)里的//main方法注释的位置,我们可以插入UDPClient类的main方法代码,具体如下所示。publicstaticvoidmain(String args[])throwsIOException

    {

    BufferedReader br=newBufferedReader(newInputStreamReader(System.in));

    client=newClientBean();

    System.out.println("客户端启动

    9b8a8a44dd1c74ae49c20a7cd451974e.png");while(true)

    {//接收用户输入content=br.readLine();//如果是end或空,退出循环if(content==null||content.equalsIgnoreCase("end")||content.equalsIgnoreCase(""))

    {break;

    }//开启新线程,发送消息newThread(newUDPClient()).start();

    }

    }

    这段代码的主要业务逻辑是,首先初始化了BufferedReader类型的br对象,该对象可以接收从键盘输入的字符串。随后启动一个while(true)

    的循环,在这个循环体里,接收用户从键盘的输入,如果用户输入的字符串不是“end”,或不是为空,则开启一个UDPClient类型的线程,并通过定义

    在run方法里的线程主体动作,发送接收到的消息。如果在循环体里,接收到“end”或空字符,则通过break语句,退出循环。

    从上述代码里,我们可以看出,对于每次UDP发送请求,UDPClient类都将会启动一个线程来发送消息。7.2.3.2开发客户端代码

    同样,我们把服务器端所需要的一些通用方法以类的形式封装,而在UDP的服务器端,通过调用封装在ServerBean类里的方法来完成信息的接收工作。

    首先,我们可以按如下的步骤,设计ServerBean类的代码。

    第一步,通过import语句,引入所用到的类库,代码如下所示。importjava.io.IOException;importjava.net.DatagramPacket;importjava.net.DatagramSocket;importjava.net.InetAddress;importjava.net.SocketException;importjava.net.UnknownHostException;

    第二步,同样定义ServerBean类里用到的变量,并给出针对这些变量操作的get和set类型的方法。由于这里的代码和ClientBean类里的非常相似,所以不再赘述,代码部分大家可以参考光盘上。

    第三步,编写该类的构造函数,在这个构造函数里,给该类里的一些重要属性赋了初值,代码如下所示。publicServerBean()throwsSocketException, UnknownHostException

    {

    buffer=newbyte[1024];

    clientport=1985;

    serverport=1986;

    content="";

    ds=newDatagramSocket(serverport);

    ia=InetAddress.getByName("localhost");

    }

    从中我们可以看到,在UDP的服务端里,为了同客户端对应,所以同样把clientport和serverport值设置为1985和1986,同时初始化了DatagramSocket对象,并把服务器的地址也设置成本地。

    第四,编写实现监听客户端请求的listenClient方法,代码如下所示。publicvoidlistenClient()throwsIOException

    {//在循环体里接收消息while(true)

    {//初始化DatagramPacket类型的变量DatagramPacket dp=newDatagramPacket(buffer,buffer.length);//接收消息,并把消息通过dp参数返回ds.receive(dp);

    content=newString(dp.getData(),0,dp.getLength());//打印消息print();

    }

    }

    在这个方法里,构造了一个while(true)的循环,在这个循环体内部,调用了封装在DatagramSocket类型里的receive方法,接收客户端发送过来的UDP报文,并通过print方法,把报文内容打印出来。

    而print方法的代码比较简单,只是通过输出语句,打印报文里的字符串。publicvoidprint()

    {

    System.out.println(content);

    }

    而UDP通讯的服务器端代码相对简单,以下是UDPServer类的全部代码。importjava.io.IOException;publicclassUDPServer

    {publicstaticvoidmain(String args[])throwsIOException

    {

    System.out.println("服务器端启动

    9b8a8a44dd1c74ae49c20a7cd451974e.png");//初始化ServerBean对象ServerBean server=newServerBean();//开启监听程序server.listenClient();

    }

    }

    从上述代码里,我们可以看到,在UDP的服务器端里,主要通过ServerBean类里提供的listenClient方法,监听从客户端发送过来的UDP报文,并通过解析得到其中包含的字符串,随后输出。7.3.2.3开发客户端代码

    由于我们已经讲述过通过Eclipse查看代码运行结果的详细步骤,所以这里我们将直接通过命令行的方式,通过javac和java等命令,查看基于多线程UDP通讯的演示效果。1.         首先我们把刚才编写好的四段java代码(即ClientBean.java、UDPClient.java、ServerBean.java和UDPServer.java)放到D盘下的work目录下(如果没有则新建)。2.         点击“开始菜单”|“运行”选项,并在“运行程序”的对话框里输入”cmd”命令,进入DOS命令界面,并进入到D:\work这个目录里。3.         如果大家已经按照第一章的说明,成功地配置好关于java的path和classpath环境变量,在这里可以直接运行javac*.java命令,编译这四个.java文件,编译后,会在D:\work目录下产生同四个java文件相对应的.class文件。4.         在这个命令窗口里运行java UDPServer命令,通过运行UDPServer代码,开启UDP服务器端程序,开启后,会出现如图7-3所示的信息。

    图7-3启动UDP服务端后的效果5.         在出现上图的效果后,别关闭这个命令窗口,按步骤(2)里说明的流程,新开启一个DOS命令窗口,并同样进入到D:\work这个目录下。6.         在新窗口里输入java UDPClient,开启UDP客户端程序。开启后,可通过键盘向服务器端输入通讯字符串,这些字符串将会以数据报文的形式发送到服务器端。

    在图7-4里,演示了UDP客户端向服务器端发送消息的效果。

    图7-4UDP客户端发送消息的效果

    每当我们在客户端发送一条消息,服务器端会收到并输出这条消息,从代码里我们可以得知,每条消息是通过为之新开启的线程发送到服务器端的。

    如果我们在客户端输入”end”或空字符串,客户端的UDPClient代码会退出。在图7-5里演示了UDP服务器端接收并输出通讯字符串的效果。

    图7-5UDP服务器端接收到消息的效果7.         由于UDPServer.java代码里,我们通过一个while(true)的循环来监听客户端的请求,所以当程序运行结束后,可通过Ctrl+C的快捷键的方式退出这段程序。

    展开全文
  • Socket接口用法详解Java中,基于TCP协议实现网络通信的类有两个,在客户端的Socket类和在服务器端的ServerSocket类,ServerSocket类的功能是建立一个Server,并通过accept()方法随时监听客户端的连接请求。...

    Socket接口用法详解

    在Java中,基于TCP协议实现网络通信的类有两个,在客户端的Socket类和在服务器端的ServerSocket类,ServerSocket类的功能是建立一个Server,并通过accept()方法随时监听客户端的连接请求。

    扩展:

    ServerSocket中常用的构造函数及方法

    构造函数:ServerSocket(int port) 这是一个构造方法,用于在当前的服务器默认的IP 地址上监听一个指定的端口,即在指定的IP和端口创建一个ServerSocket对象

    方法:

    Socket accept() 产生阻塞,监听指定的端口,直至有客户端发来连接请求

    void close() 关闭当前ServerSocket

    InetAddress getInetAddress() 返回ServerSocket监听的,本机的IP地址

    int getLocalPort() 返回ServerSocket监听的,本机的IP地址上指定的端口号

    int getSoTimeout();void setSoTimeout(int timeout) 设置连接过程中没有得到相应的等待期限时间(TimeOut)

    String toString() 以字符串的方式返回ServerSocket监听的,本机的IP地址及其端口号

    Socket类中常用的构造函数和方法

    构造函数:Socket(InetAddress address,int port) 用于创建一个链接,向指定的IP地址上指定的端口的服务器端程序发送连接请求

    Socket(String host,int port) 同上,但该方法允许通过主机名字符串向服务器发送连接请求

    方法:

    void close()关闭当前的Socket 连接

    InetAddress getInetAddress() 返回Socket建立了连接的服务器的IP地址

    InputStream getInputStream() 返回当前Socket的输入流

    OutputStream getOutStream() 返回当前Socket的输出流

    InetAddress getLocalAddress() 返回和Socket进行连接的本地的IP地址

    int getLocalPort() 返回和Socket进行连接的本地的端口号

    int getPort() 返回和Socket建立了连接的服务器的端口号

    int getSoTimeOut();void setSoTimeOut(int timeout) 设置连接过程中没有得到相应的等待期限时间

    展开全文
  • Socket接口用法详解Java中,基于TCP协议实现网络通信的类有两个,在客户端的Socket类和在服务器端的ServerSocket类,ServerSocket类的功能是建立一个Server,并通过accept()方法随时监听客户端的连接请求。...
  • Java中,基于TCP协议实现网络通信的类有两个,在客户端的Socket类和在服务器端的ServerSocket类,ServerSocket类的功能是建立一个Server,并通过accept()方法随时监听客户端的连接请求。 扩展: ServerSocket中...
  • 概念RMI(Remote Method Invocation,远程方法调用)是用Java在JDK1.2中实现的,它大大增强了Java开发分布式应用的能力。Java作为一种风靡一时的网络开发语言,其巨大的威力就体现在它强大的开发分布式网...
  • java中的socket应用详解

    2013-10-26 09:56:52
    利用Socket类的方法,就可以实现两台计算机之间的通讯。这里就介绍一下在Java中如何利用Socket进行网络编程。   在JavaSocket可以理解为客户端或者服务器端的一个特殊的对象,这个对象有
  • 反射(Reflection) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。反射是一项高级开发人员应该掌握的“黑科技”,其实反射并不是 ...
  • 主要实现了:一台手机向另外一台手机发送消息,这两台手机可以随时自由发送文本消息进行通信,类似我们常用的QQ。效果图:原理:手机通过socket发送消息到服务器,服务器每接收到一条消息之后,都会把这条消息放进一...
  • Socket实现客户端套接字的类,套接字是两台计算机之间进行通信的端点。 Socket的实际工作由SocketImpl类的实例执行 。通过更改用于创建套接字实现的套接字工厂,应用程序可以配置自身以创建适合于本地防火墙的套接...
  • 利用Socket类的方法,就可以实现两台计算机之间的通讯。这里就介绍一下在Java中如何利用Socket进行网络编程。 在JavaSocket可以理解为客户端或者服务器端的一个特殊的对象,这个对象有两个...
  • 利用Socket类的方法,就可以实现两台计算机之间的通讯。这里就介绍一下在Java中如何利用Socket进行网络编程。 在JavaSocket可以理解为客户端或者服务器端的一个特殊的对象,这个对象有两个关...
  • 前言SSL Socket通讯是对socket的扩展,增加Socket通讯的数据安全性,SSL认证分为单向和双向认证。单向认证只认证服务器端的合法性而不认证客户端的合法性。双向认证是同时认证服务端和客户端。下面我分别说说使用C#...
  • Socket 接口用法详解

    2021-01-20 03:40:44
    Java 中,基于 TCP 协议实现网络通信的类有两个,在客户端的 Socket 类和在服务器端的ServerSocket 类,ServerSocket 类的功能是建立一个 Server,并通过 accept()方法随时监听客户端的连接请求。  扩展:  ...
  • 一、概念梳理 1、Socket是什么? Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它...在业务中使用动态代理,一般是为了给需要实现方法添加预处理
  • 这篇文章主要介绍了Java查看本机端口是否被占用的主要原理,并结合具体实例给出了操作方法,需要的朋友可以参考下记得以前在写程序的时候,有一次需要查看端口的被占用情况,虽然我不会,但是有人会。所以通过网上...
  • 转载:http://www.qqread.com/java/w592395600.html Socket是网络上运行的两个程序间双向通讯的一端,它...利用Socket类的方法,就可以实现两台计算机之间的通讯。这里就介绍一下在Java中如何利用Socket进行网络编...
  • 客户端: 文件上传案例的客户端:读取本地文件,上传到服务器,读取...3.使用Socket中的方法getOutputStream,获取网络字节输出流OutputStream对象 4.使用本地字节输入流FileInputStream对象中的方法read,读取本地文件
  • HTTP协议http和httpsURLurlencode和urldecodeHTTP协议内容HTTP方法GET和POST区别状态码HTTP中header实现一个HTTP服务器(Socket API) http和https http和https都是应用层协议 应用层的协议很多时候都需要来手动指定...
  • JavaSocket/ServerSocket编程详解(中文)Socketserver和client通信流程图:Socket三次握手连接图:Socket四次握手断开连接图:Socket套接字:Socket提供了在主机之间传递原始字节的功能,以比较底层的方式访问tcp/ip...

空空如也

空空如也

1 2 3 4 5
收藏数 89
精华内容 35
关键字:

java实现socket方法详解

java 订阅