解释:BIO → Block input output(阻塞式的输入和输出)
使用场景:一般应用于传统的网络通信;请求(客户端)
、响应(服务端)
两方,约定好使用TCP/IP通信;双方约定好报文格式(报文头+报文体)
及编码格式(这里用UTF-8)
,报文头内容(约定好长度比如8位,不够前面补零)
里面内容为报文体长度,再根据报文头内容,获取后面的报问体的内容。
例如:报文示例:00000007aaaaaaa
;报文体内容为7个a,所以报文头长度为7不够八位前面补零。
一、 作为服务端
SocketServerThread: 用来监听端口的服务类
ThreadOperate:处理每一个请求的新线程
/**
* serverSocket服务
*
* 此线程只负责接收客户端请求,接收到以后再创造新的线程去处理
* @author zhb
*
*/
public class SocketServerThread extends Thread{
// 此服务端线程只会在启动时创建一个
private ServerSocket serversocket;
/**
* 创建服务端线程
* @param serversocket 服务端的一个socket
* @throws IOException
*/
public SocketServerThread(ServerSocket serversocket) throws IOException{
if(serversocket==null){
this.serversocket = new ServerSocket(Constant.serverSocketPort);
}
}
// 线程运行体
@Override
public void run(){
// 如果此服务线程没有被中断
while(!this.isInterrupted()){
try {
System.out.println("服务线程开启成功,正在等待客户端的请求");
// 服务线程一直处于阻塞状态,直到有客户端请求
Socket socket = serversocket.accept();
System.err.println("请求来了");
// 设置接收超时的时间 单位毫秒
socket.setSoTimeout(Constant.reqTimeOut);
if(socket != null && !socket.isClosed()){
// 创建一个新线程去处理请求信息
new Thread(new ThreadOperate(socket)).start();
}
} catch (IOException e) {
System.err.println("服务线程出现异常:" + e.getMessage());
}
}
}
// 开启线程主函数
public static void main(String[] args) throws IOException {
new SocketServerThread(null).start();
}
}
/**
* 处理每一个请求开的新的线程
* @author zhb
*/
public class ThreadOperate implements Runnable {
// 处理某个客户端请求的socket
private Socket socket;
public ThreadOperate(Socket socket){
this.socket = socket;
}
public void run() {
InputStream in = null;
OutputStream out = null;
System.err.println("处理客户请求的新线程开启成功");
try {
in = socket.getInputStream();
out = socket.getOutputStream();
// 存放报文头的字节数组
byte[] reqHeadByte = new byte[Constant.reqHeadLength];
//读取报文头内容
in.read(reqHeadByte);
String reqHeadStr = new String(reqHeadByte, Constant.charset);
//报文头内容是报问体的长度
int reqBodylength = Integer.valueOf(reqHeadStr);
// 创建容纳报文体的字节数组
byte[] bodyByte = new byte[reqBodylength];
in.read(bodyByte);
// 将报问体数组转成报文字符串
String reqBodyStr = new String(bodyByte, Constant.charset);
System.err.print("接受报文内容为:" + reqBodyStr);
// 这里要处理接收到的请求报文内容reqBodyStr,完后返回处理结果
//返回响应内容体
String resBodyStr = "这里是响应内容→ "+ reqBodyStr;
//返回响应内容的字节数组
byte[] respBodyByte = resBodyStr.getBytes(Constant.charset);
//转成约定好的报文头长度,不足的前面补零
StringBuilder format = new StringBuilder("%0").append(Constant.respHeadLength).append("d");
byte[] respHeadByte = String.format(format.toString(), respBodyByte.length).getBytes();
// 输出报文头信息
out.write(respHeadByte);
// 输出报文体内容
out.write(respBodyByte);
// 发送响应信息
out.flush();
} catch (IOException e) {
System.err.println("读取报文内容报异常::" + e.getMessage());
}finally{
//线程结束后的处理
endHandler(in, out);
}
}
/**
* 线程结束后的处理
* @param in
* @param out
*/
private void endHandler(InputStream in, OutputStream out) {
if(in != null){
try {
in.close();
} catch (IOException e) {
System.err.println("关闭输入流出现异常:" + e.getMessage());
}
}
if(out != null){
try {
out.close();
} catch (IOException e) {
System.err.println("关闭输出流出现异常:" + e.getMessage());
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
System.err.println("关闭线程出现异常:" + e.getMessage());
}
}
}
}
一、 作为客户端
ClientTest : 模拟客户端请求开启线程
ClientThread:具体的某个客户端线程的请求和响应
Constant : 服务端和客户端都用到的常量参数
/**
* 测试客户端发送请求
* @author zhb
*/
public class ClientTest {
// 用于测试并发用
private static CountDownLatch cdl = new CountDownLatch(1);
public static void main(String[] args) throws IOException, InterruptedException {
for(int i=0; i < 10 ; i++){
Socket socket = new Socket("localhost", Constant.serverSocketPort);
new Thread(new ClientThread(socket, i, cdl)).start();
}
Thread.sleep(3000);
cdl.countDown();
}
}
/**
* 客户端发送请求线程,并接受服务器响应信息
* @author zhb
*
*/
public class ClientThread implements Runnable {
// 客户端的socket
private Socket socket;
// 用于标记第几个请求
private int i;
// 用于模拟 客户端的并发
private CountDownLatch cdl;
public ClientThread(Socket socket) {
this.socket = socket;
}
public ClientThread(Socket socket, int i) {
this.socket = socket;
this.i = i;
}
public ClientThread(Socket socket, int i, CountDownLatch cdl) {
this.socket = socket;
this.i = i;
this.cdl = cdl;
}
public void run() {
try {
//并发测试信息号
cdl.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
OutputStream out = null;
InputStream in = null;
try {
// 客户端请求信息
clientReqSend(out);
// 接收服务端返回的信息
clientRespReceive(in);
} catch (IOException e) {
e.printStackTrace();
}finally{
//线程结束后的处理
endHandler(in, out);
}
}
/**
* 客户端发送请求信息
*
* @param out
* @throws IOException
* @throws UnsupportedEncodingException
*/
private void clientReqSend(OutputStream out) throws IOException, UnsupportedEncodingException {
out = socket.getOutputStream();
String reqBodyStr = "测试请求内容"+String.valueOf(i);
System.err.println(reqBodyStr);
// 请求报文体 字节数组
byte[] reqBodyByte = reqBodyStr.getBytes(Constant.charset);
// 请求报文头字节数组,不够位数前面补零
StringBuilder format = new StringBuilder("%0").append(Constant.respHeadLength).append("d");
byte[] reqHeadByte = String.format(format.toString(), reqBodyByte.length).getBytes(Constant.charset);
// 输出报文头内容
out.write(reqHeadByte);
// 输出报文体内容
out.write(reqBodyByte);
// 发送
out.flush();
}
/**
* 客户端接收响应信息
*
* @param in
* @throws IOException
* @throws UnsupportedEncodingException
*/
private void clientRespReceive(InputStream in) throws IOException, UnsupportedEncodingException {
in = socket.getInputStream();
// 存放报文头的字节数组
byte[] reqHeadByte = new byte[Constant.reqHeadLength];
//读取报文头内容
in.read(reqHeadByte);
String reqHeadStr = new String(reqHeadByte, Constant.charset);
//报文头内容是报问体的长度
int reqBodylength = Integer.valueOf(reqHeadStr);
// 创建容纳报文体的字节数组
byte[] bodyByte = new byte[reqBodylength];
int off = 0;
int nReadLength = 0;
//循环读取直到读取到报文体长度
while(off < reqBodylength){
nReadLength = in.read(bodyByte, off, reqBodylength - off);
if(nReadLength > 0){
off = off + nReadLength;
}else{
break;
}
}
// 将报问体数组转成报文字符串
String reqBodyStr = new String(bodyByte, Constant.charset);
System.err.println("接受服务端的请求信息内容为:" +i+ reqBodyStr);
}
/**
* 线程结束后的处理
* @param in
* @param out
*/
private void endHandler(InputStream in, OutputStream out) {
if(in != null){
try {
in.close();
} catch (IOException e) {
System.err.println("关闭输入流出现异常:" + e.getMessage());
}
}
if(out != null){
try {
out.close();
} catch (IOException e) {
System.err.println("关闭输出流出现异常:" + e.getMessage());
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
System.err.println("关闭线程出现异常:" + e.getMessage());
}
}
}
}
/**
* 系统常量
*
* @author zhb
*
*/
public class Constant {
// 请求报文头的位数
public static final int reqHeadLength = 8;
// 返回响应报文头的长度
public static final int resHeadLength = 8;
// 字节数组和字符创之间的转换时的编码
public static final String charset = "UTF-8";
//接收请求的最长时间 单位毫秒
public static final int reqTimeOut = 5000;
//接收请求的服务端口
public static final int serverSocketPort = 8080;
}
网友评论