JavaSE
网络编程
一.网络编程三要素:
- IP
- Port
- 传输层协议
1.作用
应用层:根据业务逻辑产生数据;根据业务逻辑接收数据。
下面的层(在Java中使用Socket类对象(不同的传输层协议,对应不同的Socket类)来完成):底层的进程之间数据的传输(字节数据)。
通信的每一端目的地址:
IP地址(主机)+端口号(进程 1024—65535)
传输层协议:
规定数据传输的方式:UDP,TCP
UDP:传输 数据报包(包含目标地址 ip+port)
TCP:建立连接,用流来传输数据。大量数据和连续传输时使用
不同的协议对于不同的Socket类,来实现TCP,UDP协议的输出传输。
二.UDP编程
1.版本1代码:
发送端:
/*
1.创建UDP的Socket对象(DatagramSocket)用于接收和发送。
2.将数据打包成数据报包
3.用Socket对象传输数据报包
4.释放Socket资源
*/
1.
//UDP的Socket类(DatagramSocket)对象创建:
DatagramSocket(int port) //本机ip + 指定端口号port
2.
//创建数据报包对象
DatagramPacket(byte[] bytes, int offset, int length, InetAddress address, int port) //发送到指定ip+port的目的地址。
3.
//利用DatagramSocket对象的send方法发送数据报包
public void send(DatagramPacket p)
DatagramSocket对象.send(DatagramPacket对象)
4.
//释放资源
DatagramSocket对象.close()
import java.io.IOException;
import java.net.*;
public class Sender {
public static void main(String[] args) throws IOException {
String s = "hello";
DatagramSocket datagramSocket = new DatagramSocket(10086); //创建UDP传输的Socket对象,该对象可以发送数据报包,可以接收数据报包。
byte[] bytes = s.getBytes();
InetAddress targetIp = InetAddress.getByName("192.168.17.1"); //获取本机ip(因为我们是在同个电脑通信)
int targetPort = 8888;
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, targetPort); //创建一个数据报包。参数有字节数组和其偏移量,长度。同时还有目标主机的IP地址和端口号
datagramSocket.send(datagramPacket); //调用用于UDP协议的Socket类对象的send方法,发送数据报包。
datagramSocket.close(); // 释放资源
}
}
即使没有接收端,UDP发送端也可以发送,所以可以看出UDP协议是一种无连接的不可靠协议。
接收端:
/*
1.首先创建一个用于UDP协议的Socket类对象
2.创建一个数据报包稍后用于接收数据
3.Socket类对象的receive()方法接收数据
4.释放资源
5.解析数据
*/
1.
//接收端的DatagramPacket类对象构造方法有所不同
DatagramPacket(byte[] buf, int offset, int length) //buf为传入数据报包的缓冲区;offset为偏移量,length为读取字节数。
2.
//从套接字Socket中接收数据报包
public void receive(DatagramPacket p) //数据包中不仅有字节数组的信息还包含发送端的ip和port端口号,是一个阻塞方法。
/*
注意事项:
1. receive方法是一个阻塞方法
2. 当我们两次运行Receiver接收端,会出现BindException: Address already in use (ip + port)
即一个端口号只能同时绑定一个进程,所以当两次启动Receiver端的时候,相当于要让两个进程绑定同一个端口号,这是不允许的。
*/
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Receiver {
public static void main(String[] args) throws IOException {
DatagramSocket datagramSocket = new DatagramSocket(8888); //与发送端要对应
byte[] bytes = new byte[1024]; //我们一般知道应用传过来的数据的大概大小,一般1024整数倍。
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length); //接受端的数据报包的构造方法与发送端有所不同。
datagramSocket.receive(datagramPacket); //在接收到数据前一直处于阻塞状态
datagramSocket.close();
byte[] data = datagramPacket.getData(); //data与bytes相同
int off = datagramPacket.getOffset(); //偏移量
int length = datagramPacket.getLength(); //实际传输来的字节大小。
String s = new String(data,off,length); //解析字节数组
System.out.println(datagramPacket.getAddress() + ":" + datagramPacket.getPort() + "," + s); //getAddress()返回一个InetAddress类型的数据;getPort()返回一个int类型的数据
}
}
2.版本2代码:
增加了发送端多次输入和发送终止条件。
发送端:
import java.io.*;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/*
版本二:增加多次输入,增加可退出。
*/
public class Sender {
public static void main(String[] args) throws IOException {
String data;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //缓冲字符流
DatagramSocket datagramSocket = new DatagramSocket(10086);
while((data = br.readLine()) != null) {
sendData(data, datagramSocket);
if("886".equals(data)) { //退出条件
break;
}
}
datagramSocket.close();
}
//ctrl + alt + m抽成一个方法。
private static void sendData(String s, DatagramSocket datagramSocket) throws IOException {
byte[] bytes = s.getBytes();
InetAddress targetIp = InetAddress.getByName("192.168.17.1");
int targetPort = 8888;
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, targetPort); //每次发送的内容不同,所以要多次创建,把内容打包进去
datagramSocket.send(datagramPacket);
}
}
接收端:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Receiver {
public static void main(String[] args) throws IOException {
boolean flag = true; //欺骗编译器而用
DatagramSocket datagramSocket = new DatagramSocket(8888);
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length); //这里的数据报包也类似一个容器,接收用的,只需要创建一次。
while(flag) {
boolean isFinish = receiveData(datagramSocket, datagramPacket);
if(isFinish)
break;
}
datagramSocket.close();
}
private static boolean receiveData(DatagramSocket datagramSocket, DatagramPacket datagramPacket) throws IOException {
datagramSocket.receive(datagramPacket); //在接收到数据前一直处于阻塞状态
byte[] data = datagramPacket.getData();
int off = datagramPacket.getOffset();
int length = datagramPacket.getLength();
String s = new String(data,off,length);
System.out.println(datagramPacket.getAddress() + ":" + datagramPacket.getPort() + "," + s);
if("886".equals(s)) {
return true;
}
return false;
}
}
3.版本3代码:
实现双向发送和识别886退出。但是存在缺点,只能一句一句来回发,退出需要发送端提出886,接收端不管回复什么,都会结束程序。
发送端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/*
版本三:实现收发。
*/
public class Sender {
public static void main(String[] args) throws IOException {
String data;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
DatagramSocket datagramSocket = new DatagramSocket(10086);
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length);
while((data = br.readLine()) != null) {
sendData(data, datagramSocket);
receiverResponse(datagramSocket,datagramPacket);
if("886".equals(data)) {
break;
}
}
datagramSocket.close();
}
private static void receiverResponse(DatagramSocket datagramSocket, DatagramPacket datagramPacket) throws IOException {
datagramSocket.receive(datagramPacket);
byte[] receiveData = datagramPacket.getData();
int off = datagramPacket.getOffset();
int length = datagramPacket.getLength();
String s = new String(receiveData, off, length);
System.out.println(datagramPacket.getAddress() + ":" + datagramPacket.getPort() + "," + s);
}
private static void sendData(String s, DatagramSocket datagramSocket) throws IOException {
byte[] bytes = s.getBytes();
InetAddress targetIp = InetAddress.getByName("192.168.17.1");
int targetPort = 8888;
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, targetPort);
datagramSocket.send(datagramPacket);
}
}
接收端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Receiver {
public static void main(String[] args) throws IOException {
boolean flag = true;
DatagramSocket datagramSocket = new DatagramSocket(8888);
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while(flag) {
boolean isFinish = receiveData(datagramSocket, datagramPacket);
receiverSend(datagramSocket, datagramPacket, br);
if(isFinish)
break;
}
datagramSocket.close();
}
private static void receiverSend(DatagramSocket datagramSocket, DatagramPacket datagramPacket, BufferedReader br) throws IOException {
String s = br.readLine();
byte[] responseBytes = s.getBytes();
DatagramPacket responsePacket = new DatagramPacket(responseBytes,0,responseBytes.length,datagramPacket.getAddress(),datagramPacket.getPort());
datagramSocket.send(responsePacket);
}
private static boolean receiveData(DatagramSocket datagramSocket, DatagramPacket datagramPacket) throws IOException {
datagramSocket.receive(datagramPacket); //在接收到数据前一直处于阻塞状态
byte[] data = datagramPacket.getData();
int off = datagramPacket.getOffset();
int length = datagramPacket.getLength();
String s = new String(data,off,length);
System.out.println(datagramPacket.getAddress() + ":" + datagramPacket.getPort() + "," + s);
if("886".equals(s)) {
return true;
}
return false;
}
}
4.版本4代码:
实现了同时收发,不需要发一句等一句收;
发送886之后,可以等待接收方的回复。只有接收和发送都有886时,才结束程序。
发送Task
package day21.network.edition4.oneperson;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class SendTask implements Runnable {
DatagramSocket socket;
private static int targetPort;
private static InetAddress targetIp;
public SendTask(DatagramSocket socket, String targetIp, int targetPort) throws UnknownHostException {
this.socket = socket;
this.targetIp = InetAddress.getByName(targetIp);
this.targetPort = targetPort;
}
@Override
public void run() {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
String s;
while ((s = br.readLine()) != null) {
sendData(s,socket);
if("886".equals(s)) {
synchronized (socket) {
OnePerson.isSendOffline = true;
}
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void sendData(String s, DatagramSocket datagramSocket) throws IOException {
byte[] bytes = s.getBytes();
DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length, targetIp, targetPort);
datagramSocket.send(datagramPacket);
}
}
接收Task
package day21.network.edition4.oneperson;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReceiveTask implements Runnable {
DatagramSocket socket;
public ReceiveTask(DatagramSocket socket) {
this.socket = socket;
}
@Override
public void run() {
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes,0,bytes.length);
try {
while(true) {
boolean isFinish = receiveData(socket,datagramPacket);
if(isFinish)
break;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
while(true) {
synchronized (socket) {
if(OnePerson.isSendOffline) {
break;
} else {
try {
socket.wait(100); //等待100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
socket.close();
}
}
private static boolean receiveData(DatagramSocket datagramSocket, DatagramPacket datagramPacket) throws IOException {
datagramSocket.receive(datagramPacket); //在接收到数据前一直处于阻塞状态
byte[] data = datagramPacket.getData();
int off = datagramPacket.getOffset();
int length = datagramPacket.getLength();
String s = new String(data,off,length);
System.out.println(datagramPacket.getAddress() + ":" + datagramPacket.getPort() + "," + s);
if("886".equals(s)) {
return true;
}
return false;
}
}
Person
package day21.network.edition4.oneperson;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
public class OnePerson {
public static boolean isSendOffline = false;
// public static Object closeLock = new Object();
public static void main(String[] args) throws SocketException, UnknownHostException {
DatagramSocket socket = new DatagramSocket(8888);
SendTask sendTask = new SendTask(socket,"127.0.0.1",10086);
ReceiveTask receiveTask = new ReceiveTask(socket);
new Thread(sendTask).start();
new Thread(receiveTask).start();
}
}
三.TCP编程
1.基本:客户端发送与服务器接收
发送端(客户端)直接Socket建立连接。接收端(服务器)需要先建立ServerSocket接收连接请求后获得Socket对象和客户端进行通信。流的创建依靠Socket对象。
/*
客户端发送:
1.创建一个TCP客户端Socket对象实现收发,明确服务器的地址。建立成功则表示已连接,建立失败则连接不了终止程序
2.建立连接成功,则可以用Socket对象来获取流传输数据
3.向流读取或者输出数据
4.释放资源
*/
1.public class Socket //此类实现 客户端 套接字
2.public Socket(String host, int port) //构造方法,指定服务器地址,Socket地址是 本地ip + 随机分配的port
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",9999); //对象创建成功即代表连接成功
OutputStream output = socket.getOutputStream(); //获取流
String s = "hello, tcp!";
byte[] bytes = s.getBytes();
output.write(bytes); //利用流发送数据
socket.close(); //只需要关闭套接字,流也会随之自动关闭
}
}
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
TCP中,接收端也称为服务器端。
服务器接收
1.创建TCP服务器ServerSocket对象,指定端口进行监听
2.如果监听到连接请求,则成功创建一个Socket对象来实现数据收发
3.由于该服务器要实现接收,所以该用Socket对象创建一个InputStream对象来接收数据
4.释放资源。
*/
1.public class ServerSocket //实现服务器套接字。服务器套接字等待请求通过网络传输传入
2.public ServerSocket(int port) //构造函数,指定其本身的服务器端口。客户端以此为port传输数据过来
3.public Socket accept() //ServerSocket的accept方法将监听并接收套接字(客户端在创建套接字时)的连接。阻塞方法
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept(); //阻塞方法
InputStream in = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = in.read(bytes);
String s = new String(bytes,0,len);
System.out.println("来自" + socket.getInetAddress() + ":" + socket.getPort() + "的" + s); //可以通过Socket对象的getInetAddress方法和getPort方法分别获取到发送端的IP和端口号。与UDP的DatagramPacket类似。
socket.close(); //关闭两个套接字
serverSocket.close();
}
}
2.服务器给客户端反馈消息
//客户端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",9999); //对象创建成功即代表连接成功
OutputStream output = socket.getOutputStream(); //获取流
String s = "hello, tcp!";
byte[] bytes = s.getBytes();
output.write(bytes); //利用流发送数据
InputStream inputStream = socket.getInputStream();
int len;
byte[] readBytes = new byte[1024];
len = inputStream.read(readBytes);
System.out.println(new String(readBytes,0,len));
socket.close(); //只需要关闭套接字,流也会随之自动关闭
}
}
//服务器
package day22.tcp.exercise01;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
1.创建TCP服务器ServerSocket对象,指定端口,进行监听
2.如果监听到连接请求,则创建一个Socket对象来实现数据收发
3.由于该服务器要实现接收,所以该用Socket对象创建一个InputStream对象来接收数据
4.释放资源。
*/
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = in.read(bytes);
String s = new String(bytes,0,len);
System.out.println("来自" + socket.getInetAddress() + ":" + socket.getPort() + "的" + s);
OutputStream outputStream = socket.getOutputStream();
String str = "已收到";
outputStream.write(str.getBytes());
socket.close();
serverSocket.close();
}
}
3.客户端发给服务器多次,服务器输出到控制台
//客户端
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",9999); //对象创建成功即代表连接成功
OutputStream output = socket.getOutputStream(); //获取流
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String s;
while((s = reader.readLine()) != null) {
byte[] bytes = s.getBytes();
output.write(bytes); //利用流发送数据
}
socket.close(); //只需要关闭套接字,流也会随之自动关闭
}
}
//服务器
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
int len;
byte[] bytes = new byte[1024];
while((len = in.read(bytes)) != -1) {
String s = new String(bytes,0,len);
System.out.println("来自" + socket.getInetAddress() + ":" + socket.getPort() + "的" + s);
}
socket.close();
serverSocket.close();
}
}
4.客户端发给服务器多次,服务器输出到文件
//服务器
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in)); //字节输入流转换为字符输入流
FileWriter writer = new FileWriter("e:\\b.txt",true); //字符输出流中转换流的简化形式
String s;
while((s = br.readLine()) != null) {
writer.write(s); //写入字符串s,但不包含换行符。
writer.write(System.lineSeparator()); //System.lineSeparator()返回一个换行符的字符串
writer.flush();
}
writer.close();
socket.close();
serverSocket.close();
}
}
//客户端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",9999); //对象创建成功即代表连接成功
OutputStream output = socket.getOutputStream(); //获取流
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String s;
while((s = reader.readLine()) != null) {
byte[] bytes = s.getBytes();
output.write(bytes); //利用流发送数据
output.write('\n'); //reader.Line()不读入换行符,所以要人为写入一个'\n'
}
socket.close(); //只需要关闭套接字,流也会随之自动关闭
reader.close(); // 标准输入流需要关闭,所以要调用reader.close()
}
}
5.从文本输出到文本(文件传输)
//客户端
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",9999); //对象创建成功即代表连接成功
OutputStream output = socket.getOutputStream(); //获取流
BufferedReader br = new BufferedReader(new FileReader("e:\\b.txt"));
String line;
while((line = br.readLine()) != null) {
output.write(line.getBytes());
output.write('\n');
}
socket.shutdownOutput(); //关闭客户端的输出流,让服务器知道客户端输出完毕,此时服务器就并不会继续阻塞在InputStream的读方法那里了。
//接收服务器的反馈。
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);
br.close();
socket.close(); //只需要关闭套接字,流也会随之自动关闭
}
}
//服务器
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in)); //封装成字符输入流
FileWriter writer = new FileWriter("e:\\c.txt",true); // 字符输出流的简化版本
BufferedWriter bw = new BufferedWriter(writer); //封装成缓冲字符输出流
String line;
while((line = br.readLine()) != null) { //该方法会一直阻塞,直到客户端的socket close或者客户端断开输出流。
bw.write(line);
bw.newLine();
}
//输出反馈。
OutputStream outputStream = socket.getOutputStream();
outputStream.write("已接收到文件".getBytes());
bw.close();
socket.close();
serverSocket.close();
}
}
6.传输图片
把字符输出输入流修改成字节输出输入流。
//客户端
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",9999); //对象创建成功即代表连接成功
OutputStream output = socket.getOutputStream(); //获取流
BufferedOutputStream bos = new BufferedOutputStream(output);
FileInputStream fis = new FileInputStream("e:\\lolo.jpg");
BufferedInputStream bis = new BufferedInputStream(fis);
int len;
byte[] bytes = new byte[1024];
while((len = bis.read(bytes)) != -1) {
bos.write(bytes,0,len);
}
bos.flush();
socket.shutdownOutput();
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);
bufferedReader.close();
bis.close();
socket.close(); //只需要关闭套接字,流也会随之自动关闭
}
}
//服务器
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(in);
FileOutputStream fos = new FileOutputStream("e:\\copy.jpg");
BufferedOutputStream bos = new BufferedOutputStream(fos);
int len;
byte[] bytes = new byte[1024];
while((len = bis.read(bytes)) != -1) {
bos.write(bytes,0,len);
}
OutputStream outputStream = socket.getOutputStream();
outputStream.write("已接收到文件".getBytes());
bos.close();
outputStream.close();
socket.close();
serverSocket.close();
}
}