读不在三更五鼓,功只怕一曝十寒。—郭沫若
意思是说,学习不在某一时的努力,而要锲而不舍的坚持才能成功!
这句话告诉我们,学习是一个循序渐进的积累过程,急于求成是不可取的,而想一劳永逸,想到的时候就用功一时,想不到的时候就疏于学业,这样也只能算是学无所成的。
上一次我们实现了一个服务端和客户端同时读写的程序,但是服务端在处理完一个客户端的请求之后就结束了,而不能继续接收其他客户端的请求,这种方式还不能满足我们实际开发中的需要。今天我们来实现一个更贴近实际情况的程序:
1、需求:一个服务端异步处理多个客户端的请求
核心点:服务端通过accept方法监听客户端请求,当接收到某个客户端请求时,开启一个新的线程来处理请求,然后继续回到监听状态。这样就实现了异步处理客户端请求的程序。
2、代码实现:
1)服务端代码:
class="java" name="code">
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务端
* 需求:多个客户端连接同一个服务端
* @author Sam
*
*/
public class MultipleServer {
/** 编码 */
public static final String ENCODING = "UTF-8";
/**
* @param args
*/
public static void main(String[] args) {
try {
// 创建一个服务端ServerSocket,监听指定端口的请求
ServerSocket ss = new ServerSocket(10000);
System.out.println("Server 等待客户端接入...");
while (true) {
// 一直循环监听客户端请求
Socket socket = ss.accept();
// 开启一个新线程处理Socket请求
new Thread(new AsynSocketTask(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 用于处理Socket请求的任务类
*
*/
static class AsynSocketTask implements Runnable {
private Socket socket;
private BufferedReader buffReader;
private BufferedWriter buffWriter;
public AsynSocketTask(Socket socket) {
try {
this.socket = socket;
// 获取Socket中的输入输出流,并使用带缓冲区的字符流
buffReader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), ENCODING));
buffWriter = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream(), ENCODING));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
handleSocketRequest();
}
/**
* 处理客户端请求
*/
private void handleSocketRequest() {
try {
//////////////// 读操作 //////////////////
// 获取客户端数据
String line = null;
StringBuilder sBuilder = new StringBuilder();
// 一行一行的读
while ( (line=buffReader.readLine()) != null ) {
if (line.indexOf("eof") != -1) {// 读到结束标记,则跳出循环
break;
}
sBuilder.append(line);
}
System.out.println("Server 来自客户端的数据:"+ sBuilder.toString());
//////////////// 写操作 //////////////////
// 读完之后,往客户端写一句
buffWriter.write("Hello Client!");
buffWriter.newLine();// 写一个换行符
buffWriter.write("eof");// 写一个结束标记符
buffWriter.newLine();
buffWriter.close();// 关闭该流的同时,也会释放与之关联的所有资源
buffReader.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务端代码分析:
1、在主程序中,我们使用了一个while(true)死循环,监听客户端请求,当监听到有请求过来,就开启一个新线程来处理,然后主程序回到原来的监听状态。实际开发中也是这种情况的,服务端应该一直处于开启状态,可以随时响应客户端请求。
2、为了避免出现乱码,服务端和客户端读写数据统一使用UTF-8编码格式。
3、为了提高读写效率,程序中我们使用了带缓存区的字符输入输出流,一行一行的读写数据;其中readLine方法是阻塞式操作的,只有读到一个换行、回车或换行回车符时才会执行往后的操作,否则一直处于阻塞状态。而newLine方法是往流中写一个换行符。
2)客户端代码:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
/**
* 客户端
* @author Sam
*
*/
public class MultipleClient {
/** 编码 */
public static final String ENCODING = "UTF-8";
/**
* @param args
*/
public static void main(String[] args) {
try {
// 与服务端建立连接(服务端主机号,服务端监听的端口号)
Socket socket = new Socket("127.0.0.1" , 10000);
// 获取Socket中的输入输出流,并使用带缓冲区的字符流
BufferedWriter buffWriter = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream(), ENCODING));
BufferedReader buffReader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), ENCODING));
/////////////// 写操作 ///////////////
// 往服务端写数据
buffWriter.write("Hello Server!");
buffWriter.newLine();// 写一个换行符
buffWriter.write("eof");// 写一个结束标记符
buffWriter.newLine();
buffWriter.flush();
/////////////// 读操作 ///////////////
// 写完之后,读取服务端返回的数据
socket.setSoTimeout(10*1000);// 设置超时时长10秒
String line = null;
StringBuilder sBuilder = new StringBuilder();
// 一行一行的读
while ( (line=buffReader.readLine()) != null ) {
if (line.indexOf("eof") != -1) {// 读到结束标记,则跳出循环
break;
}
sBuilder.append(line);
}
buffWriter.close();
buffReader.close();
socket.close();
System.out.println("Client 来自服务端的数据:" + sBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端代码分析:
1、这里的代码逻辑,跟前两篇文章中的代码是一样的,只是有个需要注意的方法:setSoTimeout(int timeout)
设置超时时长,单位是毫秒。当设置了超时时长大于0时,表示Socket在这一时间内,如果没读取到数据,则不会一直阻塞在那里,而是抛出一个SocketTimeoutException异常。
3、运行结果:
注意:先运行Server程序,再运行Client程序。为体现服务端可以处理多个客户端请求,把Client程序拷贝到硬盘的某个路径,编辑文件,修改往服务端写数据的内容,然后在dos控制台再运行一次。即
结果如下:
1)Server程序控制台:
2)Client程序控制台:
3)dos命令控制台:
4、总结:
经过前面两章和本章的学习,我相信大家对Socket会有了一个更深入的了解,前面两章的内容均是为了本章内容而做铺垫的,实际开发中,就是使用本章这种方式,一个服务端异步响应多个客户端请求。以后更复杂的程序也是在这基础上拓展的,要学会融会贯通,以不变应万变。
- 大小: 22.5 KB
- 大小: 9 KB
- 大小: 26.8 KB
- 大小: 68.8 KB