Socket简介
Socket综述
Socket被翻译为套接字,是端到端的链接通信方式,处在网络七层(或五层)协议模型的传输层和应用层之间,我们这里主要基于TCP/IP链接
的Socket,这是对传输层的一个抽象封装,通过简单的api就可以实现端到端的传输。
Socket相关的类
- Socket:实现通信的主要类,流的读写等操作都是通过此类来完成。
- ServerSocket:socket服务器,创建服务器,等待链接接入,接入完成返回Socket进行相关通信。
- SocketImpl:实际工作的类,Socket和ServerSocket内部都是基于此完成,有子类如AbstractPlainSocketImpl、DualStackPlainSocketImpl等。
- SocketImplFactory:工厂模式,用于产生SocketImpl对象。
Socket使用
简单使用
服务器代码:
package edu.gj.demo1;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author Simple
* @date 2020/4/23 14:58
*/
public class Server {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
OutputStream outputStream = null;
BufferedOutputStream bufferedOutputStream = null;
PrintStream printStream = null;
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader reader = null;
try {
//创建服务 等待链接
serverSocket = new ServerSocket(8888);
//链接获取到socket对象
socket = serverSocket.accept();
//获取输入输出流
outputStream = socket.getOutputStream();
inputStream = socket.getInputStream();
bufferedOutputStream = new BufferedOutputStream(outputStream);
printStream = new PrintStream(bufferedOutputStream);
inputStreamReader = new InputStreamReader(inputStream);
reader = new BufferedReader(inputStreamReader);
String info = reader.readLine();
System.out.println("服务器获取到信息:" + info);
printStream.println("客户端,你好!");
printStream.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (null != reader){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != inputStreamReader){
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != inputStream){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != printStream){
printStream.close();
}
if (null != bufferedOutputStream){
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != outputStream){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != socket){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != serverSocket){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端代码:
package edu.gj.demo1;
import java.io.*;
import java.net.Socket;
/**
* @author Simple
* @date 2020/4/23 14:58
*/
public class Client {
public static void main(String[] args) {
Socket socket = null;
OutputStream outputStream = null;
BufferedOutputStream bufferedOutputStream = null;
PrintStream printStream = null;
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader reader = null;
try {
socket = new Socket("localhost",8888);
//获取输入输出流
outputStream = socket.getOutputStream();
inputStream = socket.getInputStream();
bufferedOutputStream = new BufferedOutputStream(outputStream);
printStream = new PrintStream(bufferedOutputStream);
inputStreamReader = new InputStreamReader(inputStream);
reader = new BufferedReader(inputStreamReader);
printStream.println("服务器,你好!");
printStream.flush();
String info = reader.readLine();
System.out.println("客户端收到反馈:" + info);
} catch (IOException e) {
e.printStackTrace();
}finally {
if (null != reader){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != inputStreamReader){
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != inputStream){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != printStream){
printStream.close();
}
if (null != bufferedOutputStream){
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != outputStream){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != socket){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
启动:
先启动服务器Server代码,再启动客户端代码Client
运行结果如下:
服务器:
客户端:
通信过程总结如下:
1、服务器:
- 创建ServerSocket,等待链接。
- 链接获取到Socket对象。
- 获取输入输出流进行通信。
- 通信前程,关闭流。
2、客户端:
- 创建Socket对象,链接服务器。
- 获取输入输出流进行通信。
- 通信完成,关闭输入输出流
Socket长链接进行两个客户端之间通信
大家可以发现,上述只是一次性通信,并没有实现随时接入,也没有实现多客户端接入,下面我们解决这两个问题,实现两个客户端通信。
1、引入ServerThread类启动线程去处理多客户端接入问题问题。
2、服务器死循环实现接收多个客户端连接。
3、Socket死循环,随时接收信息,接收多条信息。
4、防止资源浪费,引入心跳机制。
5、防止消息混乱,引入消息机制。
具体代码实现如下:
1、消息分类实现,引入类MessageBean:
package edu.gj.demo;
/**
* @author Simple
* @date 2020/4/17 16:53
*/
public class MessageBean {
/**
* 消息类型
* 0 心跳消息
* 1 链接初始化消息
* 2 系统分发消息
* 3 点对点消息
*/
private int type;
/**
* 用户名 发给系统初始化信息的时候用到
*/
private String userName;
/**
* 消息发送者
*/
private String sender;
/**
* 消息接受者
*/
private String to;
/**
* 消息内容
*/
private String content;
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
2、多客户端接入问题,引入线程处理ServerThread:
package edu.gj.demo;
import com.google.gson.Gson;
import org.json.JSONObject;
import java.io.*;
import java.net.Socket;
import java.net.SocketException;
/**
* @author Simple
* @date 2020/4/17 15:00
*/
public class ServerThread extends Thread {
private String clientName;
private Socket socket;
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
OutputStream outputStream = null;
BufferedOutputStream bufferedOutputStream = null;
PrintStream printStream = null;
public ServerThread(Socket socket) {
this.socket = socket;
try {
socket.setKeepAlive(true);
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
inputStream = socket.getInputStream();
inputStreamReader = new InputStreamReader(inputStream);
bufferedReader = new BufferedReader(inputStreamReader);
outputStream = socket.getOutputStream();
bufferedOutputStream = new BufferedOutputStream(outputStream);
printStream = new PrintStream(bufferedOutputStream);
startMessageAccept(bufferedReader);
} catch (Exception e) {
e.printStackTrace();
closeResources();
}
}
private void closeResources() {
try {
inputStream.close();
inputStreamReader.close();
bufferedReader.close();
outputStream.close();
bufferedOutputStream.close();
printStream.close();
socket.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* 发送消息
*
* @param message
*/
public void sendMessage(String message) {
printStream.println(message);
printStream.flush();
}
public void startMessageAccept(BufferedReader reader) {
lastHeartTime = System.currentTimeMillis();
new Thread(new NoticeTimeRunnable()).start();
isHearting = true;
while (isHearting) {
try {
String message = reader.readLine();
MessageBean messageBean = new Gson().fromJson(message,MessageBean.class);
JSONObject jsonObject = new JSONObject(message);
if (messageBean.getType() == 0) {
//心跳消息
System.out.println("心跳消息" + jsonObject.toString());
} else {
//正常消息
handleMessage(messageBean);
}
} catch (IOException e) {
e.printStackTrace();
closeResources();
return;
}
}
}
private void handleMessage(MessageBean body) {
System.out.println("收到消息:" + new Gson().toJson(body));
if (body.getType() == 3){
Server.sendMessage(body);
}else if (body.getType() == 1){
clientName = body.getUserName();
}
}
private long lastHeartTime;
private boolean isHearting;
private class NoticeTimeRunnable implements Runnable{
public void run() {
while (true){
try {
Thread.sleep(50);
if (System.currentTimeMillis() - lastHeartTime > 1000){
isHearting = false;
closeResources();
return;
}
lastHeartTime = System.currentTimeMillis();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public String getClientName() {
return clientName;
}
}
3、两个客户端代码大同小异Client和Client02:
package edu.gj.demo;
import com.google.gson.Gson;
import org.json.JSONObject;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
* @author Simple
* @date 2020/4/16 13:55
*/
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 8080);
final PrintStream printStream = new PrintStream(new BufferedOutputStream(socket.getOutputStream()));
final BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
MessageBean messageBean = new MessageBean();
messageBean.setType(1);
messageBean.setUserName("client");
printStream.println(new Gson().toJson(messageBean));
printStream.flush();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
try {
Thread.sleep(2 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
MessageBean messageBean = new MessageBean();
messageBean.setType(0);
messageBean.setContent("心跳消息");
printStream.println(new Gson().toJson(messageBean));
printStream.flush();
}
}
}).start();
new Thread(new Runnable() {
public void run() {
while (true) {
try {
String message = reader.readLine();
MessageBean messageBean = new Gson().fromJson(message, MessageBean.class);
if (messageBean.getType() == 2) {
//系统分发:
System.out.println("系统消息:" + messageBean.getContent());
} else if (messageBean.getType() == 3) {
//点对点消息
System.out.println("收到来自:" + messageBean.getSender() + "的消息:" + messageBean.getContent());
}
} catch (IOException e) {
e.printStackTrace();
return;
}
}
}
}).start();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
MessageBean messageBeanSend = new MessageBean();
messageBeanSend.setType(3);
messageBeanSend.setContent(scanner.nextLine());
messageBeanSend.setSender("client");
messageBeanSend.setTo("client2");
printStream.println(new Gson().toJson(messageBeanSend));
printStream.flush();
}
}
}
package edu.gj.demo;
import com.google.gson.Gson;
import org.json.JSONObject;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
* @author Simple
* @date 2020/4/16 13:55
*/
public class Client02 {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 8080);
final PrintStream printStream = new PrintStream(new BufferedOutputStream(socket.getOutputStream()));
final BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
MessageBean messageBean = new MessageBean();
messageBean.setType(1);
messageBean.setUserName("client2");
printStream.println(new Gson().toJson(messageBean));
printStream.flush();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
try {
Thread.sleep(2 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
MessageBean messageBean = new MessageBean();
messageBean.setType(0);
messageBean.setContent("心跳消息");
printStream.println(new Gson().toJson(messageBean));
printStream.flush();
}
}
}).start();
new Thread(new Runnable() {
public void run() {
while (true) {
try {
String message = reader.readLine();
MessageBean messageBean = new Gson().fromJson(message, MessageBean.class);
if (messageBean.getType() == 2) {
//系统分发
System.out.println("系统消息:" + messageBean.getContent());
} else if (messageBean.getType() == 3) {
//点对点消息
System.out.println("收到来自:" + messageBean.getSender() + "的消息:" + messageBean.getContent());
}
} catch (IOException e) {
e.printStackTrace();
return;
}
}
}
}).start();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
MessageBean messageBeanSend = new MessageBean();
messageBeanSend.setType(3);
messageBeanSend.setContent(scanner.nextLine());
messageBeanSend.setSender("client2");
messageBeanSend.setTo("client");
printStream.println(new Gson().toJson(messageBeanSend));
printStream.flush();
}
}
}
4、服务端Server代码:
package edu.gj.demo;
import com.google.gson.Gson;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* @author Simple
* @date 2020/4/16 13:48
*/
public class Server {
private static List<ServerThread> serverThreads = new ArrayList<ServerThread>();
public static void main(String[] args) throws IOException {
int count = 0;
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("Server is start");
while (true) {
count++;
Socket socket = serverSocket.accept();
System.out.println("accept client count:"+count);
ServerThread serverThread = new ServerThread(socket);
serverThreads.add(serverThread);
serverThread.start();
}
}
public static void sendMessage(MessageBean messageBean){
for (ServerThread serverThread :
serverThreads) {
if (serverThread.getClientName().equals(messageBean.getTo())){
serverThread.sendMessage(new Gson().toJson(messageBean));
}
}
}
}
5、启动:
先启动Server,接着启动客户端Client和客户端Client02
启动后在Client控制台输入你好,再在客户端Client02控制台输入你好,日志如下:
服务器:
Client:
Client02:
源码
源码链接: Demo源码
总结
1、以前总觉得Socket很神秘,玩不转,其实不是那么回事,只是自己没有仔细研究而已。
2、多思考,思路很重要。
3、这里只是抛砖引玉,实现了简单的通信过程,难免有考虑不周或者思维局限。