Java开发网络程序
如果想进行网络程序的开发,最简单的应用实现就是可以通过java.net开发包来实现,在这个包中有两个类:
- java.net.ServerSocket 工作在服务器端的程序,主要是定义服务监听端口,以及接收客户端请求。
- java.net.Socket 每一个客户端都使用一个Socket的概念来描述。
1. 基础网络程序
1.1 实现服务器端程序的定义
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerSocketDemo {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9999);//本程序在9999端口进行监听
System.out.println("ServerSocket服务器端启动监听....");
//每一个连接到ServerSocket的对象都通过Socket对象来描述,所以此时要等待连接
Socket client = serverSocket.accept();//等待客户端连接
//向指定的Socket实现数据的输出,应该获取输出流
PrintStream out = new PrintStream(client.getOutputStream());
out.print("NicholasGUB---");//输出信息
client.shutdownOutput();//数据发送完毕后关闭服务
serverSocket.close();//关闭整个服务器
}
}
现在如果想验证服务器是否可以正常使用,那么最简单的做法是直接利用操作系统中的talent命令来完成(如果是windows系统,对于talent命令都需要额外的安装)。talent给出的仅仅是一个测试环境,但是对于整个程序来讲对于网络程序应该提供有一个专属的客户端代码。那么下面就通过Socket类实现客户端的处理,值得注意的是,服务器端的数据的输出实际上就属于客户端数据的输入。
1.2 实现客户端程序的定义
import java.net.Socket;
import java.util.Scanner;
public class ClientSocketDemo {
public static void main(String[] args) throws Exception{
//Socket是工作在客户端的程序类,每一个Socket描述的是一个独立的客户端
Socket client = new Socket("localhost",9999);//设置服务器端连接地址
Scanner scanner = new Scanner(client.getInputStream());//服务器端输出为客户端输入
while(scanner.hasNext()){//此时有数据,需要打印
System.out.println("[收到服务器数据]"+scanner.next());
}
client.shutdownInput();//全部输入完成后关闭客户端Socket
client.close();
}
}
2. ECHO模型
Echo模型可以理解为现在网络通讯程序的核心结构,现在的所有通讯都是基于这种Echo模型衍生而来的,所谓的Echo模型最大的特点在于:客户端发送数据给服务器端,服务器端接收到数据之后直接进行回应,并且这种回应的处理可以持续进行,当客户端确定不再需要继续进行交互的时候则断开整个服务器连接。
首先实现服务器端程序的开发,由于现在的数据需要进行持续性的交互,所以应该设置有一个数据的终止判断:
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class EchoServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9999);//绑定端口
Socket socket = serverSocket.accept();//进入阻塞状态,等待客户端连接
Scanner scanner = new Scanner(socket.getInputStream());//客户端输入流
PrintStream out = new PrintStream(socket.getOutputStream());//客户端输出流
boolean flag = true;//实现交互的状态标志
System.out.println("服务器程序");
while(flag){//如果此时可以继续交互
if(scanner.hasNext()){//如果有输入的数据
String value = scanner.next().trim();//去掉无用的空格
if("exit".equalsIgnoreCase(value)){//结束交互
out.print("[ServerSocket] 信息交互完毕,已断开与服务器连接");
flag = false;//停止交互
break;//后续代码不再执行
}
System.out.println("客户端发来数据:"+value);
out.println("RETURN:"+value);
}
}
serverSocket.close();
}
}
服务器端程序完成之后肯定要开发客户端程序代码,但是由于客户端现在需要键盘数据的输入,可以考虑创建一个工具类实现键盘输入的功能。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class KeyborderInputUtil {
private static final BufferedReader INPUT = new BufferedReader(new InputStreamReader(System.in));
public KeyborderInputUtil() {}
public static String getString(String prompt) {
System.out.println(prompt);
try {
return INPUT.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
编写客户端程序,实现交互功能的处理
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
import com.gub.util.KeyborderInputUtil;
public class EchoClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost",9999);//创建客户端连接
Scanner scanner = new Scanner(socket.getInputStream());//客户端输入流
PrintStream out = new PrintStream(socket.getOutputStream());//客户端输出流
boolean flag = true;//不断进行客户端输入
while(flag) {
String value = KeyborderInputUtil.getString("请输入要发送的数据:");
out.println(value);//通过输出流向服务器发送数据
if(scanner.hasNext()) {//等待服务器响应
System.out.println("【ECHO客户端】"+scanner.next());//服务器端响应
}
if("exit".equalsIgnoreCase(value)) {//结束后续的交互
flag = false;
scanner.close();
break;//推出整个循环
}
}
socket.close();
}
}
3. BIO处理模型
BIO(Blocking IO,阻塞IO处理)是最为传统的一种网络通讯模型的统一描述,这种通讯模型主要是为了解决服务器的并发处理问题,通过之前的程序已经成功地实现了ECHO交互模型,但是这个模型本身会存在一个问题,当前的操作属于单线程服务器的开发,也就是说同一个时间段只能有一个线程进行访问。
如果想对当前程序进行改造,那么最佳的做法是创建一个新的线程处理类,而后这个线程处理类负责包裹Socket对象实例,并且实现独立的交互操作。
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class EchoHandle implements Runnable{
private Socket client;
public EchoHandle(Socket client) {
this.client = client;
}
@Override
public void run() {
try {
Scanner scanner = new Scanner(this.client.getInputStream());//客户端输入流
PrintStream out = new PrintStream(this.client.getOutputStream());//客户端输出流
boolean flag = true;//实现交互的状态标志
System.out.println("服务器程序");
while(flag){//如果此时可以继续交互
if(scanner.hasNext()){//如果有输入的数据
String value = scanner.next().trim();//去掉无用的空格
if("exit".equalsIgnoreCase(value)){//结束交互
out.print("[ServerSocket] 信息交互完毕,已断开与服务器连接");
flag = false;//停止交互
break;//后续代码不再执行
}
System.out.println("客户端发来数据:"+value);
out.println("RETURN:"+value);
}
}
}catch(Exception e) {
e.printStackTrace();
}
}
}
修改EchoServer程序,启动多线程
import java.net.ServerSocket;
import java.net.Socket;
import echo.EchoHandle;
public class EchoServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9999);//绑定端口
boolean flag = true;
while(flag){
Socket socket = serverSocket.accept();//进入阻塞状态,等待客户端连接
new Thread(new EchoHandle(socket)).start();;//启动线程
}
serverSocket.close();
}
}
当服务器端的程序引入多线程之后,这个程序就可以并行地进行多线程的任务处理了,但是这种传统的模型存在以下问题:这种传统的开发模型操作的过程之中首先需要连接,而后由服务器端分配线程对象,可是如果此时的客户端即使没有与服务器端产生任何的交互,那么这个线程也要持续维护着,如果在一种高并发访问程序的环境下,这样的处理将造成严重的性能浪费。