可以让设备中的程序与网络上其他设备的程序进行数据交互
(这里只做简单介绍,可能有不准确的地方,建议速速学习计算机网络)
基本的通信架构有2种形式:CS架构、BS架构
1)CS架构(Client客户端/Server服务端)
需要开发客户端,与服务端建立网络通信
2)BS架构(Browser浏览器/Server服务端)
基于浏览器开发页面,与服务端建立网络通信
设备在网络中的地址,是唯一标识,它有两种形式:
1)IPv4:32 bit (4字节),用点分十进制表示,如 192.168.1.66
2)IPv6:128 bit,用冒分十六进制表示,如 2001:0db8:0000:0023:0008:0800:200c:417a
IP 域名:用于向服务器获取真实的IP地址,如 http://www.baidu.com
公网:可以连接互联网的IP地址
内网IP:也叫局域网IP,只能组织机构内部使用,192.168.开头的就是常见的局域网地址,范围即为 192.168.0.0 -- 192.168.255.255
127.0.0.1、localhost:代表本机IP,只会寻找当前所在的主机
IP常用命令:ipconfig:查看本机IP地址
ping IP地址:检查网络是否连通
public class Text {
public static void main(String[] args) throws Exception {
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostAddress());// 10.3.109.76
System.out.println(ip1.getHostName()); // LAPTOP-LDQI2FTF
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostAddress());// 110.242.68.3
System.out.println(ip2.getHostName()); // www.baidu.com
System.out.println(ip2.isReachable(2000)); // true
}
}
应用程序在设备中的唯一标识,被规定为一个16位的二进制,范围是0~65535
周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21 )
注册端口:1024~49151,分配给用户进程或某些应用程序。
动态端口:49152到65535,它一般不固定分配某种进程,而是动态分配。
注意:开发程序一般选择注册端口,且一个设备中不能出现两个程序端口号一样,否则出错
连接规则和传输数据的规则
OSI网络参考模型:全球网络互联参考标准,TCP/IP网络模型:事实上的国际标准
UDP(User Datagram Protocol):用户数据报协议
特点:无连接、不可靠通信、通信效率高
1)不事先建立连接,数据按照包发,一包数据包含:本机IP和程序端口,目的地IP、程序端口和数据(限制64KB)等
2)发送方不管对方是否在线,数据在中间丢失也不管,接收方收到数据也不返回确认,是不可靠的
应用:语音通话、视频直播、游戏
1)客户端(发送端)实现步骤:
① 创建 DatagramSocket 对象(客户端对象)
② 创建 DatagramPacket 对象封装需要发送的数据(数据包对象)
③ 使用 DatagramSocket 对象的send方法,传入 DatagramPacket 对象
④ 释放资源
public class Client {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(6666);
/*public DatagramPacket(byte[] buf, int length, InetAddress address, int port)*/
byte[] bytes = "nmmd,小毕123".getBytes();
DatagramPacket packet = new DatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),7777);
System.out.println("客户端开始发送信息");
socket.send(packet);
System.out.println("客户端已发送信息");
socket.close();
}
}
注意:创建 DatagramPacket 对象时要多注意那四个参数
2)服务端(接收端)实现步骤:
①创建 DatagramSocket 对象并指定端口 (服务端对象)
②创建 DatagramPacket 对象接收数据 (数据包对象)
③使用 DatagramSocket 对象的 receive 方法,传入 DatagramPacket 对象
④释放资源
public class Server {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(7777);
byte[] bytes = new byte[1024*32];
DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
System.out.println("服务端开始接收信息");
socket.receive(packet);
System.out.println(new String(bytes,0,packet.getLength()));
System.out.println(packet.getAddress().getHostAddress());
System.out.println(packet.getPort());
System.out.println("服务端已接收信息");
socket.close();
}
}
注意:1)在输出接收的字节数组时,要注意打印相对应的字节数组长度,不然可能会乱码
2)可以调用 DatagramPacket 的 getAddress() 和 getPort() 方法来获取客户端IP的InetAddress 对象和它的端口号
1)客户端(发送端)
public class Client {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket();
System.out.println("---客户端开始发送信息---");
Scanner sc = new Scanner(System.in);
while (true) {
System.out.print("请输入:");
String msg = sc.nextLine();
if(msg.equals("exit")){
break;
}
byte[] bytes = msg.getBytes();
DatagramPacket packet = new DatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),7777);
socket.send(packet);
}
System.out.println("---客户端结束发送信息---");
socket.close();
}
}
2)服务端(接收端)
public class Server {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(7777);
byte[] bytes = new byte[1024*32];
DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
System.out.println("---服务端开始接收信息---");
while (true) {
socket.receive(packet);
System.out.println(new String(bytes,0,packet.getLength()));
System.out.println(packet.getAddress().getHostAddress());
System.out.println(packet.getPort());
System.out.println("------------------------");
}
}
}
TCP(Transmission Control Protocol):传输控制协议
特点:面向连接、可靠通信、通信效率相对不高
目的:要保证在不可靠的信道上实现可靠的传输
原理:三次握手建立连接,传输数据进行确认,四次挥手断开连接
应用:网页,文件下载,支付
客户端 服务端
1)客户端
①创建客户端的 Socket 对象,请求与服务端的连接
②使用 socket 对象调用 getOutputStream() 方法得到字节输出流
③使用字节输出流完成数据的发送
④释放资源:关闭字节流和 socket 管道
public class Client {
public static void main(String[] args) throws Exception {
Socket socket = new Socket(InetAddress.getLocalHost().getHostName(),8888);
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
System.out.println("---客户端开始传输信息---");
dos.writeUTF("我爱你");
System.out.println("---客户端结束传输信息---");
dos.close();
socket.close();
}
}
注意:这里用到了数据字节流,方便传输和接收数据
2)服务端
① 创建 ServerSocket 对象,注册服务端端口。
② 调用 ServerSocket 对象的 accept() 方法,等待客户端的连接,并得到 Socket 管道对象。
③ 通过 Socket 对象调用 getInputStream() 方法得到字节输入流,完成数据的接收
④释放资源:关闭字节流和 socket 管道
public class Server {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("---服务端开始接收信息---");
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
System.out.println(dis.readUTF());
System.out.println(socket.getRemoteSocketAddress());
System.out.println("------------------------");
}
}
public class Client {
public static void main(String[] args) throws Exception {
Socket socket = new Socket(InetAddress.getLocalHost().getHostName(),8888);
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
System.out.println("---客户端开始传输信息---");
Scanner sc = new Scanner(System.in);
while(true){
System.out.print("请输入:");
String msg = sc.nextLine();
if(msg.equals("exit")){
break;
}
dos.writeUTF(msg);
dos.flush();
}
System.out.println("---客户端结束传输信息---");
dos.close();
socket.close();
}
}
public class Server {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("---服务端开始接收信息---");
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while(true){
try {
String msg = dis.readUTF();
System.out.println(msg);
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress()+"离线了");
System.out.println("---服务端结束接收信息---");
dis.close();
socket.close();
break;
}
}
}
}
注意:当客户端停止运行,管道断开,服务端会报异常,此时要捕获异常,然后关闭服务端
利用多线程,创建多个管道,每一个管道的传输接收都交给一个子线程去处理
原理:1)多个客户端与服务端建立 socket 管道
2)将客户端信息打包成集合,统一传输到服务端
3)将客户端的 socket 对象交给各个子线程处理
4)将接收的每一条信息通过服务端发送到所有客户端
5)客户端创建子线程来接收信息并输出
public class Server {
public static List sockets = new ArrayList<>();
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("---服务端开始接收信息---");
while (true) {
Socket socket = serverSocket.accept();
sockets.add(socket);
System.out.println(socket.getRemoteSocketAddress()+"上线了");
new ServerReaderThread(socket).start();
}
}
}
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while(true){
try {
String msg = dis.readUTF();
System.out.println(socket.getRemoteSocketAddress()+"输出:"+msg);
sendMsgToAll(msg);
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress()+"离线了");
Server.sockets.remove(socket);
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void sendMsgToAll(String msg) throws Exception {
for(Socket socket:Server.sockets){
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(msg);
dos.flush();
}
}
}
public class Client {
public static void main(String[] args) throws Exception {
Socket socket = new Socket(InetAddress.getLocalHost().getHostName(),8888);
new ClientReaderThread(socket).start();
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
System.out.println("---"+socket.getLocalPort()+"(本端)开始传输信息---");
Scanner sc = new Scanner(System.in);
while(true){
String msg = sc.nextLine();
if(msg.equals("exit")){
break;
}
dos.writeUTF(msg);
dos.flush();
}
System.out.println("---"+socket.getLocalPort()+"(本端)结束传输信息---");
dos.close();
socket.close();
}
}
public class ClientReaderThread extends Thread{
private Socket socket;
public ClientReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while(true){
try {
String msg = dis.readUTF();
System.out.println("输出:"+msg);
} catch (Exception e) {
System.out.println(socket.getLocalPort()+"(本端)离线了");
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
注意:想要传入数据,需要按照HTTP协议格式书写(这里只简单介绍)
public class Server {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("---服务端开始接收信息---");
while (true) {
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+"上线了");
new ServerReaderThread(socket).start();
}
}
}
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type:text/htmL;charset=UTF-8");
ps.println(); // 必须换行
ps.println("你真是666");
ps.close();
socket.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
注意:在高并发的场景下,一个服务器请求创建一个线程消耗资源太大,所以应该使用线程池
1)实现 socket 对象 Runnable 化
2)创建线程池来处理
public class Server {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8080);
ThreadPoolExecutor pool = new ThreadPoolExecutor(16*2,16*2,0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(16), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
System.out.println("---服务端开始接收信息---");
while (true) {
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+"上线了");
Runnable target = new ServerReaderRunnable(socket);
pool.execute(target);
}
}
}
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type:text/htmL;charset=UTF-8");
ps.println(); // 必须换行
ps.println("你真是666");
ps.close();
socket.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
注意:这个 ServerReaderRunnable 和之前的 ServerReaderThread 效果一样,未做修改
(此节属于Javaweb的开篇,一些内容这里不再介绍)