在进行聊天室编写之前,我们需要对聊天室的思想逻辑进行了解:
首先我们需要一个服务器和一个客户端进行连接。
之后想要让服务器接收到客户端的接口就需要使用到IO流,客户端是输出流,服务器是输入流
使用客户端连接服务器并让服务器接收到一条消息
想要实现从客户端写入一条数据,并传输到服务器上,原理就是通过套接字连接程序端口,然后使用IO流(客户端向服务器端传,客户端是输出流,服务器端是输入流,反之同理)。
服务器:
package com.tcp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author yuhang
*服务器
*/
public class MyServer {
public static void main(String[] args) {
try {
//步骤一:创建服务器ServerSocket
ServerSocket server=new ServerSocket(2222);
System.out.println("服务器已启动,等待客户端连接");
//步骤二:调用accept方法等待客户端连接
// 在没有客户端连接之前都属于阻塞状态
Socket client=server.accept();
System.out.println("连接此服务器的ip"+client.getInetAddress());
//步骤三:获取InputStream
InputStream is=client.getInputStream();
//想要每次读一行,需要封装成BufferedRead
BufferedReader br=new BufferedReader(new InputStreamReader(is));
String msg=br.readLine();
System.out.println("接受到的消息"+msg);
br.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端:
package com.tcp;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
/**
* @author yuhang
*客户端
*/
public class MyClient {
public static void main(String[] args) {
try {
//步骤一:创建客户端Socket
// 参数1是连接哪个主机服务器,参数2是连接哪个程序
Socket client=new Socket("192.168.*.***", 2222);
//客户端想发消息给服务器
// 步骤二:获取OutputStream
OutputStream os=client.getOutputStream();
PrintWriter pw=new PrintWriter(os);
pw.print("hello");
pw.flush();
pw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
启动服务器但是没启动客户端连接时:
启动客户端后:
Tcp\Ip传图片
服务器端:
package com.tcp.fileupload;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
public class FileServer {
public static void main(String[] args) {
try {
//创建服务器
ServerSocket server=new ServerSocket(8888);
System.out.println("服务器已启动");
//接受客户端
Socket client=server.accept();
//获取连接此服务器的ip
System.out.println(client.getInetAddress()+"已经连接");
//从客户端读取文件
InputStream is=client.getInputStream();
//将读取到的文件写入文件夹
String name=UUID.randomUUID().toString();
//根据保存的目录和随机的名称构建File对象
File saveFile = new File("E:\\IO", name + ".jpg");// 只上传图片
OutputStream os=new FileOutputStream(saveFile);
byte[] arr=new byte[1024];
int len=-1;
while((len=is.read(arr))!=-1)
{
os.write(arr, 0, len);
}
System.out.println("接收文件成功");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户端:
package com.tcp.fileupload;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
/**
*
* @author
* @version 1.0
* @Date 2020年8月6日 上午11:13:01
* @Description: 客户端:上传图片到服务器
*/
public class FileClient {
public static void main(String[] args) {
File file=null;
//扫描器
Scanner sc=new Scanner(System.in);
System.out.println("请输入上传文件的路径:");
String path=null;
while(true)
{
path=sc.nextLine();
//根据这个路径创建文件
file = new File(path);
if("quti".equals(path))
{
System.out.println("放弃上传文件");
return;
}
if(!file.exists()) {
System.out.println("路径有问题,文件不存在");
continue;
}else if(!file.isFile()) {
System.out.println("不是一个文件");
continue;
}else if(!file.getName().endsWith(".jpg")) {
System.out.println("不是jpg文件");
continue;
}else {
System.out.println("开始上传"+file.getName());
break;
}
}
try {
//创建一个客户端连接服务器
Socket client =new Socket("192.168.0.0",8888);
//获取磁盘上的文件传递给服务器
FileInputStream fis=new FileInputStream(file);
OutputStream os=client.getOutputStream();
byte[] arr=new byte[1024];
int len=-1;
while((len=fis.read(arr))!=-1)
{
os.write(arr, 0, len);
}
System.out.println("上传完成");
client.shutdownOutput();//已经把传递的内容发送给服务器了
os.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器端:
客户端:
聊天室
聊天室的逻辑原理其实是在原有的传图片的基础之上加上了转发的功能。
聊天室中服务器的功能是接收连接用户的内容,然后再将内容转发给在聊天室内除发送者之外所有人。
同理,在聊天室中客户端的功能就是发送文字内容到服务器以及接收从服务器发送过来的内容。
接下来我会分块将接服务器端的代码逻辑和功能作用。
服务器端:
首先看一下服务器端,因为TCP/Ip协议是一种连接形式的数据传输协议,明确的分明了客户端和服务端,并由服务器端开启,由客户端去连接。
(接下来的代码解释是按照逻辑来的,也就是按照写入的逻辑,并不是按照代码的位置从上往下解释的,所以可能存在放在前面的代码在后面解释)
首先使用8888端口使用serversocket套接字创建一个服务器,之后创建一个socket对象client来接收连接的客户端。为了使得能同时接受到多个客户端连接,于是使用一个多线程去“接待”每个连接的客户端,并将连接上的客户端使用一个支持多线程并发的集合ConcurrentHashMap保存。
那服务器中的线程是如何接待每个线程的呢?
前面就提到了,服务器的作用就是从客户端接收信息,然后将接收到信息转发给ConcurrentHashMap中保存的除发送者之外的所有客户端。
package com.chart;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
class ServerThread implements Runnable {
private Socket from; // 读from的消息
private ConcurrentHashMap<String, Socket> to; // 转发给to
public ServerThread(Socket from, ConcurrentHashMap<String, Socket> to) {
super();
this.from = from;
this.to = to;
}
@Override
public void run() {
// 一直读客户端发来的消息,并且把消息转发给所有的客户端
while (true) {
String ip = from.getInetAddress().getHostAddress();
try {
// ①读from的消息
InputStream is = from.getInputStream();
int len = is.available();//判断输入流中还有多少个字节可读
if (len > 0) {
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line = br.readLine();
if ("quit".equalsIgnoreCase(line.trim())) {
System.out.println(ip + "离开聊天室!");
// 把这个离开聊天室的剔除
to.remove(ip);
// System.out.println("聊天室剩余:" + to.size());
} else {
// 不转发空空格
if (!"".equals(line.trim())) {
System.out.println("服务器偷偷看下发什么:" + ip + ":" + line);
for (Socket s : to.values()) {
if (s.isConnected()) {
OutputStream os = s.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.println(ip + ":" + line);//转发
pw.flush();
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
System.out.println(ip + "---离开");
}
}
}
}
//服务器
public class ChartServer {
public static void main(String[] args) {
//存储了解到聊天室的客户端,用于转发消息
ConcurrentHashMap<String,Socket> hashMap=new ConcurrentHashMap<>();
try {
//创建服务器
ServerSocket server=new ServerSocket(8888);
System.out.println("-----聊天室已启动-----");
while(true)
{
//获取客户端接入口
Socket client=server.accept();
//获取客户端信息
String ip=client.getInetAddress().getHostName();
System.out.println(ip+"进入聊天室");
hashMap.put(ip,client);
// System.out.println(hashSet);
//为每一个用户端启动一个线程
new Thread(new ServerThread(client, hashMap)).start();
}
//
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户端的功能一方面可以给服务器端发送消息,另一方面可以接受到服务器端转发的数据。(代码中使用了与用户交互的Swing类,可忽略)
首先,使用服务器的ip地址和服务器端定义的端口号创建客户端对象,在向服务器传输文件的时候是直接通过获得连接的输出流完成(client.getOutputStream)。但是在接收来自服务器端的内容时,使用了线程接收消息,使用输入流一直读来自服务器端的文件,直到结束为止。
客户端:
package com.chart;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
//任务:不断接收服务器转发来的消息
class ReceiveMsg implements Runnable {
private Socket client;
private JTextArea showMsg;
public ReceiveMsg(Socket client,JTextArea showMsg) {
this.client = client;
this.showMsg=showMsg;
}
@Override
public void run() {
while (true) {
try {
// 读服务器发来的消息
InputStream is = client.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line = br.readLine();
System.out.println("测试:"+line);
//在面板上显示内容
showMsg.append(line+"\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class Client extends JFrame {
//客户端
private static Socket client =null;
private static JTextArea showMsg; //显示从服务器读来的消息
private static JTextField input;//文件输入框
//按回车键发送消息,不用按钮
public Client() {
try {
// 创建一个客户端
client=new Socket("192.168.43.64",8888);
System.out.println("加入聊天室");
this.setTitle("QQ客户端");
this.setBounds(300, 300, 600, 400);
this.setLayout(new BorderLayout());
showMsg=new JTextArea(600,300);
showMsg.setBackground(Color.pink);
showMsg.setFont(new Font("楷体",Font.BOLD,18));
showMsg.setLineWrap(true);
JScrollPane scrollPane=new JScrollPane(showMsg);
scrollPane.setSize(600, 300);
scrollPane.setBackground(Color.PINK);
input=new JTextField();
this.add(new JLabel("聊天室"),BorderLayout.NORTH);
this.add(scrollPane,BorderLayout.CENTER);//中间
this.add(input,BorderLayout.SOUTH);//底部
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Client window=new Client();
new Thread(new ReceiveMsg(client,showMsg)).start();// 一个线程负责接收消息
//为文本输入框获取光标
input.setFocusable(true);
showMsg.setFocusable(false);
//this.pack();//将窗体和组件捆绑一起
//为input输入框设置键盘事件
input.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//获取文本输入框中内容
String msg=input.getText().trim();
if(!"".equals(msg)) {
System.out.println("你输入的内容:"+msg);
//将消息发送给服务器
try {
PrintWriter pw=new PrintWriter(client.getOutputStream());
pw.println(msg);
pw.flush();
//把文本输入框的内容清空
input.setText("");
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
});
window.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务器端:
客户端: