我是靠谱客的博主 执着星星,这篇文章主要介绍day4 TCP客户端&服务器实现(仿QQ),现在分享给大家,希望可以做个参考。

​ 这是基于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。这样做也不是不想,但不够秀。

我们可以这样做:

  1. 我们可以通过创建interface接口RecvInterface,里面只有一个处理数据的空函数 public void handleData(String msg);
  2. 然后让Client继承RrcvInterface,改写 handleData函数。
  3. 之后只在Connect里创建一个RecvInterface对象recvInterface,并将client传给connect里的recvInterface

​ 这样当我们处理接收信息时,只需要在connect里 recvInterface.handleData(line);即可。

客户端界面

结果:

样式

大框显示接收到的数据

小框显示要发送的数据

(一) 构建接口RecvInterface

复制代码
1
2
3
4
5
/*接口很简单,只有一个处理数据的函数就行*/ public interface RecvInterface { public void handleData(String msg); }

(二) 界面Client

就是一个简单的UI设计,在初始化完成后,将该界面this作为一个 接口 传入到通信模块

复制代码
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
/*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

复制代码
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
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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

复制代码
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
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内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(53)

评论列表共有 0 条评论

立即
投稿
返回
顶部