网络编程-聊天室
网络编程是指编写运行在多个设备的计算机程序,这些设备通过网络连接起来
主要使用: java.net包:JavaSE的API包含有类和接口,它们提供低层次的通信细节。
java.net包中提供了两种常见的网络协议的支持:
TCP:TCP是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称TCP/IP。
UDP:UDP是用户数据报协议的缩写,一个无连接的协议。提供了应用程序之间要发送的数据的数据报。
Socket编程:套接字使用TCP提供两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。 当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。
因此: 1.服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。 2.服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。 3.服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。 4.Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。 5.在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。 6.连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class GreetingServer {
public static void main(String[] args) throws IOException {
ServerSocket Server=new ServerSocket(8888);
Socket so = Server.accept();
InputStream input = so.getInputStream();
byte[] bytes=new byte[1024];
int len=input.read(bytes);
System.out.println(new String(bytes,0,len));
OutputStream output = so.getOutputStream();
output.write("谢谢收到".getBytes());
so.close();
Server.close();
}
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.io.IOException;
import java.net.Socket;
public class GreetingClient {
public static void main(String[] args) throws IOException {
Socket client=new Socket("127.0.0.1",8888);
OutputStream os = client.getOutputStream();
os.write("你好服务器".getBytes());
InputStream input = client.getInputStream();
byte[] bytes=new byte[1024];
int len=input.read(bytes);
System.out.println(new String(bytes,0,len));
client.close();
}
}
聊天室功能
聊天室需要一个服务器支持,多个客户端连接服务端,服务端的作用就是接受不同的客户端数据,并转发到其他客户端。
客户端可发发送数据给服务器端,同时客户端也需要接收服务器端返回的数据。客户端的发送数据和接收数据是两个独立的通道,互不影响。 客户端的输出与输入要独立,可以使用多线程来实现。
服务端要为每一个客户端建立一个通道,服务端也使用多线程来实现。
服务端需要创建一个通道的列表,统一管理客户端的通道,为了实现自己发的消息,其他人都可以看到。
在客户端程序里为每一个客户端设置一个名称,约定以@name#开头的格式为私聊,就可以实现私聊的功能。
当程序中发生异常时,线程就停止执行。
总体:
每个客户端在连接到服务器端时,要通过控制台输入自己的名称,然后开始发送消息到服务端,服务端在接收到客户端的连接时,首先输出谁进入了聊天室,然后把客户端发来的消息转发给其他客户端,实现群聊的功能,如果客户端按照约定以@name#开头的格式输入消息,服务端需要解析到客户端要私聊的对象,把消息单独发送给要私聊的客户端。
1.处理IO异常
import java.io.Closeable;
public class Util {
public static void closeAll(Closeable... io) {
for (Closeable temp : io) {
try {
if (null != temp) {
temp.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2.服务器端多线程,维护一个客户端的通道列表,服务端实现既能接受客户端的数据,又能转发给对应的客户端
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class ChatChannel implements Runnable{
public static List<ChatChannel> all = new ArrayList<ChatChannel>();
private DataInputStream dis;
private DataOutputStream dos;
private String name;
private boolean isRunning = true;
public ChatChannel(Socket client) {
try {
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
this.name = dis.readUTF();
System.out.println(this.name + "进入了聊天室");
this.send(this.name + ",您好!欢迎您进入聊天室");
sendOthers(this.name + "进入了聊天室", true);
} catch (IOException e) {
e.printStackTrace();
Util.closeAll(dis, dos);
isRunning = false;
}
}
private String receive() {
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
e.printStackTrace();
Util.closeAll(dis);
isRunning = false;
all.remove(this);
}
return msg;
}
private void send(String msg) {
if (msg != null && !"".equals(msg)) {
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
e.printStackTrace();
Util.closeAll(dos);
isRunning = false;
all.remove(this);
}
}
}
private void sendOthers(String msg, boolean sysMsg) {
if (msg.startsWith("@") && msg.indexOf("#") > -1) {
String name = msg.substring(1, msg.indexOf("#"));
String content = msg.substring(msg.indexOf("#") + 1);
for (ChatChannel other : all) {
if (name.equals(other.name)) {
other.send(this.name + "悄悄地对您说:" + content);
}
}
} else {
for (ChatChannel other : all) {
if (other == this) {
continue;
}
if (sysMsg) {
other.send("系统信息:" + msg);
} else {
other.send(this.name + "对所有人说:" + msg);
}
}
}
}
@Override
public void run() {
while (isRunning) {
sendOthers(receive(), false);
}
}
}
3.创建服务端类Server,使用多线程和通道容器
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8888);
while (true) {
Socket client = server.accept();
ChatChannel channel = new ChatChannel(client);
ChatChannel.all.add(channel);
new Thread(channel).start();
}
}
}
4.客户端发送消息线程类Send,设定自己的名字,并发送给服务端
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 BufferedReader console;
private DataOutputStream dos;
private String name;
private boolean isRunning = true;
public Send(Socket client, String name) {
try {
console = new BufferedReader(new InputStreamReader(System.in));
dos = new DataOutputStream(client.getOutputStream());
this.name = name;
send(this.name);
} catch (IOException e) {
e.printStackTrace();
isRunning = false;
Util.closeAll(dos, console);
}
}
public void send(String msg) {
try {
if (msg != null && !"".equals(msg)) {
dos.writeUTF(msg);
dos.flush();
}
} catch (IOException e) {
e.printStackTrace();
isRunning = false;
Util.closeAll(dos, console);
}
}
private String getMsgFromConsole() {
try {
return console.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
@Override
public void run() {
while (isRunning) {
send(getMsgFromConsole());
}
}
}
5.客户端接收消息Receive,用于独立接受服务端返回的数据
mport java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
public class Receive implements Runnable{
private DataInputStream dis;
private boolean isRunning = true;
public Receive(Socket client) {
try {
dis = new DataInputStream(client.getInputStream());
} catch (IOException e) {
e.printStackTrace();
isRunning = false;
Util.closeAll(dis);
}
}
public String receive() {
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
e.printStackTrace();
isRunning = false;
Util.closeAll(dis);
}
return msg;
}
@Override
public void run() {
while (isRunning) {
System.out.println(receive());
}
}
}
6.创建客户端类Client,发送和接收数据分布使用独立的多线程
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 {
System.out.println("请输入您的名称:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String name = br.readLine();
if ("".equals(name)) {
return;
}
Socket client = new Socket("localhost", 8888);
new Thread(new Send(client, name)).start();
new Thread(new Receive(client)).start();
}
}
最后,先启动Server,然后可以启动多个客户端Client 客户端启动后,创建名字: 客户端公开发言 客户端私聊
|