通过TCP网络协议实现控制台多人聊天功能,另附私聊@功能。(java)
何为TCP?我们应该首先知道这一个概念,TCP是一种可靠的、基于连接的网络协议,它是面向字节流的,即从一个经常到另一个进程的二进制序列。每一条TCP连接需要两个端点,一个是接受消息的端口,我们通常叫它为服务端,和发送消息的端口,我们通常叫它为客户端。
客户端实现细聊
每一个用户在启动的时候就用了一个Socket,因为要实现多人聊天所以就需要给每一个用户添加一个用户线程,防止在实现过程中出现阻塞的问题。
用户线程实现Runable接口,在run()方法里面不断读取从服务端传来的消息,其代码如下所示:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class ClientThread implements Runnable{
private Socket s;
BufferedReader br = null;
public ClientThread(Socket s) throws IOException {
this.s = s;
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
}
@Override
public void run() {
try{
String content = null;
while ((content = br.readLine()) != null){
System.out.println(content);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
另外我在这里实现了3个客户,每一个客户都开启一个多线程,由于每个用户实现的代码相同,所以只贴出一份。
代码如下:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class Client1 {
public static void main(String[] args) throws IOException {
Socket s = new Socket("127.0.0.1", 3000);
ClientThread clientThread = new ClientThread(s);
new Thread(clientThread).start();
PrintStream ps = new PrintStream(s.getOutputStream());
String line = null;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.print("请输入姓名: ");
line = br.readLine();
ps.println("!" + line + "!");
while (true){
line = br.readLine();
ps.println(line);
}
}
}
服务端实现细聊
首先我们需要定义一个User类,其作用就是给每一个客户(Socket)给定一个唯一标识姓名。代码如下:
import java.net.Socket;
public class User {
private Socket socket;
private String name;
public User() {
}
public User(Socket socket, String name) {
this.socket = socket;
this.name = name;
}
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"socket=" + socket +
", name='" + name + '\'' +
'}';
}
}
当服务器开启后,就会等待客户上线,然后连接客户,此时服务端就接收到了这一个用户,然后我们将这个客户(Socket)添加到User用户类,由于此时客户只是上线了,还未输入姓名,所以此时的每一个客户姓名都为null,当我们输入姓名的时候,再返回看看客户代码我们给用户的姓名做了一点小小的操作,就是添加了两个!!,所以!xxx!,这两个!里面的就是姓名,而我们就需要去判断是否为!开头以及结尾,若是就判断他是这个客户的姓名,然后我们再为每一个用户设置一个姓名。
代码如下:
//判断是否为用户姓名,如果是就设置这个用户的姓名
flag_name = content.startsWith("!") && content.endsWith("!");
flag_port = this.s.getPort() == user.getSocket().getPort();
if (flag_name && flag_port){
//分割出用户姓名
user.setName(content.substring(1, content.length() - 1));
}
然后我们需要定义一个currentUser来确定从当前控制台输入的这个用户是谁,原理是通过从端口号是否相等来判断出是否为是这一个currentUser。代码如下所示:
//获取当前用户传来的消息
if (!flag_name && flag_port){
currentUser = user;
message = user.getName() + ": " + content;
System.out.println(message); //得到客户端传来的话
}
就这样我们就确定了当前输入消息的人是谁了,然后就可以不把该用户说的话传给自己啦。实现方法为遍历该系统中所有的用户,如果是currentUser就跳过,代码如下所示:
for (User user : users) {
if (!user.equals(currentUser)) {
PrintStream ps = new PrintStream(user.getSocket().getOutputStream());
ps.println(message);
}
}
通过以上的代码就已经实现了多人聊天功能了,然后我们在实现私聊@功能吧。
私聊往往都是通过@来实现的,但是@+姓名这才是真正的私聊某人,但是还有一种情况那就是如果@+错误的名字,那么这也会发生错误。所以我们也要排出这种情况。
当我们确定了是否为私聊后我们才能去实现发送私聊消息这些操作。我们再来分析一波这个私聊功能,当一个用户@了某一个人XXX后发送的这句消息我们只能发送给被@的这个人XXX,然后再XXX用户可以看到是谁向他说话了,分析完毕,让我们来看看代码吧,代码如下所示:
if (message.contains(": @")){
for (User value : users) {
String username = value.getName();
//私聊某人
if (message.contains(": @" + username)) {
privateName = username;
//提取出私聊某人的内容
privateMessage = message.split("@" + username)[1];
flag_private = true;
}
}
// 通过私聊用户名获取该用户
for (User user : users) {
if (user.getName().equals(privateName)) {
privateUser = user;
}
}
if (flag_private){
assert privateUser != null;
PrintStream ps = new PrintStream(privateUser.getSocket().getOutputStream());
ps.println(message.split(":")[0] + ": "+ privateMessage);
}else {
assert currentUser != null;
PrintStream ps = new PrintStream(currentUser.getSocket().getOutputStream());
ps.println("没有找到这个人嘞!!!");
}
服务器的具体功能已经实现了,若还未懂的话可以再看看下面的具体代码,如下所示:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.ArrayList;
public class ServerThread implements Runnable{
Socket s = null;
String message = null;
BufferedReader br = null;
public ServerThread() {
}
public ServerThread(Socket s) throws IOException {
this.s = s;
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
}
@Override
public void run() {
try{
String content = null;
while((content = readFormClient()) != null){
ArrayList<User> users = Server.socketList;
User currentUser = null; //当前用户
boolean flag_name = false;
boolean flag_port = false;
for (User user : Server.socketList) {
//判断是否为用户姓名,如果是就设置这个用户的姓名
flag_name = content.startsWith("!") && content.endsWith("!");
flag_port = this.s.getPort() == user.getSocket().getPort();
if (flag_name && flag_port){
//分割出用户姓名
user.setName(content.substring(1, content.length() - 1));
}
//获取当前用户传来的消息
if (!flag_name && flag_port){
currentUser = user;
message = user.getName() + ": " + content;
System.out.println(message); //得到客户端传来的话
}
}
String privateMessage = null; //私聊消息
String privateName = null; //私聊用户名
User privateUser = null; //私聊用户
boolean flag_private = false;
if (message != null){
//如果有@符号说明就私聊
if (message.contains(": @")){
for (User value : users) {
String username = value.getName();
//私聊某人
if (message.contains(": @" + username)) {
privateName = username;
//提取出私聊某人的内容
privateMessage = message.split("@" + username)[1];
flag_private = true;
}
}
// 通过私聊用户名获取该用户
for (User user : users) {
if (user.getName().equals(privateName)) {
privateUser = user;
}
}
if (flag_private){
assert privateUser != null;
PrintStream ps = new PrintStream(privateUser.getSocket().getOutputStream());
ps.println(message.split(":")[0] + ": "+ privateMessage);
}else {
assert currentUser != null;
PrintStream ps = new PrintStream(currentUser.getSocket().getOutputStream());
ps.println("没有找到这个人嘞!!!");
}
}else {
//传入当前用户给其他用户输入的消息
for (User user : users) {
if (!user.equals(currentUser)) {
PrintStream ps = new PrintStream(user.getSocket().getOutputStream());
ps.println(message);
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读取用户发来的每一条消息
* @return
*/
private String readFormClient(){
try{
return br.readLine();
}catch (IOException e){
Server.socketList.remove(s);
}
return null;
}
}
最后我们则需要在服务器里面一直开启上面这个服务器线程,另外还要定义一个ArrayList集合来装入上线的用户。代码如下所示:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class Server {
//存储所有的客户
public static ArrayList<User> socketList = new ArrayList<>();
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(3000);
while (true){
Socket s = ss.accept();
User user = new User(s, "");
socketList.add(user);
//为每一个客户添加一个服务端多线程
new Thread(new ServerThread(s)).start();
}
}
}
就这样我们实现的通过TCP网络协议实现控制台多人聊天功能,另附私聊@功能就这样完成咯。
最后我们看一看实现的效果吧,首先我们先将服务器开着,等待用户连接,当用户连接了就给自己输入一个姓名,我在这里开启了三个用户。