1.多线程服务器端原理分析
- 一般情况下同一时刻服务器都不止和一个客户端进行通信,如果服务器只有一个线程,那么在多任务操作时任务之间就需要等待,因此需要为服务器创建多条线程提供给不同的同时客户端使用。
- 为服务器创建多线程,就类似于为服务器创建"影分身",让每一条线程都能享有一个独立的"影分身"服务器。
- 为了满足上述要求,每一条线程都必须传入一个新创建的Runnable接口实现类,实现类中的run()方法运行服务器的主程序。
2.客户端代码
客户端主要功能:
- 客户端向服务器发送信息。
- 客户端接收服务器的反馈信息。
代码如下
/*实现TCP通信的客户端程序
实现步骤:
1.创建Socket对象 (主动连接服务器)
Socket(String host, int port) host为服务器的地址(此处服务器也在本机所以使用环回地址),port为服务器中应用的端口
2.OutputStream getOutputStream()
返回套接字中的字节输出流,此时就可以使用write()方法写入数据,写入服务器
3.InputStream getInputStream()
返回套接字中的字节输入流对象,调用read()方法可以读取服务器发来的数据
4.释放资源 close()
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket client = new Socket("127.0.0.1",9000);
OutputStream out = client.getOutputStream();
byte[] bytes = new byte[1024];
out.write("请求连接服务器".getBytes()); //没有写在文件中,写入了服务器(发送给了服务器)
InputStream in = client.getInputStream();
byte[] inBytes = new byte[1024];
int inLen = in.read(inBytes);
String s = new String(inBytes, 0, inLen);
System.out.println(s);
client.close();
}
}
3.服务器代码
服务器主要功能
- 服务器需要接收客户端发来的消息。
- 接收到消息后,服务器需要向客户端发送反馈信息。
由于需要为服务器创建多线程,因此服务器实现的程序需要放在Runnable实现类的run()方法中
代码如下
/*实现TCP通信中的服务器程序
实现TCP服务器步骤:
1.创建ServerSocket对象
ServerSocket(in port) 端口号
2.等待客户端的连接,如果没有客户端连接,永远等待
ServerSocket类方法 accept() (等待客户端的连接)
accept() 方法的返回值为Socket对象(客户端套接字,包含客户端的IP地址,用于回复信息)
3.Socket对象中获取字节输入流
InputStream getInputStream()
对象调用read()方法,读取客户端发来的数据
4.Socket对象中获取字节输出流
OutputStream getOutputStream()
对象调用write()方法,向客户端写入(回复)数据
5.释放资源 close()
*/
public class TCPThreadServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(9000);
//循环放在此处是为了,当每次客户端与服务器通信完成时,服务器不停止运行,而是又再一次进入侦听状态,侦听是否还有服务器向自己发送信息;
while (true) {
Socket accept = server.accept();
Thread thread = new Thread(new ThreadServer(accept));
thread.start();
}
}
}
public class ThreadServer implements Runnable{
private Socket accept;
//测试类中传入服务器的侦听accept()侦听到的客户端对象
public ThreadServer(Socket accept){
this.accept = accept;
}
@Override
public void run() {
try {
Server.ServerMethod(accept);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Server {
public static void ServerMethod(Socket accept) throws IOException {
InputStream in = accept.getInputStream();
byte[] bytes = new byte[1024];
int len = in.read(bytes);
String s = new String(bytes, 0, len);
System.out.println(s + Thread.currentThread().getName());
OutputStream out = accept.getOutputStream();
out.write("连接成功!".getBytes());
accept.close();
}
}
测试使用了6个客户端与服务器进行通信,使用这种方式模拟多线程服务器的通信,服务器端结果如下:
请求连接服务器Thread-0
请求连接服务器Thread-1
请求连接服务器Thread-2
请求连接服务器Thread-3
请求连接服务器Thread-4
请求连接服务器Thread-5
4.分析结果
对上述结果进行分析,发现被使用过的线程任务执行完毕后就死亡了,不能被再次使用,下一个客户端对服务器发起连接时将使用新new出来的Thread,旧的线程资源其实已经使用完了但是无法再次调用start()使用,这要会导致资源浪费;因此我对代码进行了一些修改,同时开启多路线程,使得每个线程内部的run()方法套上while(true)循环使得线程永不终止,将服务器的侦听器放到线程的内部,测试类只需向线程代码传递服务器的连接(套接字)对象即可;当客户端向服务器发送数据时选择进入一个线程,完成线程任务即可。
5.修改版本
代码如下
代码只修改服务器端多线程入口部分,客户端和服务器端主题代码与上述一致,此处不重复书写
public class TCPThreadServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(9000);
//创建多条线程,每条线程都会独立的侦听客户端发送来的消息;
Thread thread0 = new Thread(new ThreadServer(server));
Thread thread1 = new Thread(new ThreadServer(server));
Thread thread2 = new Thread(new ThreadServer(server));
thread0.start();
thread1.start();
thread2.start();
}
}
public class ThreadServer implements Runnable{
private ServerSocket server;
public ThreadServer(ServerSocket server){
this.server = server;
}
@Override
public void run() {
while (true){
try {
//将侦听方法写在了run()方法内,这样每条线程都可以侦听+执行服务器命令,并且都可以反复侦听,而不是像上面的方法,侦听到客户端消息后再创建新线程;
Socket accept = server.accept();
Server.ServerMethod(accept);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这样线程可以被复用,不像上面的方法每条线程只能执行一次通信任务;但是与此同时也出现了问题,如果现在没有客户端向服务器发送请求,但是三个线程还是同时开启着并且不断侦听,十分浪费,如何做到多线程时线程可以复用,同时又能做到无任务时线程可以关闭,侦听到任务时线程才开启。此时就需要使用线程池,当需要使用线程时,向线程池获取线程,此线程就被激活,试用结束后归还线程(线程不会死亡),线程进入空闲状态;这样使用后的线程可以被复用,同时又不会在无任务状态下使多个线程持续运行,解决了上述两种服务器端多线程通信的两个矛盾点;
6.采用线程池的多线程服务器端
代码如下
省略上面已有的客户端和服务器端主程序
这展示被修改部分的代码
public class TCPThreadServerDemo {
public static void main(String[] args) throws IOException {
//创建线程池对象,线程池线程容量为3
ExecutorService service = Executors.newFixedThreadPool(3);
ServerSocket server = new ServerSocket(9000);
while (true) {
Socket accept = server.accept();
// Thread thread = new Thread(new ThreadServer(accept));
//提交线程任务,从线程池中获取线程
service.submit(new ThreadServer(accept));
//System.out.println(service);
}
}
}
public class ThreadServer implements Runnable{
private Socket accept;
public ThreadServer(Socket accept){
this.accept = accept;
}
@Override
public void run() {
try {
Server.ServerMethod(accept);
} catch (IOException e) {
e.printStackTrace();
}
}
}
测试使用了7个客户端与服务器进行通信,服务器端结果如下:
请求连接服务器pool-1-thread-1
请求连接服务器pool-1-thread-2
请求连接服务器pool-1-thread-3
请求连接服务器pool-1-thread-1
请求连接服务器pool-1-thread-2
请求连接服务器pool-1-thread-3
请求连接服务器pool-1-thread-1
上述是初学者的个人想法,如果有任何问题或不同观点,或者我写的有错,都可以提出,大家一起讨论,不过请轻点喷 ^ - ^ !