Socket和HTTP的区别
1. Socket是基于TCP/IP协议,是传输层的连接;而HTTP是基于应用层的连接。
2. HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接,下次建立连接需要tcp重新进行三次握手。因此HTTP连接是一种“短连接”。要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常的做法是即使不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道客户端“在线”。若服务器长时间无法收到客户端的请求,则认为客户端“下线”;
由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开,"长链接"。
3. HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据;而Socket连接,服务器就可以直接将数据传送给客户端,实现双向通信。
Socket优点
传输数据为字节级,传输数据可自定义,数据量小;
传输数据时间短,性能高;
适合C/S之间信息实时交互;
可以加密,数据安全性高
缺点: 需要对传输的数据进行解析,转化为应用层的数据
相对于Http协议传输,增加了开发量
HTTP优点
基于应用层的接口使用方便
缺点: 传输速度慢,数据包大。
如实现实时交互,服务器性能压力大
数据传输安全性差
应用场景
Socket适用于对实时性,安全性要求较高,或连接较长的场景,比如网络游戏,银行支付,收发消息。
HTTP适用于对信息传输实时性安全性要求不高,可以一次连接解决的场景,如互联网服务。
Socket
消息目的地:socket地址 = ip地址+端口号
进程间通信需要一对socket,数据在两个Socket间通过IO传输。
对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket。
套接字之间的连接过程分为三个步骤:
1. 服务器监听(ServerSocket将在服务端监听某个端口);
2. 客户端请求(客户端的Socket提出连接请求,指出服务器端套接字的地址和端口号)
3. 连接确认(ServerSocket监听到客户端套接字的连接请求,accept该Socket的连接请求,同时在服务端建立一个对应的Socket与之进行通信)
Socket编程有两种通信协议可以选择:
1. UDP 数据报通信
UDP是一种无连接的协议,每次发送数据报时,需要同时发送本机的socket描述符和接收端的socket描述符。
每次通信都需要发送额外的数据。
2. TCP 流通信
TCP是一种基于连接的协议,在使用流通信之前,我们需要在通信的一对socket之间建立连接。其中一个socket作为服务器进行监听连接请求,另一个作为客户端进行连接请求。
区别
UDP中,数据报大小有64k的限制,而TCP无限制;
UDP不可靠,数据报不一定按照发送顺序被接受,而TCP所有数据按照接受时的顺序读取。
总之TCP适合于如远程登录和文件传输这类的网络服务,因为这些服务需要传输数据大小不确定;
而UDP更加简单轻量,用来实现实时性较高或丢包不重要的一些服务。
TCP通信的Java实现
所有socket相关的类都位于java.net包下。
客户端Client
1.创建Socket对象,指明需要连接的服务器的ip地址和端口号
2.连接建立后,通过输出流OutputStreamWriter向服务器端发送请求信息
3.通过输入流获取服务器响应的信息
4.关闭响应资源
Socket client = new Socket(host, port);
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello From Client");
writer.flush();
writer.close();
client.close();
服务器Server
1.创建ServerSocket对象,绑定监听端口
2.通过accept()方法监听客户端请求(Accept是阻塞方法,服务器端与客户端建立联系前会一直等待阻塞。)
3.连接建立后,通过输入流读取客户端发送的请求信息
4.通过输出流向客户端发送响应信息
5.关闭相关资源
常见服务器Server模型
1. 阻塞服务器
当一个消息执行发出后,这个消息在发送端的socket中处于排队状态,直到下层网络协议将这些消息发送出去。
当消息到达接收端的socket后,也会处于排队状态,直到接收端的进程对这条消息进行了接收处理。
前一个请求没有处理完成,服务器不会处理后面的请求。
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept();
Reader reader = new InputStreamReader(socket.getInputStream());
char chars[] = new char[1024];
int len;
StringBuilder builder = new StringBuilder();
while ((len=reader.read(chars)) != -1) {
builder.append(new String(chars, 0, len));
}
System.out.println("Receive from client message=: " + builder);
reader.close();
socket.close();
server.close(); // 关闭socket
2. 并发服务器
每接收到一个请求,就启动一个线程处理该请求。
这种模式的服务器,好处是不会出现阻塞式服务器请求被拥堵的情况。
存在问题:服务器启动线程有一定开销,请求过多时,将会导致服务器的资源耗尽。
解决:建立线程池来处理请求,每当请求到来时,向线程池申请线程进行处理。
ServerSocket serverSocket = new ServerSocket(SERVER_PORT, 5, serverAddr);
Executor executor = Executors.newFixedThreadPool(100);
//有客户端向服务器发起tcp连接时,accept会返回一个Socket
//该Socket的対端就是客户端的Socket
//具体过程可以查看TCP三次握手过程
Socket connection = serverSocket.accept();
//利用线程池,启动线程
executor.execute(new Runnable() {
@Override
public void run() {
//使用局部引用,防止connection被回收
Socket conn = connection;
try {
InputStream in = conn.getInputStream();
与阻塞服务器比较,差别仅在于当accept返回socket后启动线程处理,这里使用了Excutor,基于线程池进行处理。
3. 异步服务器
一般借助于系统的异步IO机制(NIO),当一个请求到达时,我们可以先将请求注册,当有数据可以读取时,会得到通知,这时候我们处理请求,这样服务器进程没有必要阻塞处理,也不会存在很大的系统开销,因此,目前对于并发量要求比较高的服务器,一般都是采用这种方式。
UDP通信的java实现
Java通过DatagramPacket类和DatagramSocket类来使用UDP套接字,客户端和服务器端都通过DatagramSocket的send()方法和receive()方法来发送和接收数据,用DatagramPacket来包装需要发送或者接收到的数据。
发送信息时,Java创建一个包含待发送信息的DatagramPacket实例,并将其作为参数传递给DatagramSocket实例的send()方法;接收信息时,Java程序首先创建一个DatagramPacket实例,该实例预先分配了一些空间,并将接收到的信息存放在该空间中,然后把该实例作为参数传递给DatagramSocket实例的receive()方法。
在创建DatagramPacket实例时,要注意:如果该实例用来包装待接收的数据,则不指定数据来源的远程主机和端口,只需指定一个缓存数据的byte数组即可(在调用receive()方法接收到数据后,源地址和端口等信息会自动包含在DatagramPacket实例中),而如果该实例用来包装待发送的数据,则要指定要发送到的目的主机和端口。
客户端
1. 定义发送信息
2. 创建DatagramPacket,包含将要发送的信息
3. 创建DatagramSocket(可以有选择地对本地地址和端口号进行设置)
4. 发送数据
//1、定义服务器的地址、端口号、数据
InetAddress address =InetAddress.getByName("localhost");
int port =10010;
byte[] data ="用户名:admin;密码:123".getBytes();
//2、创建数据报,包含发送的数据信息
DatagramPacket packet = newDatagramPacket(data,data,length,address,port);
//3、创建DatagramSocket对象
DatagramSocket socket =newDatagramSocket();
//4、向服务器发送数据
socket.send(packet);
//接受服务器端响应数据
//======================================
//1、创建数据报,用于接受服务器端响应数据
byte[] data2 = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(data2,data2.length);
//2、接受服务器响应的数据
socket.receive(packet2);
String raply = new String(data2,0,packet2.getLenth());
System.out.println("我是客户端,服务器说:"+reply);
//4、关闭资源
socket.close();
服务器
1. 创建DatagramSocket,指定端口号
2. 创建DatagramPacket
3. 接受客户端发送的数据信息
4. 读取数据
//服务器端,实现基于UDP的用户登录
//1、创建服务器端DatagramSocket,指定端口,UDP服务器为所有通信使用同一套接字
DatagramSocket socket =new datagramSocket(10010);
//2、创建数据报,用于接受客户端发送的数据
byte[] data =newbyte[1024];//
DatagramPacket packet =newDatagramPacket(data,data.length);
//3、接受客户端发送的数据
socket.receive(packet); //此方法在接受数据报之前会一直阻塞
//4、读取数据
String info =newString(data,o,data.length);
System.out.println("我是服务器,客户端告诉我"+info);
//=========================================================
//向客户端响应数据
//1、定义客户端的地址、端口号、数据
InetAddress address = packet.getAddress();
int port = packet.getPort();
byte[] data2 = "欢迎您!".geyBytes();
//2、创建数据报,包含响应的数据信息
DatagramPacket packet2 = new DatagramPacket(data2,data2.length,address,port);
//3、响应客户端
socket.send(packet2);
//4、关闭资源
socket.close();
由于UDP协议是不可靠协议,如果数据报在传输过程中发生丢失,那么程序将会一直阻塞在receive()方法处,这样客户端将永远都接收不到服务器端发送回来的数据,但是又没有任何提示。为了避免这个问题,我们在客户端使用DatagramSocket类的setSoTimeout()方法来制定receive()方法的最长阻塞时间,并指定重发数据报的次数,如果每次阻塞都超时,并且重发次数达到了设置的上限,则关闭客户端。
int TIMEOUT = 5000; //设置接收数据的超时时间
//客户端在9000端口监听接收到的数据
DatagramSocket ds = new DatagramSocket(9000);
ds.setSoTimeout(TIMEOUT);