|
本文参考《Java网络编程核心技术详解》,作者:孙卫琴,电子工业出版社出版
源代码下载地址为:http://www.javathinker.net/kecheng/javanet/javanetsourcecode.rar
Java网络程序建立在TCP/IP协议基础上,致力于实现应用层。传输层向应用层提供了套接字Socket接口,Socket封装了下层的数据传输细节,应用层的程序通过Socket来建立与远程主机的TCP连接以及进行数据传输。
站在应用层的角度,两个进程之间的一次通信过程从建立TCP连接开始,接着交换数据,到断开连接结束。套接字可看过是通信线路两端的收发器,进程通过套接字来收发数据,参见图1。
图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来通信的过程。
图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”。
图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。
图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 重新编辑]
|
|