Java利用TCP编程实现简单聊天室

前言:

本文是我在学习尚学堂JAVA300集第二季网络编程部分仿照视频内容实现而成
具体可以去尚学堂官网观看视频学习

一、实现思路

   实现聊天室的最核心部分就是JAVA的TCP网络编程。
  TCP 传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议 ,在Java中我们利用ServerSocket类来建立服务端,利用Socket类来建立客户端。这里要注意,在TCP中,Socket实际上是指
Server端与Client端建立的一个双向的流通道,我们利用这个流通道实现数据的传输。
  我们将聊天室分为两部分,客户端和服务端.
  对于客户端,主要有两个功能,信息的收与信息的发。因为这两个功能需要并行进行,并且要不停的进行收和发,所以将这两个功能抽象成两个实现Runnable接口的(Send,Recevice)类,每次客户端Client启动,建立一个Socket,并利用这个Socket建立一个收线程(Recevice类)和发线程(Send)类

  对于服务器端,因为我们要不停的监听是否有新的连接进来,所有要通过一个循环不停的接收,这里的接收函数是阻塞式的。因为我们要对所有的连接进行同时处理,所有我们将新得到连接抽象成一个实现Runnable接口的User类,利用多线程进程对每一个连接并行处理。为了方便多个连接之间的交互,我们将User类作为Server类的一个内部类使用。

二、实现过程

1.客户端

Client类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package top.dlkkill.tcp.chat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class Client {

public static void main(String[] args) throws IOException {
//从控制台获得输入
BufferedReader console=new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入您的名字:");
String name=console.readLine();
if(name.equals(""))
return;
Socket client=null;
try {
//建立新连接,注意这里创建好就已经连接上了,要保证服务端已经开启
client=new Socket("localhost", 8888);
} catch (IOException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
System.err.println(name+"连接失败");
}
//两条线路,一条负责发,一条负责收
new Thread(new Send(client,name)).start();
new Thread(new Recevice(client)).start();
}

}

Send类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package top.dlkkill.tcp.chat;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class Send implements Runnable{

//负责写出,将信息传输到服务端
private DataOutputStream os;
//负责读取控制台输入
private BufferedReader console;
//线程标识
private boolean isRun=true;

//通过死循环保证线程一直进行
@Override
public void run() {
// TODO Auto-generated method stub
while(isRun) {
send(getMsgFromConsole());
}
}

//构造方法,利用Socket类获得流
public Send(Socket client,String name) {
// TODO Auto-generated constructor stub
try {
os=new DataOutputStream(client.getOutputStream());
console=new BufferedReader(new InputStreamReader(System.in));
send(name);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
isRun=false;
CloseUtil.closeAll(os,console);
}
}

//发送函数
public void send(String msg) {

try {
if(msg!=null&&!msg.equals("")) {
os.writeUTF(msg);
os.flush();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
isRun=false;
CloseUtil.closeAll(os,console);
}
}

//从控制台不断读取信息
public String getMsgFromConsole() {
String msg=null;
try {
msg=console.readLine();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
isRun=false;
CloseUtil.closeAll(os,console);
}
return msg;
}
}

Recevice类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package top.dlkkill.tcp.chat;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

public class Recevice implements Runnable{

//负责读取服务端发送过来的信息
private DataInputStream is;
//线程标识
private boolean isRun=true;


@Override
public void run() {
// TODO Auto-generated method stub
while(isRun){
recevice();
}
}

public Recevice(Socket client) {
// TODO Auto-generated constructor stub
try {
is=new DataInputStream(client.getInputStream());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
CloseUtil.closeAll(is);
isRun=false;
}
}

public void recevice() {
String msg=null;
try {
msg=is.readUTF();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
CloseUtil.closeAll(is);
isRun=false;
}
System.out.println(msg);
}

}

2.服务端

Server类(内部有一个User类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package top.dlkkill.tcp.chat;


import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;

public class Server {

//保存所有的连接
private HashSet<User> users;
//线程标识
private boolean run=true;

public static void main(String[] args) {

//创建一个Server类
Server server=new Server();
try {
//启动服务器
server.start();
} catch (IOException e) {
e.printStackTrace();
}
}

public Server() {
run=true;
users=new HashSet<User>();
}

public void start() throws IOException {
//创建一个服务器端
ServerSocket server=new ServerSocket(8888);
while(run) {
//不断接收一个新的连接,利用新连接创建一个User线程进行处理
Socket client= server.accept();
User user=new User(client);
users.add(user);
new Thread(user).start();
}
}

public void stop() {
run=false;
}


//代表一个连接,负责信息的接收与转发
private class User implements Runnable{

//记录连接用户的名字
private String name;

public String getName() {
return name;
}
//负责接收
private DataInputStream is;
//负责发送
private DataOutputStream os;
//线程标识
private boolean isRun=true;

public User(Socket client) {

try {
is=new DataInputStream(client.getInputStream());
os=new DataOutputStream(client.getOutputStream());
isRun=true;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
isRun=false;
CloseUtil.closeAll(is,os);
}
try {
name=is.readUTF();
this.sendOther(new String("欢迎"+name+"进入聊天室"),true);
this.send(new String("系统:您已经进入了聊天室"));
}catch (Exception e) {
// TODO: handle exception
}
}

@Override
public void run() {
// TODO Auto-generated method stub
while(isRun) {
this.sendOther(this.revice(),false);
}
}

//接收信息
public String revice() {
String msg = null;
try {
msg=is.readUTF();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return msg;
}

//发送信息
public void send(String msg) {
try {
os.writeUTF(msg);
os.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

//将信息转发给其他用户,同时实现了私聊功能和系统信息功能
//因为是内部类,所以可以访问Server类中的private HashSet<User> users
//@XX:代表向XX发送私聊信息
public void sendOther(String msg,boolean admin) {
if(msg.startsWith("@")&&msg.contains(":")) {
String toname=msg.substring(1, msg.indexOf(":"));
String newmsg=msg.substring(msg.indexOf(":")+1);
for (User user : users) {
if(user.getName().equals(toname)) {
user.send(this.name+"悄悄的对你说:"+newmsg);
}
}
}else {
for (User client : users) {
if(client!=this) {
if(admin)
client.send("系统:"+":"+msg);
else
client.send(this.name+":"+msg);
}
}
}
}
}
}

3.工具类

CloseUtil类(负责关闭流)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package top.dlkkill.tcp.chat;

import java.io.Closeable;

public class CloseUtil {
public static void closeAll(Closeable ...io) {
for (Closeable closeable : io) {
try {
if(closeable!=null)
closeable.close();
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
}