跨主机的进程 to 进程
Server 进程 运行在Linux云服务器(上海)
Client 进程 运行在 我自己的电脑上(自己电脑所在地)
数据的封装过程
os代码对数据进行了封装
观察封装后的数据(抓包工具)
![](https://i-blog.csdnimg.cn/blog_migrate/de794672593b93addd96bb72ff9f038f.png)
数据是被接力到另一台主机上的
![](https://i-blog.csdnimg.cn/blog_migrate/13cfe494c7d78226f4a83a7ca0cc8ac7.png)
网络编程中的业务模型
写死请求
由用户输入单词完成翻译
变成一个命令程序
package udp.dictionary_service;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* @author jiaoer
**/
public class Client {
public static void main(String[] args)throws IOException {
//目前服务器在本机:127.0.0.1
//目前服务器的端口是:8080
DatagramSocket socket = new DatagramSocket(9999);
//目前只发依次请求
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextInt()) {
String word = scanner.nextLine();
//准备发送请求
String request = "我是Java19班的\r\n" + word + "\r\n";
byte[] bytes = request.getBytes("UTF-8");
DatagramPacket sent = new DatagramPacket(
bytes,
0,
bytes.length,
InetAddress.getLoopbackAddress(),
8080
);
socket.send(sent);
//接受方取接受数据
byte[] buf = new byte[1024];
DatagramPacket received = new DatagramPacket(buf, buf.length);
socket.receive(received); //服务器会阻塞
int n = received.getLength();
String response = new String(buf, 0, n, "UTF-8");
System.out.println(response);
}
}
}
TCP流套接字编程
和刚才UDP类似. 实现一个简单的英译汉的功能
ServerSocket API
ServerSocket 是创建TCP服务端Socket的API。
ServerSocket 构造方法:
![](https://i-blog.csdnimg.cn/blog_migrate/e55a25ec461d263f259c7af3c1504fa6.png)
ServerSocket 方法:
![](https://i-blog.csdnimg.cn/blog_migrate/9591f7a5fec07c2ebf6fd7011fd17da0.png)
Socket API
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端
Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据
的。
Socket 构造方法
![](https://i-blog.csdnimg.cn/blog_migrate/3db6f1ec876e8a94bf5f687b2b146e44.png)
Socket 方法:
![](https://i-blog.csdnimg.cn/blog_migrate/52416466e805f18aa437d744cddda536.png)
服务器
package tcp.dictionary_service;
//UDP:无连接 写信
//TCP:有链接 打电话
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Scanner;
/**
* @author jiaoer
**/
public class Server {
private static final HashMap<String,String> map = new HashMap<>();
static {
map.put("apple","苹果");
map.put("banana","香蕉");
}
public static void main(String[] args) throws IOException {
//1.开店(创建 socket)
Log.println("服务器启动,监听在 TCP:8080端口");
ServerSocket serverSocket = new ServerSocket(8080);
//进行循环
while (true) {
//1.接通链接(电话) -- accept
Log.println("等待新的客户端链接上来");
Socket socket = serverSocket.accept();//阻塞
Log.println("有等待新的客户端链接上来");
InetAddress inetAddress = socket.getInetAddress();
Log.println("对方的地址:" + inetAddress);
int port = socket.getPort();
Log.println("对方的端口:" + port);
//is: 用于读数据
InputStream is = socket.getInputStream();
//os:用于写数据
OutputStream os = socket.getOutputStream();
//2.读取请求
Scanner scanner = new Scanner(is,"UTF-8");
String header = scanner.nextLine();//我是Java19班的
//TODO:做请求格式检查
String englishWord = scanner.nextLine();
Log.println("英文单词:" + englishWord);
//3.处理业务
String chineseWord = map.getOrDefault(englishWord,"不认识");
Log.println("中文单词:" + chineseWord);
//4.发送请求
//好的\r\n苹果\r\n
//OutputStream -> OutputStreamWriter -> PrintWriter
OutputStreamWriter writer = new OutputStreamWriter(os,"UTF-8");
PrintWriter printWriter = new PrintWriter(writer);
Log.println("服务器进行发送");
printWriter.printf("好的\r\n%s\r\n", chineseWord);
printWriter.flush(); //不要忘记flush
Log.println("服务器发送成功");
socket.close();
}
}
}
客户端
package tcp.dictionary_service;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
* @author jiaoer
**/
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8080);
InputStream is = socket.getInputStream();
Scanner scanner = new Scanner(is,"UTF-8");
OutputStream os = socket.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(os,"UTF-8");
PrintWriter printWriter = new PrintWriter(writer);
//发送请求
printWriter.printf("我是Java19班的\r\napple\r\n");
printWriter.flush();
//读取响应
String header = scanner.nextLine(); //好的
String word = scanner.nextLine();//苹果
System.out.println(word);
socket.close();
}
}
小Tips:
若在运行服务器时遇到以下情况,则表示创建失败地址已经被使用,说明该端口(TCP8080)已经被其他进程占用了
![](https://i-blog.csdnimg.cn/blog_migrate/591e4d9b74c157b8eedb5dc623cf4223.jpeg)
解决办法有两个:
找到哪个进程占用的此端口,判断这个进程是否重要,如果不重要,就把对应的进程关掉,把端口让出来。
如何找到哪个进程占用的端口
windows上可以这样找:
(1)通过一个命令行程序,明确哪个pid占用的端口
![](https://i-blog.csdnimg.cn/blog_migrate/407eb35801a00e5e373399cf4706a1c9.png)
(2)根据pid,看进程的具体信息 via 任务管理器
![](https://i-blog.csdnimg.cn/blog_migrate/318a5b18ac9a243538e2b6237d3211c1.png)
换个端口重新试试。
长连接 VS 短连接
长连接:拨通电话 -> 请求 -> 响应 -> 请求 -> 响应 -> … -> 请求 -> 响应 -> 挂断电话
长连接模式下,由于TCP是面向字节流的
客户端:[请求1] [请求2] [请求3]
服务器:[请求1 + 请求2前一半] [请求2后一半 + 请求3]
所以在进行应用层协议设计时需要考虑:请求中需要有明确的分界线,帮助服务器划分出不同的请求
具体方法:
1.采用定长的方式:比如,请求的长度固定是十个字节
2.采用变长的方式:(1)先发送长度 + 真正的请求
(2)使用特殊字符区分不同的请求
长连接版本继续进化:客户端的单词来自用户输入,什么时候结束也来自用户输入
package tcp.dictionary_service;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
* @author jiaoer
**/
public class 长连接之用户输入Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8080);//拨号
Scanner systemInScanner = new Scanner(System.in);
while (systemInScanner.hasNextLine()){
String w = systemInScanner.nextLine();
InputStream is = socket.getInputStream();
Scanner socketScanner = new Scanner(is, "UTF-8");
OutputStream os = socket.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(os, "UTF-8");
PrintWriter printWriter = new PrintWriter(writer);
//发送请求
printWriter.printf("我是Java19班的\r\n%s\r\n",w);
printWriter.flush();
//读取响应
String header = socketScanner.nextLine(); //好的
String word = socketScanner.nextLine();//苹果
System.out.println(word);
}
//socket.close();
}
}
短链接:拨通电话 -> 请求 -> 响应 ->挂断电话 -> 拨通电话 -> 请求 -> 响应 ->挂断电话…
TCP与UDP的其中一个区别
TCP:面向字节流 (让A送信,A是一天送一回,可能累计数据一起发送)[abc][def][ghi] -> [abcde][fghi]
UDP:面向数据报文 (让A送信,给到A之后,A立即去送)[abc] -> [abc]