聊天室
文章目录
1 Socket API
1.1 Socket 编程
套接字使⽤TCP提供了两台计算机之间的通信机制。 客户端程序创建⼀个套接字,并尝试连接服务器的
套接字。
当连接建⽴时,服务器会创建⼀个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写⼊和
读取来进⾏通信。
java.net.Socket 类代表⼀个套接字,并且 java.net.ServerSocket 类为服务器程序提供了⼀种来监听客户端,并与他们建⽴连接的机制。
以下步骤在两台计算机之间使⽤套接字建⽴TCP连接时会出现:
- 服务器实例化⼀个 ServerSocket 对象,表示通过服务器上的端⼝通信。
- 服务器调⽤ ServerSocket 类的 accept() ⽅法,该⽅法将⼀直等待,直到客户端连接到服务器上给定的端⼝。
- 服务器正在等待时,⼀个客户端实例化⼀个 Socket 对象,指定服务器名称和端⼝号来请求连接。
- Socket 类的构造函数试图将客户端连接到指定的服务器和端⼝号。如果通信被建⽴,则在客户端创建⼀个 Socket 对象能够与服务器进⾏通信。
- 在服务器端,accept() ⽅法返回服务器上⼀个新的 socket 引⽤,该 socket 连接到客户端的socket。
连接建⽴后,通过使⽤ I/O 流在进⾏通信,每⼀个socket都有⼀个输出流和⼀个输⼊流,客户端的输出
流连接到服务器端的输⼊流,⽽客户端的输⼊流连接到服务器端的输出流。
TCP 是⼀个双向的通信协议,因此数据可以通过两个数据流在同⼀时间发送.以下是⼀些类提供的⼀套完整的有⽤的⽅法来实现 socket。
1.2 ServerSocket
服务器应⽤程序通过使⽤ java.net.ServerSocket 类以获取⼀个端⼝,并且侦听客户端请求。ServerSocket 类有四个构造⽅法:
创建⾮绑定服务器套接字。 如果 ServerSocket 构造⽅法没有抛出异常,就意味着你的应⽤程序已经成功绑定到指定的端⼝,并且侦听客户端请求。
服务端socket处理客户端socket连接是需要⼀定时间的。ServerSocket有⼀个队列,存放还没有来得及处理的客户端Socket,这个队列的容量就是backlog的含义。如果队列已经被客户端socket占满了,如果还有新的连接过来,那么ServerSocket会拒绝新的连接。也就是说backlog提供了容量限制功能,避免太多的客户端socket占⽤太多服务器资源。
⼀些 ServerSocket 类的常⽤⽅法:
1.3 Socket
java.net.Socket 类代表客户端和服务器都⽤来互相沟通的套接字。客户端要获取⼀个 Socket 对象通过实例化 ,⽽ 服务器获得⼀个 Socket 对象则通过 accept() ⽅法的返回值。
Socket 类有五个构造⽅法.
当 Socket 构造⽅法返回,并没有简单的实例化了⼀个 Socket 对象,它实际上会尝试连接到指定的服务器和端⼝。
注意客户端和服务器端都有⼀个 Socket 对象,所以⽆论客户端还是服务端都能够调⽤这些⽅法。
1.4 InetAddress
这个类表示互联⽹协议(IP)地址。下⾯列出了 Socket 编程时⽐较有⽤的⽅法:
2 单线程模式
2.1 服务端
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* 单线程聊天室服务器端
* @author ellen
*/
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);
}
}
}
2.2 客户端
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
* 单线程聊天室客户端
* @author ellen
*/
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);
}
}
}
3 多线程模式
3.1 客户端
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;
}
@Override
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);
}
}
}
/**
* 多线程聊天室客户端
* @author ellen
*/
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();
}
}
}
3.2 服务端
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;
/**
* 多线程聊天室服务端
* @author ellen
*/
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();
}
}
源码:https://github.com/Mtreellen/Chit_Chat