多数情况下,网络通信经常需要多个客户机同一个服务器进行通信,如FTP服务器是同时接受多个客户访问的服务器。本实例介绍点对面通信,即一个服务器监听多个客户端的请求的通信。
实现点对面通信的技术要点如下:
(1)创建多客户连接的socket通信方式是在服务器端创建客户连接请求的监听线程,一旦客户端发起请求,则服务器端创建用于与客户端通信的线程和Socket,服务器把与此客户的通信交给此线程进行处理。
这便是与点对点通信不同之处-循环创建通信线程。同时,继续在服务器指定端口进行监听,来响应其他客户的服务请求。
(2)每一个客户端和服务器中的线程可以认为是单客户端通信模式下客户端和服务器。一旦Socket和线程建立,服务器主程序会把与某个客户的通信完全交给线程去处理,并利用相应的Socket完成与客户的通信。
面服务器端代码
package core;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
class FaceServer extends ServerSocket {// Socket面服务器端
private int port; // 端口号
public FaceServer(int port) throws IOException {
super(port);
this.port = port;
System.out.println("服务器已启动,监听端口号为:" + port);
System.out.println("正在等待客户端连接。。。。。");
try {
while (true) {
Socket socketCon = accept();// 挂起等待客户的请求
new ServerThread(socketCon, port);// 建立服务线程
}
} catch (Exception e) {
System.out.println("没有监听到客户端信息。。。");
} finally {
close();
}
}
}
class ServerThread extends Thread { // 继承线程类实现服务线程
private Socket socketCon;
private BufferedReader in;
private PrintWriter out;
private int port;
public ServerThread(Socket s, int port) {
try {
this.port = port;
this.socketCon = s;
in = new BufferedReader(new InputStreamReader(socketCon.getInputStream(), "gb2312"));// 获得(读取客户端的数据流)
out = new PrintWriter(socketCon.getOutputStream(), true); // 获得写往客户端的(数据输出流),true表示自动刷新
out.println("服务器端连接成功。。。。");
out.println("输入exit断开与服务器的连接");
start(); // 启动线程
} catch (IOException e) {
System.out.println("启动服务器端出现错误:" + e.getMessage());
}
}
public void run() {
try {
boolean done = false;
while (!done) {
String line = in.readLine();// 读取客户端每行的内容
if (line == null)
done = true;
else {
System.out.println("客户端传来的内容: " + line);
String message = infoUpperCase(line);// 变成大写再传回客户端去
out.println("从服务器端口发送的内容 " + message); // --该处的print要加ln,否则就会无法往客户端传递消息
if (line.trim().equals("exit")) // 退出判断
done = true;
}
}
System.out.println("客户端终止发送信息...");
socketCon.close(); // 关闭通信资源
} catch (IOException e) {
System.out.println("启动服务器端出现错误:" + e.getMessage());
}
}
public String infoUpperCase(String line) {
return line.toUpperCase(); // 将字符串大写
}
}
public class TextFaceServer {
public static void main(String[] args) {
try {
new FaceServer(8080);
} catch (Exception e) {
System.out.println("测试服务器端监听出错: " + e.getMessage());
}
}
}
点客户端1代码
package core;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
class PointClient { // Socket点客户端
private String host; // IP地址
private int port; // 端口号
public PointClient(String host, int port) {
this.host = host;
this.port = port;
connectSocket();// 调用连接方法
}
public void connectSocket() {
Socket socketConn; // 声明客户端的Socket连接
try {
if (host.equals("localhost") || host.equals("127.0.0.1")) {// 判断IP地址(域名)如果是本机localhost
socketConn = new Socket(InetAddress.getLocalHost(), port);// 创建本地Socket连接
//如果该方法InetAddress.getLocalHost()报错,则要在sudo vi /private/etc/hosts 中添加本机地址与你主机名的映射,类似 127.0.0.1 主机名
//然后终端执行命令 dscacheutil -flushcache,之后主机地址便可正常解析
} else {
socketConn = new Socket(InetAddress.getByName(host), port);// 创建远程socket连接
}
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));// 获得从键盘输入的流
PrintWriter out = new PrintWriter(socketConn.getOutputStream(), true);// 往服务器写内容的数据流
// 从服务器获得信息
BufferedReader in = new BufferedReader(new InputStreamReader(socketConn.getInputStream()));// 接收服务器发送内容的输入流
System.out.println("服务器信息:" + in.readLine());
System.out.println("服务器信息:" + in.readLine());
System.out.println("请输入>");
boolean done = false;
while (!done) {
String line = stdin.readLine();// 获得从键盘输入的每行字符
out.println(line);// 发送到服务器端 --该处的print要加ln,否则就会无法往服务器端传递消息
if (line.equalsIgnoreCase("exit")) // 读到exit则结束循环
done = true;
String info = in.readLine(); // 从服务器读取字符串
System.out.println("服务器信息:" + info);// 显示从服务器发送来的数据
if (!done)
System.out.println("请输入>");
}
socketConn.close(); // 关闭资源
} catch (SecurityException e) {
System.out.println("连接服务器出现安全问题!"+e.getMessage());
} catch (IOException e) {
System.out.println("连接服务器出现I/O问题!"+e.getMessage());
}
}
}
public class TextPointClient {// Socket客户端类
public static void main(String[] args) {
try {
new Client("localhost", 8080); // IP地址为本机,端口为8080
} catch (Exception e) {
System.out.println("测试客户端连接出错:" + e.getMessage());
}
}
}
点客户端2代码
代码和上面-点客户端1代码完全一样
package core;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
class Client { // Socket客户端
private String host; // IP地址
private int port; // 端口号
public Client(String host, int port) {
this.host = host;
this.port = port;
connectSocket();// 调用连接方法
}
public void connectSocket() {
Socket socketConn; // 声明客户端的Socket连接
try {
if (host.equals("localhost") || host.equals("127.0.0.1")) {// 判断IP地址(域名)如果是本机localhost
socketConn = new Socket(InetAddress.getLocalHost(), port);// 创建本地Socket连接
//如果该方法InetAddress.getLocalHost()报错,则要在sudo vi /private/etc/hosts 中添加本机地址与你主机名的映射,类似 127.0.0.1 主机名
//然后终端执行命令 dscacheutil -flushcache,之后主机地址便可正常解析
} else {
socketConn = new Socket(InetAddress.getByName(host), port);// 创建远程socket连接
}
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));// 获得从键盘输入的流
PrintWriter out = new PrintWriter(socketConn.getOutputStream(), true);// 往服务器写内容的数据流
// 从服务器获得信息
BufferedReader in = new BufferedReader(new InputStreamReader(socketConn.getInputStream()));// 接收服务器发送内容的输入流
System.out.println("服务器信息:" + in.readLine());
System.out.println("服务器信息:" + in.readLine());
System.out.println("请输入>");
boolean done = false;
while (!done) {
String line = stdin.readLine();// 获得从键盘输入的每行字符
out.println(line);// 发送到服务器端 --该处的print要加ln,否则就会无法往服务器端传递消息
if (line.equalsIgnoreCase("exit")) // 读到exit则结束循环
done = true;
String info = in.readLine(); // 从服务器读取字符串
System.out.println("服务器信息:" + info);// 显示从服务器发送来的数据
if (!done)
System.out.println("请输入>");
}
socketConn.close(); // 关闭资源
} catch (SecurityException e) {
System.out.println("连接服务器出现安全问题!"+e.getMessage());
} catch (IOException e) {
System.out.println("连接服务器出现I/O问题!"+e.getMessage());
}
}
}
public class TextSocketClient {// Socket客户端类
public static void main(String[] args) {
try {
new Client("localhost", 8080); // IP地址为本机,端口为8080
} catch (Exception e) {
System.out.println("测试客户端连接出错:" + e.getMessage());
}
}
}
面服务器端截图:
点客户端1运行截图
点客户端2运行截图
源程序解读:
FaceServer类继承服务器端Socket类(ServerSocket),因此继承服务器设置端口的方法。在构造方法中运用super(port)方法继承服务器端的方法,运用标识为真条件进行循环,根据基类的accpet()方法创建Socket对象,
循环挂起等待客户端的请求,并根据端口和Socket对象建立服务线程,监听来自客户端的信息。
(2)ServerThread类继承线程Thread类,继承Thread必须实现run()方法。ServerThread类的构造方法中初始化端口和Socket通信对象,并运用通信对象的getInputStream()方法创建缓冲对象,用来获取客户端的数据流。运用通信对象的getOutputStream()方法创建写往客户端的数据输出流,其中true参数是数据自动刷新从缓存写入到指定位置。调用start()方法启动线程。
(3)ServerThread类中的run()方法根据标识为真条件进行循环,按每行从获得的数据流红读取客户端的内容。当读取的内容为exit时设置标识为假,退出循环。
(4)TextPointClient类的构造方法初始主机和端口并调用connectSocket()方法来调用相关主机连接和获得服务器端信息。
(5)connectSocket()方法判断传入的主机,如果是本机则创建本地socket连接对象,否则创建远程socket连接对象。根据封装从键盘输入的流创建缓冲读对象,根据getOutputStream()方法获得服务器写内容的数据流创建写数据流。运用标识为条件循环读取键盘输入的每行字符,如果读取的数据为exit则退出循环。缓冲读对象的readLine()方法获得从服务器端读取的信息。