网络编程套接字: 是操作系统给应用程序提供的API(socket API), socket API是应用层和传输层沟通的渠道. 传输层的核心协议有 UDP协议, TCP协议, 对应的也有
流套接字:使用传输层TCP协议,
数据报套接字:使用传输层UDP协议
两者的区别也不小:
UDP API比较简单, 主要涉及到两个类:
回显服务就是请求是啥,返回的响应就是啥. 整个回显服务可以分成两部分:
// 创建 UDP 服务器,首先要打开一个 socket 文件
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
启动服务器:
public class UdpEchoServer {
// 创建 UDP 服务器,首先要打开一个 socket 文件
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
//启动服务器
public void start() throws IOException{
System.out.println("服务器启动");
while(true){
//1. 读取客户端发来的请求
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
//2. 对请求进行解析, 把DatagrapPacket 转成一个 String
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
//3. 根据请求 处理响应,单独搞个方法
String response = process(request);
// 4. 把响应构造成 DatagramPacket 对象
// 构造响应对象, 要搞清楚, 对象要发给谁 谁给发的请求,就把响应发给谁
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length
,requestPacket.getSocketAddress());
// 5. 把这个DatagramPacket 对象返回给客户端
socket.send(responsePacket);
System.out.printf("[%s:%d] req=%s; resp=%s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),
request,response);
}
}
//通过这个方法,实现根据请求计算响应 这个过程
//回显服务器 ,不涉及到其他逻辑
//其他服务器, 可以在process里 加上其他逻辑处理
public String process(String req){
return req;
}
public static void main(String[] args) throws IOException {
//真正启动服务器 ,端口号是随便写 范围 0->65535
// 一般来说 1024 以下的端口 系统保留
//因此自己写代码 尽量选择 1024以上 65535以下
UdpEchoServer server = new UdpEchoServer(8000);
server.start();;
}
}
客户端的端口号, 一般无需自己指定. 操作系统会自己分配端口号.
public class UdpEchoClient {
private DatagramSocket socket = null;
public UdpEchoClient() throws SocketException {
// 客户端的端口号,一般由操作系统自动分配
socket = new DatagramSocket();
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true){
// 1. 让客户端从控制台读取一个请求数据
System.out.print("> ");
String request = scanner.next();
// 2. 把这个字符串请求发送给服务器, 构造 DatagramPacket
// 构造的 Packet 既要包含 要传输的数据 又要包含把数据发到哪里
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName("127.0.0.1"),8000);
// 3. 把数据报 发给服务器
socket.send(requestPacket);
// 4. 从服务器读取响应数据
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
// 5. 把响应数据获取出来,转成转成字符串
String response = new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.printf("res: %s; resp: %s\n",request,response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient();
client.start();
}
}
展示效果:
启动服务器:

启动客户端:

效果:


通过上述基础的回显服务,我们可以加上一些业务逻辑,例如实现一个字典翻译:
public class UdpDictServer extends UdpEchoServer{
private Map<String,String> dict = new HashMap<>();
public UdpDictServer(int port) throws SocketException {
super(port);
// 数据可以无限的构造下去
dict.put("cat","小猫");
dict.put("dog"," 小狗");
}
// 和 UdpEchoServer 相比, 只是 process 不同,就重写这个方法就可
@Override
public String process(String req){
return dict.getOrDefault(req,"该词未收录");
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpDictServer(8000);
server.start();
}
}
展示效果:

