- 1.UDP
- 1.1 网络编程
- 1.2 怎么查看进程的端口号
- 1.2 InetAddress
- 1.3 端口协议:
- 1.4 UDP协议
- 1.5 UDP 数据交互代码演示
- 2.TCP
- 2.1 TCP 三次握手
- 2.2 TCP四次挥手
- 2.2 基本代码演示
- 2.3 TCP发送端 接收端 使用多种方式实现字符串的传输和就收
- 2.4 TCP 实现图片的传输,优化后结合多线程
- 3 注意点
网络编程
意义:能在不同的电脑上,传输数据
网络三要素:ip 端口,协议
问题:访问百度首页,本质上是访问百度的服务器
ipv4是点分十进制,如192.168.23.44 ipv6 冒分十六进制。因为ipv4资源枯竭(255255255*255),使用ipv6进行补充( 很多app也支持ipv6 如支付宝app的首页,手机设置中也可以看到)
1.2 怎么查看进程的端口号1.第一步:任务管理器 -->详细信息–>查看pid —>飞秋
第二步:查到飞秋的pid后,在cmd执行netstat -ano 通过pid 对应端口
// static InetAddress getByName(String host)确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
// String getHostName() 获取此IP地址的主机名
//String getHostAddress() 返回文本显示中的IP地址字符串
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
//InetAddress address = InetAddress.getByName("itheima");
InetAddress address = InetAddress.getByName("192.168.1.66");
//public String getHostName():获取此IP地址的主机名
String name = address.getHostName();
//public String getHostAddress():返回文本显示中的IP地址字符串
String ip = address.getHostAddress();
System.out.println("主机名:" + name);
System.out.println("IP地址:" + ip);
}
}
1.3 端口协议:
端口:
设备上应用程序的唯一标识
端口号: 了解:
用两个字节表示的整数,它的取值范围是065535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
如果平常使用udp 或者tcp 建议直接使用10000以上的端口---->避免端口冲突
如:443—https 80–>http 3306—>数据库的端口
无连接通信协议,在通信前不需要建立逻辑连接,所以发送的数据可能丢失,发送数据的时候不管你接不接受得到,发完就不管了,接收端也不会向发送端发送反馈信息。
由于使用UDP协议消耗系统资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
1.5 UDP 数据交互代码演示UDP三种通讯方式分别是什么?
单播(一对一) 单播用于两个主机之间的端对端通信 组播(多对一) 组播用于对一组特定的主机进行通信 广播(一对多)
广播用于一个主机对整个局域网上所有主机上的数据通信
发送数据
public class ClientDemo {
public static void main(String[] args) throws IOException {
//1.找码头--找一个快递员
DatagramSocket ds = new DatagramSocket();
//2.打包礼物
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
String s = "送给村长老丈人的礼物";
byte[] bytes = s.getBytes();
//将ip换成了对象
//指定唯一 一个发送的IP,这是单播,(一对一)
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10000;
// 如 包裹,包裹的重量,包裹的地址,包裹的电话号码
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
//3.由码头发送包裹
ds.send(dp);
//4.付钱走羊
ds.close();
}
}
接收数据
public class ServerDemo {
//注意点:
//1.要先运行接收端,再运行发送端
//2.如果接收端再启动之后,没有接收到数据,那么会死等(阻塞).
//3.在接收数据的时候,需要调用一个getLength方法,表示接收到了多少字节
public static void main(String[] args) throws IOException {
//1.找码头 ---- 表示接收端从10000端口接收数据的.
DatagramSocket ds = new DatagramSocket(10000);
//2,创建一个新的箱子
byte [] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
//3.接收礼物,把礼物放到新的箱子中
System.out.println("-----------接收前----------");
ds.receive(dp);
System.out.println("------------接收后---------");
//4.从新的箱子里面获取礼物
byte[] data = dp.getData();
// 获得数据的长度
int length = dp.getLength();
System.out.println(new String(data,0,length));
//5.拿完走羊
ds.close();
}
}
代码的练习
UDP使用组播发送消息:
package com.ww.udp2;
import java.io.IOException;
import java.net.*;
public class Client {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
String s="大家好本组的成员们";
byte[] bytes = s.getBytes();
int port=10000;
InetAddress address = InetAddress.getByName("224.0.1.2");//设置组播的地址,向在一个组的接收端发消息
DatagramPacket dp = new DatagramPacket(bytes,0,bytes.length,address,port);
ds.send(dp);
ds.close();
}
}
UDP使用组播接收消息:
package com.ww.udp2;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class ServiceZu {
public static void main(String[] args) throws IOException {
MulticastSocket ms = new MulticastSocket(10000);
byte [] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, 0, bytes.length);
//白名单,只能接收这些IP发来的消息
InetAddress address = InetAddress.getByName("224.0.1.2");
InetAddress address2= InetAddress.getByName("224.0.1.3");
InetAddress address3= InetAddress.getByName("224.0.1.4");
ms.joinGroup(address);//将ms加入本组的组播地址,这样只要是加入这个组的接收端都能接收到发送端发来的信息
ms.joinGroup(address2);//将ms加入本组的组播地址,这样只要是加入这个组的接收端都能接收到发送端发来的信息
ms.joinGroup(address3);//将ms加入本组的组播地址,这样只要是加入这个组的接收端都能接收到发送端发来的信息
ms.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data,0,length));
ms.close();
}
}
UDP的广播案例代码实现
发送端
package com.ww.udp;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
//创建码头
DatagramSocket ds = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while (true) {
String s= sc.nextLine();
if ("886".equals(s)){
break;
}
byte[] bytes = s.getBytes();
//"192.168.12.35"
//这里设置的是广播地址,那么这个发送端发送的消息,所有的接收端都能接收,前提是在同一个端口 发送端和接收端要使用同一个端口
//为什么要使用同一个端口,就相当于QQ只能发消息给QQ,不能发给微信,每个软件都有自己的端口号,
// 并且每个人在电脑上的同一款软件,使用的都是默认的端口号,所以同一款软件,在不同的电脑中使用的端口号是相同的,
// 这样就能解释为什么需要端口号相同才能接收到信息了
InetAddress address = InetAddress.getByName("255.255.255.255");
//创建数据
DatagramPacket dp = new DatagramPacket(bytes,0,bytes.length,address,10000);
ds.send(dp);
}
ds.close();
}
}
接收端
package com.ww.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.concurrent.*;
// Base64util.GenerateImage(new String(bytes,0,length),"mz.jpg")
public class Service {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(10000);
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
3,
10,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
//192.168.12.125
for (int i=0;i<15;i++) {
poolExecutor.submit(//这里使用了线程池实现多线程,多次提交任务
new Runnable() {
@Override
public void run() {
Object o =1;
synchronized (o){
byte [] arr = new byte[1024];
DatagramPacket dp = new DatagramPacket(arr,0,arr.length);
try {
System.out.println("准备接收信息...");
ds.receive(dp);//receive()方法会阻塞线程除非接收到数据
System.out.println("接收到信息了....");
} catch (IOException e) {
e.printStackTrace();
}
//从箱子中获取数据
// byte[] data = dp.getData();
int length = dp.getLength();
String hostName = dp.getAddress().getHostName();
System.out.println("来自"+hostName+"小朋友:"+new String(arr,0,length));
}
}
});
}
// poolExecutor.shutdown();
// ds.close();
}
}
2.TCP
2.1 TCP 三次握手
TCP协议:面向连接、通信效率低、数据传输可靠无差错,大小没有限制
2.2 TCP四次挥手 2.2 基本代码演示了解:
tcp协议建立连接时需要经过三次握手确保连接建立。为什么是三次握手呢?
发送端向接收端发送一个连接信息,然后接收端回馈一个消息给发送端。这里就经过了两次握手了,那么发送端可以确定自己发送的消息接收端可以接收到,但是接收端还不能确定自己反馈的消息有没有被发送端接收到,所以发送端接收到接收端的反馈信息后,还需要向接收端发一条消息告诉接收端它的反馈已经被接收。这样就能确定接收端和发送端互相发的消息都能接收到
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务器端的Socket对象(ServerSocket)
//ServerSocket(int port) 创建绑定到指定端口的服务器套接字
ServerSocket ss = new ServerSocket(10000);
//Socket accept() 侦听要连接到此套接字并接受它
Socket s = ss.accept();
//获取输入流,读数据,并把数据显示在控制台
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
String data = new String(bys,0,len);
System.out.println("数据是:" + data);
//释放资源
s.close();
ss.close();
}
}
2.3 TCP发送端 接收端 使用多种方式实现字符串的传输和就收
Client 发送端 发送字符串 读写包含多种方式
package com.ww.tcp;
import java.io.*;
import java.net.Socket;
//使用tcp来发送信息,需要经过三次握手
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",10000);
OutputStream os = socket.getOutputStream();
String s= "嗨喽";
//第一种方式:-------------------------------------
// 使用字节输出流一次写一个字节数组
// os.write(s.getBytes());
//----------------------------------------------
//第二种方式:--------------------------------------------
//使用转换流,将字节输出流,转换成字符输出流,一次写一个字符串
OutputStreamWriter osw = new OutputStreamWriter(os, "utf-8");
osw.write(s);
osw.flush();//这里需要使用flush()将数据从流冲刷出去,这样才能被服务端接收到数据
//因为后面还需要使用到socket所以不能直接使用socket.close,只能使用shutdownOutput(),
// 告诉服务端一个介素标记我已经写完,服务端才能展示读到的数据
socket.shutdownOutput();
//--------------------------------------------------------
//使用缓冲流接收服务端回复的消息
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("服务器的回复信息:"+br.readLine());
//释放资源
socket.close();
}
}
Service 接收端 接收字符串 读写包含多种方式
package com.ww.tcp;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Service {
public static void main(String[] args) throws IOException, InterruptedException {
ServerSocket ss = new ServerSocket(10000);
Socket accept = ss.accept();
InputStream is = accept.getInputStream();
//第三种方式:使用转化流将字节输入流转换成字符输入流,读取字符
InputStreamReader isr = new InputStreamReader(is, "utf-8");
int a;
while ((a= isr.read())!=-1){//一次读取一个字符
System.out.print((char)a);//输出字符串
}
Thread.sleep(2000);//回馈的时候用休息一下
//使用缓冲流回复客户端,告诉客户端我已经接收到消息
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("我接收到你的嗨喽了,很高兴认识你!");
bw.flush();//这里写完之后为什么没有不用accept调用shutdownOutput(),给客户端传递结束标记
//因为下面的accept.close()的close()方法就包含了shutdownOutput(),所以不用再调用shutdownOutput()方法了
//释放资源
accept.close();
ss.close();
}
}
2.4 TCP 实现图片的传输,优化后结合多线程
Client 发送端:
package com.ww.tcp;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Service {
public static void main(String[] args) throws IOException, InterruptedException {
ServerSocket ss = new ServerSocket(10000);
Socket accept = ss.accept();
InputStream is = accept.getInputStream();
//第三种方式:使用转化流将字节输入流转换成字符输入流,读取字符
InputStreamReader isr = new InputStreamReader(is, "utf-8");
int a;
while ((a= isr.read())!=-1){//一次读取一个字符
System.out.print((char)a);//输出字符串
}
Thread.sleep(2000);//回馈的时候用休息一下
//使用缓冲流回复客户端,告诉客户端我已经接收到消息
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("我接收到你的嗨喽了,很高兴认识你!");
bw.flush();//这里写完之后为什么没有不用accept调用shutdownOutput(),给客户端传递结束标记
//因为下面的accept.close()的close()方法就包含了shutdownOutput(),所以不用再调用shutdownOutput()方法了
//释放资源
accept.close();
ss.close();
}
}
自定义的Runnable的 实现类 ThreadSocket把需要完成的任务放进run()方法中执行,从而在Server中使用线程池来实现多线程
Server 接收端
package com.ww.tcp2;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
//192.168.232.1
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
3, //核心线程数量
8, //线程池的总数量
60, //临时线程空闲时间
TimeUnit.SECONDS, //临时线程空闲时间的单位
new LinkedBlockingQueue<>(10), //阻塞队列 自定义阻塞队列的长度为10
Executors.defaultThreadFactory(), //创建线程的方式 使用工厂创建线程
new ThreadPoolExecutor.AbortPolicy() //任务拒绝策略
);
while (true) {
Socket accept = ss.accept();
ThreadSocket ts = new ThreadSocket(accept);//ts是Runnable的实现类
//new Thread(ts).start();//实现多线程
poolExecutor.submit(ts);//使用自定义的线程池来实现多线程
}
//ss.close(); 死循环服务器端就不关闭了
}
}
3 注意点
注意事项:
-
accept方法是阻塞的,作用就是等待客户端连接
-
客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
-
针对客户端来讲,是往外写的,所以是输出流
针对服务器来讲,是往里读的,所以是输入流
这里的客户端和服务器端都是在内存中的,输入输出都是相对于内存来说的
-
read方法也是阻塞的
创建客户端对象,创建输入流对象指向文件,每读一次数据就给服务器输出一次数据,输出结束后使用shutdownOutput()方法告知服务端传输结束 -
客户端在关流的时候,还多了一个往服务器写结束标记的动作
-
最后一步断开连接,通过四次挥手协议保证连接终止



