关键字:IP,TCP,UDP,客户端,服务器端,C/S ,B/S,Echo,ServerSocket,Socket
简介
网络编程可以让多台电脑实现串联,网络编程就是服务器端与客户端编程的开发。主流网络编程有两种形式:
第一种是C/S结构,也就是客户、服务器结构,缺点是麻烦,维护不便,因为要开发两套代码,也要维护两套代码。但是好处是安全,因为使用自己的连接端口,使用自己的通讯协议。
第二种是B/S结构,只开发一套服务器端代码,客户端使用浏览器访问。好处是利于开发和维护,坏处是使用公共的HTTP协议和公共的80端口,造成我们的程序安全性不高。
在C/S结构中,程序又可以分为两类,TCP程序与UDP程序。TCP使用可靠的连接方式进行传输。UDP使用不可靠的协议,属于数据包协议。
在Java中为我们提供的类就是ServerSocket类(服务器类)和Socket类(客户端类)。服务器类主要在服务器端工作,用于接收用户请求。客户端类表示每一个连接到服务器的用户。
我们使用一个经典的程序开发模型——Echo程序来做一个简单的演示。
要求:我们要实现客户端发出一个数据,服务器端接收到之后 加上一个"Echo"进行返回。允许多次输入,不能在每次连接之后都关闭服务器端。如果输入"exit"则退出程序。
我们来看一段代码。
package com.chuchu.server ;
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 server = new ServerSocket(9999) ;
System.out.println("等待客户端连接。。。");
Socket client = server.accept() ; // 客户端连接
Scanner scan = new Scanner(client.getInputStream()) ;
PrintStream out = new PrintStream(client.getOutputStream()) ;
boolean flag = true ;
while(flag) {
if(scan.hasNext()) {
String value = scan.next() ;
if("Exit".equalsIgnoreCase(value))
flag = false ;
else
out.println("【ECHO】" + value);
}
}
server.close();
client.close();
scan.close();
}
}
package com.chuchu.client ;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class EchoClient {
private static final BufferedReader INPUT = new BufferedReader(new InputStreamReader(System.in)) ;
public static void main(String[] args) throws Exception {
Socket client = new Socket("localhost", 9999) ;
Scanner scan = new Scanner(client.getInputStream()) ;
scan.useDelimiter("\n") ;
PrintStream out = new PrintStream(client.getOutputStream()) ;
boolean flag = true ;
while(flag) {
String input = getString("请输入要发送的内容") ;
out.println(input);
if(scan.hasNext()) {
System.out.println(scan.next());
}
if("Exit".equalsIgnoreCase(input)) {
flag = false ;
}
}
client.close();
scan.close();
INPUT.close();
}
public static String getString(String promot) throws Exception {
System.out.println(promot);
String value = INPUT.readLine() ;
return value ;
}
}
在上面的代码中我们首先使用了Java提供的两个类实例化了服务器端和客户端,在服务器端我们使用ServerSocket对象中的accept()方法连接到客户端,获取Socket对象,然后再利用Socket对象中的getInputStream()方法和getOutputStream()方法获取输入输出流。在这里我们可以使用PrintStream类帮助我们在每次获取完成数据之后进行刷新操作。
客户端类大同小异,只不过我们可以将输入数据的代码封装成一个方法,并将键盘输入流封装成一个常量。
我们来看一下运行结果:
通过运行结果我们看到,服务器端成功接收到数据并将其处理后返回给了客户端。一个简单的Echo模型就算是完成了。但是当我们尝试再次启动一个客户端的时候,问题就出现了。
单线程Echo程序带来的问题
我们发现报错,Exception in thread “main” java.net.ConnectException: Connection refused: connect
这是由于我们的这个程序是单线程的,要向被多个用户访问我们需要将其改造成多线程。服务器端的每一个线程都为一个客户端实现Echo服务支持。
对刚才代码的改进
package com.chuchu.server;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import com.sun.security.ntlm.Client;
public class EchoServer {
private static class ClientThread implements Runnable {
private Socket client = null;
private Scanner scan = null;
private PrintStream out = null;
private boolean flag = true;
public ClientThread(Socket client) throws Exception {
this.client = client;
scan = new Scanner(client.getInputStream());
scan.useDelimiter("\n");
out = new PrintStream(client.getOutputStream());
}
@Override
public void run() {
while (flag) {
if (scan.hasNext()) {
String value = scan.next();
if ("exit".equalsIgnoreCase(value)) {
out.println("欢迎下次使用");
flag = false;
} else {
out.println("【Echo】" + value);
}
}
}
try {
scan.close();
out.close();
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
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();
}
}
package com.chuchu.client;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class EchoClient {
private static final BufferedReader INPUT = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws Exception {
Socket client = new Socket("localhost", 9999);
Scanner scan = new Scanner(client.getInputStream());
scan.useDelimiter("\n");
PrintStream out = new PrintStream(client.getOutputStream());
boolean flag = true;
while (flag) {
String input = getString("请输入要发送的内容");
out.println(input);
if (scan.hasNext()) {
System.out.println(scan.next());
}
if ("Exit".equalsIgnoreCase(input)) {
flag = false;
}
}
client.close();
scan.close();
INPUT.close();
}
public static String getString(String promot) throws Exception {
System.out.println(promot);
String value = INPUT.readLine();
return value;
}
}
在上面代码中我们实现了一个内部类,这个类实现Runnable接口,构造方法里面为客户对象赋值,获取输入输出流。在run()方法中我们执行所有业务操作。
而在main()方法中我们写一个while循化接收客户端对象,没接收到一个就为此对象单开一个线程,为其服务。
最后不要忘记关闭资源。