网络可以使不同物理位置上的计算机达到资源共享和通信的目的,在Java中也提供了专门的网络开发程序包--java.net,以方便开发者进行网络程序的开发,本章将讲解TCP与UDP程序开发
19.1 网络编程简介
将地理位置不同的、具有独立功能的多台计算机连接在一起就形成了网络,网络形成后网络中的各台主机就需要具有通信功能,所以才为网络创造一系列的通信协议。在整个通信过程中往往会分为两种端点:服务端与客户端,所以围绕着服务端和客户端的程序开发就有了两种模式。
C/S(Client/Server):要开发两套程序,一套是服务器端;另一套是与之对应的客户端,但是这种模式在进行维护的时候,需要维护两套程序,而且客户端的程序更新也必须及时,此类程序安全性能好。
B/S(Browser/Server):只需针对服务器端开发一整套程序,客户端使用浏览器进行访问。这种程序在日后进行程序维护的时候只需维护服务器端即可,客户端不需要做任何修改。此类程序使用公共端口,包括公共协议,所以安全性很差。
本章重点讲解C/S接口的程序的两种实现:TCP模型和UDP模型
提问:HTTP是什么
在日常使用中,很多时候使用HTTP进行访问,HTTP通信和TCP通信有什么联系。
回答:TCP是HTTP通信的基础协议
实际上HTTP通信也是基于TCP协议的一种应用,是在TCP协议基础上追加了一些HTTP标准后形成的HTTP通信。HTTP通信不仅仅在B/S结构上被广泛应用,同时在一些分布式开发中也使用较为广泛。JavaWeb开发(JSP、Servlet、Ajax等)就是java针对HTTP协议提供的开发支持。
顺带提醒的是,Java只提供最为核心的网络开发支持,而如果真的要想开发一个具有稳定通信能力的网络程序是,开发者必须精通各种通信协议。为了简化此类开发,开源世界提供了一个Netty开发框架,可以帮助开发者轻松实现各种常见的TCP、UDP、HTTP、WebSocket通信协议。
19.2 Echo程序模型
在TCP编程模型中,Echo是一个经典的程序案例,Echo程序模型的基本思想在于,客户端通过键盘输入一个信息,把此信息发送给服务器端后,服务器端会将此信息反馈给客户端进行显示,本操作主要通过java.net包的两个类实现。
ServerSocket类:封装TCP协议类,工作在服务器端,常用的方法如表
Socket类:封装TCP协议的操作类,每一个Socket对象都表示一个客户端
Echo通信模型的实现需要通过ServerSocket类在服务器端定义数据监听端口,在没有客户端连接时将一直处于等待连接状态。每一个客户端连接到服务器端后都通过Socket实例描述,通过Socket可以获取客户端和输出端的实例,这样就可以实现I/O通信
范例:定义Echo服务器端
package cn.mldn.demo;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.util.Scanner;
public class EchoServer
{
public static void main(String[]args)throws Exception
{
ServerSocket server=new ServerSocket(9999);//设置服务器监听端口
System.out.println("等待客户端连接。。。。。。。。。");
Socket client=server.accept();
//首先需要先接收客户端发送来的信息,而后才可以将信息处理后发送给客户端
Scanner scan=new Scanner(client.getInputStream());//客户端输入流
scan.useDelimiter("\n");//设置分隔符
PrintStream out=new PrintStream(client.getOutputStream());//客户端输出流
boolean flag=true;
while(flag)
{
if(scan.hasNext())
{
String val=scan.next().trim();//接收数据内容
if("byebye".equalsIgnoreCase(val))
{
out.println("byebyebye");
flag=false;
}else
{
out.println("echo"+val);
}
}
}
scan.close();
out.close();
client.close();
server.close();
}
}
本程序在主线程实例化了一个ServerSocket类对象,并且设置了在本季的9999端口上进行监听,当有客户端连接到服务器端后(accept()方法在客户端连接前会一直泽色程序运行),会获取客户端Socket的输入流和输出流,进行数据的接收与回应处理。
提示:使用telnet命令测试
在Windows、Linux等系统中都会提供一个telnet的测试命令,进行服务器端的使用测试,开发者只需要输入telnet localhost9999命令即可直接与服务器端程序连接。如果在Windows系统中此命令默认不开启,则开发者选择启用或关闭Windows功能。服务端的主要功能是进行客户端发送数据的回显处理,客户端需要实现键盘数据的输入并且通过Socket实例实现数据的发送与回应处理。
范例:编写客户端程序
package cn.mldn.demo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class EchiClient
{
private static finale BufferedReader KEYBORAD_INPUT=new BufferedReader(new InputStreamReader(System.in));
public static String getString(String prompt)throws Exception
{
//键盘信息的输入
System.out.print(prompt);
String str=KEYBOARD_INPUT.readLine();
return str;
}
public static void main(String[]args)throws Exception
{
Socket client=new Socket("localhost",9999);//定义服务器端的连接信息
//现在的客户端需要有输入与输出的操作支持,所以依然要准备出Scanner和PrintWriter
Scanner scan=new Scanner(client.getInputStream());//接收服务器端输入内容
scan.useDelimiter("\n");
PrintStream out=new PrintStream(client.getOutputStream());//向服务器端发送内容
boolean flag=true;//循环标记
while(flag)
{
String input=getString("请输入要发送的内容").trim();//获取键盘输入数据
out.println(input);//加换行
if(scan.hasNext())
{
System.out.println(scan.next());//输出回应消息
}
if("byebye").equlasIgnoreCase(input){flag=false;}
}
}
scan.close();
out.close();
client.close();
}
19.3 BIO处理模型
此时的Echo模型是基于单线程(主线程)的处理机制实现的网络通信,这样会造成一个问题,在同一段时间内只允许有一个客户端连接到服务器端并进行通信处理,并且当次客户端退出后服务器端也会随之关闭,所以为了提升服务器段的处理性能,就可以利用多线程来处理多个客户端的通信需求
根据上图可以发现,在服务器端中的每一个ServerSocket需要连接多个Socket,同时可以将每一个客户端的Socket实例封装在一个线程中,这样一个服务器端就可以同时处理多个客户端请求。
范例:修改服务器端实现
package cn.mldn.demo;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.util.Scanner;
public class EchoServer
{
private static vlass ClientThread implements Runnable
{
private Socket client=null;//客户端Socket
private Scanner scan=null;//输入流
private PrintStream out=null;//输出流
private boolean flag=true;
public ClientThread(Socket client)throws Exception
{
this.client=client;//保存Socket
this.scan=new Scanner(client.getInputStream());//输入流
this.scan.useDelimiter("\n");//设置分隔符
this.out=new PrintStream(client.getOutputStream());//输出流
}
@Override
public void run()
{
while(this.flag)
{
if(scan.hasNext())
{
String val=scan.next().trim();
if("byebye".equalsIgnoreCase(val))
{
out.println("byebyebye");
this.flag=false;
}else
{
out.println("【ECHO】"+val);
}
}
}
scan.close();
out.close();
client.close();
}
}
}
public static void main(String[]args)throws Exception
{
ServerSocket server=new ServerSocket(9999);//设置服务监听端口
System.out.println("等待客户端连接......");
boolean flag=true;
while(flag)
{
Socket client=server.accept();//有客户端连接
new Thread(new ClientThread(client)).start();
}
server.close();
}
本程序服务器端将采用玄幻的形式实现多个Socket客户端的连接,并且将每一个接受到的Socket实例封装到独立的线程中进行独立的Echo回应处理。
提示:本模型属于BIO模型
在本程序中虽然是用了多线程修改了服务器端处理模式,但是在程序开发中并没有对服务器端可用线程数量进行限制,这也就意味着如果并发客户端访问量增加,则服务器端将会出现严重的性能问题。所以JDK1.4以前就必须对县城熟练进行有效控制,需要追加客户端等待了解机制才可以正常使用,这种模式称为BIO(Blocking IO,阻塞IO)模式。
19.4 UDP程序
TCP的所有操作都必须建立可靠的连接才可以通信,但是这种做法肯定会浪费大量的系统性能,为了减少这种开销,在网络中又提供了另外一种传输协议--UDP(不可靠连接),即利用数据报的形式进行数据发送,由于没有建立可靠连接,此时接收端可能处于关闭状态,所以利用UDP发送的数据,客户端不一定接收到。在Java中使用DatagramPacket类和DatagramSocket类,完成UDP程序的开发
提示:关于UDP开发中服务器端和客户端的解释
使用UDP开发的网络程序,类似于平常使用手机,手机实际上相当于一个客户端,如果现在手机要是想正常收信息,则手机肯定要先打开才行。
范例:实现一个UDP客户端进行信息接收
在进行UDP客户端编写时需要设置一个客户端的监听端口,结束到的数据信息可以利用DatagramPacket类对象进行接收,这样在客户端打开的情况下会自动接收到服务器端发送来的消息。
package cn.mldn.demo;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPClient
{
public static void main(String[]args)throws Exception
{
DatagramSocket client=new DatagramSocket(9999);//接收数据信息
byte data[]=new byte[1024];//保存接收数据
DatagramPacket packet=new DatagramPacket(data,data.length);//创建数据报
System.out.println("客户端等待接收发送的消息");
client.receive(packet);//接收消息内容
System.out.println("接受到的信息内容为"+new String(data,0,packet,getLength()));
client.close();
}
}
在进行UDP客户端编写时需要设置一个客户端的监听端口,接收到的数据信息可以利用DatagramPacket类对象进行接收,这样在客户端打开的情况下会自动接收到服务器端发来的信息。
范例:编写一个UDP服务器端程序发送数据报
package cn.mldn.demo;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPServer
{
public static void main(String[]args)throws Exception
{
DatagramSocket server=new DatagramSocket(9000);
String str="AAA";
DatagramPacket packet=new DatagramPacket(str.getBytes(),0,str.length(),InetAdress.getByName(localhost),9999);
server.send(packet);
server.close();
}
}