TCP编程中加入多线程
今天学习的时候了解到,TCP编程中,服务器会可能有多个外来客户端的连接,产生的多个Socket
对象。
对于这种情况,我们希望每当有一个Socket
对象产生,服务器程序就产生一个线程去处理这个Socket
对象
那么服务器端口的main
线程可以当作一个获取Socket
对象的线程,阻塞式产生Socket
对象,一旦获取就创建线程去处理
ServerSocket ss = new ServerSocket(55565);
for(;;) {
Socket s = ss.accept();
Thread t = new MyThread(s);
t.start();
}
class MyThread extends Thread {
@Override
public void run() {
// TODO...
}
}
另外,我们还可以让客户端或者服务器端实现发送数据->接受数据->发送数据的交替模式。
这里使用到了线程间通信模式,每一个Socket
对象都创建两个线程分别处理其返回的InputStream
和OutputStream
,两个线程间通过wait()
和notify()
通信。
比如,客户端
Socket s = new Socket("127.0.0.1", 55565);
new Thread(new CustomerHandleOutputStream(s)).start();
new Thread(new CustomerHandleInputStream(s)).start();
class CustomerHandleOutputStream implements Runnable {
// TODO
}
class CustomerHandleInputStreamimplements Runnable {
// TODO
}
完整代码如下
服务器端
main方法入口
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
/**
* boolean getMessage = 判断是否获取了客户端发来的信息
* 默认为false
*/
private static boolean isGetMessage = false;
/**
* @param isGetMessage=设置getMessage
*/
public static void setGetMessage(boolean isGetMessage) {
Server.isGetMessage = isGetMessage;
}
public static boolean getIsGetMessage() {
return isGetMessage;
}
/**
* 启动服务器
*/
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(55565);
System.out.println("建立服务器完毕");
while(true) {
Socket s = ss.accept();
new Thread(new ServerHandleInputStream(s)).start();
new Thread(new ServerHandleOutputStream(s)).start();
}
}
}
处理服务器端的InputStream
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* 服务器端用来处理输入流的线程任务
*/
public class ServerHandleInputStream implements Runnable {
private Socket socket;
private final BufferedReader reader;
/**
*
* @param socket=服务器端Socket获取的输入流
*/
public ServerHandleInputStream(Socket socket) throws IOException {
this.socket = socket;
this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
/**
* 当服务器没有收到消息的时候,线程进入等待
* 收到消息的时候,线程获取每行内容并打印
*/
@Override
public void run() {
for (int i = 0; i < 5; ++i) {
synchronized (socket) {
while(!Server.getIsGetMessage()) {
try {
socket.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
String text = reader.readLine();
System.out.println("收到来自客户端" + text);
Server.setGetMessage(false); // 读取后,将getMessage设置为false
socket.notifyAll();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
处理服务器端的OutputStream
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
/**
* 服务器处理输出流的类
*/
public class ServerHandleOutputStream implements Runnable {
private final Socket socket;
private final PrintStream ps;
/**
*
* @param socket=服务器端Socket获取的输出流
*/
public ServerHandleOutputStream(Socket socket) throws IOException {
this.socket = socket;
this.ps = new PrintStream(socket.getOutputStream());
}
/**
* 当服务器收到消息的时候,线程进入等待
* 没有收到消息的时候,线程开始发送消息
*/
@Override
public void run() {
for (int i = 0; i < 5; ++i) {
synchronized (socket) {
while(Server.getIsGetMessage()) {
try {
socket.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("向客户端发送信息");
ps.println("你好,客户端!");
Server.setGetMessage(true); // 发送后将getMessage设置为true
socket.notifyAll();
}
}
}
}
客户端
main方法入口
import java.io.*;
import java.net.Socket;
public class Customer {
private static boolean isGetMessage = false;
public static void setIsGetMessage(boolean isGetMessage) {
Customer.isGetMessage = isGetMessage;
}
public static boolean getIsGetMessage() {
return isGetMessage;
}
public static void main(String[] args) throws IOException, InterruptedException {
Socket s = new Socket("127.0.0.1", 55565);
Thread t1 = new Thread(new CustomerHandleOutputStream(s));
Thread t = new Thread(new CustomerHandleInputStream(s));
t.start();
t1.start();
Thread.sleep(10000);
}
}
客户端处理OutputStream
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class CustomerHandleOutputStream implements Runnable {
private final Socket socket;
private final PrintStream ps;
public CustomerHandleOutputStream(Socket socket) throws IOException {
this.socket = socket;
ps = new PrintStream(socket.getOutputStream());
}
@Override
public void run() {
for (int i = 0; i < 5; ++i) {
synchronized (socket) {
while(Customer.getIsGetMessage()){
try {
socket.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("向服务器发送信息!");
ps.println("你好,服务器!");
Customer.setIsGetMessage(true);
socket.notifyAll();
}
}
}
}
处理InputStream
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class CustomerHandleInputStream implements Runnable{
private final Socket socket;
private final BufferedReader reader;
public CustomerHandleInputStream(Socket socket) throws IOException {
this.socket = socket;
this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
@Override
public void run() {
for (int i = 0; i < 5; ++i) {
synchronized (socket){
System.out.println("收到来自服务器:");
while(!Customer.getIsGetMessage()) {
try {
socket.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
String text = reader.readLine();
System.out.println("收到来自服务器:" + text);
Customer.setIsGetMessage(false);
socket.notifyAll();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行结果
突然想到某个注意事项:
对于这里的类似single-ProducerThread和single-ConsumerThread,在判断Thread是否进入WAITING
状态的语句可以使用if(flag)
,但是对于multi-ProducerThread和multi-ConsumerThread来说,必须使用while(flag)
,这是因为当一个producer thread调用notifyAll()
,唤醒其他所有等待该锁的线程时,很有可能会被一个被唤醒的producer thread抢到锁的执行权,那么该抢到锁的producer thread会接着执行wait()
后面的代码执行——继续生产,那么就会出现p->p->p->p->c->c的线程切换的情况,而不是p->c->c->c。