为什么要用多线程和线程池
当我们用scoket建立通信的时候,往往不是简单的传输数据。每一个数据包之后会有一定的逻辑处理,然后再向客户端返回相应的数据。而处理业务逻辑的时候有另外的客户端发送数据就只能等待。
多线程 就是将每一个流分开处理,接收一个包,开启一个线程。当开启太多的时候就会引发服务器崩溃,所以用了线程池,有固定的线程数量,无用的线程会继续等待下一个连接。但是如果连接数量真的超过了线程池的最大连接量,还是会造成线程阻塞。在这里我用了java自带的线程池
一、 建立一个简单的服务端
package com.websocket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.asiainfo.service.UserService;
@Service
public class ScoketServer {
// 服务器IP
public static final String SERVER_IP = "127.0.0.1";
// 服务器端口号
public static final int SERVER_PORT = 10005;
// 请求终结字符串
public static final char REQUEST_END_CHAR = '#';
//
@Autowired
private UserService userService;
/***
* 启动服务器
*
* @param 服务器监听的端口号
* ,服务器ip无需指定,系统自动分配
*/
public void startServer(String serverIP, int serverPort) {
// 创建服务器地址对象
InetAddress serverAddr;
try {
serverAddr = InetAddress.getByName(serverIP);
} catch (UnknownHostException e1) {
e1.printStackTrace();
return;
}
System.out.println("scoket服务器开始运行");
// Java提供了ServerSocket作为服务器
// 这里使用了Java的自动关闭的语法,很好用
try (ServerSocket serverSocket = new ServerSocket(SERVER_PORT, 5,
serverAddr)) {
ExecutorService executor = Executors.newFixedThreadPool(100);
// serverSocket.setSoTimeout(timeout);
while (true) {
System.out.println("scoket服务器等待连接");
try {
// 有客户端向服务器发起tcp连接时,accept会返回一个Socket
// 该Socket的対端就是客户端的Socket
// 具体过程可以查看TCP三次握手过程
final Socket connection = serverSocket.accept();
System.err.println("scoket处理客户端所发信息");
//利用线程池,启动线程
final StringBuilder recvStrBuilder = new StringBuilder();
executor.execute(new Runnable() {
@Override
public void run() {
//使用局部引用,防止connection被回收
Socket conn = connection;
String result=null;
try {
InputStream in = conn.getInputStream();
int s;
//读取客户端的请求包包头,包头固定长度32
for (int i=0; (s=in.read())!=-1&&i<32; i++) {
recvStrBuilder.append((char)s);
}
System.out.println("接收请求");
String recvHead = recvStrBuilder.toString();
try {
Thread.sleep(9000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("处理完成1");
if(recvHead.length()<32){
//请求包包头长度不足
result="error:请求包包头数据错误";
}else{
result="完成";
}
//向客户端写出处理后的字符串
OutputStream out = conn.getOutputStream();
out.write(result.getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
二、将该类添加进监听类中
springMVC项目一般是用tomcat启动,所以当项目启动的时候我们应该让scoket客户端也启动
新建监听类
package com.listener;
import javax.servlet.ServletContext;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.ServletContextAware;
import com.asiainfo.util.PropertyPlaceholder;
import com.asiainfo.websocket.ScoketServer;
public class ServerSocketListener implements InitializingBean,
ServletContextAware {
// log־
private static Logger logger = Logger.getLogger(ServerSocketListener.class
.getName());
// 服务器IP
public static final String SERVER_IP = PropertyPlaceholder.getProperty("server_ip");
// 服务器端口号
public static final int SERVER_PORT = Integer.valueOf(PropertyPlaceholder.getProperty("server_port"));
@Autowired
private ScoketServer toUpperTCPNonBlockServer;
@Override
public void setServletContext(ServletContext sce) {
new Thread(new Runnable() {
@Override
public void run() {
System.err.println("scoket服务端");
toUpperTCPNonBlockServer.startServer(SERVER_IP, SERVER_PORT);
}
}).start();
}
@Override
public void afterPropertiesSet() throws Exception {
}
}
记得要把这个监听类添加近springMVC的配置文件中,启动项目时启动该类
<!-- 开机启动 -->
<bean class="com.listener.ServerSocketListener"></bean>
到这里时候一个简单的scoket服务端就搭建完成了
三、测试
写一个客户端测试一下
package com.copy;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
public static void main(String[] args) {
try {
Client.socketTest("22222222222222222222222222222222222222");
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void socketTest(String str) throws UnknownHostException,
IOException {
// 1.创建Socket对象,和服务端建立连接
Socket socket = new Socket("127.0.0.1", 10005);
//发送消息
PrintWriter output = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"),true);
output.println(str);
//接收服务端传回的消息
InputStreamReader reader = new InputStreamReader(socket.getInputStream(),"UTF-8");
BufferedReader input = new BufferedReader(reader);
//读取
System.out.println(input.readLine());
//关闭流
output.close();
reader.close();
}
}
先启动服务端,再启动客户端,如果两边都正常打印的话,就已经成功了