Socket网络通信,通信协议主要包含两种方式:TCP/UDP
其中TCP保证服务端获取到了数据,才能继续向服务端写入数据,而UDP则是不管服务端有没有收到消息,持续向服务端写入数据
Socket网络通信基本模型如下图:
首先 ,服务端开启连接监听服务,监听连接到服务端的连接
此时客户端,开始尝试向服务端获取连接,
当服务端接收到了客户端的连接后,通过此连接创建对应的Socket对象,并等待客户端发送数据
客户端此时发现已经连接上了服务端后,开始向服务端写入数据,
服务端接收到了客户端的数据,开始解析数据,并选择性的是否对客户端进行回应或者结束会话
客户端发送完数据后,可以选择等待接收服务端发送的数据,也可以结束会话
当客户端或者服务断开socket服务或者读写IO流关闭的时候,会话结束
项目最终代码已上传至GitHub:点击进入
1 socket初次尝试
新建项目
这里建了一个空的socketChat项目,同时新建了2个模块,一个是客户端client,一个是服务端service
IEDA开发工具,右键项目,选择new Module,
服务端开启服务并监听连接:
package com.lgli.socketservice;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
/**
* TCPService
* 基于TCP协议的socket服务端
* @author lgli
* @since 1.0
*/
public class TCPService {
/**
* 监听端口
*/
private static final int port = 8080;
public static void main(String[] args) throws Exception{
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("等待客户端等待连接......");
Socket socket = serverSocket.accept();
InetAddress inetAddress = socket.getInetAddress();
System.out.println("服务端获取到了客户端的一个连接......"+inetAddress);
InputStream inputStream = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String result = "";
while((result = reader.readLine()) != null){
System.out.println(inetAddress+"说的是:"+result);
}
reader.close();
inputStream.close();
socket.close();
}
}
客户端开启连接,并发送数据
package com.lgli.socketclient;
import java.io.BufferedOutputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* TCPClient
* 客户端发送请求连接
* @author lgli
* @since 1.0
*/
public class TCPClient {
public static void main(String[] args) throws Exception{
System.out.println("开始连接localhost服务端......");
Socket socket = new Socket("localhost",8080);
System.out.println("连接localhost服务端成功......");
OutputStream outputStream = socket.getOutputStream();
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
String str = "你好,服务端,我想连接你";
bufferedOutputStream.write(str.getBytes(),0,str.getBytes().length);
bufferedOutputStream.flush();
bufferedOutputStream.close();
}
}
然后开启服务端:
服务端等待客户端的连接
此时开启客户端:
然后来看服务端的处理结果:
服务端成功收到客户端发来的数据,并记录了连接来源127.0.0.1,(在本地自己搭建的,所以就是在自娱自乐了)
2 socket会话改进
基于上面的过程,如果服务端根据客户端发送的数据,回复数据,然后连接不关闭,继续监听,客户端同时也接收服务端发送的数据,然后根据服务端发送的数据,回复数据……持续这个过程,那么一个简易的一对一的聊天工具就成型了。
那么改造上述代码
先来服务端,改造2件事:持续监听、根据接收数据回复数据
package com.lgli.socketservice;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* TCPService
* 基于TCP协议的socket服务端
* @author lgli
* @since 1.0
*/
public class TCPService {
/**
* 监听端口
*/
private static final int port = 8080;
public static void main(String[] args) throws Exception{
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("等待客户端等待连接......");
Socket socket = serverSocket.accept();
InetAddress inetAddress = socket.getInetAddress();
System.out.println("服务端获取到了客户端的一个连接......"+inetAddress);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while(true){
//持续监听获取输入的数据
String result = reader.readLine();
System.out.println("客户端:"+result);
//服务端回复数据
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
writer.write(next);
writer.newLine();
writer.flush();
}
}
}
如代码所示,服务端持续获取客户端发送的数据,同时,接收后,等待用户输入,然后发送给客户端
客户端,类似的改造2件事,持续向服务端发送数据、接收服务端返回的数据
package com.lgli.socketclient;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
- TCPClient
- 客户端发送请求连接
- @author lgli
- @since 1.0
*/
public class TCPClient {
public static void main(String[] args) throws Exception{
System.out.println(“开始连接localhost服务端…”);
Socket socket = new Socket(“localhost”,8080);
System.out.println(“连接localhost服务端成功…”);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while(true){
//持续输入数据
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
writer.write(next);
writer.newLine();
writer.flush();
//读取服务端发送的数据
String s = reader.readLine();
System.out.println(“服务端:”+s);
}
}
}
先启动服务端,后启动客户端,我们可以看到理想的效果了。。。
额 CSDN上传的图片不能超过5M,而且不能上传视频。。。
有需要的可以关注我的公众号,那里更详细些
3 服务端和多客户端通信
目前这里服务端和客户端是一对一的关系,这里我们需要服务端同时可以接受到来自许多的客户端的连接,并建立会话。如何做?
简单来说就是,服务端需要持续监听这个连接,然后来一个连接,则创建会话,同时自己也要依然监听。这里很简单的,我们需要用一个多线程来做这个操作,当创建好一个套接字Socket连接,则获取一个线程去做会话操作,主线程依然在循环监听socket连接。
客户端很简单的,直接复制一个出来,叫clients,代码几乎一模一样了。
需要对服务端做一些修改:
package com.lgli.socketservice;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* TCPService
* 基于TCP协议的socket服务端
* @author lgli
* @since 1.0
*/
public class TCPService {
/**
* 监听端口
*/
private static final int port = 8080;
public static void main(String[] args) throws Exception{
//循环监听,监听有连接,则放入线程,让线程去实现方法
ServerSocket serverSocket = new ServerSocket(port);
while(true){
System.out.println("等待客户端等待连接......");
//获取到一个socket连接
final Socket socket = serverSocket.accept();
//获取Runnable对象
Runnable runnable = ()->{
try{
InetAddress inetAddress = socket.getInetAddress();
int port = socket.getPort();
String clientName = inetAddress+":"+port;
System.out.println("服务端获取到了客户端的一个连接......"+clientName);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while(true){
//持续监听获取输入的数据
String result = reader.readLine();
System.out.println("客户端("+clientName+"):"+result);
//服务端回复数据
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
writer.write(next);
writer.newLine();
writer.flush();
}
}catch (Exception e){
e.printStackTrace();
}
};
//启动新的线程去执行这个会话操作
Thread thread = new Thread(runnable);
thread.start();
}
}
}
这里我们也就是多开启线程去执行操作。上面代码实例化Runnable对象,使用了Java8的lambda表达式,相对于之前的写法,此写法方便更好的阅读代码。具体可查看官方介绍
此时,我们也可以得到理想的效果了。可以到公众号上去看。
4 服务端和客户端UDP协议通信
UDP协议通信,客户端不在需要得到服务端的相应,只管发送数据,不需要得到服务端的相应
这里就简单的举个例子
服务端
package com.lgli.socketservice;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* UDPService
*
* @author lgli
* @since 2020/5/4 19:35
* @since 1.0
*/
public class UDPService {
private static final int port = 8081;
public static void main(String[] args) throws Exception{
DatagramSocket datagramSocket = new DatagramSocket(port);
//预先执行获取打包数据
byte [] receive = new byte[2048];
DatagramPacket datagramPacket = new DatagramPacket(receive,receive.length);
datagramSocket.receive(datagramPacket);
System.out.println("服务端接收到来自"+datagramPacket.getAddress()+":"+datagramPacket.getPort()+"的数据:"+datagramPacket.getData());
}
}
客户端
package com.lgli.socketclient;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* UDPClient
*
* @author lgli
* @since 2020/5/4 10:58
* @since 1.0
*/
public class UDPClient {
public static void main(String[] args) throws Exception{
String data = "你好,客户端";
//打包发送数据
DatagramPacket datagramPacket = new DatagramPacket(data.getBytes(),data.getBytes().length, InetAddress.getByName("localhost"),8081);
DatagramSocket datagramSocket = new DatagramSocket();
while(true){
datagramSocket.send(datagramPacket);
System.out.println("客户端1发送了数据.....");
}
}
}
这里我们单纯只是运行客户端,我们可以看到,没有报错发生,而且数据也持续的在发送,
启动服务端,接收到客户端发送的数据:
基于UDP通信方式,相较于TCP而言,服务端接收数据可能存在不连续,数据丢失等情况,对于响应速度而言,UDP是要优于TCP的。