>>分享孙卫琴的Java技术专稿和著作 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 25164 个阅读者 刷新本主题
 * 贴子主题:  【Java网络编程专题】用Java套接字编写基本的客户/服务器程序 回复文章 点赞(0)  收藏  
作者:sunweiqin    发表时间:2019-12-23 02:02:38     消息  查看  搜索  好友  邮件  复制  引用

本文参考《Java网络编程核心技术详解》,作者:孙卫琴,电子工业出版社出版
源代码下载地址为:http://www.javathinker.net/kecheng/javanet/javanetsourcecode.rar

Java网络程序建立在TCP/IP协议基础上,致力于实现应用层。传输层向应用层提供了套接字Socket接口,Socket封装了下层的数据传输细节,应用层的程序通过Socket来建立与远程主机的TCP连接以及进行数据传输。

站在应用层的角度,两个进程之间的一次通信过程从建立TCP连接开始,接着交换数据,到断开连接结束。套接字可看过是通信线路两端的收发器,进程通过套接字来收发数据,参见图1。
点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小  
图1   套接字可看过是通信线路两端的收发器

在图1,如果把进程A1和程B1比作两个人,那么图中的两个Socket就像两个人各自持有的电话机的话筒,只要拨通了电话,两个人就能通过各自的话筒进行通话。

在Java中,有三种套接字类:java.net.Socket、java.net.ServerSocket和DatagramSocket。其中Socket和ServerSocket类建立在TCP协议基础上,DatagramSocket类建立在UDP协议基础上。Java网络程序都采用客户/服务通信模式。
本节以EchoServer和EchoClient为例,介绍如何用ServerSocket和Socket来编写服务器程序和客户程序。

1.1  创建EchoServer

服务器程序通过一直监听端口,来接收客户程序的连接请求。在服务器程序中,需要先创建一个ServerSocket对象,在构造方法中指定监听的端口:
ServerSocket server=new ServerSocket(8000); //监听8000端口

ServerSocket的构造方法负责在操作系统中把当前进程注册为服务器进程。服务器程序接下来调用ServerSocket对象的accept()方法,该方法一直监听端口,等待客户的连接请求,如果接收到一个连接请求,accept()方法就会返回一个Socket对象,这个Socket对象与客户端的Socket对象形成了一条通信线路:


Socket socket=server.accept();  //等待客户的连接请求

Socket类提供了getInputStream()方法和getOutputStream()方法,分别返回输入流InputStream对象和输出流OutputStream对象。程序只需向输出流写数据,就能向对方发送数据;只需从输入流读数据,就能接收来自对方的数据。图2演示了服务器与客户利用ServerSocket和Socket来通信的过程。
点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小  
图2  服务器与客户利用ServerSocket和Socket来通信

与普通I/O流一样,Socket的输入流和输出流也可以用过滤流来装饰。在以下代码中,先获得输出流,然后用PrintWriter装饰它,PrintWriter的println()方法能够写一行数据;以下代码接着获得输入流,然后用BufferedReader装饰它,BufferedReader的readLine()方法能够读入一行数据:


OutputStream socketOut = socket.getOutputStream();
//参数true表示每写一行,PrintWriter缓存就自动溢出,把数据写到目的地
PrintWriter pw=PrintWriter(socketOut,true);  
InputStream socketIn = socket.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(socketIn));

以下是EchoServer的源程序。


/*  EchoServer.java */
import java.io.*;
import java.net.*;
public class EchoServer {
  private int port=8000;
  private ServerSocket serverSocket;

  public EchoServer() throws IOException {
    serverSocket = new ServerSocket(port);
    System.out.println("服务器启动");
  }

  public String echo(String msg) {
    return "echo:" + msg;
  }

  private PrintWriter getWriter(Socket socket)throws IOException{
    OutputStream socketOut = socket.getOutputStream();
    return new PrintWriter(socketOut,true);
  }

