最近看了Java的IO包源码,对BIO有了较深刻的理解。Socket编程其实也是基于IO流操做,而且其流操做都是阻塞的,就想着写一个Socket程序并对其一步一步优化,来加深对IO的理解。本文主要从简单的Socket链接开始,一步一步优化,最后使用线程池等技术提升并发。Socket源码本篇未涉及,等有时间我再研究一番。java
Socket编程的基本流程以下图(图片来自网络),一个IP地址和一个端口号称为一个套接字(socket)。 spring
以下,最基本的客户端发送消息,服务端接收消息输入。须要注意的是,因为中文的utf8编码是3个字节,若是使用buffer来分段接收字节流,可能致使乱码。另外,read()是堵塞的,若是不判断read() == -1来表示结束,那么read()方法会一直堵塞。编程
package me.zebin.demo.javaio;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
@RunWith(SpringRunner.class)
@SpringBootTest
public class JavaioApplicationTests {
@Test
public void server() throws Exception {
// 指定端口
ServerSocket ss = new ServerSocket(9999);
System.out.println("server starting...");
// 等待链接
Socket s = ss.accept();
// 获取输入流,接收客户端的消息
InputStream is = s.getInputStream();
// 缓存buffer,utf8编码中文是3个字节,这里也但是使用BufferedReader解码
byte[] buffer = new byte[5];
while(true){
int cnt = is.read(buffer);
// 若是不判断流结束,上面的read()读不到数据会一直堵塞
if(cnt == -1){
break;
}
String str = new String(buffer, 0, cnt, "utf8");
System.out.println(str);
}
s.close();
ss.close();
}
@Test
public void client() throws Exception{
// 指定端口
Socket s = new Socket("127.0.0.1", 9999);
// 获取输出流,向服务端发消息
OutputStream os = s.getOutputStream();
// 发送消息,utf8编码中文是3个字节,服务端使用buffer可能致使乱码
String str = "我是客户端";
os.write(str.getBytes("utf8"));
s.close();
}
}
复制代码
以上程序,若是buffer设置为5,运行结果以下,出现乱码。 数组
@Test
public void server() throws Exception {
// 指定端口
ServerSocket ss = new ServerSocket(9999);
System.out.println("server starting...");
// 等待链接
Socket s = ss.accept();
// 获取输入流,接收客户端的消息
InputStream is = s.getInputStream();
// 输入字节流封装为Scanner,读取整行
Scanner sc = new Scanner(is, "utf8");
while (sc.hasNextLine()){
System.out.println(sc.nextLine());
}
s.close();
ss.close();
}
复制代码
运行结果以下,没有乱码了。 缓存
上面的版本有一个弊端,就是一个服务器只能提供给一个客户端进行链接,若是将链接的用线程处理,服务器能够处理更多的客户端链接,代码以下:服务器
@Test
public void server() throws Exception {
// 指定端口
ServerSocket ss = new ServerSocket(9998);
System.out.println("server starting...");
while(true){
// 等待链接
Socket s = ss.accept();
System.out.println("得到链接");
Thread t = new Thread(new ServerThread(s));
t.start();
}
}
class ServerThread implements Runnable{
private Socket s;
ServerThread(Socket s){
this.s = s;
}
@Override
public void run(){
// 获取输入流,接收客户端的消息
InputStream is = null;
try {
is = s.getInputStream();
// 使用Scanner封装
Scanner sc = new Scanner(is, "utf8");
while (sc.hasNextLine()){
System.out.println(sc.nextLine());
}
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
复制代码
以上多线程版本咱们使用了多线程来处理并发,不过线程的建立和销毁都会消耗大量的资源和时间,同时,高并发下会建立很是多的线程,且不说操做系统能开启的线程数有限,操做系统维护和切换大量的线程也会很是耗时。因此使用线程池,只用4个线程,用队列将未执行到的线程排队处理,减小了线程数量,同时也避免了建立和销毁线程带来的性能问题。网络
@Test
public void server() throws Exception {
// 指定端口
ServerSocket ss = new ServerSocket(9998);
System.out.println("server starting...");
// 建立线程队列
BlockingQueue bq = new ArrayBlockingQueue(100);
// 拒绝策略
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
Executor executor = new ThreadPoolExecutor(4, 8, 1, TimeUnit.MINUTES, bq, handler);
while(true){
// 等待链接
Socket s = ss.accept();
System.out.println("得到链接");
Thread t = new Thread(new ServerThread(s));
executor.execute(t);
}
}
复制代码
以上,本篇结束。多线程