首先来看一下单线程下的聊天框架,其实代码很简单,就是利用Socket和ServerSocket类的一些功能实现,但要整个深入了解聊天系统,则需要一定的TCP/IP协议相关的知识与操作系统方面的知识
服务器端:服务器端是用来接收消息并发送消息的
package mylittlemessage;;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/** * 单线程服务器端
*/
public class SingleServer
{
public static void main(String[] args) throws Exception
{
// 创建服务端Socket,端口号为6666
ServerSocket serverSocket = new ServerSocket(6666);
try {
System.out.println("等待客户端连接ing...");
// 等待客户端连接,有客户端连接后返回客户端的Socket对象,否则线程将一直阻塞
Socket client = serverSocket.accept();
System.out.println("有新的客户端连接,端口号为: "+client.getPort());
// 获取客户端的输入输出流
Scanner clientInput = new Scanner(client.getInputStream());
clientInput.useDelimiter("\n");
PrintStream clientOut = new PrintStream(client.getOutputStream(),true,"UTF-8");
/*
* 此处需要注意,因为是单线程所以大家都是先读在写,防止阻塞
*/
// 读取客户端输入
if (clientInput.hasNext())
{
System.out.println(client.getInetAddress()+"说: "+clientInput.next());
}
// 向客户端输出
clientOut.println("Hello I am Server,Welcome!");
// 关闭输入输出流
clientInput.close();
clientOut.close();
serverSocket.close();
}catch (IOException e)
{
System.err.println("服务端通信出现异常,错误为"+e);
}
}
}
客户端,客户端就是建立连接,接受消息,并发送消息
package mylittlemessage;;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/** *
单线程聊天室客户端 *
*/
public class SingleClient
{
public static void main(String[] args) throws Exception
{
String serverName = "127.0.0.1";
Integer port = 6666;
try {
// 创建客户端Socket连接服务器
Socket client = new Socket(serverName,port);
System.out.println("连接上服务器,服务器地址为: "+client.getInetAddress());
// 获取输入输出流
PrintStream out = new PrintStream(client.getOutputStream(), true,"UTF-8");
Scanner in = new Scanner(client.getInputStream());
in.useDelimiter("\n"); // 向服务器输出内容
out.println("Hi I am Client!!"); // 读取服务器输入
if (in.hasNext())
{
System.out.println("服务器发送消息为: "+in.next());
}
in.close();
out.close();
client.close();
}
catch (IOException e)
{
System.err.println("客户端通信出现异常,错误为"+e);
}
}
}
多线程下的聊天系统
用来由于单线程的局限性,客户端只能一次完成一次收发,而服务器一次也只能服务一个客户,所以,有了下面的多线程聊天系统,使得聊天能够更加灵活,并实现了(注册,群聊,私发消息)等多种功能
客户端
package mylittlemessage;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/** * 读取服务器信息线程 */
class ReadFromServerThread implements Runnable
{
private Socket client;
public ReadFromServerThread(Socket client)
{
this.client = client;
}
@Override
public void run()
{
try
{
// 获取客户端输入流
Scanner in = new Scanner(client.getInputStream());
in.useDelimiter("\n");
while (true)
{
if (in.hasNext())
{
System.out.println("从服务器发来的消息为: "+in.next());
}
// 此客户端退出
if (client.isClosed())
{
System.out.println("客户端已关闭");
break;
}
}
in.close();
}
catch (IOException e)
{
System.err.println("客户端读线程异常,错误为 "+e);
}
}
}
/** * 将信息发送给服务器线程 */
class WriteToServerThread implements Runnable
{
private Socket client;
public WriteToServerThread(Socket client)
{
this.client = client;
}
public void run()
{
try{
// 获取键盘输入
Scanner scanner = new Scanner(System.in);
scanner.useDelimiter("\n"); // 获取客户端输出流
PrintStream out = new PrintStream(client.getOutputStream());
while (true)
{
System.out.println("请输入要发送的消息..");
String strToServer;
if (scanner.hasNextLine())
{
strToServer = scanner.nextLine().trim();
out.println(strToServer);
// 客户端退出标志
if (strToServer.equals("byebye"))
{
System.out.println("关闭客户端");
scanner.close();
out.close();
client.close();
break;
}
}
}
}
catch (IOException e)
{
System.out.println("客户端写线程异常,错误为 "+e);
}
}
}
/**
* 多线程聊天室客户端
*/
public class MultiThreadClient
{
public static void main(String[] args)
{
try
{
Socket client = new Socket("127.0.0.1",6666);
// 读取服务器消息线程
Thread readFromServer = new Thread(new ReadFromServerThread(client));
Thread writeToServer = new Thread(new WriteToServerThread(client));
readFromServer.start();
writeToServer.start();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
服务器端
package bitekeji;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 多线程聊天室服务端 *
*/
public class MultiThreadServer
{
// 存储所有注册的客户端
private static Map<String, Socket> clientMap = new ConcurrentHashMap<String, Socket>();
// 具体处理与每个客户端通信的内部类
private static class ExecuteClient implements Runnable
{
private Socket client;
public ExecuteClient(Socket client)
{
this.client = client;
}
@Override
public void run()
{
try {
// 获取客户端输入流
Scanner in = new Scanner(client.getInputStream());
String strFromClient;
while (true)
{
if (in.hasNextLine())
{
strFromClient = in.nextLine();
// windows下将默认换行/r/n中的/r替换为空字符串
Pattern pattern = Pattern.compile("\r");
Matcher matcher = pattern.matcher(strFromClient);
strFromClient = matcher.replaceAll("");
// 注册流程
if (strFromClient.startsWith("userName"))
{
String userName = strFromClient.split("\\:")[1];
registerUser(userName,client);
continue;
}
// 群聊流程
if (strFromClient.startsWith("G"))
{
String msg = strFromClient.split("\\:")[1];
groupChat(msg);
continue;
}
// 私聊流程
if (strFromClient.startsWith("P"))
{
String userName = strFromClient.split("\\:")[1].split("-")[0];
String msg = strFromClient.split("\\:")[1].split("-")[1];
privateChat(userName,msg);
}
// 用户退出
if (strFromClient.contains("byebye"))
{
String userName = null;
// 根据Socket找到UserName
for (String keyName : clientMap.keySet())
{
if (clientMap.get(keyName).equals(client))
{
userName = keyName;
}
}
System.out.println("用户"+userName+"下线了!");
clientMap.remove(userName);
continue;
}
}
}
}
catch (IOException e)
{
System.err.println("服务器通信异常,错误为 "+e);
}
}
// 注册方法
private void registerUser(String userName,Socket client)
{
System.out.println("用户姓名为: "+userName);
System.out.println("用户"+userName+"上线了!");
System.out.println("当前群聊人数为: "+(clientMap.size()+1)+"人");
// 将用户信息保存到map中
clientMap.put(userName,client);
try
{
PrintStream out = new PrintStream(client.getOutputStream(),true,"UTF-8");
// 告知用户注册成功
out.println("用户注册成功!");
} catch (IOException e)
{
e.printStackTrace();
}
}
// 群聊流程
private void groupChat(String msg)
{
// 取出clientMap中所有Entry遍历发送群聊信息
Set<Map.Entry<String,Socket>> clientSet = clientMap.entrySet();
for (Map.Entry<String,Socket> entry : clientSet)
{
try {
Socket socket = entry.getValue();
// 取得每个客户端的输出流
PrintStream out = new PrintStream(socket.getOutputStream(),true,"UTF-8");
out.println("群聊信息为: "+msg);
}
catch (IOException e)
{
System.err.println("群聊异常,错误为 "+e);
}
}
}
// 私聊流程
private void privateChat(String userName,String msg)
{
Socket privateSocket = clientMap.get(userName);
try {
PrintStream out = new PrintStream(privateSocket.getOutputStream(),true,"UTF-8");
out.println("私聊信息为: "+msg);
}
catch (IOException e)
{
System.err.println("私聊异常,错误为"+e);
}
}
}
public static void main(String[] args) throws Exception
{
ExecutorService executorService = Executors.newFixedThreadPool(20);
ServerSocket serverSocket = new ServerSocket(6666);
for (int i = 0 ; i < 20 ; i++)
{
System.out.println("等待客户端连接...");
Socket client = serverSocket.accept();
System.out.println("有新的客户端连接,端口号为: "+client.getPort());
executorService.submit(new ExecuteClient(client));
}
executorService.shutdown();
serverSocket.close();
}
}