  private BufferedReader getReader(Socket socket)throws IOException{
    InputStream socketIn = socket.getInputStream();
    return new BufferedReader(new InputStreamReader(socketIn));
  }

  public void service() {
    while (true) {
      Socket socket=null;
      try {
        socket = serverSocket.accept();  //等待客户连接
        System.out.println("New connection accepted "
                        +socket.getInetAddress() + ":" +socket.getPort());
        BufferedReader br =getReader(socket);
        PrintWriter pw = getWriter(socket);

        String msg = null;
        while ((msg = br.readLine()) != null) {
          System.out.println(msg);
          pw.println(echo(msg));
          if (msg.equals("bye")) //如果客户发送的消息为“bye”,就结束通信
            break;
        }
      }catch (IOException e) {
         e.printStackTrace();
      }finally {
         try{
           if(socket!=null)socket.close();  //断开连接
         }catch (IOException e) {e.printStackTrace();}
      }
    }
  }

  public static void main(String args[])throws IOException {
    new EchoServer().service();
  }
}

EchoServer类最主要的方法为service()方法,它不断等待客户的连接请求,当serverSocket.accept()方法返回一个Socket对象,就意味着与一个客户建立了连接。接下来从Socket对象中得到输出流和输入流,并且分别用PrintWriter和BufferedReader来装饰它们。然后不断调用BufferedReader的readLine()方法读取客户发来的字符串XXX,再调用PrintWriter的println()方法向客户返回字符串echo:XXX。当客户发来的字符串为“bye”,就会结束与客户的通信,调用socket.close()方法断开连接。

1.2  创建EchoClient

在EchoClient程序中,为了与EchoServer通信,需要先创建一个Socket对象:


String host="localhost";  
String port=8000;
Socket socket=new Socket(host,port);  

在以上Socket的构造方法中,参数host表示EchoServer进程所在的主机的名字,参数port表示EchoServer进程监听的端口。当参数host的取值为“localhost”,表示EchoClient与EchoServer进程运行在同一个主机上。如果Socket对象成功创建,就表示建立了EchoClient与EchoServer之间的连接。接下来,EchoClient从Socket对象中得到了输出流和输入流,就能与EchoServer交换数据。

以下是EchoClient的源程序。


/*   EchoClient.java  */
import java.net.*;
import java.io.*;
import java.util.*;
public class EchoClient {
  private String host="localhost";
  private int port=8000;
  private Socket socket;
  
  public EchoClient()throws IOException{
     socket=new Socket(host,port);  
  }
  public static void main(String args[])throws IOException{
    new EchoClient().talk();
  }
  private PrintWriter getWriter(Socket socket)throws IOException{
    OutputStream socketOut = socket.getOutputStream();
    return new PrintWriter(socketOut,true);
  }
  private BufferedReader getReader(Socket socket)throws IOException{
    InputStream socketIn = socket.getInputStream();
    return new BufferedReader(new InputStreamReader(socketIn));
  }
  public void talk()throws IOException {
    try{
      BufferedReader br=getReader(socket);
      PrintWriter pw=getWriter(socket);
      BufferedReader localReader=  
              new BufferedReader(new InputStreamReader(System.in));
      String msg=null;
      while((msg=localReader.readLine())!=null){

        pw.println(msg);
        System.out.println(br.readLine());

        if(msg.equals("bye"))
          break;
      }
    }catch(IOException e){
       e.printStackTrace();
    }finally{
      try{socket.close();}catch(IOException e){e.printStackTrace();}
    }
  }
}

在EchoClient类中,最主要的方法为talk()方法。该方法不断读取用户从控制台输入的字符串,然后把它发送给EchoServer,再把EchoServer返回的字符串打印到控制台。如果用户输入的字符串为“bye”,就会结束与EchoServer的通信,调用socket.close()方法断开连接。

