Spring集成TCP server
C++与JAVA的不同
首先我说一说我对spring的感想,大家可能不知道我一直是做C++开发的,首先spring对我来说最让我不习惯的是它管理了你的单例对象,一般来讲我之前写完整端口或者是其他涉及到多线程编程的时候我是紧张的一B处处写的非常小心,因为很容易出问题,特别是服务端程序。很多开发者刚开始写C++程序的时候,如果程序涉及到的功能稍微复杂一点那么很容易就崩溃原因有几点:野指针、空指针、数组越界、内存溢出、线程安全。这些问题在C/C++里面是要命的事,原因在于语言编译的机制是非常底层的,没有一个上层的东西来给你管理内存那么只能自己来搞。一旦出现上面几种我所说的情况那么对不起,系统会强制杀掉你的程序。这就意味着只要出现这些问题你的程序就直接崩溃了。但是在JAVA里面比较NB的第一个事情就是内存管理,JAVA的底层有一个虚拟机,你写的java代码不是真正意义上面的编译语言,而是一个供虚拟机执行的脚本,这个理解是没有什么错误的应该,这就是为什么他可以实现对像与json的相互转换和RPC通信。这样的好处在于全局底层有一个东西来处理你的野指针、空指针等等东西了。他不会让你真正的去直接访问内存而是让你对告诉它你要干什么然后底层去操作内存,如果一旦内存地址不可用虚拟机便直接告诉你不能用就行了但是这样你的程序还在自己运行没有什么影响无非就是业务失败而已。Spring我为什么不习惯呢?因为我们在处理一些具体功能的时候很多情况会有全局的一个声明或者是全局唯一的一个实例化的对象,这个东西我们一般就叫做单例。我们在处理单例东西的时候一般情况还好一旦是多线程使用单例那么上来就必须保证线程的安全。对应我就需要对这个对象的调用实现线程互斥锁或者临界区。换句话说这是非常重要的一件事情,需要你自己来处理。但是spring里面的所有组件都是单例,任何你打了标注注入到spring之中的对象都被全局初始化为一个组件然后在你的业务里面处理。一般来讲一个java开发的后端都是在底层使用servlet那套东西那么它本身就是多线程实现的你每在控制器之中使用一个spring注入的组件便意味着你在跨线程使用一个全局单例!!!这就是我最不爽的地方了,举个例子意思就是仅仅因为你打了一个@Component,他就全局单例了而且不用管什么对象生命周期什么的。一开始我肯定是不适应的但是现在我的结论是:真香!!!
JAVA的TCP服务端带线程池
上面说一堆废话接下来就对TCP通信线程池进行一个简单的讲解。因为我现在正在做的物联网项目里面有一个模块使用了这个东西所以来出来分享一下。直接上代码我在慢慢讲。
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
@Component
public class TcpSocket implements Runnable{
private Integer Port;
private ServerSocket Server;
private ExecutorService ThreadPool;
public Vector<Socket> AllClients;
public TcpSocket()
{
try {
AllClients = new Vector<>();
AllClients.clear();
Port = 9588;
ThreadPool = Executors.newCachedThreadPool();
Server = new ServerSocket(Port);
}
catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}
}
@Override
public void run() {
while(true) {
try {
Socket socket = Server.accept();
if(socket!=null)
{
SocketReceive socketReceive = new SocketReceive(socket, AllClients);
ThreadPool.submit(socketReceive);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这里其实非常简单,这是一个继承JAVA的Runnable的类,它既然是runnable那么他就可以丢给一个线程去执行,因为我在run之中实现对一个TCPserver的监听,这就意味着线程的阻塞那么它就必须运行在一个独立的线程不能影响到主线程。代码非常的好懂意思就是监听着如果有链接来了就创建另一个对象然后将他扔给线程池。我们这个类是打了组件标注的意味着他是一个spring组件,即他是一个全局的单例!而这个新创建的对象就是SocketReceive这个我下面会具体的讲解。
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
public class SocketReceive implements Runnable {
private Socket socket;
private Vector<Socket> allclients;
public SocketReceive() {
}
public SocketReceive(Socket socket, Vector<Socket> clients) {
this.socket = socket;
allclients = clients;
}
@Override
public void run() {
while ( true ){
if ( null == socket ) {
if(allclients.contains(socket)){
allclients.remove(socket);
}
return ;
}
boolean isClosed = socket.isClosed();
if (isClosed) {
if(allclients.contains(socket))
{
allclients.remove(socket);
}
return;
}
try {
DataInputStream in = new DataInputStream(socket.getInputStream());
byte[] buffer = new byte[1024*4];
if(in.read(buffer)==-1) {
if(allclients.contains(socket))
{
allclients.remove(socket);
}
return;
}
DataAnalysis dataanalysis = SpringUtil.getBean(DataAnalysis.class);
dataanalysis.Analysis(buffer,socket);
} catch (IOException e) {
System.out.println(e);
if(allclients.contains(socket)){
allclients.remove(socket);
}
try {
socket.close();
return;
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
return;
} catch (ServerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClientException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
这个就是我们的那个socket的接收类,当然了他不可能是spring的组件,毕竟他会实例化多个对象出来这个是我们的消息接收部分,也很简单它实现对一个接受的socket进行处理阻塞监听消息如果有消息就给到spring的一个服务里面去处理,这样一来在这个服务之中我就可以继续使用。因为我们的消息接收对象是在线程池里面去运行的,他又不能注入到spring那想要使用spring的功能还需要进行一定的处理那就是在非spring组件之中获取到spring的单例对象。这个就是一句代码的事情而已:
1
DataAnalysis dataanalysis = SpringUtil.getBean(DataAnalysis.class);
这里的SpringUtil类提供了一个静态的函数来获取对应的spring组件,当我获得这个组件之后我就可以将对应的消息与socket对象给到这个单例之中去执行这样意味着我可以使用spring的单例服务了上层我定义的所有东西都在这个单例里面去完成具体的获取方法代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
}
这样一来我们就可以进行对消息的处理,同时也可以完成对spring的调用,这里的只有接受socket的类我们使用的是标准java类其他所有的东西都是spring的组件是不是很爽?最后一件事情就是全局去启动这个监听线程,毕竟监听线程的了之后才能进行对对线程池的创建。这里可能大家都发现了一个问题,既然监听线程的对象是一个spring的组件意味着这个对象是由spring去生成然后由spring管理,意味着这个东西在我们的项目启动运行的时候就已经初始化完毕了,那么怎么对他开独立线程呢?其实也很简单,答案就是在spring的所有组件加载完毕之后创建一个线程将这个对象给进去就可以了是吧?代码如下:
1
2
3
4
5
6
7
8
9
10
11
@Component
public class SpringFinishedListener implements InitializingBean {
@Autowired
private TcpSocket tcpsocket;
@Override
public void afterPropertiesSet() {
Thread serverthread = new Thread(tcpsocket);
serverthread.start();
}
}
这样一来我们逻辑就非常明显了,项目运行初始化我们的TcpSocket对象然后通过继承spring的initalizingbean类重写一下加载完毕组件的监听去创建一个线程执行server socket的监听,然后一旦server接受了一个连接请求之后创建一个新的对象将其扔给线程池执行,在线程池之中这个对象可以通过我们继承于ApplicationContextAware 类的重写去得到对应的spring组件然后由这个组件去完成我们对应的业务。好处不用我说了,一旦可以使用spring那么就和常规开发没有什么区别了。但是最爽的是线程池之中的逻辑无论发生什么样的错误或者异常都不会影响到线程池本身,最多就是这个死掉了,但是一旦它死掉了之后这个线程就自动的退出了这个时候我们线程池依旧是照常运行没有丝毫影响。目前我经过了一个简单的稳定性测试还没有发现什么问题。
总结
和往常一样,我的总结一般都是非常简单的,这次的总结也一样就只有几个字:JAVA是真的耐操!真香!!!!