网络编程
网络通信的基本模式
- Client——Server(CS):客户端——服务器
- Browser——Server(BS):浏览器——服务器
网络通信的三要素
IP地址
-
IP:全程“互联网协议地址”,是分配给上网设备的唯一标志
-
常见的IP分类:IPv4(32bit4字节点分十进制)和IPv6(128位16个字节冒分16进制)
-
IP地址:设备在网络中的地址,是唯一标识
-
IP地址形式:
-
公网地址和私有地址(局域网使用)
-
192.168.开头的就是常见的局域网地址,范围为192.168.0.0——192.168.255.255,专门为组织机构内部使用
-
-
IP常用命令
- ipconfig:查看本机IP地址
- ping IP地址,检查网络是否连通
-
特殊IP地址
- 本机IP:172.0.0.1或localhost:称为回送地址也可称为本地回环地址,只会寻找当前所在本机
-
InetAddress
-
此类表示Internet协议(IP)地址
-
InetAddress API如下
-
名称 说明 public static InetAddress getLocalHost() 返回本主机的地址对象 public static Inet Address getByName(String host) 得到指定主机的IP对象,参数是域名或者IP地址 public String getHostName() 获取此IP主机名 public String getHostAddress() 返回IP地址字符串 public boolean isReachable(int timeout) 在指定毫秒内连通该IP地址对应的主机,连通返回true
-
端口
-
端口:应用程序在设备中的唯一标识
-
端口号:标识正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是:0~65535
-
端口类型:
- 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
- 注册端口:1024~49151,分配给用户进程或某些应用程序(Tomcat占用8080,Mysql占用3306)
- 动态端口:49125~65535,是因为它一般不固定分配某种进程,而是动态分配。
协议
- 协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议
OSI参考模型 | TCP/IP参考模型 | 各层对应 | 面向操作 |
---|---|---|---|
应用层 | 应用层 | HTTP、FTP、DNS、SMTP | 应用程序需要关注的:浏览器,邮箱,程序员一般在这一层开发 |
表示层 | 应用层 | ||
会话层 | 应用层 | ||
传输层 | 传输层 | TCP、UDP | 选择使用TCP、UDP |
网络层 | 网络层 | IP、ICMP | 封装源和目标IP,进行路径选择 |
数据链路层 | 数据链路层+物理层 | 物理寻址、比特流 | 物理设备中传输 |
物理层 | 数据链路层+物理层 |
-
TCP协议特点:
- 使用TCP协议,必须双方先建立连接,他是一种面向连接的可靠通信协议
- 传输前,采用“三次握手”的方式建立连接,所以是可靠的
- 在连接中可进行大量数据的传输
- 连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低
-
TCP通讯场景
- 对信息安全要求较高的场景,例如:文件下载,金融等数据通信
-
UDP协议
-
用户数据报协议
-
UDP是一种无连接、不可靠传输的协议
-
将数据源IP、目的地IP和端口封装成数据包,不需要建立连接
-
每个数据包的大小限制在64KB内
-
发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
-
可以广播发送,发送数据结束时无需释放资源,开销小,速度快
-
-
UDP协议通信场景:
- 语音通话,视频会话
UDP通信
DatagramPacket:数据包对象
构造器 | 说明 |
---|---|
public DatagramPacket(byte[] buf,int length,InetAddress address,int port) | 创建发送端数据包对象;buf:要发送的内容,字节数组;length:要发送内容的字节长度;address:接收端的IP地址对象;port:接收端的端口号 |
public DatagramPacket(byte[] buf,int length) | 创建接收端的数据包对象;buf:用来存储接收的内容;length:能够接受内容的长度 |
DatagramSocket:发送端和接收端对象
构造器 | 说明 |
---|---|
public DatagramSocket() | 创建发送端的Socket的对象,系统会随机分配一个端口号 |
public DatagramSocket(int port) | 创建接收端的Socket对象并指定端口号 |
DatagramSocket类成员方法
方法 | 说明 |
---|---|
public void send (DatagramPacket dp) | 发送数据包 |
public void receive(DatagramPacket p) | 接收数据包 |
一发一收
package com.wly.InterAddress;
//发送端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class ClientDemo1 {
//创建发送端对象
public static void main(String[] args) throws Exception {
System.out.println("客户端启动");
DatagramSocket socket=new DatagramSocket();
//创建一个数据包对象封装数据
//参数一:封装要发送的数据
byte[] buffer="===给你数据======".getBytes();
//参数二:发送数据的大小
//参数三:服务端的ip地址
//参数四:服务端端口号
DatagramPacket packet=new DatagramPacket(buffer,buffer.length,InetAddress.getLocalHost(),8888);
// public DatagramPacket(byte buf[], int length,
// InetAddress address, int port) {
// this(buf, 0, length, address, port);
// }
//发送数据出去
socket.send(packet);
socket.close();
}
}
package com.wly.InterAddress;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ServerDemo1 {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动");
//创建接收端对象
DatagramSocket socket=new DatagramSocket(8888);
//创建一个数据包接受数据
byte[] buffer=new byte[1024*64];
DatagramPacket packet=new DatagramPacket(buffer,buffer.length);
//接收数据
socket.receive(packet);
//取出数据
int len=packet.getLength();//获取数据长度
String rs=new String(buffer,0,len);//指定长度
System.out.println("收到了"+rs);
//获取发送端的ip和端口
String ip=packet.getSocketAddress().toString();
System.out.println("对方地址"+ip);
//关闭资源
socket.close();
}
}
多发多收
- 客户端
- 创建DatagramSocket对象(发送端对象)
- 使用while死循环不断地接收用户的数据输入,如果用户输入exit则退出
- 如果用户输入的不是exit,把数据封装成DatagramPacket
- 使用DatagramSocket对象的send方法将数据包对象进行发送
- 接收端
- 创建DatagramSocket对象并指定端口(接收端对象)
- 创建DatagramPacket对象接受数据(数据包对象)
- 使用while死循环不断执行第四步
- 使用DatagramSocket对象的receive方法传入DatagramPacket对象
package com.wly.UDP;
//发送端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class ClientDemo2 {
//创建发送端对象
public static void main(String[] args) throws Exception {
System.out.println("客户端启动");
DatagramSocket socket=new DatagramSocket();
Scanner sc=new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg=sc.nextLine();
//输入exit释放资源
if ("exit".equals(msg)){
System.out.println("离线成功");
socket.close();
break;
}
byte[] buffer=msg.getBytes();
DatagramPacket packet=new DatagramPacket(buffer,buffer.length,InetAddress.getLocalHost(),8888);
//发送数据出去
socket.send(packet);
}
}
}
package com.wly.UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ServerDemo2 {
public static void main(String[] args) throws IOException {
System.out.println("服务端启动");
//创建接收端对象
DatagramSocket socket=new DatagramSocket(8888);
//创建一个数据包接受数据
byte[] buffer=new byte[1024*64];
DatagramPacket packet=new DatagramPacket(buffer,buffer.length);
//接收数据
while (true) {
socket.receive(packet);
//取出数据
int len=packet.getLength();//获取数据长度
String rs=new String(buffer,0,len);//指定长度
System.out.println("收到了"+packet.getAddress()+"端口是:"+packet.getPort()+"的消息:"+rs);
}
}
}
UDP的三种通信方式
- 单播:单台主机与单台主机之间的通信
- 广播:当前主机与所在网络中的所有主机通信
- 发送端发送的数据包的目的地写的是广播地址,且指定端口
- 本机所在网段的其他主机的程序只要匹配端口成功即就可以收到消息了
- 组播:当前主机与选定一组主机的通信
- 使用组播地址:224.0.0.0~239.255.255.255
- 发送端的数据包的目地的是组播ip(例如:224.0.1.1,端口:9999)
- 接收端必须绑定该组播ip(224.0.1.1),端口还要对应发送端的目的端口9999,这样即可接
- 受该组播消息
- DatagramSocket的子类MulticastSocket可以在接收端绑定组播ip
TCP通信
Socket
构造器 | 说明 |
---|---|
public Socket(String host,int port) | 创建发送端的Socket对象,参数为服务端程序的ip和端口 |
Socket类成员方法
方法 | 说明 |
---|---|
OutputStream getOutputStream() | 获得字节输出流对象 |
InputStream getInputStream() | 获得字节输入流对象 |
客户端
- 创建客户端的Socket对象,请求与服务端的连接
- 使用socket对象调用getOutputStream()方法得到字节输出流
- 使用字节输出流完成数据的发送
- 释放资源:关闭socket管道
ServerSocket(服务端)
构造器 | 说明 |
---|---|
public ServerSocket(int port) | 注册服务端端口 |
ServerSocket类成员方法
方法 | 说明 |
---|---|
public Socket accept() | 等待接受客户端的Socket通信连接;连接成功返回Socket对像与客户端建立端到端通信 |
TCP通信基本原理
- 客户端怎么发,服务器端怎么收
- 客户端如果没有消息,服务器端进入阻塞等待
- Socket一方关闭或出现异常,对方Socket也会失效或出错
一发一收
-
使用死循环控制服务端收完消息继续等待接受下一个消息
-
客户端也可以使用死循环等待用户不断输入消息
-
客户端一旦输入exit,则关闭客户端程序,并释放资源
-
package com.wly.TCP; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.net.Socket; //完成Socket网络编程,实现一发一收 public class ClientSocketDemo1 { public static void main(String[] args) { System.out.println("====客户端"); //创建Socket通信管道请求有服务端的连接 //参数1:服务端的IP地址 //参数2:服务端的端口 try { Socket socket=new Socket("127.0.0.1",7777); //从Socket通信管道得到一个字节输出流,负责发送数据 OutputStream os=socket.getOutputStream(); //把低级的字节流包装成打印流 PrintStream ps=new PrintStream(os); //发送消息 ps.println("我是客户端,我已经与你对接并发出邀请"); ps.flush(); // socket.close();//不使用的情况下关闭管道 } catch (IOException e) { e.printStackTrace(); } } }
-
package com.wly.TCP; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; //开发Socket网路编程服务端,实现消息接受 public class ServerSocketDemo1 { public static void main(String[] args) { //注册端口 ServerSocket serverSocket; try { System.out.println("服务端启动"); serverSocket = new ServerSocket(7777); //调用accept方法:等待接受客户端的Socket连接请求,建立Socket通信管道 Socket socket=serverSocket.accept(); //从socket通信管道中得到一个字节输入流 InputStream is=socket.getInputStream(); //把字节输入流包装成缓冲字符输入流进行消息的接受 BufferedReader br=new BufferedReader(new InputStreamReader(is)); String msg; if((msg=br.readLine())!=null){//收到一行消息,一发一收 System.out.println(socket.getRemoteSocketAddress()+"说了:"+msg); } } catch (IOException e) { e.printStackTrace(); } } }
多发多收
package com.wly.TCP;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
//完成Socket网络编程,实现一发一收
public class ClientSocketDemo2 {
public static void main(String[] args) {
System.out.println("====客户端=====");
//创建Socket通信管道请求有服务端的连接
//参数1:服务端的IP地址
//参数2:服务端的端口
try {
Socket socket=new Socket("127.0.0.1",7777);
//从Socket通信管道得到一个字节输出流,负责发送数据
OutputStream os=socket.getOutputStream();
//把低级的字节流包装成打印流
PrintStream ps=new PrintStream(os);
//发送消息
Scanner sc=new Scanner(System.in);
while (true){
System.out.println("请说:");
String msg=sc.nextLine();
ps.println(msg);
ps.flush();
}
// socket.close();//不使用的情况下关闭管道
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.wly.TCP;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
//开发Socket网路编程服务端,实现消息接受
public class ServerSocketDemo2 {
public static void main(String[] args) {
//注册端口
ServerSocket serverSocket;
try {
System.out.println("服务端启动");
serverSocket = new ServerSocket(7777);
//调用accept方法:等待接受客户端的Socket连接请求,建立Socket通信管道
while (true){
Socket socket = serverSocket.accept();
//从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
//把字节输入流包装成缓冲字符输入流进行消息的接受
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine()) != null) {//收到一行消息,一发一收,进入循环
System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
同时处理多个客户端消息(创建子线程处理多个客户端发来的消息)
package com.wly.TCP;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
//完成Socket网络编程,实现一发一收
public class ClientSocketDemo3 {
public static void main(String[] args) {
System.out.println("====客户端=====");
//创建Socket通信管道请求有服务端的连接
//参数1:服务端的IP地址
//参数2:服务端的端口
try {
Socket socket=new Socket("127.0.0.1",7777);
//从Socket通信管道得到一个字节输出流,负责发送数据
OutputStream os=socket.getOutputStream();
//把低级的字节流包装成打印流
PrintStream ps=new PrintStream(os);
//发送消息
Scanner sc=new Scanner(System.in);
while (true){
System.out.println("请说:");
String msg=sc.nextLine();
ps.println(msg);
ps.flush();
}
// socket.close();//不使用的情况下关闭管道
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.wly.TCP;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
//开发Socket网路编程服务端,实现消息接受
public class ServerSocketDemo3 {
public static void main(String[] args) {
//注册端口
ServerSocket serverSocket;
try {
System.out.println("服务端启动");
serverSocket = new ServerSocket(7777);
//调用accept方法:等待接受客户端的Socket连接请求,建立Socket通信管道
while (true) {
Socket socket=serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+"上线了");
//每收到一个客户端Socket管道,交给一个独立的子线程负责读取消息
//开始创建独立线程处理Socket
new ServerReaderThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.wly.TCP;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
//把字节输入流包装成缓冲字符输入流进行消息的接受
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine()) != null) {//收到一行消息,一发一收
//追踪上线远程地址
System.out.println(socket.getRemoteSocketAddress()+"上线了给你发了一条消息");
System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
}
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress()+"下线了");
}
}
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
}
使用线程池优化
package com.wly.TCP;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
//使用线程池优化,实现通信
public class ClientSocketDemo4 {
public static void main(String[] args) {
System.out.println("====客户端=====");
//创建Socket通信管道请求有服务端的连接
//参数1:服务端的IP地址
//参数2:服务端的端口
try {
Socket socket=new Socket("127.0.0.1",7777);
//从Socket通信管道得到一个字节输出流,负责发送数据
OutputStream os=socket.getOutputStream();
//把低级的字节流包装成打印流
PrintStream ps=new PrintStream(os);
//发送消息
Scanner sc=new Scanner(System.in);
while (true){
System.out.println("请说:");
String msg=sc.nextLine();
ps.println(msg);
ps.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.wly.TCP;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
//开发Socket网路编程服务端,实现消息接受
public class ServerSocketDemo4 {
//使用静态变量记住一个静态线程池
private static ExecutorService pool=new ThreadPoolExecutor(3,5,
6, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
//注册端口
ServerSocket serverSocket;
try {
System.out.println("服务端启动");
serverSocket = new ServerSocket(7777);
//调用accept方法:等待接受客户端的Socket连接请求,建立Socket通信管道
while (true) {
Socket socket=serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+"上线了");
// Runnable target=new ServerReaderRunnable(socket);
// pool.execute(target);
pool.execute(new ServerReaderRunnable(socket));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.wly.TCP;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
//把字节输入流包装成缓冲字符输入流进行消息的接受
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine()) != null) {//收到一行消息,一发一收
//追踪上线远程地址
System.out.println(socket.getRemoteSocketAddress()+"上线了给你发了一条消息");
System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
}
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress()+"下线了");
}
}
}