其然IT教育一直秉承“用良心做教育”的理念,致力于打造IT教育全产业链
人才服务平台,公司总部位于北京,目前已在深圳、上海、郑州、广州、大连、武汉、成都、西安、杭州、青岛、重庆、长沙、哈尔滨、南京、太原成
立了分公司,年培养优质人才20000余人,同期在校学员5000余人,合作院校超500所,合作企业超10000家,每年有数十万名学员受益于千锋互联组织
的技术研讨会、技术培训课、网络公开课及免费教学视频。
其然IT历程精彩纷呈,获得荣誉包括:中关村移动互联网产业联盟副理事长
单位、中国软件协会教育培训委员会认证一级培训机构、中关村国际孵化软件协会授权中关村移动互联网学院、教育部教育管理信息中心指定移动互联
网实训基地等。
其然IT教育面授课程包含HTML5大前端培训、JavaEE 分布式开发培训、
Python全栈 人工智能培训、全链路UI/UE设计培训、物联网 嵌入式培训、360网络安全、大数据 人工智能培训、全栈软件测试培训、PHP全栈 服务器
集群培训、云计算 信息安全培训、Unity游戏开发培训、区块链、红帽RHCE认证,采用全程面授高品质、高成本培养模式,教学大纲紧跟企业需求,拥
有全国一体化就业保障服务,成为学员信赖的IT职业教育品牌。
培训Java与自学Java的差距
我以前也是自学Java,在一家公司跟着别人学,以前是别人眼中的菜鸟,现
在是别人眼中的大神,Java很简单的,贵在坚持和多练,没必要花那培训钱。如果真的要去学的话,
选择Java培训机构要注意这两点基本上就能避免一些坑:
1. 老师没有正经公司工作经历,或者没有已经在线上正常运转的产品。一
些所谓培训班的老师水平往往比较一般,甚至还有培训出来后又接着培训别人的。
2、是不是会承诺帮你找到工作,要找到好的工作,不是靠别人给你保证的
,还是要靠自己提升能力。
建议多自己学习加上找些好的代码主动学习。例如github,多练习网上很多
网站里真正好的代码。作为Java工程师,可以多看看spring文档,看看很多已经成熟的框架,深入去体会。另外,学软件等等**好还是自己多学,找点
视频教程之类,也省点钱。
.数据库关键技术
-
01Mysql 基础
-
1.Mysql的安装和使用
-
2.图解Mysql程序结构
-
3.Mysql服务器的配置
-
4.Mysql 客户端使用
-
5.用户权限管理
-
6.Mysql数据库的使用
-
02SQL基础
-
1.SQL语句的三种类型
-
2.DML、DDL、DCL的应用
-
3.数据处理
-
4.子查询
-
5.创建和管理表
-
6.约束与分页
-
03JDBC
-
1.JDBC概述
-
2.获取数据库连接
-
3.数据库连接池C3P0 与 DBCP
-
4.使用JDBC 完成数据库DML操作
-
5.大数据的操作
-
6.批量处理与元数据
-
04DBUtils
-
1.使用QueryRunner
-
2.可插拔式结果集处理
-
3.批量处理
-
4.大数据结果集处理
-
5.自定义结果集处理
-
6.利用DBUtils编写通用 DAO
详解基于java的Socket聊天程序——客户端(附demo)
>这篇文章主要介绍了详解基于java的Socket聊天程序——客户端(附demo),客户端设计主要分成两个部分,分别是socket通讯模块设计和UI相关设计。有兴趣的可以了解一下。
写在前面:
上周末抽点时间把自己写的一个简单Socket聊天程序的初始设计和服务端细化设计记录了一下,周二终于等来毕业前考的软考证书,然后接下来就是在加班的日子度过了,今天正好周五,打算把客户端的详细设计和Common模块记录一下,因为这个周末开始就要去忙其他东西了。
设计:
客户端设计主要分成两个部分,分别是socket通讯模块设计和UI相关设计。
客户端socket通讯设计:
这里的设计其实跟服务端的设计差不多,不同的是服务端是接收心跳包,而客户端是发送心跳包,由于客户端只与一个服务端进行通讯(客户端之间的通讯也是由服务端进行分发的),所以这里只使用了一个大小为2的线程池去处理这两件事(newFixedThreadPool(2)),对应的处理类分别是ReceiveListener、KeepAliveDog,其中ReceiveListener在初始化的时候传入一个Callback作为客户端收到服务端的消息的回调,Callback的默认实现是DefaultCallback,DefaultCallback根据不同的事件**HF分发给不同Handler去处理,而ClientHolder则是存储当前客户端信息,设计如下:
Socket通讯模块具体实现:
[Client.java]
Client是客户端连接服务端的入口,创建Client需要指定一个Callback作为客户端接收服务端消息时的回调,然后由Client的start()方法启动对服务端的监听(ReceiveListener),当ReceiveListener接收到服务端发来的数据时,调用回调(Callback)的doWork()方法去处理;同时Client中还需要发送心跳包来通知服务端自己还在连接着服务端,发心跳包由Client中keepAlive()启动,由KeepAliveDog实现;这两个步骤由一个固定大小为2为线程池newFixedThreadPool(2)去执行,可能这里使用一个newFixedThreadPool(1)和newScheduledThreadPool(1)去处理更合理,因为心跳包是定时发的,服务端就是这样实现的(这个后续调整),Client的具体代码如下(这里暴露了另外两个方法用于获取socket和当前socket所属的用户):
?
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
|
/** * 客户端 * @author yaolin * */ public class Client { PRivate final Socket socket; private String from; private final ExecutorService pool; private final Callback callback; public Client(Callback callback) throws IOException { this .socket = new Socket(ConstantValue.SERVER_ip, ConstantValue.SERVER_PORT); this .pool = Executors.newFixedThreadPool( 2 ); this .callback = callback; } public void start() { pool.execute( new ReceiveListener(socket, callback)); } public void keepAlive(String from) { this .from = from; pool.execute( new KeepAliveDog(socket, from)); } public Socket getSocket() { return socket; } public String getFrom() { return from; } }
|
[KeepAliveDog.java]
客户端在与服务端建立连接之后(该程序中是指登陆成功之后,因为登陆成功之后客户端的socket才会被服务端的SocketHolder管理),需要每个一段时间就给服务端发送心跳包告诉服务端自己还在跟服务端保持联系,不然服务端会在一段时间之后将没有交互的socket丢弃(详见服务端那篇博客),KeepAliveDog的代码实现如下(后期可能会调整为newScheduledThreadPool(1),所以这里的代码也会调整):
?
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
|
/** * KeepAliveDog : tell Server this client is running; * * @author yaolin */ public class KeepAliveDog implements Runnable { private final Socket socket; private final String from; public KeepAliveDog(Socket socket, String from) { this .socket = socket; this .from = from; } @Override public void run() { while (socket != null && !socket.isClosed()) { try { PrintWriter out = new PrintWriter(socket.getOutputStream()); AliveMessage message = new AliveMessage(); message.setFrom(from); out.println(JSON.toJSON(message)); out.flush(); Thread.sleep(ConstantValue.KEEP_ALIVE_PERIOD * 1000 ); } catch (Exception e) { LoggerUtil.error( "Client send message failed !" e.getMessage(), e); } } } }
|
[ReceiveListener.java]
Client的start()方法启动对服务端的监听由ReceiveListener实现,ReceiveListener接收到服务端的消息之后会回调Callback的doWork()方法,让回调去处理具体的业务逻辑,所以ReceiveListener只负责监听服务端的消息,具体的处理由Callback负责,这里需要提一下的是当消息类型是文件类型的时候会睡眠配置执行的间隔时间,这样Callback中的doWork才能对读取来至服务端的文件流,而不是直接进入下一次循环,这里的设计跟服务端是类似的。ReceiveListener的具体实现代码如下:
?
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
|
public class ReceiveListener implements Runnable { private final Socket socket; private final Callback callback; public ReceiveListener(Socket socket, Callback callback) { this .socket = socket; this .callback = callback; } @Override public void run() { if (socket != null ) { while (!socket.isClosed()) { try { InputStream is = socket.getInputStream(); String line = null ; StringBuffer sb = null ; if (is.available() > 0 ) { BufferedReader bufr = new BufferedReader( new InputStreamReader(is)); sb = new StringBuffer(); while (is.available() > 0 && (line = bufr.readLine()) != null ) { sb.append(line); } LoggerUtil.trach( "RECEIVE [" sb.toString() "] AT " new Date()); callback.doWork(socket, sb.toString()); BaseMessage message = JSON.parSEObject(sb.toString(), BaseMessage. class ); if (message.getType() == MessageType.FILE) { LoggerUtil.trach( "CLIENT:PAUSE TO RECEIVE FILE" ); Thread.sleep(ConstantValue.MESSAGE_PERIOD); } } else { Thread.sleep(ConstantValue.MESSAGE_PERIOD); } } catch (Exception e) { LoggerUtil.error( "Client send message failed !" e.getMessage(), e); } } } } }
|
[Callback.java、DefaultCallback.java]
从上面可以看出Client对消息的处理是Callback回调,其Callback只是一个接口,所有Callback实现该接口根据自己的需要对消息进行相应地处理,这里Callback默认的实现是DefaultCallback,DefaultCallback只对三种消息进行处理,分别是聊天消息、文件消息、返回消息。对于聊天消息,DefaultCallback将**UI中的Router路由获取到相应的界面(详见下面的UI设计),然后将消息展现在对应的聊天框中;对于文件消息,DefaultCallback则是将文件写入到配置中指定的路径中(这里没有**用户的允许就接收文件,这种设计不是很友好,目前先这样);对于返回消息,DefaultCallback会根据返回消息中的KEY叫给不同的Handler去处理。具体代码如下:
?
1
2
3
|
public interface Callback { public void doWork(Socket server, Object data); }
|
?
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
|
public class DefaultCallback implements Callback { @Override public void doWork(Socket server, Object data) { if (data != null ) { BaseMessage message = JSON.parseObject(data.toString(), BaseMessage. class ); switch (message.getType()) { case MessageType.CHAT: handleChatMessage(data); break ; case MessageType.FILE: handleFileMessage(server, data); break ; case MessageType.RETURN: handleReturnMessage(data); break ; } } } private void handleChatMessage(Object data) { ChatMessage m = JSON.parseObject(data.toString(), ChatMessage. class ); String tabKey = m.getFrom(); JComponent comp = Router.getView(ChatRoomView. class ).getComponent(ChatRoomView.CHATTABBED); if (comp instanceof JTabbedPane) { JTabbedPane tab = (JTabbedPane) comp; int index = tab.indexOfTab(tabKey); if (index == - 1 ) { tab.addTab(tabKey, ResultHolder.get(tabKey).getScrollPane()); } JTextArea textArea = ResultHolder.get(tabKey).getTextArea(); textArea.setText( new StringBuffer() .append(textArea.getText()).append(System.lineSeparator()).append(System.lineSeparator()) .append( " [" ).append(m.getOwner()).append( "] : " ).append(System.lineSeparator()) .append(m.getContent()) .toString()); textArea.setCaretPosition(textArea.getText().length()); } } private void handleFileMessage(Socket server, Object data) { FileMessage message = JSON.parseObject(data.toString(), FileMessage. class ); if (message.getSize() > 0 ) { OutputStream os = null ; try { if (server != null ) { InputStream is = server.getInputStream(); File dir = new File(ConstantValue.CLIENT_RECEIVE_DIR); if (!dir.exists()) { dir.mkdirs(); } os = new FileOutputStream( new File(PathUtil.combination(ConstantValue.CLIENT_RECEIVE_DIR, new Date().getTime() message.getName()))); int total = 0 ; while (!server.isClosed()) { if (is.available() > 0 ) { byte [] buff = new byte [ConstantValue.BUFF_SIZE]; int len = - 1 ; while (is.available() > 0 && (len = is.read(buff)) != - 1 ) { os.write(buff, 0 , len); total = len; LoggerUtil.debug( "RECEIVE BUFF [" len "]" ); } os.flush(); if (total >= message.getSize()) { LoggerUtil.info( "RECEIVE BUFF [OK]" ); break ; } } } } } catch (Exception e) { LoggerUtil.error( "Receive file failed ! " e.getMessage(), e); } finally { if (os != null ) { try { os.close(); } catch (Exception ignore) { } os = null ; } } } } private void handleReturnMessage(Object data) { ReturnMessage m = JSON.parseObject(data.toString(), ReturnMessage. class ); if (StringUtil.isNotEmpty(m.getKey())) { switch (m.getKey()) { case Key.TIFY: HF.getHandler(Key.TIFY).handle(data); break ; case Key.LOGIN: HF.getHandler(Key.LOGIN).handle(data); break ; case Key.REGISTER: HF.getHandler(Key.REGISTER).handle(data); break ; case Key.LISTUSER: HF.getHandler(Key.LISTUSER).handle(data); break ; case Key.TIP: HF.getHandler(Key.TIP).handle(data); break ; } } } }
|
[Handler.java、HF.java、ListUserHdl.java...]
Handler组件负责对服务端返回消息类型的消息进行处理,DefaultCallback根据不同的KEY将消息分发给不同的Handler进行处理,这里也算一套简单的工厂组件吧,跟服务端处理接收到的数据设计是类似的,完整的类图如下:
下面给出这一块的代码,为了缩小篇幅,将所有Handler实现的代码收起来。
?
1
2
3
|
public interface Handler { public Object handle(Object obj); }
|
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class HF { public static Handler getHandler(String key) { switch (key) { case Key.TIFY: return new NotifyHdl(); case Key.LOGIN: return new LoginHdl(); case Key.REGISTER: return new RegisterHdl(); case Key.LISTUSER: return new ListUserHdl(); case Key.TIP: return new TipHdl(); } return null ; } }
|
?
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
|
public class ListUserHdl implements Handler { @Override public Object handle(Object obj) { if (obj != null ) { try { ReturnMessage rm = JSON.parseObject(obj.toString(), ReturnMessage. class ); if (rm.isSuccess() && rm.getContent() != null ) { ClientListUserDTO dto = JSON.parseObject(rm.getContent().toString(), ClientListUserDTO. class ); JComponent comp = Router.getView(ChatRoomView. class ).getComponent(ChatRoomView.LISTUSRLIST); if (comp instanceof JList) { @SuppressWarnings ( "unchecked" ) JList<String> listUsrList = (JList<String>) comp; List<String> listUser = new LinkedList<String>(); listUser.addAll(dto.getListUser()); Collections.sort(listUser); listUser.add( 0 , ConstantValue.TO_ALL); listUsrList.setListData(listUser.toArray( new String[]{})); } } } catch (Exception e) { LoggerUtil.error( "Handle listUsr failed! " e.getMessage(), e); } } return null ; } }
|
?
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
|
public class LoginHdl implements Handler { @Override public Object handle(Object obj) { if (obj != null
张家港Java编程培训收费
课程价格:¥详询
市场价:¥详询
有问题请留言
Copyright © 2006-2018 kaoshi.china.com
|