基于BIO的Socket通信

告知对方命令发送完毕

  • 关闭socket:socket.close()
  • 关闭流:socket.shutdownOutput(),ocket.shutdownInput()
  • 约定终结符
  • 指定数据长度

单工通信

  • 通过约定终结符的方式关闭连接

  • 通过关闭流的方式告诉对方发送完毕

  • Server.java

package demo0;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets; public class Server {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null; // 读取客户端 try {
// 初始化服务端socket并且绑定9999端口
serverSocket = new ServerSocket(8888);
// 等待客户端的连接
socket = serverSocket.accept(); // 获取输入流,并且指定统一的编码格式
is = socket.getInputStream();
inputStreamReader = new InputStreamReader(is, StandardCharsets.UTF_8);
bufferedReader = new BufferedReader(inputStreamReader); // 读取一行数据
String str;
// 通过while循环不断读取信息,读到终结符会去掉终结符并获取这一行内容而不是继续阻塞
while ((str = bufferedReader.readLine()) != null) {
// 终止连接,并且此次不返回信息
if (str.equals("**")) break; //输出打印
System.out.println(str);
}
// 关闭输入流
socket.shutdownInput(); } catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("服务器释放资源...");
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
  • Client.java
package demo0;

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets; public class Client {
public static void main(String[] args) {
Socket socket = null;
OutputStream os = null;
OutputStreamWriter outputStreamWriter = null;
BufferedWriter bufferedWriter = null; // 写给服务器
BufferedReader bufferedReader = null; // 读取控制台输入
InputStreamReader inputStreamReader = null; try {
// 初始化一个socket
socket = new Socket("127.0.0.1", 8888); // 通过socket获取字符流,先服务器发送信息
os = socket.getOutputStream();
outputStreamWriter = new OutputStreamWriter(os, StandardCharsets.UTF_8);
bufferedWriter = new BufferedWriter(outputStreamWriter); // 读取控制台输入
inputStreamReader = new InputStreamReader(System.in, StandardCharsets.UTF_8);
bufferedReader = new BufferedReader(inputStreamReader); while (true) {
// 读到终结符\r或者\n会去掉终结符并且返回一行内容
String str = bufferedReader.readLine();
bufferedWriter.write(str);
// 让服务器的readLine能够接收一行,如果发过去的没有终结符,服务器的readLine会一直阻塞
bufferedWriter.write("\n");
// bufferedWriter.write("\r666\n"); // 这种情况,相当于两行,服务器第二次读到的就是666\n
// 清空缓存立刻发出
bufferedWriter.flush(); // 终止连接
if (str.equals("**")) break;
}
// 关闭输出流
socket.shutdownOutput(); } catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("客户端释放资源...");
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStreamWriter != null) {
try {
outputStreamWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedWriter != null) {
try {
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

客户端单次收发

  • MyServer.java
package demo1;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket; public class MyServer { public static void main(String[] args) {
ServerSocket serverSocket = null; // 新建ServerSocket对象
Socket socket = null; // 接收客户端连接
InputStream is = null; // 用于读取客户端消息
OutputStream os = null;
ByteArrayOutputStream baos = null; try {
// 1.创建指定端口的连接
serverSocket = new ServerSocket(8888); while (true) {
// 2.监听 没有连接就阻塞在此
socket = serverSocket.accept(); // 3.从socket取出来自客户端的数据
is = socket.getInputStream(); // 解析数据
// 方法一 读到缓冲数组里
baos = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len;
while ((len = is.read(buff)) != -1) {
baos.write(buff, 0, len);
}
System.out.println("服务器:" + baos);
// 关闭输入流
socket.shutdownInput(); // 方法二
/* InputStreamReader reader = new InputStreamReader(is);
BufferedReader bufReader = new BufferedReader(reader);
String s;
StringBuffer sb = new StringBuffer();
while ((s = bufReader.readLine()) != null) {
sb.append(s);
}
System.out.println("服务器:" + sb);
// 关闭输入流
socket.shutdownInput();*/ // 2.2接受图片
/*
FileOutputStream fos = new FileOutputStream(new File("receive.jpg"));
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
socket.shutdownInput();
*/ // 4.向socket写入数据,发送给客户端
os = socket.getOutputStream();
os.write(("服务端返回给客户端的信息").getBytes());
// 强制将缓冲区中的数据发送出去,不必等到缓冲区满
os.flush();
// 关闭输出流,会自动关闭socket
socket.shutdownOutput();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
/**
* 在使用TCP编程的时候,最后需要释放资源,关闭socket(socket.close());
* 关闭socket输入输出流(socket.shutdownInput()以及socket.shutdownOutput());关闭IO流(is.close() os.close())。
* 需要注意的是:关闭socket的输入输出流需要放在关闭io流之前。
* 因为关闭IO流会同时关闭socket,一旦关闭了socket的,就不能再进行socket的相关操作了。
* 而只关闭socket输入输出流(socket.shutdownInput()以及socket.shutdownOutput())不会完全关闭socket,此时任然可以进行socket方面的操作。
* 所以要先调用socket.shutdownXXX,然后再调用io.close();
*/
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close(); // 关闭IO流会同时关闭socket
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null) {
try {
socket.shutdownOutput();
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
// 关闭socket
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (serverSocket != null) {
try {
// 关闭serverSocket
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
  • MyClient2.java
package demo1;

import java.io.*;
import java.net.Socket; public class MyClient2 { public static void main(String[] args) {
Socket socket = null;
OutputStream os = null; try {
// 1.创建socket连接
socket = new Socket("127.0.0.1", 8888); // 2.向socket写入数据,发送给服务端
// 2.1发送文字
os = socket.getOutputStream();
os.write(("主机客户端" + Thread.currentThread() + "--->服务器ing...").getBytes());
os.flush();
// 关闭输出流
socket.shutdownOutput(); // 2.2发送图片
/* os = socket.getOutputStream();
// 读取文件
FileInputStream fis = new FileInputStream(new File("haha.jpg"));
// 写出文件到输出流中
byte[] buffer = new byte[1024];
int len;
while ((len=fis.read(buffer))!=-1){
os.write(buffer,0,len);
}
os.flush();
socket.shutdownOutput();*/ // 3.从socket取出来自服务端的数据
InputStream is = socket.getInputStream();
// 解析服务器返回的数据
InputStreamReader reader = new InputStreamReader(is);
BufferedReader bufReader = new BufferedReader(reader);
String s;
final StringBuffer sb = new StringBuffer();
while ((s = bufReader.readLine()) != null) {
sb.append(s);
}
System.out.println("主机客户端接收到:" + sb);
// 关闭输入流
socket.shutdownInput(); } catch (IOException e) {
e.printStackTrace();
} finally {
// 4.释放所有资源
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

约定数据长度

  • 指定前两个字节表示数据长度

  • 或者第一个字节表示后面几个字节是用来表示数据长度,实现变长方式表示长度

  • SocketServer.java

package demo4;

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets; public class SocketServer {
public static void main(String[] args) throws Exception {
// 监听指定的端口
int port = 8888;
ServerSocket server = new ServerSocket(port); // server将一直等待连接的到来
System.out.println("监听ing...");
Socket socket = server.accept(); // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
InputStream inputStream = socket.getInputStream(); byte[] bytes;
// 因为可以复用Socket且能判断长度,所以可以一个Socket用到底
while (true) {
// 首先读取两个字节表示的长度
// int四字节
int first = inputStream.read();
System.out.println("first:" + first);
// 如果读取的值为-1 说明到了流的末尾,Socket已经被关闭了,此时将不能再去读取
if (first == -1) {
break;
} int second = inputStream.read();
System.out.println("second:" + second);
int length = (first << 8) + second;
System.out.println("(first << 8) + second:" + length);
// 然后构造一个指定长的byte数组
bytes = new byte[length]; // 然后读取指定长度的消息即可
inputStream.read(bytes);
System.out.println("get message from client: " + new String(bytes, StandardCharsets.UTF_8));
}
inputStream.close();
socket.close();
server.close();
}
}
  • SocketClient.java
package demo4;

import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets; public class SocketClient {
public static void main(String[] args) throws Exception {
// 与服务端建立连接
Socket socket = new Socket("127.0.0.1", 8888); // 建立连接后获得输出流
OutputStream outputStream = socket.getOutputStream(); // UTF-8编码:一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。中文标点占三个字节,英文标点占一个字节。
String message = "abcdefg";
// 首先需要计算得知消息的长度
byte[] sendBytes = message.getBytes(StandardCharsets.UTF_8);
System.out.println("长度:" + sendBytes.length);
socket.shutdownOutput(); // 然后将消息的长度优先发送出去
// 用,只取int的低16位
// 先有符号右移8位,发送高八位表示的字节
outputStream.write(sendBytes.length >> 8);
// 在
outputStream.write(sendBytes.length); // 然后将消息再次发送出去
outputStream.write(sendBytes);
outputStream.flush(); //==========此处重复发送一次,实际项目中为多个命名,此处只为展示用法
message = "第二条消息";
sendBytes = message.getBytes(StandardCharsets.UTF_8);
outputStream.write(sendBytes.length >> 8);
outputStream.write(sendBytes.length);
outputStream.write(sendBytes);
outputStream.flush(); outputStream.close();
socket.close();
}
}

全双工通信

package demo5;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set; public class MyServer {
// 区分不同的服务线程,一个线程处理一个socket
private static int id = 0;
// 监听端口
private ServerSocket serverSocket;
// 管理所有的服务线程
private final HashMap<Integer, ServerThread> hashMap = new HashMap<>(); // 传入监听的端口
public MyServer(int port) {
try {
serverSocket = new ServerSocket(port);
System.out.println("服务器已启动");
} catch (IOException e) {
e.printStackTrace();
}
} // 启动服务器
public void start() { // 启动服务器,先让Writer对象启动等待键盘输入
new Writer().start(); // 不停的监听端口
try {
while (true) {
// 等待客户端接入
Socket socket = serverSocket.accept();
System.out.println("客户端" + ++id + "连接成功: intentAddress=" + socket.getInetAddress() + " port=" + socket.getPort());
// 放到线程里执行
ServerThread serverThread = new ServerThread(id, socket);
serverThread.start();
// 放入hashmap管理线程
hashMap.put(id, serverThread);
}
} catch (IOException e) {
e.printStackTrace();
}
} // 关闭服务器的资源
public void close() {
// 发送广播,告诉所有客户端关闭连接
sendAll("exit"); try {
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
} // 单播
private void send(int id, String data) {
// 找到对应的服务线程
ServerThread thread = hashMap.get(id);
// 发送信息
thread.send(data);
// 让连接关闭
if ("exit".equals(data)) {
thread.close();
}
} // 广播 /**
* 遍历存放连接的Map,把他们的id全部取出来,注意这里不能直接遍历Map,不然可能报错
* 报错的情况是,当试图发送 `*:exit` 时,这段代码会遍历Map中所有的连接对象,关闭并从Map中移除
* java的集合类在遍历的过程中进行修改会抛出异常
*/
public void sendAll(String data) {
LinkedList<Integer> list = new LinkedList<>();
Set<Map.Entry<Integer, ServerThread>> set = hashMap.entrySet();
for (Map.Entry<Integer, ServerThread> entry : set) {
list.add(entry.getKey());
}
for (Integer id : list) {
send(id, data);
}
} // 每次接收一个客户端就放到一个服务进程处理读写
private class ServerThread extends Thread {
private int id;
private Socket socket;
private InputStream inputStream;
private OutputStream outputStream;
private PrintWriter printWriter; public ServerThread(int id, Socket socket) {
try {
this.id = id;
this.socket = socket;
this.inputStream = socket.getInputStream();
this.outputStream = socket.getOutputStream();
printWriter = new PrintWriter(outputStream);
} catch (IOException e) {
e.printStackTrace();
}
} // 向客户端发送信息
// 同时只能有一个键盘输入,所以输入交给服务器管理而不是服务线程
// 服务器负责选择socket连接和发送的消息内容,然后调用服务线程的write方法发送数据
public void send(String data) {
if (!socket.isClosed() && data != null && !"exit".equals(data)) {
printWriter.println(data);
printWriter.flush();
}
} // 读写不能阻塞,新开线程进行读操作
@Override
public void run() {
new Reader().run();
} public void close() {
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
if (printWriter != null) {
printWriter.close();
}
if (socket != null) {
socket.close();
}
// 移除服务线程
hashMap.remove(id);
} catch (IOException e) {
e.printStackTrace();
}
} private class Reader extends Thread {
// 获取这个客户端的输入
private final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
private final BufferedReader bufferedReader = new BufferedReader(inputStreamReader); @Override
public void run() {
try {
String line = "";
while (!socket.isClosed() && line != null && !line.equals("exit")) {
line = bufferedReader.readLine();
if (line != null) {
System.out.println("客户端" + id + ":" + line);
}
} System.out.println("客户端" + id + "主动断开连接");
close();
} catch (IOException e) {
e.printStackTrace();
System.out.println(id + "连接已关闭");
} finally {
try {
if (inputStreamReader != null) {
inputStreamReader.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
} private class Writer extends Thread {
// 从键盘获取要发送给客户端的消息
private final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); @Override
public void run() {
String line = "";
// 不停的接收键盘发送的命令
while (true) {
// 服务器控制台输入exit退出服务器
try {
line = bufferedReader.readLine();
// 变量放前面不用做空指针异常处理
if ("exit".equals(line))
break;
} catch (IOException e) {
e.printStackTrace();
} // 否则就解析命令
if (line != null) {
try {
// [连接id]:[要发送的内容]
String[] data = line.split(":");
if ("*".equals(data[0])) {
// 广播
sendAll(data[1]);
} else {
// 单播
send(Integer.parseInt(data[0]), data[1]);
}
// 有可能发生的异常
} catch (NumberFormatException e) {
System.out.print("必须输入连接id号");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.print("发送的消息不能为空");
} catch (NullPointerException e) {
System.out.print("连接不存在或已经断开");
}
}
}
System.out.println("服务器关闭");
close();
}
} public static void main(String[] args) {
new MyServer(8888).start();
}
}
  • MyClient.java
package demo5;

import java.io.*;
import java.net.Socket; public class MyClient {
private Socket socket;
private InputStream inputStream;
private OutputStream outputStream; public MyClient(String address, int port) {
try {
socket = new Socket(address, port);
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("客户端启动成功");
} public void start(){
new Reader().start();
new Writer().start();
} private class Reader extends Thread {
private final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
private final BufferedReader bufferedReader = new BufferedReader(inputStreamReader); @Override
public void run() { try {
String line = "";
while (!socket.isClosed() && line != null && !line.equals("exit")) {
line = bufferedReader.readLine();
System.out.println("服务器发来:" + line);
} System.out.println("服务器关闭了连接");
close();
} catch (IOException e) {
e.printStackTrace();
System.out.println("连接已关闭");
} finally {
try {
if (inputStreamReader != null) {
inputStreamReader.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} private class Writer extends Thread {
// 发给服务器
private final PrintWriter printWriter = new PrintWriter(outputStream);
// 从键盘获取要发送给服务器的消息
private final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); @Override
public void run() {
try {
String line = "";
while (!socket.isClosed() && line != null && !"exit".equals(line)) {
line = bufferedReader.readLine();
if (!"".equals(line)) {
// 写出
printWriter.println(line);
printWriter.flush();
} else {
System.out.println("不能发送给服务器空的数据");
}
} System.out.println("客户端关闭了连接");
close();
} catch (IOException e) {
e.printStackTrace();
System.out.println("连接已关闭");
} finally {
try {
if (printWriter != null) {
printWriter.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} // 关闭资源
private void close() {
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
new MyClient("127.0.0.1", 8888).start();
}
}

生产者消费者模式版

  • 为每个socket连接创建一个服务线程
  • 服务线程启动一个读线程和一个写线程
  • 客户端先写后读,交替进行;服务端先读后写,交替进行
  • 用Lock锁精准唤醒实现生产者消费者模型
  • 加上了简单的头尾校验
  • MyServer.java
package demo9;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class MyServer {
// 区分不同的服务线程,一个线程处理一个socket
private static int id = 0;
// 监听端口
private ServerSocket serverSocket;
// 管理所有的服务线程
private HashMap<Integer, ServerThread> hashMap = new HashMap<>(); /**
* 传入监听的端口
*
* @param port
*/
public MyServer(int port) {
try {
serverSocket = new ServerSocket(port);
System.out.println("服务器已启动");
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 启动服务器
*/
public void start() {
// 不停的监听端口
try {
while (true) {
// 等待客户端接入
Socket socket = serverSocket.accept();
System.out.println("客户端" + ++id + "连接成功: intentAddress=" + socket.getInetAddress() + " port=" + socket.getPort());
// 放到线程里执行
ServerThread serverThread = new ServerThread(id, socket);
serverThread.start();
// 放入hashmap管理线程
hashMap.put(id, serverThread);
}
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 关闭服务器的资源
*/
public void closeMyServer() {
// 关闭serverSocket
try {
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 每次接收一个客户端就放到一个服务线程处理读写
* 一个服务线程有一个写线程读线程
*/
private class ServerThread extends Thread {
private int id;
private Socket socket;
private InputStream inputStream;
private OutputStream outputStream;
private PrintWriter printWriter;
public static final String CHECK_ERROR = "数据校验错误";
// 待返回的处理结果
private String message = "";
// 1是轮到读线程读,2是轮到写线程写(读线程先执行)
private int num = 1;
// 锁
private Lock lock = new ReentrantLock();
// 更新返回的处理结果
private Condition condition_reader = lock.newCondition();
// 发送返回的处理结果
private Condition condition_writer = lock.newCondition();
// 是不是已经关闭socket
private boolean flag = false; /**
* 初始化serverThread的资源
*
* @param id
* @param socket
*/
public ServerThread(int id, Socket socket) {
try {
this.id = id;
this.socket = socket;
this.inputStream = socket.getInputStream();
this.outputStream = socket.getOutputStream();
printWriter = new PrintWriter(outputStream);
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 开启读写线程
*/
@Override
public void run() {
new Reader().start();
new Writer().start();
} /**
* 释放serverThread的资源
*/
public void closeServerThread() {
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
if (printWriter != null) {
printWriter.close();
}
if (socket != null) {
socket.close();
}
// 移除服务线程
hashMap.remove(id);
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 去除头尾
*
* @param str
* @return
*/
public String removeHeadAndTail(String str) {
// 检验头尾是否是0x55 0xAA
int head = str.charAt(0);
int tail = str.charAt(str.length() - 1);
if (head != 85 || tail != 170) {
// 数据接收不对的处理
return CHECK_ERROR;
} else {
// 去掉头尾后的json字符串
return str.substring(1, str.length() - 1);
}
} /**
* 加上头尾
*
* @param str
* @return
*/
public String addHeadAndTail(String str) {
return ((char) 85) + str + ((char) 170);
} /**
* 读线程
*/
private class Reader extends Thread {
// 获取这个客户端的输入
private InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
private BufferedReader bufferedReader = new BufferedReader(inputStreamReader); @Override
public void run() {
// 加锁
lock.lock(); /**
* 与写线程交替读写
*/
try {
String line = ""; while (!socket.isClosed() && line != null && !"exit".equals(line)) { /**
* 如果没轮到自己读,就阻塞自己
*/
while (num != 1) {
condition_reader.await();
} /**
* 一次读
* 去除头尾才是json字符串
*/
System.out.println("等着读呢");
// TODO: 2021/10/15 客户端直接断开,此处会抛异常 Connection reset
line = bufferedReader.readLine(); if (line != null) {
// 去头尾
String cmd = removeHeadAndTail(line); if (Objects.equals(CHECK_ERROR, cmd)) {
// 校验错误
message = CHECK_ERROR;
System.out.println(CHECK_ERROR);
} else {
// 校验成功并去除了头尾
System.out.println("客户端" + id + "发来:" + cmd);
// 处理客户端发来的命令,并获得处理结果
message = new CallbackUtil().getMessage(cmd);
// 将处理结果返回给客户端
System.out.println("处理结果:" + message);
}
} /**
* 读完一次并执行相应操作并设置完返回结果
* 就把执行权交给写线程
* 然后唤醒writer,让他把返回信息发给客户端
*/
num = 2;
condition_writer.signalAll();
} /**
* 特例:客户端的指令是exit
* 必须唤醒一下writer,不然会阻塞
*/
flag = true;
condition_writer.signalAll();
System.out.println("客户端" + id + "主动断开连接");
closeServerThread();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
System.out.println(id + "连接已关闭");
} finally {
// 解锁
lock.unlock();
// 释放Reader的资源
try {
if (inputStreamReader != null) {
inputStreamReader.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} /**
* 写线程
*/
private class Writer extends Thread {
// 发给客户端
private PrintWriter printWriter = new PrintWriter(outputStream);
private InputStream is;
private BufferedReader buf; @Override
public void run() {
// 加锁
lock.lock(); /**
* 与读线程交替读写
*/
try {
String str; // 接收输入内容
byte[] data;
OUT:
while (!socket.isClosed() && !"exit".equals(message)) {
/**
* 如果没轮到自己写,就阻塞自己
*/
while (num != 2) {
condition_writer.await();
// 客户端已关闭连接,最后一次的处理结果不用返回
if (flag) {
break OUT;
}
} /**
* 一次写
* 返回的是加上头尾的json字符串
*/
String result = addHeadAndTail(message);
data = result.getBytes(StandardCharsets.UTF_8);
is = new ByteArrayInputStream(data);
buf = new BufferedReader(new InputStreamReader(is));
str = buf.readLine();
// 写入输出流
printWriter.println(str);
// 清空缓存立刻发出
printWriter.flush(); /**
* 发送完就把执行权交给读线程
* 然后唤醒reader
*/
num = 1;
condition_reader.signalAll();
} closeServerThread();
} catch (InterruptedException | IOException e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
// 释放资源
try {
if (is != null) {
printWriter.close();
}
if (buf != null) {
buf.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
} public static void main(String[] args) {
new MyServer(9999).start();
}
}
  • MyClient.java
package demo9;

import java.io.*;
import java.net.Socket;
import java.util.Objects;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class MyClient { private Socket socket;
private InputStream inputStream;
private OutputStream outputStream;
// 1是轮到写线程写,2是轮到读线程读 (写线程先执行)
private int num = 1;
// 锁
private Lock lock = new ReentrantLock();
// 写线程运行条件
private Condition condition_writer = lock.newCondition();
// 读线程运行条件
private Condition condition_reader = lock.newCondition();
// 是不是已经关闭socket
private boolean flag = false;
public static final String CHECK_ERROR = "数据校验错误"; /**
* 初始化客户端的资源
*/
public MyClient(String address, int port) {
try {
socket = new Socket(address, port);
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("客户端启动成功");
} /**
* 启动读、写线程,读写交替进行
*/
public void start() {
new Reader().start();
new Writer().start();
} /**
* 去除头尾
*
* @param str
* @return
*/
public String removeHeadAndTail(String str) {
// 检验头尾是否是0x55 0xAA
int head = str.charAt(0);
int tail = str.charAt(str.length() - 1);
if (head != 85 || tail != 170) {
// 数据接收不对的处理
return CHECK_ERROR;
} else {
// 去掉头尾后的json字符串
return str.substring(1, str.length() - 1);
}
} /**
* 加上头尾
*
* @param str
* @return
*/
public String addHeadAndTail(String str) {
return ((char) 85) + str + ((char) 170);
} /**
* 读线程
*/
private class Reader extends Thread {
// 获取服务器发来的信息
private InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
private BufferedReader bufferedReader = new BufferedReader(inputStreamReader); @Override
public void run() {
// 加锁
lock.lock(); /**
* 与写线程交替读写
* 直到服务器返回exit命令
* 或者是由客户端发出的exit命令
* 然后关闭连接释放资源
*/
try {
String line = "";
OUT:
while (!socket.isClosed() && line != null && !"exit".equals(line)) {
/**
* 如果没轮到自己读,就阻塞自己
*/
while (num != 2) {
condition_reader.await();
// 连接关闭后跳出两层循环,不执行bufferedReader.readLine();
if (flag) {
break OUT;
}
} /**
* 一次读
*/
// TODO: 2021/10/15 服务器直接断开,此处会抛异常 Connection reset
line = bufferedReader.readLine(); if (line != null) {
// 去头尾
String result = removeHeadAndTail(line); if (Objects.equals(CHECK_ERROR, result)) {
// 校验错误
System.out.println(CHECK_ERROR);
} else {
// 校验成功并去除了头尾
System.out.println("服务器发来:" + result);
}
} /**
* 读完一次就把执行权交给写线程
* 然后唤醒writer
*/
num = 1;
condition_writer.signalAll();
} closeMyClient();
System.out.println("关闭了连接");
} catch (InterruptedException | IOException e) {
e.printStackTrace();
System.out.println("连接已关闭");
} finally {
// 解锁
lock.unlock();
// 释放Reader的资源
try {
if (inputStreamReader != null) {
inputStreamReader.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} /**
* 写线程
*/
private class Writer extends Thread {
// 发给服务器
private PrintWriter printWriter = new PrintWriter(outputStream);
// 从键盘获取要发送给服务器的消息
private BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); @Override
public void run() {
// 加锁
lock.lock(); /**
* 与读线程交替读写
* 直到客户端自己发出exit命令
* 然后关闭连接释放资源
*/
try {
String line = "";
// 连接没关闭且数据不为空且指令不是exit退出指令,就发送数据
while (!socket.isClosed() && line != null && !"exit".equals(line)) {
/**
* 如果没轮到自己写,就阻塞自己
*/
while (num != 1) {
condition_writer.await();
} /**
* 一次写
*/
line = bufferedReader.readLine();
String cmd = addHeadAndTail(line);
if (!"".equals(cmd)) {
// 写出
printWriter.println(cmd);
printWriter.flush();
System.out.println("发送了" + line); /**
* 发送完就把执行权交给读线程
* 然后唤醒reader
*/
num = 2;
condition_reader.signalAll();
} else {
System.out.println("不能发送给服务器空的数据");
}
}
// 关闭连接
closeMyClient();
System.out.println("客户端主动关闭了连接"); /**
* 特例:客户端的指令是exit
* 直接就关闭资源,不用发送给服务器信息
* 必须唤醒一下reader,不然会阻塞
*/
flag = true;
condition_reader.signalAll();
} catch (InterruptedException | IOException e) {
e.printStackTrace();
System.out.println("连接已关闭");
} finally {
// 解锁
lock.unlock();
// 释放Writer的资源
try {
if (printWriter != null) {
printWriter.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} /**
* 关闭资源
*/
private void closeMyClient() {
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
new MyClient("127.0.0.1", 9999).start();
}
}

基于BIO的Socket通信的更多相关文章

  1. 基于android的Socket通信

    一.Socket通信简介 Android与服务器的通信方式主要有两种,一是Http通信,一是Socket通信.两者的最大差异在于,http连接使用的是“请求—响应方式”,即在请求时建立连接通道,当客户 ...

  2. IOS 基于TCP的socket通信详解(原创)

    最近在整理通信层相关知识,这篇文章是边整理边写的,有些地方可能不够准确,还请各位路过的大牛专家指出来.这次整理的socket通信是基于TCP的,实现方式是GCD形式,以下记录的都是些理论知识,方便自己 ...

  3. 基于NIO的Socket通信

    一.NIO模式的基本原理: 服务端: 首先,服务端打开一个通道(ServerSocketChannel),并向通道中注册一个通道调度器(Selector):然后向通道调度器注册感兴趣的事件Select ...

  4. 基于TCP协议Socket通信

    服务器线程处理类 package demo4; import java.io.*; import java.net.Socket; /** * 服务器线程处理类 * @ClassName Server ...

  5. 基于tcp的socket通信

    # socket # socekt是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,socket其实就是一个门面模式,它 # 把复杂的tcp/ip协议族隐藏在socket接 ...

  6. python 之网络编程(基于TCP协议Socket通信的粘包问题及解决)

    8.4 粘包问题 粘包问题发生的原因: 1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包),这样接收端,就难于分辨出来了,必须提供科学的拆包机制. ...

  7. 基于TCP的socket通信过程及例子

    Socket也叫套接字,用来实现网络通讯,通过调用系统提供的API,可以和远程的机子传输数据.Socket有很多种协议,而这篇文章主要讨论TCP部分的内容,也就是说后面说的内容主要是指TCP Sock ...

  8. [java]基于UDP的Socket通信Demo

    java课编程作业:在老师给的demo的基础上实现客户端发送数据到服务器端,服务器端接受客户端后进行数据广播. 整体功能类似于聊天室,代码部分不是太难,但是在本机测试的时候出现这样的问题: 服务端通过 ...

  9. 基于多线程的TCP socket通信经典案例

    服务器端 package com.thinkvenus.study.socket; import java.io.BufferedReader; import java.io.IOException; ...

  10. Android之从TCP/IP、HTTP看Socket通信

    1.概念 TCP/IP:属于传输层/网络层协议.手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接.TCP协议可以对上层网络提供接口,使上层网络数据的传 ...

随机推荐

  1. 在深度计算框架MindSpore中如何对不持续的计算进行处理——对数据集进行一定epoch数量的训练后,进行其他工作处理,再返回来接着进行一定epoch数量的训练——单步计算

    如题所述: 深度学习框架MindSpore是华为公司研发的,由于性能设计的原因,MindSpore的一些使用方式和TensorFlow和PyTorch有一些不同,其中的一点就是在进行单步计算或者是非持 ...

  2. X86架构CPU下Ubuntu系统环境源码编译pytorch-gpu-2.0.1版本

    本文操作步骤与 aarch64架构CPU下Ubuntu系统环境源码编译pytorch-gpu-2.0.1版本大致相同,只是CPU架构不同而已,因此这里只记录不同的地方. 重点: 一个个人心得,那就是要 ...

  3. windows系统下安装gym运行atari游戏报错:ale_interface/ale_c.dll OSError

    安装gym的atari支持: pip  install  gym[atari] 为gym下的atari环境下载游戏镜像ROMs文件: https://www.cnblogs.com/devilmayc ...

  4. Git 使用中的一些例子

    本文紧接前文: .gitignore文件的使用方法(学习总结版)-- .gitignore 文件的配合用法 ============================================= ...

  5. jQuery Eazyui的学习和使用(二)

    既然这么优秀,我们赶紧来学学吧~ 下载地址 解压得到这些文件夹: 下面开始学习使用.(根据提供的demo学习常用基础控件的使用) 基础面板 引入对应样式文件和js文件后就可以直接使用了. 代码: &l ...

  6. 关于Vue + element plus包装Component理解

    关于Vue + element plus包装Component理解 一.关于编写思路 我以设计el-select选择框进行举例说明 父组件与Component传递params与Function使用Pr ...

  7. rcc of stm32

    1. G0 2. F0 / F1 / F3 F0 F1 F3 3. F2/F4 F205 f429 f7

  8. 聊一聊SQL优化

    晚上睡不着,脑子里总想着一些问题,试着写一写对于SQL查询优化的见解. 首先,数据库有自己的查询优化器,执行一条查询SQL优化器会选择最优的方式(不走索引.走索引.走哪个索引), 所以索引不是越多越好 ...

  9. CSS – 冷知识 (新手)

    <img> extra 4px at the bottom 参考: Extra 4px at the bottom of html <img> The mysterious 4 ...

  10. MySQL 用户、权限管理,C/C++连接与使用

    目录 用户 用户管理 查询所有用户 查看当前用户 查看当前连接数 创建用户 删除用户 修改密码规则 查看规则/策略 规则说明 临时设置 持久设置 修改密码 权限 数据库提供的 权限列表 查看权限 给用 ...