聊天室

聊天室

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

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值