单线程示例
实现功能
服务端与客户端1对1聊天通信,需要先运行服务器端,后运行客户端,用户输入bye时,关闭socket,终止聊天。
具体运行过程
- 启动服务器端,创建ServerSocket,并阻塞于accept方法,等待客户端连接。
- 启动客户端,连接服务器。
- 服务器端接受连接,得到ServerSocket.accept( )返回的socket对象。
- 服务器端和客户端分别创建各自监听消息的线程,之后可以通过socket的输入输出流进行信息交互。
- 当某一方输入bye时,socket关闭,断开连接
- 关闭相关资源,通信结束
代码
服务器端
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(SocketUtils.PORT);
Socket socket = serverSocket.accept();
new ReceiveMsg(socket).start();
SocketUtils.sendMsg(socket, socket.getOutputStream());
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
public class ClientDemo {
public static void main(String[] args) {
try {
Socket socket = new Socket(InetAddress.getLocalHost(),SocketUtils.PORT);
new ReceiveMsg(socket).start();
SocketUtils.sendMsg(socket, socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
}
封装的工具类
SocketUtils.java :封装了发送用户控制台输入消息的方法,以及定义了端口常量。
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class SocketUtils {
public static final int PORT = 8888;
public static void sendMsg(Socket socket, OutputStream os){
PrintWriter pw = new PrintWriter(os);
Scanner sc = new Scanner(System.in);
String msg;
while (!socket.isClosed() && (msg = sc.next())!=null){
pw.write(msg+'\n');
pw.flush();
if ("bye".equals(msg)){
break;
}
}
System.out.println("消息传输完毕,程序结束");
try {
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ReceiveMsg.java :创建新线程监听信息,并打印信息到控制台
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.SocketException;
public class ReceiveMsg extends Thread {
InputStream inputStream;
Socket socket;
public ReceiveMsg(Socket socket) {
this.socket = socket;
try {
this.inputStream = socket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
System.out.println("线程被启动");
InputStreamReader isr = new InputStreamReader(inputStream);
BufferedReader br = new BufferedReader(isr);
try {
String msg;
while ((msg=br.readLine())!=null) {
System.out.println("msg:"+msg);
if ("bye".equals(msg)){
System.out.println("接收消息socket关闭");
socket.close();
break;
}
}
} catch (IOException e) {
if (!(e instanceof SocketException)){
e.printStackTrace();
}
}
}
}
多线程示例
使server可以支持多个client连接并聊天
只需要增加一个线程类,稍微改动一下ServerDemo.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(SocketUtils.PORT);
while (true){
Socket socket = serverSocket.accept();
new ServerThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
增加的线程类ServerThread.java
import java.io.IOException;
import java.net.Socket;
public class ServerThread extends Thread{
Socket socket;
public ServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
super.run();
new ReceiveMsg(socket).start();
try {
SocketUtils.sendMsg(socket, socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
}
思考
不太明白的地方:
但同时这又会带来一个问题,单线程进行聊天时,可以注意发送消息的这一段代码
while (!socket.isClosed() && (msg = sc.next())!=null){
pw.write(msg+'\n');
pw.flush();
if ("bye".equals(msg)){
break;
}
}
当server启动之后,会阻塞于accept方法,client启动后,双方的代码都会阻塞在sc.next( ),socket.isClosed() 返回的是false。这时,任意一方输入bye关闭socket,另外一方还需要输入一行才能彻底结束。
- 可能可行的解决方法:通过注册–通知机制实现事件监听(观察者模式),达到实时监听socket是否关闭,还有待实现和验证…
此外,还存在一个问题,当使用多线程时,会造成聊天混乱的情况,两个连接client的线程并发运行,会导致server需要交替输入。
参考资料:
Scanner 类抛出java.util.NoSuchElementException
java 线程监听事件_java中的事件监听是怎样实现随时监听的,是通过线程吗
java-用java.net.Socket和java.net.ServerSocket实现简单的聊天程序
|