TCP是以字节流为单位来传输的, TCP主要涉及到两个类:
建立连接
因为TCP是有连接的, 所以我们要先通过 socket的accept()方法来建立连接.
public void start() throws IOException {
System.out.println("服务器启动");
while (true) {
//如果当前没有客户端佬建立连接, 就会阻塞等待
Socket clientSocket = serverSocket.accept();
processConnect(clientSocket);
}
}
处理请求
// 通过这个方法 给当前连上的客户端 提供服务
// 一个连接过来 服务方式 两种
// 1. 一个连接只进行一次数据交互(一个请求 一个响应) 短 连接
// 2. 一个连接进行多次数据交互 (n个请求 n个响应) 长连接
//此处来写 长连接版本
public void processConnect(Socket clientSocket) throws IOException{
System.out.printf("[%s:%d] 建立连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
// 长连接的写法,需要通过 循环 来获取多次交互情况
while(true){
if(!scanner.hasNext()){
// 当客户端断开连接的时候, hasNext 返回 false
System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
// 1. 读取请求并解析
String request = scanner.next();
// 2. 根据请求计算响应
String response = process(request);
// 3. 把响应写回客户端
printWriter.println(response);
// 刷新缓冲区,避免数据没有发出去
printWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\n",
clientSocket.getInetAddress().toString(),clientSocket.getPort(),
request,response);
}
}finally {
//加在此处
clientSocket.close();
}
}
服务端整体代码:
public class TcpEchoServer {
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true) {
//如果当前没有客户端佬建立连接, 就会阻塞等待
Socket clientSocket = serverSocket.accept();
processConnect(clientSocket);
}
}
// 通过这个方法 给当前连上的客户端 提供服务
// 一个连接过来 服务方式 两种
// 1. 一个连接只进行一次数据交互(一个请求 一个响应) 短 连接
// 2. 一个连接进行多次数据交互 (n个请求 n个响应) 长连接
//此处来写 长连接版本
public void processConnect(Socket clientSocket) throws IOException{
System.out.printf("[%s:%d] 建立连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
// 长连接的写法,需要通过 循环 来获取多次交互情况
while(true){
if(!scanner.hasNext()){
// 当客户端断开连接的时候, hasNext 返回 false
System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
// 1. 读取请求并解析
String request = scanner.next();
// 2. 根据请求计算响应
String response = process(request);
// 3. 把响应写回客户端
printWriter.println(response);
// 刷新缓冲区,避免数据没有发出去
printWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\n",
clientSocket.getInetAddress().toString(),clientSocket.getPort(),
request,response);
}
}finally {
//加在此处
clientSocket.close();
}
}
public String process(String req){
return req;
}
public static void main(String[] args) throws IOException{
TcpEchoServer server = new TcpEchoServer(8000);
server.start();
}
}
使用Socket构造对象. 客户端的socket不用绑定端口号:
public class TcpEchoClient {
private Socket socket = null;
public TcpEchoClient() throws IOException {
// new对象 需要和服务器建立连接
//建立连接 需要知道服务器在哪里
socket = new Socket("127.0.0.1",8000);
}
public void start() throws IOException {
// 实现长连接,一个连接处理 N 个 请求和响应
Scanner scanner = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
Scanner scannerNet = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while(true){
// 1.从控制台读取用户的输入
System.out.println("> ");
String request = scanner.next();
// 2. 将请求发送给服务器
printWriter.println(request);
printWriter.flush();
// 3. 从服务器读取响应
String response = scannerNet.next();
// 4. 将结果显示到界面上
System.out.printf("req: %s; resp: %s \n", request,response);
}
}
}
public static void main(String[] main) throws IOException {
TcpEchoClient tcpEchoClient = new TcpEchoClient();
tcpEchoClient.start();
}
}
效果展示
服务端:

客户端:

上面的版本只能一次连接一个客户端, 我们可以用多线程来完成连接多个客户端版本:
只修改start部分:
public class TcpEchoServer {
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while(true){
Socket clientSocket = serverSocket.accept();
//[版本2] 多线程版本, 主线程负责拉客, 新线程负责通信
//虽然有提升, 但是涉及到频繁创建销毁线程,在高并发情况下,负担较重
Thread t = new Thread(() ->{
try {
processConnect(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
});
t.start();
}
}
// 通过这个方法 给当前连上的客户端 提供服务
// 一个连接过来 服务方式 两种
// 1. 一个连接只进行一次数据交互(一个请求 一个响应) 短 连接
// 2. 一个连接进行多次数据交互 (n个请求 n个响应) 长连接
//此处来写 长连接版本
public void processConnect(Socket clientSocket) throws IOException{
System.out.printf("[%s:%d] 建立连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
// 长连接的写法,需要通过 循环 来获取多次交互情况
while(true){
if(!scanner.hasNext()){
// 当客户端断开连接的时候, hasNext 返回 false
System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
// 1. 读取请求并解析
String request = scanner.next();
// 2. 根据请求计算响应
String response = process(request);
// 3. 把响应写回客户端
printWriter.println(response);
// 刷新缓冲区,避免数据没有发出去
printWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\n",
clientSocket.getInetAddress().toString(),clientSocket.getPort(),
request,response);
}
}finally {
//加在此处
clientSocket.close();
}
}
public String process(String req){
return req;
}
public static void main(String[] args) throws IOException{
TcpEchoServer server = new TcpEchoServer(8000);
server.start();
}
}
效果显示:
打开两个客户端
客户端1:

客户端2:

服务端:

public class TcpEchoServer {
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
ExecutorService service = Executors.newCachedThreadPool();
while(true){
Socket clientSocket = serverSocket.accept();
// 使用线程池,来解决频繁创建销毁线程的问题
//此处不太适合使用"固定个数的"
service.submit(new Runnable(){
@Override
public void run() {
try{
processConnect(clientSocket);
}catch(IOException e){
e.printStackTrace();
}
}
});
}
}
// 通过这个方法 给当前连上的客户端 提供服务
// 一个连接过来 服务方式 两种
// 1. 一个连接只进行一次数据交互(一个请求 一个响应) 短 连接
// 2. 一个连接进行多次数据交互 (n个请求 n个响应) 长连接
//此处来写 长连接版本
public void processConnect(Socket clientSocket) throws IOException{
System.out.printf("[%s:%d] 建立连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
// 长连接的写法,需要通过 循环 来获取多次交互情况
while(true){
if(!scanner.hasNext()){
// 当客户端断开连接的时候, hasNext 返回 false
System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
// 1. 读取请求并解析
String request = scanner.next();
// 2. 根据请求计算响应
String response = process(request);
// 3. 把响应写回客户端
printWriter.println(response);
// 刷新缓冲区,避免数据没有发出去
printWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\n",
clientSocket.getInetAddress().toString(),clientSocket.getPort(),
request,response);
}
}finally {
//加在此处
clientSocket.close();
}
}
public String process(String req){
return req;
}
public static void main(String[] args) throws IOException{
TcpEchoServer server = new TcpEchoServer(8000);
server.start();
}
}