在上一篇文章《网络通信——1对1,单次通信》中实现了1对1,单次通信,本篇文章会实现1对1,多次通信。
任何需要通信的双方,都需要三个元素,Socket,DataInputStream和DataOutputStream,就能完成通信。
对于服务器端部分,当接收到来自客户端的“byebye”消息时,停止通信
对于客户端部分,需要实现用户从键盘输入,将输入的消息发送到服务器端,使用专门的方法接收键盘输入消息,并完成消息发送。*
还有一个问题,我们在聊天过程中,一边在接受别人消息时,一边自己这里还要敲文字,给别人发过去。而在上一篇的执行方案中,程序在执行message = dis.readUTF();
时,若是没有服务器向这个客户端发送消息,这条语句就一直处于等待状态,不能输入信息。为此,我们使用线程来处理“侦听来自对端的消息”
同样的,先用接口定义端口号:
package gw.about_net.core;
public interface INet {
int DEFAULT_PORT = 54187;
}
1.服务器
package gw.about_net.server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import gw.about_net.core.INet;
public class SingleClientServer {
private ServerSocket server;
private int port;
public SingleClientServer() {
this.port = INet.DEFAULT_PORT;
}
public SingleClientServer setPort(int port) {
this.port = port;
return this;
}
public void startup() {
try {
System.out.println("开始建立服务器......");
server = new ServerSocket(port);
System.out.println("服务器建立成功,开始侦听来自客户端的连接......");
Socket socket = server.accept();
String clientIp = socket.getInetAddress().getHostAddress();
System.out.println("侦听到一个客户端[" + clientIp + "]的连接请求,并已连接!");
new Thread(new Communicate(socket)).start();
} catch (IOException e) {
e.printStackTrace();
}
}
class Communication implements Runnable{
private DataInputStream dis;
private DataOutputStream dos;
private volatile boolean goon; //线程标志
public Communication(Socket socket) {
try {
dis =new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
goon = true;
} catch (IOException e) {
e.printStackTrace();
}
}
public void send(String message) {
try {
dos.writeUTF(message);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//专注于侦听来自客户端的信息
String message = null;
while(goon) {
try {
message = dis.readUTF();
System.out.println("来自星星的消息:["+message+"]");
if(message.equalsIgnoreCase("byebye")) {
goon = false;
}else {
send("["+message+"]");
}
} catch (IOException e) {
if(goon == true) {
System.out.println("客户端异常掉线!");
}
}
}
close();
}
private void close() {
goon = false;
try {
if (dis != null) {
dis.close();
}
} catch (IOException e) {
} finally {
dis = null;
}
try {
if (dos != null) {
dos.close();
}
} catch (IOException e) {
} finally {
dos = null;
}
try {
if (server != null && !server.isClosed()) {
server.close();
}
} catch (IOException e) {
} finally {
server = null;
}
}
}
}
2.客户端
package gw.about_net.client;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
import gw.about_net.core.INet;
public class SingleClientClient {
public static final String DEFAULT_IP = "127.0.0.1";
private int port;
private String ip;
private Socket socket;
public SingleClientClient() {
this.ip = DEFAULT_IP;
this.port = INet.DEFAULT_PORT;
}
public SingleClientClient setServerIp(String serverIp) {
this.ip = serverIp;
return this;
}
public SingleClientClient setServerPort(int serverPort) {
this.port = serverPort;
return this;
}
public void ConnectToServer() {
try {
socket = new Socket(ip, port);//连接服务器
Communication communication = new Communication(socket);
new Thread(communication).start();//启动线程
Scanner in = new Scanner(System.in);
String message = "";
while(!message.equalsIgnoreCase("byebye")) {
message = in.next();
communication.send(message);
}
communication.close();
in.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
class Communication implements Runnable{
private DataInputStream dis;
private DataOutputStream dos;
private volatile boolean goon;
public Communication(Socket socket) {
try {
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
goon = true;
} catch (IOException e) {
e.printStackTrace();
}
}
public void send(String message) {
try {
dos.writeUTF(message);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
String message = null;
while(goon) {
try {
message = dis.readUTF();
System.out.println("来自太阳的消息:"+message);
} catch (IOException e) {
if(goon == true) {//当goon为true,说明没有主动关闭,即没有接收“byebye”
System.out.println("服务器异常宕机!");
}
goon = false;
}
}
close();
}
private void close() {
goon = false;
try {
if (dis != null) {
dis.close();
}
} catch (IOException e) {
} finally {
dis = null;
}
try {
if (dos != null) {
dos.close();
}
} catch (IOException e) {
} finally {
dos = null;
}
try {
if (socket != null && !socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
} finally {
socket = null;
}
}
}
}
运行结果:
客户端:
服务器端:
3.通信层
在上述的服务器端与客户端中都有一个方法Communication,用于实现与对端的通信,其中,需要实现的功能是一样的。那么,接下来该怎么做呢?对,就是抽象出来,形成单独的类。
该类负责建立与对端的通信信道,提供发送,接收消息和关闭连接的功能。
package gw.about_net.core;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public abstract class Communication implements Runnable {
protected Socket socket;
protected DataInputStream dis;
protected DataOutputStream dos;
private volatile boolean goon;
//建立与对端的通信信道
protected Communication(Socket socket) {
this.socket = socket;
try {
this.dis = new DataInputStream(socket.getInputStream());
this.dos = new DataOutputStream(socket.getOutputStream());
this.goon = true;
new Thread(this).start();
} catch (IOException e) {
e.printStackTrace();
}
}
//发送消息
public void send(String message) {
try {
dos.writeUTF(message);
} catch (IOException e) {
e.printStackTrace();
}
}
public abstract void dealNetMessage(String message) ; //处理对端消息
public abstract void peerAbnormalDrop(); //对端异常掉线
@Override
public void run() {
String message = null;
while(goon) {
try {
message = dis.readUTF();
//需要根据具体消息进行不同处理,给一个抽象方法
dealNetMessage(message);
} catch (IOException e) {
if(goon == true) {
//根据客户端与服务端的不同,处理对端异常掉线也不同
peerAbnormalDrop();
goon = false;
}
}
}
close();
}
//关闭连接
public void close() {
goon = false;
try {
if (dis != null) {
dis.close();
}
} catch (IOException e) {
} finally {
dis = null;
}
try {
if (dos != null) {
dos.close();
}
} catch (IOException e) {
} finally {
dos = null;
}
try {
if (socket != null && !socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
} finally {
socket = null;
}
}
}
下面,给一张图,来表示提取出Communication前后通信的接收消息和发送消息端的变化: