我们在开发过程中遇到http请求和socket请求。大部分前后交互都是通过http请求的方式,那socket请求怎么使用,什么情况下使用呢?
基本概念
http请求:基于http协议的soap协议,常见的http数据请求方式有get和post,web服务。
socket请求:实现服务器与客户端之间的物理连接,并进行数据传输。主要有TCP/UDP两个协议。Socket处于网络协议的传输层。
(1)、TCP:传输控制协议,面向连接的的协议,稳定可靠。在客户端和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。
(2)、UDP:广播式数据传输,UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
网络传输架构层面一般分为上图的7层,http协议在TCP/IP分层模型上处于应用层,需要从上向下依次经历所有的层面发送请求,在由下向上依次传输返回请求处理后的数据。socket请求是位于传输层面,所以在向下发送请求和返回数据都要相对http请求更短。应用层面大多都是http请求,大多是用户进行业务操作发起,一般使用B/S架构。传输层面一般非用户层面的一些业务处理比较多,C/S架构相对比较合适。
socket请求的优缺点:
优点:
传输数据为字节级,传输数据可自定义,数据量小。相应的移动端开发,手机费用低;
传输数据时间短,性能高;
适合C/S之间信息实时交互;
可以加密,数据安全性高;
缺点:
需要对传输的数据进行解析,转化为应用级的数据;
对开发人员的开发水平要求高;
相对于Http协议传输,增加了开发量;
http请求的优缺点:
优点:
基于应用级的接口使用方便;
要求的开发水平不高,容错性强;
缺点:
传输速度慢,数据包大。
如实现实时交互,服务器性能压力大;
数据传输安全性差;
注意:
HTTP协议:简单对象访问协议,对应于应用层 ,HTTP协议是基于TCP连接的。
tcp协议: 对应于传输层;
ip协议: 对应于网络层;
TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。
Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。
两者的区别:
http连接:http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断掉;
socket连接:socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉;但是由于各种环境因素可能会使连接断开,比如说:服务器端或客户端主机down了,网络故障,或者两者之间长时间没有数据传输,网络防火墙可能会断开该连接以释放网络资源。所以当一个socket连接中没有数据的传输,那么为了维持连接需要发送心跳消息~~具体心跳消息格式是开发者自己定义的。
长连接
指在一个连接上可以连续发送多个数据包,在连接保持期间,如果没有数据包发送,需要双方发链路检测包。整个通讯过程,客户端和服务端只用一个Socket对象,长期保持Socket的连接。
短连接
短连接服务是每次请求都建立链接,交互完之后关闭链接
长连接与短连接的优势
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是短连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,下次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。
适用场景:
Socket适用场景:网络游戏,银行交互,支付。
http适用场景:公司OA服务,互联网服务。
socket代码示例:
1、简单示例
服务端:
package socket.socket1.socket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerSocketTest {
public static void main(String[] args) {
try {
// 初始化服务端socket并且绑定9999端口
ServerSocket serverSocket =new ServerSocket(9999);
//等待客户端的连接
Socket socket = serverSocket.accept();
//获取输入流
BufferedReader bufferedReader =new BufferedReader(new InputStreamReader(socket.getInputStream()));
//读取一行数据
String str = bufferedReader.readLine();
//输出打印
System.out.println(str);
}catch (IOException e) {
e.printStackTrace();
}
}
}
客户端:
package socket.socket1.socket;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class ClientSocket {
public static void main(String[] args) {
try {
Socket socket =new Socket("127.0.0.1",9999);
BufferedWriter bufferedWriter =new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String str="你好,这是我的第一个socket";
bufferedWriter.write(str);
}catch (IOException e) {
e.printStackTrace();
}
}
}
启动服务端:可以看到在正常等待被连接
启动客户端:
启动客户端的同时发现服务端抛出异常
上诉服务断抛出异常,说明客户端请求服务端是成功的。问题的原因,首先我们需要了解。socket通信是阻塞的,他会在以下几个地方进行阻塞。第一个是accept方法,调用这个方法后,服务端一直阻塞在那里,直到有客户端连接进来。第二个是read方法,调用read方法也会进行阻塞。上诉的问题就是read方法阻塞。解决方法是客户端发送完成请求后需要bufferedWriter.flush();一下。
改进客户端请求代码:
package socket.socket1.socket;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class ClientSocket {
public static void main(String[] args) {
try {
Socket socket =new Socket("127.0.0.1",9999);
BufferedWriter bufferedWriter =new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String str="你好,这是我的第一个socket";
bufferedWriter.write(str);
//刷新输入流
bufferedWriter.flush();
//关闭socket的输出流
socket.shutdownOutput();
}catch (IOException e) {
e.printStackTrace();
}
}
}
由于socket通信是阻塞式的,假设我现在有A和B两个客户端同时连接到服务端上,当客户端A发送信息给服务端后,那么服务端将一直阻塞在A的客户端上(就是我们上诉说的accept阻塞),通过while循环从A客户端读取信息,此时如果B给服务端发送信息时,将进入阻塞队列,直到A客户端发送完毕,并且退出后,B才可以和服务端进行通信。简单地说,我们现在实现的功能,虽然可以让客户端不间断的和服务端进行通信,与其说是一对一的功能,因为只有当客户端A关闭后,客户端B才可以真正和服务端进行通信,这显然不是我们想要的。
2、多线程时的socket通信
上面我们说到socket的accept方法是阻塞的,我们可以通过多线程来解决
服务端:
package socket.socket1.socket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerSocketTest {
public static void main(String[] args)throws IOException {
// 初始化服务端socket并且绑定9999端口
ServerSocket serverSocket =new ServerSocket(9999);
while (true){
//等待客户端的连接
Socket socket = serverSocket.accept();
//每当有一个客户端连接进来后,就启动一个单独的线程进行处理
new Thread(new Runnable() {
@Override
public void run() {
//获取输入流,并且指定统一的编码格式
BufferedReader bufferedReader =null;
try {
bufferedReader =new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
//读取一行数据
String str;
//通过while循环不断读取信息,
while ((str = bufferedReader.readLine())!=null){
//输出打印
System.out.println("客户端说:"+str);
}
}catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
客户端A和B代码相同如下:
package socket.socket1.socket;
import java.io.*;
import java.net.Socket;
public class ClientSocket {
public static void main(String[] args) {
try {
//初始化一个socket
Socket socket =new Socket("127.0.0.1",9999);
//通过socket获取字符流
BufferedWriter bufferedWriter =new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//通过标准输入流获取字符流
BufferedReader bufferedReader =new BufferedReader(new InputStreamReader(System.in,"UTF-8"));
while (true){
String str = bufferedReader.readLine();
bufferedWriter.write(str);
bufferedWriter.write("\n");
bufferedWriter.flush();
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
客户端A启动
客户端B启动
服务端可以看到数据
说明我们可以通过服务端多线程的方式解决accpet阻塞的问题。
服务端代码改良,使用线程池
package socket.socket1.socket;
import java.beans.Encoder;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ServerSocketTest {
public static void main(String[] args)throws IOException {
// 初始化服务端socket并且绑定9999端口
ServerSocket serverSocket =new ServerSocket(9999);
//创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(100);
while (true) {
//等待客户端的连接
Socket socket = serverSocket.accept();
Runnable runnable = () -> {
BufferedReader bufferedReader =null;
try {
bufferedReader =new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
//读取一行数据
String str;
//通过while循环不断读取信息,
while ((str = bufferedReader.readLine()) !=null) {
//输出打印
System.out.println("客户端说:" + str);
}
}catch (IOException e) {
e.printStackTrace();
}
};
executorService.submit(runnable);
}
}
}