原文博主禁止转载,不过我还是希望把一些关键的地方笔记下来,阅读请移步 原文
以下是学习之后的个人笔记
一、Socket通信基本实例
通过服务器-客户端模式引入Socket通信
服务器端
package cn.itcast.net;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ChatServer {
public static void main(String[] args) throws Exception {
// 监听指定的端口
int port = 55533;
ServerSocket server = new ServerSocket(port);
// server将一直等待连接的到来
System.out.println("server将一直等待连接的到来");
// while (true) { // 如果服务器是不断的等待连接,那么客户端就能发送多次
Socket socket = server.accept();
// 建立好连接后,从socket中获取输入流
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(new String(bytes, 0, len,"UTF-8"));
}
System.out.println("get message from client: " + sb);
inputStream.close();
socket.close();
// }
server.close();
}
}
客户端
127.0.0.1不理解的看我博客吧~这里
package cn.itcast.net;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String args[]) throws Exception {
// 要连接的服务端IP地址和端口
String host = "127.0.1.1";
int port = 55533;
// 与服务端建立连接
Socket socket = new Socket(host, port);
// 建立连接后获得输出流
// OutputStream outputStream = socket.getOutputStream();
//不理解这句存在的必要,希望路过的大神指出
String message="你好 yiwangzhibujian?";
socket.getOutputStream().write(message.getBytes("UTF-8"));
//outputStream.close();
socket.close();
}
}
输出
server将一直等待连接的到来
get message from client: 你好 yiwangzhibujian
二、消息通信优化
2.1 双向通信,发送消息并接收消息
服务器端
和上一个的区别是,在接收完数据的后,也能发送数据给客户端。也就是说可收可发
package cn.itcast.net;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ChatServer {
public static void main(String[] args) throws Exception {
// 监听指定的端口
int port = 55533;
ServerSocket server = new ServerSocket(port);
// server将一直等待连接的到来
System.out.println("server将一直等待连接的到来");
Socket socket = server.accept();
// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
//只有当客户端关闭它的输出流的时候,服务端才能取得结尾的-1
while ((len = inputStream.read(bytes)) != -1) {
// 注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(new String(bytes, 0, len, "UTF-8"));
}
System.out.println("get message from client: " + sb);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello Client,I get the message.".getBytes("UTF-8"));
inputStream.close();
outputStream.close();
socket.close();
server.close();
}
}
客户端
在客户端也添加了接收来自服务器端消息的功能
package cn.itcast.net;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String args[]) throws Exception {
// 要连接的服务端IP地址和端口
String host = "127.0.0.1";
int port = 55533;
// 与服务端建立连接
Socket socket = new Socket(host, port);
// 建立连接后获得输出流
OutputStream outputStream = socket.getOutputStream();
String message = "你好 yiwangzhibujian";
socket.getOutputStream().write(message.getBytes("UTF-8"));
//通过shutdownOutput高速服务器已经发送完数据,后续只能接受数据
socket.shutdownOutput();
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(new String(bytes, 0, len,"UTF-8"));
}
System.out.println("get message from server: " + sb);
inputStream.close();
outputStream.close();
socket.close();
}
}
2.2 双向通信的必要性
客户端打开一个输出流,如果不做约定,也不关闭它,那么服务端永远不知道客户端是否发送完消息,那么服务端会一直等待下去,直到读取超时。所以怎么告知服务端已经发送完消息就显得特别重要。
2.2.1 指定发送规则
文章中推荐一种方法规定发送方式,这种方法是:先指明你接下去要发送的消息的长度,然后发送这么长的消息。具体的看原文博客,现在使用简易版本。用前两个字节表示长度。
服务器端:
package cn.itcast.net;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ChatServer {
public static void main(String[] args) throws Exception {
// 监听指定的端口
int port = 55533;
ServerSocket server = new ServerSocket(port);
// server将一直等待连接的到来
System.out.println("server将一直等待连接的到来");
Socket socket = server.accept();
// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
InputStream inputStream = socket.getInputStream();
byte[] bytes;
while (true) {
int first = inputStream.read();
if (first == -1) {
break;
}
int second = inputStream.read();
int len = (first << 8) + second;
bytes = new byte[len];
inputStream.read(bytes);
System.out.println("get message from client: " + new String(bytes, "UTF-8"));
}
//反馈消息给客户端,也适用本方法,这里就先不演示了
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello Client,I get the message.".getBytes("UTF-8"));
inputStream.close();
outputStream.close();
socket.close();
server.close();
}
}
客户端:
package cn.itcast.net;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String args[]) throws Exception {
// 要连接的服务端IP地址和端口
String host = "127.0.0.1";
int port = 55533;
// 与服务端建立连接
Socket socket = new Socket(host, port);
// 建立连接后获得输出流
OutputStream outputStream = socket.getOutputStream();
String message = "你好 yiwangzhibujian";
//计算消息的长度,转换成字节数组
byte[] sendBytes = message.getBytes("UTF-8");
//实现发送消息的长度,两个字节
// 1、先发送长度的高8位
outputStream.write(sendBytes.length >> 8);
// 2、发送长度的低8位
outputStream.write(sendBytes.length);
// 这才发送消息
outputStream.write(sendBytes);
outputStream.flush();
//==========此处重复发送一次,实际项目中为多个命名,此处只为展示用法
message = "第二条消息";
sendBytes = message.getBytes("UTF-8");
outputStream.write(sendBytes.length >>8);
outputStream.write(sendBytes.length);
outputStream.write(sendBytes);
outputStream.flush();
//==========此处重复发送一次,实际项目中为多个命名,此处只为展示用法
message = "the third message!";
sendBytes = message.getBytes("UTF-8");
outputStream.write(sendBytes.length >>8);
outputStream.write(sendBytes.length);
outputStream.write(sendBytes);
//通过shutdownOutput高速服务器已经发送完数据,后续只能接受数据,也可使用这种方法,这里先不演示了
socket.shutdownOutput();
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(new String(bytes, 0, len,"UTF-8"));
}
System.out.println("get message from server: " + sb);
inputStream.close();
outputStream.close();
socket.close();
}
}
三、服务器并发处理
通常服务器会处理多个Scoket请求,而不是像上面的接收一个Scoket连接后,就关闭服务器,因此模板大概是这样。
package cn.itcast.net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ChatServer {
public static void main(String args[]) throws IOException {
// 监听指定的端口
int port = 55533;
ServerSocket server = new ServerSocket(port);
// server将一直等待连接的到来
System.out.println("server将一直等待连接的到来");
while(true){
Socket socket = server.accept();
// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
// 注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(new String(bytes, 0, len, "UTF-8"));
}
System.out.println("get message from client: " + sb);
inputStream.close();
socket.close();
}
}
}
这种一般也是新手写法,但是能够循环处理多个Socket请求,不过当一个请求的处理比较耗时的时候,后面的请求将被阻塞,所以一般都是用多线程的方式来处理Socket,即每有一个Socket请求的时候,就创建一个线程来处理它。
不过在实际生产中,创建的线程会交给线程池来处理,为了:
- 线程复用,创建线程耗时,回收线程慢
- 防止短时间内高并发,指定线程池大小,超过数量将等待,方式短时间创建大量线程导致资源耗尽,服务挂掉
线程池开启线程处理
package cn.itcast.net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ChatServer {
public static void main(String args[]) throws IOException {
// 监听指定的端口
int port = 55533;
ServerSocket server = new ServerSocket(port);
// server将一直等待连接的到来
System.out.println("server将一直等待连接的到来");
//如果使用多线程,那就需要线程池,防止并发过高时创建过多线程耗尽资源
ExecutorService threadPool = Executors.newFixedThreadPool(100);
while (true) {
Socket socket = server.accept();
Runnable runnable = () -> {
// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
InputStream inputStream = null;
try {
System.out.println("线程开启");
inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
// 注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(new String(bytes, 0, len, "UTF-8"));
}
System.out.println("get message from client: " + sb);
inputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
};
threadPool.submit(runnable);
}
}
}
lambda被我替换成下面这段,应该也是可以的 lambda学习链接
threadPool.submit(new Runnable() {
@Override
public void run() {
InputStream inputStream = null;
try {
System.out.println("线程开启");
inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
// 注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(new String(bytes, 0, len, "UTF-8"));
}
System.out.println("get message from client: " + sb);
inputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});