概述
这是基于TCP通信的客户端与服务器的实现。以简单的聊天窗作为模板,实现多个客户端之间的通信
一、客户端
分析
我们的客户端分为2个模块,界面显示模块Client和通信模块Connect。
本文中,首字母大写的Client和Connect表示类,首字母小写的client和connect表示所创建的对象
发送数据:
是通过点击按钮button来发送数据,于是我们可以对button创建一个用于发送信息的监听器,当我们点击button时,调用**通信模块的发送函数connect.send(String msg)**来发送消息。可以发现,这个模块通过监听器实现起来比较轻松,因为只有点击按钮时,才会发送消息。这意味着我们只需要把发送函数写好就行。
接收数据
由于我们无法判断对方什么时候给我们发送数据,所以我们需要一个while(true)循环来接收信息。这个循环应该在connect的初始化函数的最后。
但是,由于我们要先构造Connect的对象connect,之后才能根据connect构建按钮的监听器,所以,如果我们直接在那添加循环,我们的程序就会卡在这,影响我们构建监听器,从而影响我们发送信息。为了解决这个问题,我们可以构建一个线程,在线程中进行while(true)循环,这样就不会影响我们接下来的程序运行了。
解决了这个问题,我们还面临着一个问题,就是如何把在通信模块connect接收到的消息传送个界面模块client。
模块Client 与 通信模块Connect 之间的消息传输
我们需要一个something来在这2个模块之间实现数据传输。
发送数据好说,在client里创建一个connect对象,把信息String msg当成参数传递给connect里的发送函数,形如 connect.send(msg);
接收数据就有点难度了。最简单的方法就是在Connect对象里创建一个Client对象,当在Client对象client里创建一个Connect对象connect时,把client自身传给connect。这样做也不是不想,但不够秀。
我们可以这样做:
- 我们可以通过创建interface接口RecvInterface,里面只有一个处理数据的空函数
public void handleData(String msg);
。 - 然后让Client继承RrcvInterface,改写
handleData
函数。 - 之后只在Connect里创建一个RecvInterface对象recvInterface,并将client传给connect里的recvInterface
这样当我们处理接收信息时,只需要在connect里 recvInterface.handleData(line);
即可。
客户端界面
结果:
大框显示接收到的数据
小框显示要发送的数据
(一) 构建接口RecvInterface
/*接口很简单,只有一个处理数据的函数就行*/
public interface RecvInterface {
public void handleData(String msg);
}
(二) 界面Client
就是一个简单的UI设计,在初始化完成后,将该界面this作为一个 接口 传入到通信模块
/*Client继承接口RecvInterface,方面处理接收到的消息*/
public class Client implements RecvInterface {
private Connect connect = new Connect();
private JTextArea log;
/*初始化模块*/
public void initUI() {
/*界面设计*/
JFrame jf = new JFrame();
jf.setDefaultCloseOperation(3);
jf.setLayout(new FlowLayout());
//JTextPane可以实现字体颜色和图片插入
log = new JTextArea(12, 40);
//滚动面板
JScrollPane sp = new JScrollPane(log);
jf.add(sp);
JTextField input = new JTextField(20);
jf.add(input);
JButton btn = new JButton("Send");
jf.add(btn);
jf.setSize(600, 500);
jf.setVisible(true);
/*
创建通信模块connect
初始化connect
将自己this传入connect
*/
connect.init();
connect.addRecvInterface(this);
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String sendMsg = input.getText();
connect.send(sendMsg);
}
});
}
/*重写RecvInterface里的处理数据函数*/
public void handleData(String msg) {
log.setText(log.getText()+"rn"+msg);
}
/*主函数用来启动客户端client*/
public static void main(String[] args) {
Client client = new Client();
client.initUI();
}
}
(二) 构建通信模块Connect
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Date;
import java.util.Random;
public class Connect {
/*
端口socket用来选定要发送的服务器地址
inputstream & outputstream 用来处理发送和接收的数据
RecvInterface 用来把信息msg传给界面client
*/
private Socket socket;
private InputStream inputStream;
private OutputStream outputStream;
private RecvInterface recvInterface;
/*
用来将client界面传给connect
以此来实现将接收到的消息传给client
*/
public void addRecvInterface(RecvInterface recvInterface) {
this.recvInterface = recvInterface;
}
/*
初始化函数
*/
public boolean init() {
//如果失败(出现异常)返回失败
try {
//实例化对象
socket = new Socket("127.0.0.1", 8000);
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
/*
由于是仿qq,每个客户端都有自己的ID/编号/昵称
于是在此随机生成
*/
Random random = new Random();
outputStream.write(("张三"+random.nextInt(100)+"n").getBytes("gbk"));
outputStream.flush();
//启动接受接收消息的线程,用函数实现
startRecvThread();
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/*
创建线程
实现循环接收信息
*/
Thread recvThread;
public void startRecvThread() {
recvThread = new Thread() {
//开始:
public void run() {
//如果线程没有中断
while(!Thread.interrupted()) {
try {
//如果得到了信息
String line = readLine(inputStream);
//收到信息后要传递给界面,监听器设计模式
if(recvInterface != null) {
recvInterface.handleData(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
recvThread.start();
}
/*
发送函数
*/
public boolean send(String msg) {
try {
//选择发送的编码gbk,发送的信息加上换行
outputStream.write((msg+"n").getBytes("gbk"));
//通过flush()强制刷新,发送出去
outputStream.flush();
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/*
从输入流读取一行字符串
*/
private String readLine(InputStream inputStream) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int c = inputStream.read();
while(c != 'n') {
bos.write(c);
c = inputStream.read();
}
byte[] bytes = bos.toByteArray();
return new String(bytes,"gbk");
}
/*
这个关闭是拓展内容:
当你"有需求"要关闭接收的端口就可以调用这个函数
本次样例里没有这个函数的使用样例
*/
public void close() {
if(recvThread != null) {
recvThread.interrupt();
}
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
二、服务器
分析
服务器也是2个模块:
Server
用来启动服务器,打开端口Socket,接收来自客户端Client的请求,并把请求的Client存入一个ClientUser队列,然后单独开一个属于该client的线程,用来处理消息
ClientUer
存入所有请求该服务器的信息,作为一个线程来处理来自该客户端的信息
(一) Server
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
//创建一个服务端套接字,绑定8000端口
ServerSocket ss = new ServerSocket(8000);
//接受客户端访问
while(true) {
Socket socket = ss.accept();
System.out.println("收到一个客户端访问");
//把该客户端user加入ClientUser列表里
ClientUser user = new ClientUser(socket);
//启动user的线程
user.start();
}
}
}
(二) ClientUser
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
public class ClientUser extends Thread {
/*
把 客户端的请求处理 抽象成一个ClientUser类
创建一个队列
用来存放 客户端的请求处理 user
*/
private static ArrayList<ClientUser> userList = new ArrayList<ClientUser>();
/*
每一个客户端的请求处理对象都有
端口 socket
各自的名字 nickname
输入输出流 inputStream & outputStream
*/
private Socket socket;
private String nickname;
private InputStream inputStream;
private OutputStream outputStream;
/*
构造函数
*/
public ClientUser(Socket socket) {
/*实例化各种对象*/
super();
this.socket = socket;
try {
this.inputStream = socket.getInputStream();
this.outputStream = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
/*
把这个对象加入到userlist队列里
方便发送消息
*/
userList.add(this);
}
/*
运行函数:为每个ClientUser对象创建一个线程
用来处理各自的信息发送
(信息的接收由客户端自己处理)
*/
public void run() {
//读取每个客户端client的用户名
try {
this.nickname = readLine(inputStream);
} catch (IOException e1) {
e1.printStackTrace();
}
//启动线程,向发送所有的其他客户端发送消息
while(!Thread.interrupted()) {
try {
String line = readLine(inputStream);
//发送向所有的其他客户端发送消息
for(int i=0; i<userList.size(); i++) {
ClientUser user = userList.get(i);
user.sendMsg(nickname+"说"+line);
}
} catch (SocketException e) {
/*
如果某一客户端关闭(断开连接)
打印消息
将其从用户列表中删除
*/
System.out.println("客户端: "+this.nickname+" close");
userList.remove(this);
break;
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*发送消息的函数*/
public void sendMsg(String msg) {
try {
outputStream.write((msg+"n").getBytes("gbk"));
} catch (IOException e) {
e.printStackTrace();
}
}
/*从输入流读取字符串的函数*/
public String readLine(InputStream inputStream) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int c = inputStream.read();
while(c != 'n') {
bos.write(c);
c = inputStream.read();
}
byte[] bytes = bos.toByteArray();
return new String(bytes,"gbk");
}
}
最后
以上就是执着星星为你收集整理的day4 TCP客户端&服务器实现(仿QQ)的全部内容,希望文章能够帮你解决day4 TCP客户端&服务器实现(仿QQ)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复