前言
网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务器监听的地址发起连接请求,如果连接建立成功 ,双方就可以通过Socket进行通信。
在传统的BIO网络编程中,ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作。连接成功之后,双方通过输入和输出流进行同步阻塞式通信。
下面我们会一步一步的实现BIO的网络编程,并且对代码进行不断的修改和优化。
BIO网络编程
正如上文所言,进行BIO网络编程需要Server和Client,客户端我们只编写一个版本,保证能够向服务器发送请求,而服务端我们会从最基本的只能够进行一次请求处理的版本开始,不断的完善。
客户端
package nio;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
public class BIOClient {
public static void main(String[] args) throws Exception{
for (int i=0;i<=99;i++){
System.out.println(sendMsg("你好"+i));
}
}
public static String sendMsg(String msg) throws Exception{
// 创建socket 只负责发送请求 不需要监听
Socket socket = new Socket();
// 连接服务器
socket.connect(new InetSocketAddress("localhost",9999));
// 写入数据给服务器
OutputStream outputStream = socket.getOutputStream();
// java 基础的IO操作
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(msg);
printWriter.flush();
// 关闭输出流
socket.shutdownOutput();
// 接受服务器的响应信息
InputStream inputStream = socket.getInputStream();
// java 基础的IO操作
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuffer stringBuffer = new StringBuffer();
String value = null;
while ((value=bufferedReader.readLine())!=null){
stringBuffer.append(value);
}
socket.close();
return "客户端收到:"+ stringBuffer.toString();
}
}
服务端
第一版
服务器启动一次就关闭
优化方向:保证能够连续的处理请求
public static void test1() throws Exception{
// 创建一个ServerSocket
ServerSocket ss = new ServerSocket();
// 绑定服务器监听端口
ss.bind(new InetSocketAddress(9999));
//代表服务器和客户端的会话 (InputStream = HttpServletRequest | OutputStream = HttpServletResponse)
// socket阻塞的 accept会监听有没有请求
System.out.println("我在9999监听");
Socket socket = ss.accept();
System.out.println("请求处理");
//获取请求数据 InputStream
InputStream inputStream = socket.getInputStream();
// java 基础的IO操作
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuffer stringBuffer = new StringBuffer();
String value = null;
while ((value=bufferedReader.readLine())!=null){
stringBuffer.append(value);
}
System.out.println("服务器收到:"+ stringBuffer.toString());
// 获取响应 OutputStream
OutputStream outputStream = socket.getOutputStream();
// java 基础的IO操作
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("你好,我是服务器");
printWriter.flush();
// 关闭资源
socket.close();
}
第二版
使用死循环,保证可以一直处理请求。
缺点:
- 虽然可以处理多次请求,但是所有的请求都是依次执行,并不是实际意义上的并发处理,也就是说没有实现高并发
- 串行执行如果第一个请求没有处理结束,不可以处理第二个请求
升级方向:多线程
public static void test2() throws Exception{
// 创建一个ServerSocket
ServerSocket ss = new ServerSocket();
// 绑定服务器监听端口
ss.bind(new InetSocketAddress(9999));
//代表服务器和客户端的会话 (InputStream = HttpServletRequest | OutputStream = HttpServletResponse)
// socket阻塞的 accept会监听有没有请求
while (true){
System.out.println("我在9999监听");
Socket socket = ss.accept();
System.out.println("请求处理");
//获取请求数据 InputStream
InputStream inputStream = socket.getInputStream();
// java 基础的IO操作
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuffer stringBuffer = new StringBuffer();
String value = null;
while ((value=bufferedReader.readLine())!=null){
stringBuffer.append(value);
}
System.out.println("服务器收到:"+ stringBuffer.toString());
// 获取响应 OutputStream
OutputStream outputStream = socket.getOutputStream();
// java 基础的IO操作
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("你好,我是服务器");
printWriter.flush();
// 关闭资源
socket.close();
}
}
版本三
多线程处理 一旦有请求过来就交给一个新的线程处理 缺点:
- 虽然实现了线程并发处理,但是并没有对线程创建做限制,如果每次处理10秒,并发量10万,服务器会阻塞成千上万个线程
- 直接new线程比较耗费系统的资源,在高并发服务器中会使用多线程,不可能无限制的new线程,会耗尽服务器的资源
改进方向:使用线程池
public static void test3() throws Exception{
// 创建一个ServerSocket
ServerSocket ss = new ServerSocket();
// 绑定服务器监听端口
ss.bind(new InetSocketAddress(9999));
//代表服务器和客户端的会话 (InputStream = HttpServletRequest | OutputStream = HttpServletResponse)
// socket阻塞的 accept会监听有没有请求
while (true){
System.out.println("我在9999监听");
Socket socket = ss.accept();
// 多线程处理 一旦有请求过来就交给一个新的线程处理
SocketProcessRunable socketProcessRunable = new SocketProcessRunable(socket);
Thread thread = new Thread(socketProcessRunable);
thread.start();
}
}
class SocketProcessRunable implements Runnable{
private Socket socket;
public SocketProcessRunable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
Thread.sleep(1000);
//获取请求数据 InputStream
InputStream inputStream = socket.getInputStream();
// java 基础的IO操作
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuffer stringBuffer = new StringBuffer();
String value = null;
while ((value=bufferedReader.readLine())!=null){
stringBuffer.append(value);
}
System.out.println("服务器收到:"+ stringBuffer.toString());
// 获取响应 OutputStream
OutputStream outputStream = socket.getOutputStream();
// java 基础的IO操作
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("你好,我是服务器");
printWriter.flush();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
版本四
使用线程池管理线程
public static void main(String[] args) throws Exception{
// 创建线程池 Executors创建线程的缺点
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 创建一个ServerSocket
ServerSocket ss = new ServerSocket();
// 绑定服务器监听端口
ss.bind(new InetSocketAddress(9999));
//代表服务器和客户端的会话 (InputStream = HttpServletRequest | OutputStream = HttpServletResponse)
// socket阻塞的 accept会监听有没有请求
while (true){
System.out.println("我在9999监听");
Socket socket = ss.accept();
// 多线程处理 一旦有请求过来就交给一个新的线程处理
SocketProcessRunable socketProcessRunable = new SocketProcessRunable(socket);
// 将任务交给线程池处理
executorService.submit(socketProcessRunable);
}
}
class SocketProcessRunable implements Runnable{
private Socket socket;
public SocketProcessRunable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
Thread.sleep(1000);
//获取请求数据 InputStream
InputStream inputStream = socket.getInputStream();
// java 基础的IO操作
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuffer stringBuffer = new StringBuffer();
String value = null;
while ((value=bufferedReader.readLine())!=null){
stringBuffer.append(value);
}
System.out.println("服务器收到:"+ stringBuffer.toString());
// 获取响应 OutputStream
OutputStream outputStream = socket.getOutputStream();
// java 基础的IO操作
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("你好,我是服务器");
printWriter.flush();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
我不能保证每一个地方都是对的,但是可以保证每一句话,每一行代码都是经过推敲和斟酌的。希望每一篇文章背后都是自己追求纯粹技术人生的态度。
永远相信美好的事情即将发生。