进程指运行中的程序,进程的任务就是执行程序中的代码。EchoPlayer类是一个独立的Java程序,它可以在任意一台安装了JDK的主机上运行:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class EchoPlayer {
public String echo(String msg){
return "echo:"+msg;
}
public void talk() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String msg=null;
while ((msg=bufferedReader.readLine())!=null){
System.out.println(echo(msg));
if(msg.equals("bye")){
break;
}
}
}
public static void main(String[] args) throws IOException {
new EchoPlayer().talk();
}
}
运行“java EchoPlayer”命令,就启动了EchoPlayer进程,该进程执行EchoPlayer类的main()方法。下图演示了EchoPlayer进程的运行过程,它从本地控制台中获得标准输入流和标准输出流。本地控制台为用户提供了基于命令行的用户界面,用户通过控制台与EchoPlayer进程交互。
提示:确切地说,运行“java EchoPlayer”命令,就启动了一个Java虚拟机(Java Virtual Machine,JVM)进程,该进程执行EchoPlayer类的main()方法。为了叙述方便,把运行EchoPlayer类的main()方法的JVM进程直接称为EchoPlayer进程。
EchoPlayer类的echo(String msg)方法负责生成响应结果。如果需要把生成响应结果的功能(即echo(String msg)方法)移动到一个远程主机上,那么上面的EchoPlayer类无法满足这一需求。在这种情况下,要创建两个程序:客户程序EchoClient和服务器程序EchoServer。EchoClient程序有两个作用:
EchoServer程序负责接收EchoClient发送的字符串,然后把响应结果发送给EchoClient。下图演示了EchoClient与EchoServer的通信过程。客户机和远程服务器是通过网络连接的两台主机。客户机上运行EchoClient进程,远程服务器上运行EchoServer进程。
张三给李四打电话,两者顺利通话的前提条件是他们各自的电话机都连接到了电话网络上。张三和李四只需关注他们谈话的具体内容,而不必考虑如何把自己的话音传输到对方的电话机上。传输语音信息的任务是由电话网络来完成的。
同样,两个进程顺利通信的前提条件是它们所在的主机都连接到了计算机网络上。EchoClient与EchoServer只需关注它们通信的具体内容,例如EchoClient发送信息“hello”,那么EchoServer返回信息“echo:hello”。EchoClient和EchoServer都无须考虑如何把信息传输给对方。传输信息的任务是由计算机网络来完成的。
Java开发人员的任务是编写EchoClient和EchoServer程序,接下来在两台安装了JDK的主机上分别运行它们,两个进程就会有条不紊地通信。
由于进程之间的通信建立在计算机网络的基础上,Java开发人员有必要对计算机网络有基本的了解,这有助于更容易地掌握Java网络编程技术。
计算机网络是现代通信技术与计算机技术相结合的产物。所谓计算机网络,指把分布在不同地理区域的计算机用通信线路互联起来的一个具有强大功能的网络系统。在计算机网络上,众多计算机可以方便地互相通信,共享硬件、软件和数据信息等资源。通俗地说,计算机网络就是通过电缆、电话线或无线通信设施等互联的计算机的集合。
网络中每台机器称为节点(Node)。大多数节点是计算机,此外,打印机、路由器、网桥、网关和哑终端等也是节点。我们通常用“节点”指代网络中的任意一个设备,用“主机”指代网络中的计算机节点。
如下图所示,人与人之间通过某种语言来交流,网络中的主机之间也通过“语言”来交流,这种语言称为网络协议,这是对网络协议的通俗解释,后面还会更深入地介绍网络协议的概念。
网络中的每个主机都有地址,它是用于标识主机的字节序列。字节序列越长,可以表示的地址数目就越多,这意味着可以有越多的设备连入网络。
按照计算机联网的区域大小,可以把网络分为局域网(Local Area Network,LAN)和广域网(Wide Area Network,WAN)。局域网指在一个较小地理范围内的各种计算机互联在一起的通信网络,可以包含一个或多个子网,通常局限在几千米的范围之内。例如在一个房间、一座大楼,或是在一个校园内的网络可称为局域网。广域网连接地理范围较大,常常是一个国家或是一个洲,是为了让分布较远的各局域网互联。
Internet是由许多小的网络互联成的国际性大网络,在各个小网络内部使用不同的协议,那么如何使不同的网络之间能进行信息交流呢?如下图所示,上海人讲上海方言,广东人讲广东方言,上海人与广东人用普通话沟通。
与此相似,不同网络之间的互联靠网络上的标准语言—TCP/IP。的互联靠网络上的标准语言—TCP/IP。如下图所示,一个网络使用协议A,另一个网络使用协议B,这两个网络通过TCP/IP进行互联。
在计算机网络产生之初,每家计算机厂商都有一套自己的网络体系结构,它们之间互不相容。为此,国际标准化组织(International Organization for Standization,ISO)在1979年建立了一个分委员会,来专门研究一种用于开放系统互联(Open System Interconnection,OSI)的体系结构,“开放”这个词意味着:一个网络系统只要遵循OSI模型,就可以和位于世界上任何地方的、也遵循OSI模型的其他网络系统连接。这个分委员会提出了OSI参考模型,它为各种异构系统互联提供了概念性的框架。
OSI参考模型把网络分为7层,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,如下图示。每一层都使用下层提供的服务,并为上层提供服务。
不同主机之间的相同层称为对等层。例如主机A中的表示层和主机B中的表示层互为对等层,主机A中的会话层和主机B中的会话层互为对等层。OSI参考模型中各层的主要功能如下:
OSI参考模型把网络分为多个层次,每个层次都有明确的分工,这简化了网络系统的设计过程。例如在设计应用层时,只需考虑如何创建满足用户实际需求的应用,在设计传输层时,只需考虑如何在两个主机之间传输数据,在设计网络层时,只需考虑如何在网络上找到一条发送数据的路径,即路由。
对等层之间互相通信需要遵守一定的规则,如通信的内容和通信的方式,这种规则被称为网络协议(Protocol)。值得注意的是,OSI参考模型并没有具体的实现方式,它没有在各层制定网络协议,但它为其他计算机厂商或组织制定网络协议提供了参考框架。网络的各个层次都有相应的协议,以下归纳了OSI各个层的一些典型协议,这些协议均由第三方提供:
国际标准化组织ISO制订的OSI参考模型提出了网络分层的思想,这种思想对网络的发展具有重要的指导意义。但由于OSI参考模型过于庞大和复杂,所以它难以被投入实际运用中。与OSI参考模型相似的TCP/IP参考模型吸取了网络分层的思想,但是对网络的层次做了简化,并且在网络各层(除主机-网络层外)都提供了完善的协议,这些协议构成了TCP/IP集,简称TCP/IP。TCP/IP是目前最流行的商业化协议,它是当前的工业标准或“事实标准”,TCP/IP主要用于广域网,在一些局域网中也有运用。
TCP/IP参考模型是美国国防部高级研究计划局计算机网(Advanced Research Project Agency Network,ARPANET)以及后来的Internet使用的参考模型。ARPANET是由美国国防部(U.S.Department of Defense,DoD)赞助的研究网络。最初,它只连接了美国境内的四所大学。在随后的几年中,它通过租用的电话线连接了数百所大学和政府部门。最终,ARPANET发展成为全球规模最大的互联网络——Internet。最初的ARPANET则于1990年永久关闭。下图把TCP/IP参考模型和OSI参考模型进行了对比。
TCP/IP参考模型分为4个层次:应用层、传输层、网络互联层和主机-网络层。每一层都有相应的协议。确切地说,TCP/IP应该被称为TCP/IP集,它是TCP/IP参考模型的除主机-网络层以外的其他三层的协议的集合,而IP和TCP则是协议集的最核心的两个协议。下表列出了各层的主要协议,其中主机-网络层的协议是由第三方提供的。
在TCP/IP参考模型中,去掉了OSI参考模型中的会话层和表示层,这两层的功能被合并到应用层,同时将OSI参考模型中的数据链路层和物理层合并到主机-网络层。下面分别介绍各层的主要功能。
IP网络(即在网络层采用IP的网)中的每台主机都有唯一的IP地址,IP地址用于标识网络中的每个主机。IP地址分为IPv4和IPv6。
下面以IPv4为例,介绍IP地址的组成。IP地址由两部分组成:IP网址和IP主机地址。IP网址表示网络的地址,IP主机地址表示网络中的主机的地址。网络掩码用来确定IP地址中哪部分是网址,哪部分是主机地址。
网络掩码的形式与IP地址相同,但有一定的限制。在网络掩码的二进制序列中,前面部分都为1,后面部分都为0。假定IP地址192.166.3.4的网络掩码为255.255.255.0。这个网络掩码的二进制序列为11111111.11111111.11111111.00000000。把网络掩码与IP地址进行二进制与操作,得到的结果就是IP网址。因此,IP地址192.166.3.4的网址为192.166.3.0。如果把网络掩码设为255.255.0.0,那么IP网址为192.166.0.0。
提示:在Internet上,每个主机都必须有全球范围内唯一的IP地址。国际机构NIC(Internet Network Information Center)统一负责全球地址的规划和管理,与此同时,InterNIC、APNIC、RIPE和CNNIC等机构具体负责美国及全球其他地区的IP地址分配。中国地区的IP地址分配由CNNIC机构负责。
下图展示了两个互联的网络的配置,从该图可以看出,每个网络都有IP网址,两个网络之间用路由器连接。
一个公司可能拥有一个网址和多个主机。例如,如果网址为192.166.0.0,则可以有216(即65536)个主机加入网中。为了更好地管理网络,提高网络性能和安全性,可以把网络划分为多个子网。子网可包括某地理位置内(如某大楼或相同局域网中)的所有主机。例如对于网址为“192.166.0.0”的网络,可从整个网络中分出3个子网,这3个子网的网址分别为:192.166.1.0,192.166.2.0和192.166.3.0,这些子网的掩码都为255.255.255.0。
IP是面向包的协议,即数据被分成若干小数据包,然后分别传输它们。IP网络上的主机只能直接向本地网上的其他主机(也就是具有相同IP网址的主机)发送数据包。主机实际上有两个不同性质的地址:物理地址和IP地址。物理地址是由主机上的网卡来标识的,物理地址才是主机的真实地址。如下图所示,主机A向同一个网络上的另一个主机B发包时,会通过地址解析协议(Address Resolution Protocol,ARP)获得对方的物理地址,然后把包发给对方。ARP的运行机制为主机A在网络上广播一个ARP消息:“要寻找地址为192.166.3.5的主机”,接着,具有这个IP地址的主机B就会做出响应,把自身的物理地址告诉主机A。
当主机A向另一个网络上的主机B发送包时,主机A利用ARP找到本地网络上的路由器的物理地址,把包转发给它。路由器会按照如下步骤处理数据包:
从以上路由器的处理步骤可以看出,IP并不保证一定把数据包送达目标主机,在发送过程中,会因为数据包结束生命周期,或者找不到路由而丢弃数据包。
虽然IP地址能够唯一标识网络上的主机,但IP地址是数字型的,用户记忆数字型的IP地址很不方便,于是人们又发明了另一种字符型的地址,即所谓的域名(Domain Name)。域名地址具有易于理解的字面含义,便于记忆。IP地址和域名一一对应。例如JavaThinker网站的域名为www.javathinker.net,对应的IP地址为:43.247.68.17。
域名是从右至左来表述其意义的,最右边的部分为顶层域,最左边的则是这台主机的机器名称。域名一般可表示为:主机机器名.单位名.网络名.顶层域名。如:mail.xyz.edu.cn,这里的mail是xyz学校的一个主机的机器名,xyz代表一个学校的名字,edu代表中国教育科研网,cn代表中国,顶层域一般是网络机构或所在国家地区的名称缩写。
DNS(Domain Name System)协议采用DNS服务器提供把域名转换为IP地址的服务。DNS服务器分布在网络的各个地方,它们存放了域名与IP地址的映射信息。用户需要访问网络上某个主机时,只需提供主机直观的域名,DNS协议首先请求地理位置比较近的DNS服务器进行域名到IP地址的转换,如果在该服务器中不存在此域名信息,那么DNS协议再让远方的DNS服务器提供服务。
URL(Uniform Resource Locator),即统一资源定位器。它是专为标识网络上资源位置而设的一种编址方式,大家熟悉的网页地址就属于URL。URL一般由3部分组成:
应用层协议://主机IP地址或域名/资源所在路径/文件名
例如JavaThinker网站提供的JDK安装软件包的URL为:
其中“http”指超文本传输协议,“www.javathinker.net”是Web服务器的域名,“software”是文件所在路径,“jdk8.exe”才是相应的文件。
在URL中,常见的应用层协议还包括ftp和file等,比如:
以上file协议用于访问本地计算机上的文件,使用这种协议的URL以“file:///”开头。
IP在发送数据包的途中会遇到各种状况,例如可能路由器突然崩溃,使数据包丢失。再例如可能前面的数据包沿低速链路移动,而后面的数据包沿高速链路移动而超过前面的包,最后使得数据包的顺序混乱。
TCP使两台主机上的进程顺利通信,不必担心数据包丢失或顺序混乱。TCP跟踪数据包顺序,并且在数据包顺序混乱时按正确顺序对其进行重组。如果数据包丢失,则TCP会请求源主机重新发送。
如下图所示,两台主机上都会运行许多进程。当主机A上的进程A1向主机B上的进程B1发送数据时,IP根据主机B的IP地址,把进程A1发送的数据送达主机B。接下来,TCP需要决定把数据发送到主机B中的哪个进程。TCP采用端口来区分进程。端口不是物理设备,而是用于标识进程的逻辑地址,更确切地说,是用于标识TCP连接端点的逻辑地址。当两个进程进行一次通信时,就意味着建立了一个TCP连接,TCP连接的两个端点用端口来标识。在下图中,进程A1与进程B1之间建立了一个TCP连接,进程B1的端口为80,因此进程B1的地址为主机B:80。进程A1的端口为1000,因此进程A1的地址为主机A:1000。每个进程都有了唯一的地址,TCP就能保证把数据顺利送达特定的进程。
提示:在客户/服务器模型中,客户进程可能会与服务器进程同时建立多个TCP连接,在客户端,每一个TCP连接都被分配一个端口。参见下图:
端口号的范围为0到65535,其中0到1023的端口号一般被固定分配给一些服务。比如21端口被分配给FTP服务,25端口被分配给SMTP(简单邮件传输)服务,80端口被分配给HTTP(超级文本传输)服务,135端口被分配给RPC(远程过程调用)服务等等。
从1024到65535的端口号供用户自定义的服务使用。比如假定范例中的EchoServer服务使用8000端口。当EchoServer程序运行时,就会占用8000端口,当程序运行结束时,就会释放所占用的端口。
客户进程的端口一般由所在主机的操作系统动态分配,当客户进程要求与一个服务器进程进行TCP连接时,操作系统会为客户进程随机地分配一个还未被占用的端口,当客户进程与服务器进程断开连接时,这个端口就被释放。
此外还要指出的是,TCP和UDP都用端口来标识进程。在一个主机中,TCP端口与UDP端口的取值范围是各自独立的,允许存在取值相同的TCP端口与UDP端口。如下图所示,在主机A中,进程A1占用FTP端口1000,进程A2占用UDP端口1000,这是被允许的。
TCP/IP是以RFC(Request For Comment)文档的形式发布的。RFC指描述互联网相关技术规范的文档。
RFC由个人编写,这些人自愿编写某一新协议或规范的提议草案,并提交给Internet工程任务组织(The Internet Engineering Task Force,IETF)。IETF负责审阅和发布这些被统称为RFC的文档,每个文档都有一个RFC编号,并且处于以下六种类型之一:
在FRC的官方网站上已经发布了8000多份RFC文档。下表列出了与TCP/IP 协议相关的RFC文档编号。
在FRC的官方网站上输入网址:http://www.ietf.org/rfc/rfcXXXX.txt,就能查看相关的FRC文档,这里的XXXX表示文档编号。
RFC文档一旦被正式发布,其编号和内容就不允许改变。如果需要更新RFC文档,则会对更新后的RFC文档赋予新的编号,再将它发布。例如HTTP1.0协议对应的RFC文档编号为RFC1945,它的升级版本HTTP1.1协议对应的RFC文档编号为RFC2068。
TCP/UDP推动了客户/服务器通信模式的广泛运用。在通信的两个进程中,一个进程为客户进程,另一个进程为服务器进程。客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求。如下图所示,通常,一个服务器进程会同时为多个客户进程服务,图中服务器进程B1同时为客户进程A1、A2和B2提供服务。以下伪代码演示了服务器进程的大致工作流程:
服务器进程可以提供各种各样的服务,例上面提到的EchoServer提供的服务为根据EchoClient发出的字符串XXX,返回字符串“echo:XXX”。除了像EchoServer这样的由用户自定义的服务,网络上还有许多众所周知的通用服务,最典型的是HTTP服务。网络应用层的协议规定了客户程序与这些通用服务器程序的通信细节,例如HTTP规定了HTTP客户程序发出的请求的格式,还规定了HTTP服务器程序发回的响应的格式。
在现实生活中,有些重要的服务机构的电话是固定的,这有助于人们方便地记住电话和获得服务,比如众所周知的电话110、120和119分别是报警、急救和火警电话。同样,在网络上有些通用的服务有着固定的端口,下表对常见的服务以及相应的协议和端口做了介绍。
Java网络程序都建立在TCP/IP基础上,致力于实现应用层。传输层向应用层提供了套接字Socket接口,Socket封装了下层的数据传输细节,应用层的程序通过Socket来建立与远程主机的TCP连接以及进行数据传输。
站在应用层的角度,两个进程之间的一次通信过程从建立TCP连接开始,接着交换数据,到断开连接结束。套接字可以被看作是通信线路两端的收发器,进程通过套接字来收发数据,如下图所示:
在上图中,如果把进程A1和程B1比作两个人,那么图中的两个Socket就像两个人各自持有的电话机的话筒,只要拨通了电话,两个人就能通过各自的话筒进行通话。
在Java中,有3种套接字类:java.net.Socket、java.net.ServerSocket和DatagramSocket。其中Socket和ServerSocket类建立在TCP基础上,DatagramSocket类建立在UDP基础上。Java网络程序都采用客户/服务通信模式。
服务器程序通过一直监听端口,来接收客户程序的连接请求。在服务器程序中,需要先创建一个ServerSocket对象,在构造方法中指定监听的端口:
ServerSocket的构造方法负责在操作系统中把当前进程注册为服务器进程。服务器程序接下来调用ServerSocket对象的accept()方法,该方法一直监听端口,等待客户的连接请求,如果接收到一个连接请求,accept()方法就会返回一个Socket对象,这个Socket对象与客户端的Socket对象形成了一条通信线路:
Socket类提供了getInputStream()方法和getOutputStream()方法,分别返回输入流InputStream对象和输出流OutputStream对象。程序只需向输出流写数据,就能向对方发送数据;只需从输入流读数据,就能接收来自对方的数据。下图演示了服务器与客户利用ServerSocket和Socket来通信的过程。
与普通I/O流一样,Socket的输入流和输出流也可以用过滤流来装饰。在以下代码中,先获得输出流,然后用PrintWriter装饰它,PrintWriter的println()方法能够写一行数据;以下代码接着获得输入流,然后用BufferedReader装饰它,BufferedReader的readLine()方法能够读入一行数据。
下面是EchoServer的源程序:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @title EchoServer
* @description 测试
* @author: yangyongbing
* @date: 2023/12/5 10:04
*/
public class EchoServer {
private int port=8080;
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 socketOutputStream = socket.getOutputStream();
return new PrintWriter(socketOutputStream,true);
}
private BufferedReader getReader(Socket socket) throws IOException {
InputStream socketInputStream = socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketInputStream));
}
public void service(){
while (true){
Socket socket=null;
try {
// 等待客户端连接
socket=serverSocket.accept();
System.out.println("New connection accepted "+socket.getInetAddress()+":"+socket.getPort());
BufferedReader bufferedReader = getReader(socket);
PrintWriter printWriter = getWriter(socket);
String msg=null;
while ((msg=bufferedReader.readLine())!=null){
System.out.println(msg);
printWriter.println(echo(msg));
// 如果客户端发送的消息为"bye",则通信结束
if(msg.equals("bye")){
break;
}
}
}catch (IOException e){
e.printStackTrace();
}finally {
if(socket!=null){
// 断开连接
try {
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()方法断开连接。
在EchoClient程序中,为了与EchoServer通信,需要先创建一个Socket对象:
在以上Socket的构造方法中,参数host表示EchoServer进程所在的主机的名字,参数port表示EchoServer进程监听的端口。当参数host的取值为“localhost”时,表示EchoClient与EchoServer进程运行在同一个主机上。如果Socket对象被成功创建,就表示建立了EchoClient与EchoServer之间的连接。接下来,EchoClient从Socket对象中得到了输出流和输入流,就能与EchoServer交换数据。
下面为EchoClient的源程序:
import java.io.*;
import java.net.Socket;
/**
* @title EchoClient
* @description 测试
* @author: yangyongbing
* @date: 2023/12/5 10:32
*/
public class EchoClient {
private String host="localhost";
private Socket socket;
private int port=8080;
public EchoClient() throws IOException {
socket = new Socket(host,port);
}
private PrintWriter getWriter(Socket socket) throws IOException {
OutputStream socketOutputStream = socket.getOutputStream();
return new PrintWriter(socketOutputStream,true);
}
private BufferedReader getReader(Socket socket) throws IOException {
InputStream socketInputStream = socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketInputStream));
}
public void talk() throws IOException {
try {
BufferedReader bufferedReader = getReader(socket);
PrintWriter printWriter = getWriter(socket);
BufferedReader localReader = new BufferedReader(new InputStreamReader(System.in));
String msg=null;
while ((msg=localReader.readLine())!=null){
printWriter.println(msg);
System.out.println(bufferedReader.readLine());
if(msg.equals("bye")){
break;
}
}
}catch (IOException e){
e.printStackTrace();
}finally {
try {
socket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
new EchoClient().talk();
}
}
在EchoClient类中,最主要的方法为talk()方法。该方法不断读取用户从控制台输入的字符串,然后把它发送给EchoServer,再把EchoServer返回的字符串打印到控制台。如果用户输入的字符串为“bye”,就会结束与EchoServer的通信,调用socket.close()方法断开连接。
在运行范例时,需要打开两个命令行界面,先在一个命令行界面中运行“java EchoServer”命令,再在另一个命令行界面中运行“java EchoClient”命令。下图显示了运行这两个程序的命令行界面。在EchoClient控制台,用户输入字符串“hi”,程序就会输出“echo:hi”。
提示:如果希望在一个命令行控制台中同时运行EchoServer和EchoClient程序,那么可以先运行“start java EchoServer”命令,再运行“java EchoClient”命令。“start java EchoServer”命令中“start”的作用是打开一个新的命令行控制台,然后在该控制台中运行“java EchoServer”命令。
在EchoServer程序的service()方法中,每当serverSocket.accept()方法返回一个Socket对象,都表示建立了与一个客户的连接,这个Socket对象中包含了客户的地址和端口信息,只需调用Socket对象的getInetAddress()和getPort()方法就能分别获得这些信息:
从上图可以看出,EchoServer的控制台显示EchoClient的IP地址为127.0.0.1,端口为9001。127.0.0.1是本地主机的IP地址,表明EchoClient与EchoServer在同一个主机上。EchoClient作为客户程序,它的端口是由操作系统随机产生的。每当客户程序创建一个Socket对象,操作系统都会为客户分配一个端口。假定在客户程序中先后创建了两个Socket对象,这就意味着客户与服务器之间同时建立了两个连接:
操作系统为客户的每个连接分配一个唯一的端口,如上图所示。
在客户进程中,Socket对象包含了本地以及对方服务器进程的地址和端口信息,在服务器进程中,Socket对象也包含了本地以及对方客户进程的地址和端口信息,Socket类的以下方法用于获取这些信息:
客户进程允许建立多个连接,每个连接都有唯一的端口。在图上图中,客户进程占用两个端口:9001和10028。在编写网络程序时,一般只需要显式地为服务器程序中的ServerSocket设置端口,而不必考虑客户程序所用的端口。
简单地理解,计算机网络的任务就是传输数据。为了完成这一复杂的任务,国际标准化组织ISO提供了OSI参考模型,这种模型把互联网络分为七层,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。每个层有明确的分工,并且在层与层之间,下层为上层提供服务。这种分层的思想简化了网络系统的设计过程。例如在设计应用层时,只需考虑如何创建满足用户实际需求的应用,在设计传输层时,只需考虑如何在两个主机之间传输数据,在设计网络层时,只需考虑如何在网络上找到一条发送数据的路径,即路由。
由于OSI参考模型过于庞大和复杂,使它难以投入实际运用中。与OSI参考模型相似的TCP/IP参考模型吸取了网络分层的思想,但是对网络的层次做了简化,并且在网络各层(除主机-网络层外)都提供了完善的协议,这些协议构成了TCP/IP集,简称TCP/IP。TCP/IP参考模型分为四层,分别是应用层、传输层、网络互联层和主机-网络层。每一层都有相应的协议,IP和TCP是协议集中最核心的两个协议。
IP位于网络互联层,用IP地址来标识网络上的各个主机,IP把数据分为若干数据包,然后为这些数据包确定合适的路由。路由指把数据包从源主机发送到目标主机的路径。
建立在TCP/IP基础上的网络程序一般都采用客户/服务器通信模式。服务器程序提供服务,客户程序请求获得服务。服务器程序一般昼夜运行,时刻等待客户的请求并及时做出响应。
Java网络程序致力于实现应用层。传输层向应用层提供了套接字Socket接口,Socket封装了下层的数据传输细节,应用层的程序通过Socket来建立与远程主机的TCP连接以及进行数据传输。在Java中,有3种套接字类:java.net.Socket、java.net.ServerSocket和DatagramSocket。其中Socket和ServerSocket类建立在TCP基础上;DatagramSocket类建立在UDP基础上。