首先来思考几个问题:
- 如何把互联网上的网页抓下来?
- 如何与互联网上的网络资源通信?
- 如何在两个Java程序之间建立网络?
- 面向连接与非面向连接的通信方式有什么区别?
接下来以此篇文章来学习:
- 理解计算机网络编程的概念,掌握如何使用Java在一台或多台计算机之间进行基于TCP/IP协议的网络通讯。
- 通过理解TCP/IP协议的通讯模型,以JDK提供的java.net包为工具,掌握各种基于Java的网络通讯的实现方法。
会涉及到的重难点:
- 基于URL的网络编程(主要针对WWW资源)
- 基于TCP的C/S网络编程(单客户、多客户)
- 基于UDP的C/S网络编程
一. 网络编程基础
1. URL对象
此点介绍如何用Java访问外部资源,尤其是Java的URL对象来访问网络资源。
(1)网络基础知识:
IPv4地址(32位,4个字节),如:116.111.136.3;166.111.52.80
Ipv6地址(128位,16字节)
主机名(hostname),如:www.baidu.com
比如 116.111.136.3,就是一个IP地址,随着互联网资源越来越多,原有的地址已经无法表示那么多资源,继而出现了Ipv6地址,它能够表示的地址范围更加宽广。又因为IP地址不容易记住,互联网出现一套机制,即主机名也叫域名,例如 www.baidu.com,它容易记住,背后也对应着一个IP地址。
端口号(port number),如:80,21,1~1024位保留端口号
服务类型(service):如http、telnet、ftp、smtp
当我们一台服务器或者一个IP地址在提供服务的时候,它可以提供多种服务,并且每种服务通过端口号来区分,例如我们通常访问外部资源使用的是80端口,进行ftp时使用的是21端口,发送邮件使用的是25端口。在整个端口号当中,1~1024为保留端口,一般在进行开发时最好不要使用这些端口。这个所谓的端口号就像是生活中银行的窗口,每个窗口为我们提供不同的服务,而这家银行就相当于服务器,在服务器中提供着多类型的服务,例如http、telnet、ftp。
了解以上网络基础概念后,思考如何获取互联网上的信息,举个例子抓取新浪网上的新闻信息,查看以下代码:
import java.io.*;
import java.net.*;
public class URLReader {
public static void main(String args[]) throws Exception{
URL url = new URL("http://www.sina.com/");
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.print(inputLine);
in.close();
}
}
以上类URLReader 功能为网络资源的读取器,首先传入域名来创建URL对象来表示网络资源,接着构造一个缓冲输入流,需要在其构造方法中传入参数,此参数就是URL对象的openStream方法返回的资源流,这样缓冲输入流就可获取到资源信息,接着一行行去读取并输出来。
(2)URL类(Uniform Resource Locator)定义
一切资源定位器的简称,它表示Internet上某一资源的地址。
(3)URL的组成:protocol:resourceName
协议名指明获取资源所使用的传输协议,如http、ftp、gopher、file等,资源名则应该是资源的完整地址,包括主机名、端口号、文件名,甚至是文件内部的一个引子
(4)构造URL对象方法
- public URL(String spec)
以网络资源字符串的形式作为参数传递给URL构造函数
URL urlBase = new URL("http://www.baidu.com");
- public URL(URL context, String spec)
可以用一个URL对象来表示上下文环境,例如以下例子,给一个资源名字,后面再跟一个超文本文件等等来构造一个URL对象。
URL gamelan = new URL("http://www.gamelan.com");
URL gamelanGames = new URL(gamelan, "Gamelan.game.html");
URL gamelanNetWork = new URL(gamelan, "Gamelan.net.html");
- public URL(String protocol, String host, String file)
通过字符串确定URL的访问协议,通过字符串确定主机名和文件名
new URL("http", "www.gamelan.com", "/pages/Gamelan.net.html");
- public URL(String protocol, String host, int port, String file)
此构造方法还可以确定端口号
new URL("http", "www.gamelan.com", 80, "/pages/Gamelan.net.html");
最后需要注意的是创建URL对象时需要进行try catch处理,主要是预防MalformedURLException异常,因为用户给出的URL地址很可能是不符合规范的。
(5)获取URL对象属性
Tables | Are |
---|---|
public String getProtocol() | 返回URL对象采用的协议 |
public String getHost() | 返回URL对象的主机名 |
public String getPort() | 返回URL对象的端口号 |
public String getFile() | 返回URL对象采用的文件名 |
public String getRef() | 返回URL对象的引用地址 |
2. URLConnection对象
(1)定义
一个URLConnection对象代表一个URL资源与Java程序的通讯连接,可以通过它对这个URL资源读或写。
(2)URLConnection与URL的区别
- URL是单向的,只能去访问这个对象,读取器资源内容。URLConnection是双向的,还可以发送信息给目标资源。
- URLConnection可已查看服务器的响应消息的首部
- URLConnection可以设置客户端请求消息的首部
(3)使用URLConnection通信的一般步骤(按顺序):
- 构造一个URL对象
- 调用URL对象的 openConnection()方法获取对应该URL的URLConnection对象
- 配置此URLConnection对象
- 读取首部字段
- 获得输入流读取数据
- 获得输出流写入数据
- 关闭连接
(4)实例展示
查看代码示例,通上个例子不同的是,此例采用URLConnection 对象来获取资源数据:
public class URLReader {
public static void main(String args[]){
try {
URL url = new URL("http://www.sina.com/");
URLConnection urlConnection = url.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.print(inputLine);
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
3. GET请求 和 POST请求
http中最常见的请求GET和POST可以来访问互联网上的资源,上节介绍的 URLConnection对象不仅可以获取资源,还可以发送一些信息到资源服务器上,将两者结合,首先来了解URLConnection编写GET请求:
(1)GET请求
public static String sendGet(String url, String param){
String result = "";
BufferedReader in = null;
try {
String urlName = url + "?" +param;
URL realUrl = new URL(urlName);
//打开和URL的连接
URLConnection urlConnection = realUrl.openConnection();
//设置通用的请求属性
urlConnection.setRequestProperty("accept", "*/*");
urlConnection.setRequestProperty("connection", "Keep-Alive");
//建立实际的连接
urlConnection.connect();
//定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭输入流
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
很显然,根据此方法的两个参数来构造最后的URL地址,根据URL对象获取到URLConnection对象,获取目标资源流并且设置了通用的请求属性,接着调用URLConnection对象的connect()
方法与URL建立了实际的连接,后面就是那个缓存输入流对象将读取的数据存储到字符串result中,最后返回。
(2)POST请求
POST方法就是由java程序向服务器发送请求的时候先发送一些参数,然后服务器再响应本地并返回相应内容,此过程(即POST请求的参数)需要通过URLConnection的输出流来写入参数,查看以下实例:
public static String sendPost(String url, String param){
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL readUrl = new URL(url);
//打开与URL之间的连接
URLConnection urlConnection = readUrl.openConnection();
//设置通用的请求属性
urlConnection.setRequestProperty("accept", "*/*");
urlConnection.setRequestProperty("connection", "Keep-Alive");
//允许输出流
urlConnection.setDoOutput(true);
//获取URLConnection对象对应的输出流
out = new PrintWriter(urlConnection.getOutputStream());
//发送请求参数
out.print(param);
//flush输出流的缓冲
String line;
while ((line = in.readLine()) != null) {
result += line;
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭输入流
try {
if(out != null){
out.close();
}
if(in != null){
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
POST请求与GET请求不同处从允许URLConnection对象的输出流开始,借助PrintWriter类,传入URLConnection对象对应的输出流作为参数获取PrintWriter对象,发送请求参数到服务器。接着等待服务器返回数据流,随之处理并存储到字符串中返回出去。(后续处理逻辑相同,不赘述)
(3)总结
以上两个例子是Java中典型的GET和POST请求,通过此例子可学习使用URLConnection发送请求。总之,在URLConnection的基础上提供了一系列针对http请求的内容,对Java网络编程部分起着重要作用,例如以下:
- HTTP状态码(例如HTTP_OK:200)
- setRequestMethod(设置请求方法GET、POST等)
- getResponseCode(获取HTTP响应)
二. Socket学习
1. Socket解析
Socket通信原理即如何在两个Java程序之间建立网络连接,再了解此之前先熟悉一下TCP传输协议。
(1)TCP(Transport Control Protocol)
面向连接的能够提供可靠的流式数据传输的协议。类似于生活中的打电话过程,通常拨完电话后会有一小段时间来建立连接,为了能够很好地进行语音传输。Java中有个类是使用TCP协议来进行网络通讯,例如:URL、URLConnection、Socket、ServerSocket。
(2)Socket通讯含义
网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个socket。socket通常用来实现客户端与服务端之间的连接。
查看上图,右侧的服务器可能提供多种类型的服务,例如http,通过端口80来提供服务。互联网上的用户通过网络连接到服务器上的http服务,而其它用户还可以连接到服务器的其它服务,例如SMTP(端口25),即发邮件的服务。
那么如何用Socket达到客户端和服务端连接的目的呢?
(3)Socket通讯原理
查看上图,客户端和服务器分别有一个Socket,由客户端向服务端发送一个Socket连接请求,服务器接收到后返回一个响应信号,这样两者之间建立了一个Socket连接。
上图是以代码的角度来讲解:在服务器有一个ServerSocket类的对象一直在运行着,等待客户端是否发起了请求,当它收到请求后ServerSocket会调用方法创建并返回一个Socket对象,此对象就是用来与客户端进行对等连接。当客户端和服务器建立Socket连接后,下一步就是传送数据,即通过Socket对象获取双方的输入输出流进行读写。
举个例子,当客户端发一个数据到服务器,接收到后再发送一个数据给客户端,此过程实际就是IO读写的方式,当网络建立起来后,所谓网络通讯就是IO读写。当两者皆完成了读写目的后,下一步则调用Socket的close
方法进行关闭,此过程结束。
2. Socket代码实例
接下来以代码实际例子来实现Socket通讯,而Java中正有一个类为Socket,来学习此类使用.
(1)Socket创建
Socket()
Socket(InetAddress address, int port)
InetAddress 代表需要构造Socket远程目标的连接地址,port则是连接目标对象的端口号。Socket(String host, int port)
同上一个构造方法类似,只是主机名由字符串形式表示。Socket(InetAddress host, int port, InetAddress localAddr, int localPort)
后两个参数代表本机的地址和端口号。Socket(String host, int port, InetAddress localAddr, int localPort)
含义相同,只是第一个参数主机名由字符串形式表示。
(2)客户端与服务器的Socket创建
客户端Socket的建立
try{
Socket socket = new Socket("127.0.0.1", 2000);
}catch(IOException e){
System.out.println("Error:"+e);
}
创建Socket的第一个参数代表目标服务器的地址,而以上展示的 127.0.0.1 通常指本机,因为在调试程序时只有一台电脑,用它来同时启动两个虚拟机来互相连接,第二个参数是端口号,原则上不取1024以下的保留端口号即可,注意客户端与服务器端口号应一致。
服务器Socket的建立
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(2000);
}catch (Exception e){
System.out.print("can not listen to :" + e);
}
Socket socket = null;
try {
socket = serverSocket.accept();
}catch (Exception e){
System.out.print("Error :" + e);
}
可以看到服务端首先构造的并非是Socket 对象,而是ServerSocket 对象,传入构造方法中的参数就是端口号,同需连接客户端的端口号一致。接下来通过ServerSocket 对象的accept()
方法来获取Socket对象,此方法被称为阻塞方法,因为它一直在运行,等待客户端发送的Socket连接请求,若未收到请求,accept()
方法就一直在循环执行,始终不返回结果,直到收到请求后,accept()
方法会返回发送请求的Socket 对象。构造完客户端与服务端的Scocket对象后,接下来可以进行网络通讯了。
输入流与输出流
在通讯之前还需要利用Socket对象来构建输入输出流,代码如下所示:
【方式一:】
PrintStream outputStream = new PrintStream(new BufferedOutputStream(socket.getOutputStream()));
DataInputStream inputStream = new DataInputStream(socket.getInputStream());
【方式二:】
PrintWriter output = new PrintWriter(socket.getOutputStream(), true);
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
输出流重点:通过Socket对象获取到输出流,先用BufferedOutputStream将其封装一层,外面再用PrintStream包一层,这样可以利用PrintStream来进行通讯。输出流的主要作用就是往外发送消息。
输入流重点:通过Socket对象获取到输出流,紧接着在外面包装一层DataInputStream ,从而获得DataInputStream 对象。输入流的主要作用就是接收来自另外一方的数据。方法二中的输入流,首先InputStreamReader 将字节流转换为字符流,BufferedReader 流能够读取文本行 , 通过向 BufferedReader 传递一个 Reader 对象 , 来创建一个 BufferedReader 对象 。
(3)简单聊天实例
以下以一个简单的例子——命令行聊天程序,来实践以上讲解的知识点。
客户端
【客户端 TalkClient】
public class TalkClient{
public static void main(String args[]) throws IOException {
Socket socket = new Socket("127.0.0.1", 4700);
BufferedReader inSystem = new BufferedReader(new InputStreamReader(System.in));
PrintWriter outputStream = new PrintWriter(socket.getOutputStream());
BufferedReader inputStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readline = inSystem.readLine();
while (!readline.equals("bye")) {
outputStream.println(readline);
outputStream.flush();
System.out.println("Client:" + readline);
System.out.println("Server:" + inputStream.readLine());
readline = inSystem.readLine();
}
outputStream.close();
inputStream.close();
socket.close();
}
}
首先构造Socket对象,再依次构建三个流,分别是一个输入流来获取键盘输入,再构造一个输出流将信息发送给对方网络,最后构造一个输入流获取响应的信息。接着读取键盘输入,判断内容若不是“bye”则将信息发送给对方,再接收来自对方的信息,直到读取键盘输入“bye”时关闭输入输出流与socket连接。
服务端
【服务端 TalkServer 】
public class TalkServer {
public static void main(String args[]) throws IOException {
ServerSocket serverSocket = null;
Socket socket = null;
boolean listening = true;
try {
serverSocket = new ServerSocket(4700);
socket = serverSocket.accept();
} catch (Exception e) {
e.printStackTrace();
}
String line ;
BufferedReader inSystem = new BufferedReader(new InputStreamReader(System.in));
PrintWriter outputStream = new PrintWriter(socket.getOutputStream());
BufferedReader inputStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("Client"+ inputStream.readLine());
line = inSystem.readLine();
while (line.equals("bye")){
outputStream.println(line);
outputStream.flush();
System.out.println("Server"+ line);
System.out.println("Client"+ inputStream.readLine());
line = inSystem.readLine();
}
outputStream.close();
inputStream.close();
socket.close();
}
}
首先构造一个ServerSocket对象,通过服务端server的accept
方法来获取客户端发送的socket请求,该方法会返回socket对象,接下来需要站在服务端的角度进行通讯。还是照例构造三个流,含义不再赘述,接着后续逻辑与客户端相同。
(4)实例总结
服务端与客户端的例子很大部分内容相同,只是服务端需要构造一个ServerSocket,通过ServerSocket得到和客户端连接的Socket对象,得到对象后可以构造输入、出流进行IO流的读写,最后关闭流、Socket即可。所以网络编程到最后演化成UI编程,呈现出的效果:
Clent: hello!
Server:hey
Clent: how are you
Server:i am ok
...
Clent: bye
Server:bye
(最后需要注意的是想要实现以上效果,需先启动一个虚拟机运行服务端程序,再启动第二个虚拟机运行客户端程序。由于只是一个简单程序,所以两端之间只能一句一句互相说,可自行扩展)
三. Socket通讯进阶
下面会着重介绍多个程序之间进行通讯甚至是广播的知识点,首先来思考几个问题:
- Java中的Socket多客户端机制如何实现?
- 数据报是如何通信的?
- 广播通信如任何实现?
1. Socket多客户端通信实现
(1)多客户机制原理
通过一个例子来解释多客户机制,在服务端有一个ServerSocket一直在等待客户端发送请求。如图所示,ClientA发送请求到服务端,此时服务端实例化一个线程来处理与ClientA聊天相关的事,ClientB、ClientC也是如此。
(2)Socket多客户端例子
客户端
【客户端代码与第二节的简单聊天例子中的客户端TalkClient类完全相同,在此不重复贴】
服务端
【服务端 MultiTalkServer 】
public class MultiTalkServer {
static int clientNum = 0;
public static void main(String args[]) throws IOException {
ServerSocket serverSocket = null;
boolean listening = true;
try {
serverSocket = new ServerSocket(4700);
}catch (Exception e){
e.printStackTrace();
}
while (listening){
new ServerThread(serverSocket.accept(), clientNum).start();
clientNum++;
}
serverSocket.close();
}
public static class ServerThread extends Thread{
Socket socket = null;
int clientNum;
public ServerThread(Socket socket, int num) {
this.socket = socket;
clientNum = num + 1;
}
public void run(){
try {
String line ;
BufferedReader inSystem = new BufferedReader(new InputStreamReader(System.in));
PrintWriter outputStream = new PrintWriter(socket.getOutputStream());
BufferedReader inputStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("Client"+ clientNum + ":" + inputStream.readLine());
line = inSystem.readLine();
while (line.equals("bye")){
outputStream.println(line);
outputStream.flush();
System.out.println("Server"+ line);
System.out.println("Client"+ clientNum + ":" + inputStream.readLine());
line = inSystem.readLine();
}
outputStream.close();
inputStream.close();
socket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
服务端 类中有个静态成员变量clientNum用来记录发起请求的客户端数量,构造ServerSocket对象,在while循环方法体创建ServerThread线程。相当于main
方法一直在循环等待客户端socket请求,一旦接收到socket请求,就实例化一个线程与之交互聊天,线程记录数量加一。
来看ServerThread实现,内部维护了socket对象和客户端数量,run
方法中首先创建了三个流,意义与客户端相同,其实已经很是熟悉其中概念了,用一个输入流获取键盘输入,再用输出流将信息发送给对方网络,最后构造一个输入流获取响应的信息。同客户端相同,接下来判断键盘输入文本,为“bye”则停止发送,聊天结束;否则继续读取键盘输入流发送信息,最后关闭流和socket的连接。
注意:
(以上是展示的例子,如要运行查看效果,需先启动一个虚拟机运行服务端程序,再启动第二个虚拟机运行客户端程序,从而可以启动第三、四个去运行客户端程序,查看一个服务端同时与多个客户端进行聊天的效果)
2. 数据报通信
此节将讲解数据报通信的原理,即不需要面向连线的通信方式,数据报通信方式采用的是UDP(User Datagram Protocol)协议,之前介绍过,这里同TCP作比较再次介绍。
(1)UDP与TCP学习
UDP(User Datagram Protocol)
非面向连接的提供不可靠的数据包式的数据传输协议。类似于从邮局发送信件的过程,发送信件是通过邮局系统一站一站进行传递,中间也有可能丢失。Java中有些类是基于UDP协议来进行网络通讯的,有DatagramPacket、DatagramSocket、MulticastSocket等类。TCP(Transport Control Protocol)
面向连接的能够提供可靠的流式数据传输的协议。类似于打电话的过程,在拨完电话后两者之间会先建立连接,为了更好的通话,确保通话连接后两者开始互相传输信息。相对应的类有URL、URLConnection Socket、ServerSocket等UDP 与 TCP的区别
TCP有建立时间,UDP无
- UDP传输有大小限制,每一个数据报需在64K以内
- TCP常见应用:Telnet远程登录、Ftp文件传输
- UDP常见应用:ping指令,用来检测网络上某台服务器是否还在提供服务。
(2)数据报学习
构造数据报的通信使用到的类
- DatagramSocket()
- DatagramSocket(int port)
- DatagramPacket(byte ibuf[], int ilengh) //接收
- DatagramPacket(byte ibuf[], int ilengh, InetAddress iaddr, int port) //发送
DatagramSocket实际上是一种数据报通信的Socket,构造它时可指定端口号。需要发送的数据存放在DatagramPacket对象中,第一种构造方法的第一个参数类型是字节数组,用来接收数据报,第二个参数即数据报的长度。第二种构造方法中的前两个参数意义相同,后两个个参数是指数据报被发送到的目标地址以及对应的端口号。
- 接收数据报
DatagramPacket packet = new DatagramPacket(buf, 256);
socket.receive(packet);
- 发送数据报
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);
socket.send(packet);
(3)数据报发送与接收实例
QuoteClient类主要目的是想服务器询问某些股票的信息,而服务端按照本地文件虚拟响应数据返回给客户端。
客户端
【客户端 QuoteClient 】
public class QuoteClient {
public static void main(String[] args)throws IOException{
if(args.length != 1){
System.out.println("Usage:java QuoteClient <hostname>");
return;
}
DatagramSocket socket = new DatagramSocket();
//发送请求
byte[] buf = new byte[256];
InetAddress address = InetAddress.getByName(args[0]);
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 4445);
socket.send(packet);
//获取响应
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
//显示响应数据
String received = new String(packet.getData());
System.out.println("Quote of the Moment:"+ received);
socket.close();
}
}
QuoteClient类中main
方法中第一行if判断参数args是否等于1,意味着程序在执行时必须跟一个参数,即目标服务器的主机名,若无会打印Usage:java QuoteClient <hostname>
提示。接下来依次构造Socket对象和数据报 ,这个目标地址InetAddress就是main
方法中的参数args[0],再构造一个数据报发送。发送结束后想要接收到服务端的回信,即包含了客户端想要的股票信息。最后关闭socket连接。
服务端
【服务端 QuoteServer 】
public class QuoteServer {
public static void main(String[] args)throws IOException{
new QuoteServerThread().start();
}
public static class QuoteServerThread extends Thread {
protected DatagramSocket socket = null;
protected BufferedReader in = null;
protected boolean moreQuotes = true;
public QuoteServerThread() throws IOException{
this("QuoteServerThread");
}
public QuoteServerThread(String name) throws IOException{
super(name);
socket = new DatagramSocket(4445);
try{
in = new BufferedReader(new FileReader("one-lines.txt"));
}catch(FileNotFoundException e){
e.printStackTrace();
}
}
public void run(){
while(moreQuotes){
try{
byte[] buf = new byte[256];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
String dString = null;
if(in == null){
dString = new Date().toString();
}else{
dString = getNextQuote();
}
buf = dString.getBytes();
//返回数据给客户端(address + port)
InetAddress address = packet.getAddress();
int port = packet.getPort();
packet = new DatagramPacket(buf, buf.length, address, port);
socket.send(packet);
}catch(Exception e){
e.printStackTrace();
moreQuotes = false;
}
}
socket.close();
}
private String getNextQuote() {
String returnValue = null;
try{
if((returnValue = in.readLine()) == null){
in.close();
moreQuotes = false;
returnValue = "NO more quotes. Goodbye!";
}
}catch(Exception e){
e.printStackTrace();
}
return returnValue;
}
}
}
服务端QuoteServer类main
方法开启了一个线程,首先来看线程QuoteServerThread的构造方法,主要是创建Socket,构造一个文件输入流,因为这是在模仿服务端发送信息到客户端,所以将股票信息写到此文件中,这样每次有客户端发送请求咨询股票价格时,就从文件读出对应股票的价格返回给客户端。再看run
方法,首先一个While循环代表文件中信息未读完的话一直读取,在接收客户端发来的接收包后发送回信,判断服务端的文件内容是否为空,是则返回客户端当前日期,否则获取下一条股票信息将其返回。注意返回信息的前提是构造一个数据报包,需要知道客户端的地址和端口号,而正好利用接收到客户端发来的数据报包,通过数据报包对象来获取地址和端口号。
(4)例子总结
通过以上例子可以得出结论,整个过程非常类似于生活中互相写信的通讯方式。客户端构造一个DatagramPacket对象,填充一些信息,通过DatagramSocke对象t的send()
方法发送到服务端。服务端接收到数据报包后,同时也得知了客户端的地址和端口号,服务端也构造一个DatagramPacket数据报,填充响应的数据返回给客户端。
3. 数据报进行广播通信
其实利用数据报包可进行广播通讯,只适用于小范围局域网。之前介绍的DatagramSocket 只允许存放一个目的地址,但是MulticastSocket 类可以把数据报以广播的形式发送到所有监听该端口的客户端。MulticastSocket 在客户端使用,来监听服务器广播来的数据。下面通过一个实例来学习:
【客户端 MulticastClient】
public class MulticastClient {
public static void main(String args[]) throws IOException{
MulticastSocket multicastSocket = new MulticastSocket(4446);
InetAddress address = InetAddress.getByName("230.0.0.1");
multicastSocket.joinGroup(address);
DatagramPacket packet;
//获取接收数据
for(int i=0; i<5; i++){
byte[] buf = new byte[255];
packet = new DatagramPacket(buf, buf.length);
multicastSocket.receive(packet);
String received = new String(packet.getData());
System.out.println("Quote of the Moment:"+received);
}
multicastSocket.leaveGroup(address);
multicastSocket.close();
}
}
首先构造一个广播socket对象,同时需要传入端口号到构造方法,接着构造一个IP地址,调用广播socket对象的joinGroup
方法将socket对象加入一个组,即IP地址所标识的组。接下来以for循环来模拟客户端接收广播信息的过程。最后循环5次接收数据后调用广播socket对象的 leaveGroup
的方法,即离开当初关注的那个组,然后关闭socket连接。
以上是Java网络编程着重于Socket的学习笔记,中间加述了大多例子来实践综合了Socket知识点,但这篇文章并不代表记录了全部的知识点,只是尽我所能来记录现有可掌握到的知识。如有错误,望指正~
希望对你们有帮助:)