什么是网络编程:
网络通信模式:
IP地址:设备在网络中的地址,是唯一的标识
端口:应用程序在设备中唯一的标识
协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议
IP(Internet Protocol):全称"互联网协议地址",是分配给上网设备的唯一标志,用来定位网络上的设备的
1.常见的IP分类为:IPv4和IPv6
2.IP地址基本寻路:
如果没有在本地dns服务器找到对应的IP地址(比如我这是一台新电脑,或者我第一次访问这个网站)会再将信息发送到运营商的dns服务器上,运营商的肯定有
3.IP地址形式:
4.IP常用命令:
5.特殊IP地址:
java中IP地址操作类:InetAddress:
InetAddress常用API
名称 | 说明 |
---|---|
public static InetAddress getLocalHost() | 返回本机的地址对象 |
public static InetAddress getByName(String host) | 得到指定主机的IP地址对象,参数是域名或者IP地址 |
public String getHostName() | 获取此IP地址的主机名 |
public String getHostAddress() | 返回IP地址字符串 |
public boolean isReachable(int timeout) | 在指定毫秒内连通该IP地址对应的主机,连通返回true |
public class InetAddressDemo01 {
public static void main(String[] args) throws Exception {
// 1.获取本机地址对象。
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());
// 2.获取域名ip对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
// 3.获取公网IP对象。
InetAddress ip3 = InetAddress.getByName("110.242.68.4");
System.out.println(ip3.getHostName());
System.out.println(ip3.getHostAddress());
// 4.判断是否能通: ping 5s之前测试是否可通
System.out.println(ip3.isReachable(5000));
}
}
端口号:标识正在计算机设备上运行的进程(程序),被视为一个16位的二进制,范围是0~65535
端口类型:
1.我们自己开发的程序选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错2.不同设备的相同程序端口号要一样
1.通信协议:
2.网络通信协议有两套参考模型:
OSI参考模型 | TCP/IP参考模型 | 各层对应 | 面向操作 |
---|---|---|---|
应用层 表示层 会话层 | 应用层 | HTTP、FTP、DNS、SMTP… | 应用程序需要关注的:浏览器,邮箱.程序员一般在这一层开发 |
传输层 | 传输层 | TCP、UDP… | 选择使用的TCP,UDP协议 |
网络层 | 网络层 | IP、ICMP… | 封装源和目标IP,进行路径选择 |
数据链路层 物理层 | 数据链路层+物理 | 物理寻址、比特流… | 物理设备中传输 |
3.传输层的2个常见协议
第一个是:TCP(Transmission Control Protocol) :传输控制协议:
第二个是:UDP(User Datagram Protocol):用户数据报协议:
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) | 接收数据包 |
public class ClientDemo1 {
public static void main(String[] args) throws Exception {
System.out.println("=====客户端启动======");
// 1、创建发送端对象:发送端自带默认的端口号(也可以手动设置端口号)
// DatagramSocket socket = new DatagramSocket(6666);
DatagramSocket socket = new DatagramSocket();
// 2、创建一个数据包对象封装数据
/**
public DatagramPacket(byte buf[], int length,
InetAddress address, int port)
参数一:封装要发送的数据(韭菜)
参数二:发送数据的大小
参数三:服务端的主机IP地址
参数四:服务端的端口
*/
byte[] buffer = "我是一颗快乐的韭菜,你愿意吃吗?".getBytes();
DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
InetAddress.getLocalHost() , 8888);
// 3、发送数据出去
socket.send(packet);
socket.close();
}
}
public class ServerDemo2 {
public static void main(String[] args) throws Exception {
System.out.println("=====服务端启动======");
// 1、创建接收端对象:注册端口
DatagramSocket socket = new DatagramSocket(8888);
// 2、创建一个数据包对象接收数据
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 3、等待接收数据。(服务端启动后只要没接收到数据就会一直在这里等待)
socket.receive(packet);
// 4、取出数据即可
// getLength()获取接收数据的长度
int len = packet.getLength();
String rs = new String(buffer,0, len);
System.out.println("收到了:" + rs);
// 获取发送端的ip和端口
String ip =packet.getSocketAddress().toString();//方法返回值是SocketAddress类型,调用其toString()获得对象中的内容
System.out.println("对方地址:" + ip);
int port = packet.getPort();//方法返回值是int类型
System.out.println("对方端口:" + port);
socket.close();
}
}
需求:使用UDP通信方式开发接收端和发送端
分析:
public class ClientDemo1 {
public static void main(String[] args) throws Exception {
System.out.println("=====客户端启动======");
// 1、创建发送端对象:发送端自带默认的端口号
DatagramSocket socket = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
if("exit".equals(msg)){
System.out.println("离线成功!");
socket.close();
break;
}
// 2、创建一个数据包对象封装数据
byte[] buffer = msg.getBytes();
DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
InetAddress.getLocalHost() , 8888);
// 3、发送数据出去
socket.send(packet);
}
}
}
public class ServerDemo2 {
public static void main(String[] args) throws Exception {
System.out.println("=====服务端启动======");
// 1、创建接收端对象:注册端口
DatagramSocket socket = new DatagramSocket(8888);
// 2、创建一个数据包对象接收数据
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
// 3、等待接收数据。
socket.receive(packet);
// 4、取出数据即可
int len = packet.getLength();
String rs = new String(buffer,0, len);
System.out.println("收到了来自:" + packet.getAddress() +", 对方端口是" + packet.getPort() +"的消息:" + rs);
}
}
}
如果我想开多个客户端怎么办呢(前提是new发送端对象时没有设置客户端端口号而是让每个客户端有一个随机的端口号)?
然后点击apply即可(此时会跳出提示,不用管,直接关闭就可以了)
问:UDP的接收端为什么可以接收很多发送端的消息:
UDP的三种通信方式:
等学完计网再回来研究
特点:
那我怎么知道有没有用TCP协议呢:在java中只要是使用java.net.Socket类实现通信,底层即是使用了TCP协议
1.客户端
Socket(客户端)
构造器 | 说明 |
---|---|
public Socket(String host , int port) | 创建发送端的Socket对象与服务端连接,参数为服务端程序的ip和端口 |
Socket类成员方法:
方法 | 说明 |
---|---|
OutputStream getOutputStream() | 获得字节输出流对象 |
InputStream getInputStream() | 获得字节输入流对象 |
2.服务端
ServerSocket(服务端)
构造器 | 说明 |
---|---|
public ServerSocket(int port) | 注册服务端端口 |
ServerSocket类成员方法
方法 | 说明 |
---|---|
public Socket accept() | 等待接受客户端的Socket通信连接,连接成功返回Socket对象与客户端建立端到端通信 |
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求与服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
// 4、发送消息
ps.println("我是TCP的客户端,我已经与你对接,并发出邀请:约吗?");
ps.flush();
// 关闭资源
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// 2、调用accept方法
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
if ((msg = br.readLine()) != null){
//socket.getRemoteSocketAddress()获取客户端ip
System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.第23行socket.close()关闭资源:
2.第18行的println方法:
3.第38行Socket socket = serverSocket.accept():
4.第45行br.readLine():
5.1.TCP通信的基本原理?
需求:使用TCP通信方式实现:多发多收消息
具体要求:
和一发一收区别是两端都多了个死循环,别的没区别,这里就不再展示代码
本案例实现了多发多收,那么是否可以同时接收多个客户端的消息?
如何才能让服务端可以处理多个客户端的通信需求:引入多线程
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 3、开始创建独立线程处理socket
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
//监视客户端是否下线(因为下线后这一条线程会出错,我们捕获异常后打印输出哪个客户端下线了)
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求与服务端的连接
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
目前的通信架构存在什么问题:
public class ServerDemo2 {
// 使用静态变量记住一个线程池对象
private static ExecutorService pool = new ThreadPoolExecutor(1,
1, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1)
, Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(6666);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 任务对象负责读取消息。
Runnable target = new ServerReaderRunnable(socket);
pool.execute(target);
}
} catch (Exception e) {
System.out.println("别来了,已经满了");
}
}
}
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求与服务端的连接
Socket socket = new Socket("127.0.0.1", 6666);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
本次使用线程池的优势在哪里:
即时通信是什么含义,要实现怎么样的设计:
public class ServerDemo2 {
public static List<Socket> onLineSockets = new ArrayList<>();
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(6868);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 把当前客户端管道Socket加入到在线集合中去
onLineSockets.add(socket);
// 3、开始创建独立线程处理socket
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
// 把这个消息进行端口转发给当前所有在线socket管道
sendMsgToAll(msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
// 从在线集合中抹掉本客户端socket
ServerDemo2.onLineSockets.remove(socket);
}
}
private void sendMsgToAll(String msg) {
try {
// 遍历全部的在线 socket给他们发消息
for (Socket onLineSocket : ServerDemo2.onLineSockets) {
// 除了自己的socket,其他socket我都发!!
if(onLineSocket != socket){
PrintStream ps = new PrintStream(onLineSocket.getOutputStream());
ps.println(msg);
ps.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 6868);
// 马上为客户端分配一个独立的线程负责读取它收到的消息
new ClientReaderThread(socket).start();
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ClientReaderThread extends Thread{
private Socket socket;
public ClientReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "收到了: " + msg);
}
} catch (Exception e) {
System.out.println("服务端把你踢出去了~~");
}
}
}
总结:即时通信是什么含义,要实现怎么样的设计?
之前的客户端都是什么样的:
BS结构是什么样的,需要开发客户端吗:
浏览器与服务端建立Socket管道,服务端将这些管道封装成任务交给线程池处理,最后响应网页给浏览器显示,注意:服务器必须给浏览器响应HTTP协议格式的数据,否则浏览器不识别
HTTP响应数据的协议格式:就是给浏览器显示的网页信息,格式如下:
服务器给浏览器响应数据时必要严格按照上图编写代码:
ps.println("HTTP/1.1 200 OK"); // 协议类型和版本 响应成功的消息!
ps.println("Content-Type:text/html;charset=UTF-8"); // 响应的数据类型:文本/网页
ps.println(); // 必须发送一个空行
// 才可以响应数据回去给浏览器
ps.println("《最牛的149期》 ");
完整案例代码:
public class BSserverDemo {
// 使用静态变量记住一个线程池对象
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) {
try {
// 1.注册端口
ServerSocket ss = new ServerSocket(8080);
// 2.创建一个循环接收多个客户端的请求。
while(true){
Socket socket = ss.accept();
// 3.交给一个独立的线程来处理!
pool.execute(new ServerReaderRunnable(socket));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 浏览器 已经与本线程建立了Socket管道
// 响应消息给浏览器显示
PrintStream ps = new PrintStream(socket.getOutputStream());
// 必须响应HTTP协议格式数据,否则浏览器不认识消息
ps.println("HTTP/1.1 200 OK"); // 协议类型和版本 响应成功的消息!
ps.println("Content-Type:text/html;charset=UTF-8"); // 响应的数据类型:文本/网页
ps.println(); // 必须发送一个空行
// 才可以响应数据回去给浏览器
ps.println("《最牛的Chinese》 ");
ps.close();
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}