1、网络编程三要素
IP地址
要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识端口
网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识协议
通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议-
端口号
- 用两个字节表示的整数,它的取值范围是065535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
获取当前主机IP
InetAddress address = InetAddress.getByName("localhost");
String hostName = address.getHostName();
System.out.println("主机名为" + hostName);
String ip = address.getHostAddress();
System.out.println("IP为" + ip);
2、UDP通信
-
UDP发送数据
方法名 说明 DatagramSocket() 创建数据报套接字并将其绑定到本机地址上的任何可用端口 DatagramPacket(byte[] buf,int len,InetAddress add,int port) 创建数据包,发送长度为len的数据包到指定主机的指定端口 void send(DatagramPacket p) 发送数据报包 void close() 关闭数据报套接字 void receive(DatagramPacket p) 从此套接字接受数据报包 -
UDP接收数据
方法名 说明 DatagramPacket(byte[] buf, int len) 创建一个DatagramPacket用于接收长度为len的数据包 byte[] getData() 返回数据缓冲区 int getLength() 返回要发送的数据的长度或接收的数据的长度 客户端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class ClientDemo {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
//创建接收端的Socket对象
DatagramSocket ds = new DatagramSocket();
while (true) {
String s = sc.nextLine();
byte[] bytes = s.getBytes();
// 构造数据报套接字并将其绑定到本地主机上的任何可用端口
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10000;
//调用DatagramSocket对象的方法接收数据
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
ds.send(dp);
// 结束
if("404".equals(s)){
break;
}
}
//关闭此数据报套接字
ds.close();
}
}
服务端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建接收端的Socket对象
DatagramSocket ds = new DatagramSocket(10000);
while (true) {
//创建一个数据包,用于接收数据
byte [] bytes = new byte[1024];
//调用DatagramSocket对象的方法接收数据
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
ds.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
String s = new String(data, 0, length);
System.out.println(s);
// 结束
if("404".equals(s)){
break;
}
}
ds.close();
}
}
3、UDP三种通讯方式
- 单播
单播用于两个主机之间的端对端通信 - 组播
组播用于对一组特定的主机进行通信 - 广播
广播用于一个主机对整个局域网上所有主机上的数据通信
组播
// 发送端
public class ClinetDemo {
public static void main(String[] args) throws IOException {
// 1. 创建发送端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket();
String s = "hello 组播";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("224.0.1.0");
int port = 10000;
// 2. 创建数据,并把数据打包(DatagramPacket)
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
// 3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
ds.send(dp);
// 4. 释放资源
ds.close();
}
}
// 接收端
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 1. 创建接收端Socket对象(MulticastSocket)
MulticastSocket ms = new MulticastSocket(10000);
// 2. 创建一个箱子,用于接收数据
DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
// 3. 把当前计算机绑定一个组播地址,表示添加到这一组中.
ms.joinGroup(InetAddress.getByName("224.0.1.0"));
// 4. 将数据接收到箱子中
ms.receive(dp);
// 5. 解析数据包,并打印数据
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data,0,length));
// 6. 释放资源
ms.close();
}
}
-
广播
// 发送端 public class ClientDemo { public static void main(String[] args) throws IOException { // 1. 创建发送端Socket对象(DatagramSocket) DatagramSocket ds = new DatagramSocket(); // 2. 创建存储数据的箱子,将广播地址封装进去 String s = "广播 hello"; byte[] bytes = s.getBytes(); InetAddress address = InetAddress.getByName("255.255.255.255"); int port = 10000; DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port); // 3. 发送数据 ds.send(dp); // 4. 释放资源 ds.close(); } } // 接收端 public class ServerDemo { public static void main(String[] args) throws IOException { // 1. 创建接收端的Socket对象(DatagramSocket) DatagramSocket ds = new DatagramSocket(10000); // 2. 创建一个数据包,用于接收数据 DatagramPacket dp = new DatagramPacket(new byte[1024],1024); // 3. 调用DatagramSocket对象的方法接收数据 ds.receive(dp); // 4. 解析数据包,并把数据在控制台显示 byte[] data = dp.getData(); int length = dp.getLength(); System.out.println(new String(data,0,length)); // 5. 关闭接收端 ds.close(); } }
4.TCP通信程序
-
TCP发送数据
方法名 说明 Socket(InetAddress address,int port) 创建流套接字并将其连接到指定IP指定端口号 Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号 InputStream getInputStream() 返回此套接字的输入流 OutputStream getOutputStream() 返回此套接字的输出流 -
TCP接收数据
方法名 说明 ServletSocket(int port) 创建绑定到指定端口的服务器套接字 Socket accept() 监听要连接到此的套接字并接受它 -
注意事项
- accept方法是阻塞的,作用就是等待客户端连接
- 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
- 针对客户端来讲,是往外写的,所以是输出流
针对服务器来讲,是往里读的,所以是输入流 - read方法也是阻塞的
- 客户端在关流的时候,还多了一个往服务器写结束标记的动作
- 最后一步断开连接,通过四次挥手协议保证连接终止
一次TCP通信
- 客户端
import java.io.*;
import java.net.Socket;
public class ClientDemo {
public static void main(String[] args) throws IOException {
// 创建socket 绑定IP和端口
Socket socket = new Socket("127.0.0.1",10000);
// 创建socket发送流
OutputStream os = socket.getOutputStream();
os.write("hello".getBytes());
//仅仅关闭输出流.并写一个结束标记,对socket没有任何影响
socket.shutdownOutput();
// 创建socket接收流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine())!=null){
System.out.println(line);
}
// 关闭资源
br.close();
os.close();
socket.close();
}
}
- 服务器
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 创建socServerSocketket 监听端口
ServerSocket ss = new ServerSocket(10000);
//等待客户端连接
Socket accept = ss.accept();
// 输入流
InputStream is = accept.getInputStream();
int b;
while((b = is.read())!=-1){
System.out.println((char) b);
}
// 输出流
System.out.println("看看我执行了吗?");
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("你谁啊?");
bw.newLine();
bw.flush();
// 关闭资源
bw.close();
is.close();
accept.close();
ss.close();
}
}
TCP上传文件
方法名 | 说明 |
---|---|
void shutdownInput() | 将此套接字的输入流放置在“流的末尾” |
void shutdownOutput() | 禁止用此套接字的输出流 |
客户端
import java.io.*;
import java.net.Socket;
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",10000);
//是本地的流,用来读取本地文件的.
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.jpg"));
//写到服务器 --- 网络中的流
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
int b;
while((b = bis.read())!=-1){
bos.write(b);//通过网络写到服务器中
}
bos.flush();
//给服务器一个结束标记,告诉服务器文件已经传输完毕
socket.shutdownOutput();
//接收流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine()) !=null){
System.out.println(line);
}
bis.close();
socket.close();
}
}
服务器
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket accept = ss.accept();
//网络中的流,从客户端读取数据的
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
//本地的IO流,把数据写到本地中,实现永久化存储
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("3.jpg"));
int b;
while((b = bis.read()) !=-1){
bos.write(b);
}
//将字节输入流FileInputStream 转成 字符输入流 Fliereader
//通过转换流
InputStreamReader inputStreamReader = new InputStreamReader(accept.getInputStream());
//通过缓冲输入字符流
BufferedReader br = new BufferedReader(inputStreamReader);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(accept.getOutputStream());
BufferedWriter bw = new BufferedWriter(outputStreamWriter);
bw.write("上传成功");
bw.newLine();
bw.flush();
bos.close();
accept.close();
ss.close();
}
}
TCP服务端优化
异步线程完成socket接收流程
import java.io.*;
import java.net.Socket;
import java.util.UUID;
// 线程任务类
public class ThreadSocket implements Runnable {
private Socket acceptSocket;
public ThreadSocket(Socket accept) {
this.acceptSocket = accept;
}
@Override
public void run() {
BufferedOutputStream bos = null;
try {
//网络中的流,从客户端读取数据的
BufferedInputStream bis = new BufferedInputStream(acceptSocket.getInputStream());
//本地的IO流,把数据写到本地中,实现永久化存储
bos = new BufferedOutputStream(new FileOutputStream( UUID.randomUUID().toString() + ".jpg"));
int b;
while((b = bis.read()) !=-1){
bos.write(b);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (acceptSocket != null){
try {
acceptSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
线程池控制socket创建
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,//核心线程数量
10, //线程池的总数量
60, //临时线程空闲时间
TimeUnit.SECONDS, //临时线程空闲时间的单位
new ArrayBlockingQueue<>(5),//阻塞队列
Executors.defaultThreadFactory(),//创建线程的方式
new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
);
while (true) {
Socket accept = ss.accept();
ThreadSocket ts = new ThreadSocket(accept);
pool.submit(ts);
}
//ss.close();
}
}