运行范例时,需要打开两个DOS界面,先在一个DOS界面中运行“java EchoServer”命令,再在另一个DOS界面中运行“java EchoClient”命令。图3显示了运行这两个程序的DOS界面。在EchoClient控制台,用户输入字符串“hi”,程序就会输出“echo:hi”。
点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小  
图3  运行EchoServer和EchoClient程序

如果希望在一个DOS控制台中同时运行EchoServer和EchoClient程序,那么可以先运行“start java EchoServer”命令,再运行“java EchoClient”命令。“start java EchoServer”命令中“start”的作用是打开一个新的DOS控制台,然后在该控制台中运行“java EchoServer”命令。

在EchoServer程序的service()方法中,每当serverSocket.accept()方法返回一个Socket对象,就表示建立了与一个客户的连接,这个Socket对象中包含了客户的地址和端口信息,只需调用Socket对象的.getInetAddress()和getPort()方法就能分别获得这些信息:


socket = serverSocket.accept();  //等待客户连接
System.out.println("New connection accepted "
                +socket.getInetAddress() + ":" +socket.getPort());

从图3可以看 出,EchoServer的控制台显示EchoClient的IP地址为127.0.0.1,端口为1874。127.0.0.1是本地主机的IP地址,表明EchoClient与EchoServer在同一个主机上。EchoClient作为客户程序,它的端口是由操作系统随机产生的。每当客户程序创建一个Socket对象,操作系统就会为客户分配一个端口。假定在客户程序中先后创建了两Socket对象,这意味着客户与服务器之间同时建立了两个连接:


Socket socket1=new Socket(host,port);
Socket socket2=new Socket(host,port);

操作系统为客户的每个连接分配一个唯一的端口,参见图4。
点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小  
图4  客户与服务器进程之间同时建立了两个连接

在客户进程中,Socket对象包含了本地以及对方服务器进程的地址和端口信息,在服务器进程中,Socket对象也包含了本地以及对方客户进程的地址和端口信息,Socket类的以下方法用于获取这些信息:

(1)getInetAddress():获得远程被连接进程的IP地址。
(2)getPort():获得远程被连接进程的端口。
(3)getLocalAddress():获得本地的IP地址。
(4)getLocalPort():获得本地的端口。

客户进程允许建立多个连接,每个连接都有唯一的端口。在图4中,客户进程占用两个端口:1874和1875。在编写网络程序时,一般只需要显式的为服务器程序中的ServerSocket设置端口,而不必考虑客户程序所用的端口。



程序猿的技术大观园:www.javathinker.net



[这个贴子最后由 admin 在 2021-10-09 10:55:13 重新编辑]
  Java面向对象编程-->集合(下)
  JavaWeb开发-->Web运作原理(Ⅳ)
  JSP与Hibernate开发-->通过JPA API检索数据
  Java网络编程-->通过JDBC API访问数据库
  精通Spring-->Vue组件开发高级技术
  Vue3开发-->Vue Router路由管理器
  【Vue.js技术专题】路由管理器的基本用法
  【Vue.js技术专题】组件的递归
  【Vue.js技术专题】自定义指令范例:v-drag指令
  【Spring Cloud Alibaba专题】GateWay与Nacos整合
  【Spring专题】@Query注解设定查询语句
  从《精通Spring》和《精通Vue.js》的写作分享学习新技术的经...
  【持久化专题】用@MapsId注解映射派生主键
  【持久化专题】@Enumerated注解映射枚举类型
  【持久化专题】Hibernate的配置文件
  【持久化专题】通过JPA API调用存储过程
  【Java网络编程专题】用Apache FTPClient在FTP服务器上创建目...
  【Java网络编程专题】异步通道和异步运算结果
  【持久化专题】比较JPA的EntityManager接口与Hibernate的Ses...
  【Java基础编程专题】用内部类实现回调
  【Java基础编程专题】Java继承的利弊和使用原则
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。