什么是Socket?
Socket,又叫套接字,是一种软件形式的抽象,用于表达两台机器间一个连接的“终端”。服务端的socket在服务端机器上特定的端口进行等待,客户端的socket绑定了服务端的IP地址以及它正在监听着的端口,所以两者通过IP和端口连接起来,就如同一条虚拟的通道。可以理解为我们现实中打电话的过程,当没有人打电话给你时,你的手机是处于监听等待的状态,一旦有人拨打了你的号码,就会收到,并接通。Socket处在网络通信中的什么位置?
在同一台计算机,进程之间可以这样通信,如果是不同的计算机呢?网络上不同的计算机,也可以通信,那么就得使用网络套接字(socket)。socket就是在不同计算机之间进行通信的一个抽象。他工作于TCP/IP协议中应用层和传输层之间的一个抽象。如下图:下面通过一个一对一聊天的实例来理解Socket的通信过程:
具体思路如下:
1.创建一个服务端
2.创建服务端的两个线程,分别用于读客户端发来的消息和用于写消息给客户端
3.创建一个客户端
4.创建客户端的两个线程,分别用于读服务端发来的消息和用于写消息给服务端
5.客户端和服务端均通过DataOutputStream进行写入操作,通过DataInputStream进行读取操作
工程目录:
代码如下:
服务端主程序:
public class Server{
public static void main(String[] args){
try{
//设定服务端在8888端口进行等待
ServerSocket s = new ServerSocket(8888);
Socket s1 = s.accept();//sever等待链接
System.out.println("服务器已开启...");
//实例化输出流
DataOutputStream dos = new DataOutputStream(s1.getOutputStream());
//实例化输入流
DataInputStream dis = new DataInputStream(s1.getInputStream());
//实例化两个线程的对象
Thread msr = new ServerReadThread(dis);
Thread msw = new ServerWriteThread(dos);
//启动线程
msr.start();
msw.start();
}
//捕获异常
catch(SocketException e){
System.out.println(e);
}catch(IOException e){
System.out.println(e);
}
}
}
服务端读取消息的进程:
public class ServerReadThread extends Thread{
private DataInputStream stream;
public ServerReadThread(DataInputStream stream){
this.stream = stream;
}
@Override
public void run() {
// TODO Auto-generated method stub
String msg;
while(true){
try {
msg = stream.readUTF();//读取客户端的消息
System.out.println("客户端:"+msg); //打印客户端的消息
//如果接收到对方发来的为q的消息,则退出程序
if(msg.equals("q")){
System.out.println("对方下线,程序退出");
System.exit(0);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
服务端发送消息的进程:
public class ServerWriteThread extends Thread{
private DataOutputStream stream;
public ServerWriteThread(DataOutputStream stream) {
// TODO Auto-generated constructor stub
this.stream = stream;
}
@Override
public void run() {
// TODO Auto-generated method stub
Scanner scanner = new Scanner(System.in);
String msg;
try{
while(true){
msg = scanner.nextLine();
stream.writeUTF(msg);
//如果输入q,则退出程序
if(msg.equals("q")){
System.out.println("主动下线,程序退出");
System.exit(0);
}
}
}catch(IOException e){
System.out.println(e);
}
}
}
客户端主程序:
public class Client{
public static void main(String[] args){
try{
//设定服务端在8888端口进行等待
Socket s = new Socket("127.0.0.1",8888);
//实例化输出流
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
//实例化输入流
DataInputStream dis = new DataInputStream(s.getInputStream());
//实例化两个线程的对象
Thread msr = new ServerReadThread(dis);
Thread msw = new ServerWriteThread(dos);
//启动线程
msr.start();
msw.start();
}
//捕获异常
catch(SocketException e){
System.out.println(e);
}catch(IOException e){
System.out.println(e);
}
}
}
客户端读取消息的进程:
public class ClientReadThread extends Thread{
private DataInputStream stream;
public ClientReadThread(DataInputStream stream){
this.stream = stream;
}
@Override
public void run() {
// TODO Auto-generated method stub
String msg;
while(true){
try {
msg = stream.readUTF(); //读取来自服务端的消息
System.out.println("服务器:"+msg); //打印
//如果接收到对方发来的为q的消息,则退出程序
if(msg.equals("q")){
System.out.println("对方下线,程序退出");
System.exit(0);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
客户端发送消息的进程:
public class ClientWriteThread extends Thread{
private DataOutputStream stream;
public ClientWriteThread(DataOutputStream stream) {
// TODO Auto-generated constructor stub
this.stream = stream;
}
@Override
public void run() {
// TODO Auto-generated method stub
String msg;
Scanner scanner = new Scanner(System.in);
try{
while(true){
msg = scanner.nextLine();
stream.writeUTF(msg);//写入消息
//如果输入q,则退出程序
if(msg.equals("q")){
System.out.println("主动下线,程序退出");
System.exit(0);
}
}
}catch(IOException e){
System.out.println(e);
}
}
}
运行结果:
注意:
服务端和客户端的端口号必须一致,否则会报错:connection refuse在服务端,通过调用socket的accept()方法,让服务器一直在指定的端口进行等待,一旦客户端发送请求,则建立连接。
在服务端和客户端的read线程中,都通过while(true)死循环来不断从datainputstream流中读取消息,一旦有消息,就打印出来。
服务端和客户端的stream要调用相对应的方法,即readUTF()和writeUTF()
短连接与长连接:
短连接:客户端发起请求,服务端接收请求,双方建立连接,读写一次之后,就主动close,优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段。适用于多个客户端对应一个服务端的情况。长连接:客户端发起请求,服务端接收请求,双方建立连接,但是在读写一次之后并不会主动关闭,而是一直循环监听着是否有消息。长连接存在一个不足,就是但客户端变得多了,服务端会承受不住,所以需要有额外的手段来控制,适用于一个客户端对应另外一个端的情况,即点对点。
Socket与http的区别:
HTTP连接使用的是“请求—响应”的方式,HTTP在每次请求结束后都会主动释放连接,是一种“短连接”,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据,而socket则是一旦连接之后,除非双方有一方断开了,否则连接一直存在。举个例子:假如服务器端要向客户端主动推送消息,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端。