什么是套接字Socket
Socket本身就是网络上的两个程序可以互相发送请求和接收请求,应用程序可以利用套接字在网络上进行数据传输。
Socket本身作为应用程序和网络层TCP/UDP之间的一个抽象层,在TCP中主要采用流套接字,采用的方式就是点对点通过字节流传输;UDP主要采用数据报套接字。
面向连接的入门例子
功能:客户端发送请求,服务器接收请求
服务端:
public class MyServer {
class HandleTask {
private Socket socket;
public HandleTask() {}
public HandleTask(Socket socket) {
this.socket = socket;
}
public void handle() {
StringBuilder sb = new StringBuilder("Hello: ");
InputStream is = null;
BufferedReader br = null;
try {
is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
sb.append(br.readLine());
System.out.println(sb.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != br) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (null != is) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sb.toString() + "-- 结束执行 --");
}
}
public static void main(String[] args) throws IOException{
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(9999);
while (true) {
socket = serverSocket.accept();
new MyServer().new HandleTask(socket).handle();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (null != socket) {
socket.close();
}
if (null != serverSocket) {
serverSocket.close();
}
}
}
}
思路:
① 服务器端启动了ServerSocket,然后绑定了端口9999。
② 调用accept方法,阻塞等待客户端连接。
③ 当接收到客户端的请求之后,通过字节流获取客户端发来的信息。
多个客户端:
public class MyClient {
public static void main(String[] args) {
Socket socket = null;
BufferedWriter bw = null;
Scanner scanner = new Scanner(System.in);
try {
socket = new Socket("localhost", 9999);
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write(scanner.nextLine());
bw.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
scanner.close();
try {
if (null != bw) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (null != socket) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class MyClient2 {
public static void main(String[] args) {
Socket socket = null;
BufferedWriter bw = null;
Scanner scanner = new Scanner(System.in);
try {
socket = new Socket("localhost", 9999);
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write(scanner.nextLine());
bw.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
scanner.close();
try {
if (null != bw) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (null != socket) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:
1.jpg
解释:
上面这个例子的缺陷就是如果是多个请求去等待服务器端去处理,那么服务器端会按照收到请求的顺序执行,这样如果第一个请求执行时间很长,那么第二个请求就很长时间等不到执行。
多线程执行多个客户端
场景:
基于上面的例子,思考:服务器在很多情况下是需要接收来自很多个客户端的请求的。
解决办法:
根据多线程时间分片方式来解决问题:多个客户端的请求,那么服务器采用每次接收到一个请求就创建一个线程来执行。
采用多线程改进的服务端:
public class MyThreadServer {
class HandleTask extends Thread{
private Socket socket;
public HandleTask() {}
public HandleTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
StringBuilder sb = new StringBuilder("Hello: ");
InputStream is = null;
BufferedReader br = null;
try {
is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
sb.append(br.readLine());
System.out.println(sb.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != br) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (null != is) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sb.toString() + "-- 结束执行 --");
}
}
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(9999);
while (true) {
socket = serverSocket.accept();
new MyThreadServer().new HandleTask(socket).start();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (null != socket) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (null != serverSocket) {
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.jpg
3.jpg
方案使用的模型:
上面的程序专门使用一个接收者线程来专门负责监听客户端的请求,接收到客户端的请求,就为其创建一个新的线程去执行业务处理,最后通过字节流进行返回响应。这种模型就是BIO(阻塞IO)。
方案的缺点:
这种工作的模型有一个问题,就是当请求数很多时,就会创建和请求数一样多匹配线程。最终当并发量上来,那么系统的性能将会下降。
线程池执行多个客户端
场景:
基于上面的例子,使用多线程方式来处理请求,每个请求都创建一个匹配的线程,浪费线程资源。采用线程池管理线程,可以充分利用线程。
采用线程池改进的服务端:
public class MyThreadPoolServer {
private static ExecutorService es = Executors.newCachedThreadPool();
class HandleTask extends Thread {
private Socket socket;
public HandleTask() {}
public HandleTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
StringBuilder sb = new StringBuilder("Hello: ");
InputStream is = null;
BufferedReader br = null;
try {
is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
sb.append(br.readLine());
System.out.println(sb.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != br) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (null != is) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sb.toString() + "-- 结束执行 --");
}
}
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(9999);
while (true) {
socket = serverSocket.accept();
es.execute(new MyThreadServer().new HandleTask(socket));
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (null != socket) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (null != serverSocket) {
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.jpg
方案使用的模型:
5.jpg
网友评论