网络编程主要有两个问题,1.如何定义一台或多台主机?2.找到主机后如何进行通信?
对问题1需要得到某台主机的IP地址和端口,不同进程有不同端口,用来区分软件,端口号规定范围:0~65535,在单个协议下(TCP/UDP),端口号是不能重复的
对问题2主要有TCP和UDP两种方式进行计算机间通信。TCP/IP 中有两个具有代表性的
传输层协议
,分别是 TCP 和 UDP。下面这篇博客对TCP,UDP特点,区别,应用场景有详细介绍:
解决问题1:学习InetAddress和InetSocketAddress两个类
InetAddress类
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.SQLOutput;
public class TestInetAddress {
public static void main(String[] args) throws UnknownHostException {
//获取本地的一个InetAddress对象
InetAddress ia1=InetAddress.getLocalHost();
System.out.println(ia1);
//通过IP获取一个InetAddress对象
InetAddress ia2=InetAddress.getByName("127.0.0.1");
System.out.println(ia2);
//通过域名获取一个InetAddress对象
InetAddress ia3=InetAddress.getByName("www.baidu.com");
System.out.println(ia3);
/**获取InetAddress类对象的三种方式:
1.通过getLocalhost获取本机InetAddress对象
2.通过getByName方法并传入IP地址
3.通过getByName方法并传入域名
*/
//常用的两个方法
sout(ia1.getHostAddress()) //获取ia1的ip地址
sout(ia1.getHostName()) //获取ia1的主机名
}
}
InetSocketAddress类
InetSocketAddress和InetAddress类的主要区别是有端口的设置
import java.net.InetSocketAddress;
public class TestInetSocketAddress {
public static void main(String[] args) {
//通过localhost创建对象
InetSocketAddress isa1=new InetSocketAddress("localhost",9090);
System.out.println(isa1);
//通过IP创建对象
InetSocketAddress isa2=new InetSocketAddress("127.0.0.1",9090);
System.out.println(isa2);
System.out.println(isa1.getAddress());//获取isa1的IP
System.out.println(isa1.getPort());//获取端口
System.out.println(isa1.getHostName());//获取isa1的主机名
}
}
解决问题2:方式一TCP通信
TCP通信只能实现一对一传信息,发送文件等,有明确的客户端和服务器,连接比udp更加稳定,但是效率比udp低,连接涉及三次握手,断开连接涉及四次挥手
三次握手:
客户端首先向服务器发送连接请求,这是第一次握手;服务器做出应答并进入等待状态,这是第二次握手;客户端发送信号给服务器,建立连接成功,这是第三次握手。
四次挥手:
断开时,客户端向服务器发断开请求,这是第一次挥手;服务器收到请求后让应用层要释放 TCP 链接。然后会发送 ACK 包,并进入等待,这是第二次挥手;服务器有未发送的信息会在这时再次发送,完毕后会向 A 发送连接释放请求,这是第三次挥手;客户端发送最后信号确认断开给服务端,断开成功,这是第四次挥手。
TCP应用1:客户端给服务器发送消息
万物皆对象,创建两个类,客户端类和服务器类
客户端:
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class TcpClientDemo1 {
public static void main(String[] args) throws Exception{
//1.要知道服务器的端口,ip
InetAddress serverIP=InetAddress.getByName("localhost");
int port=9999;
//2.要建立一个socket连接
Socket s=new Socket(serverIP,port);
//3.输出流要传递的信息
OutputStream os=s.getOutputStream();
os.write("你好啊,服务器!".getBytes());//字符串是无法传递的,需要getBytes()变成字节流
//4.关闭资源
s.close();
os.close();
}
}
服务器:
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServerDemo1 {
public static void main(String[] args) throws Exception {
//1.首先服务器要有一个地址
ServerSocket serverSocket=null;
serverSocket=new ServerSocket(9999);
//2.等待客户端连接过来
Socket socket=null;
socket=serverSocket.accept();
//3.读取客户端发来的信息
InputStream is=socket.getInputStream();
//4.将收到的字节流放入缓冲区
ByteArrayOutputStream baos=new ByteArrayOutputStream();
byte[] buffer=new byte[1024];
int len;
while((len=is.read(buffer))!=-1){//读取is里的信息放入buffer
baos.write(buffer,0,len);//将buffer里的信息写到baos
}
//5.输出接收的信息
System.out.println(baos.toString());
//6.关闭资源
socket.close();
is.close();
baos.close();
serverSocket.close();
}
}
TCP应用2:客户端给服务器传文件
万物皆对象,创建两个类,客户端类和服务器类
客户端:
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class TcpClientDemo2 {
public static void main(String[] args) throws Exception {
//1.要知道服务器的端口,ip
InetAddress serverIP=InetAddress.getByName("localhost");
int port=9999;
//2.要建立一个socket连接
Socket s=new Socket(serverIP,port);
//3.读取要传给服务器的图片文件
FileInputStream fis=new FileInputStream(new File("src/go.jpg"));
//4,将文件输入流放入缓冲区并通过os输出
//创建一个输出流
OutputStream os=s.getOutputStream();
byte[] buffer =new byte[1024];//缓冲区
int len;
while ((len=fis.read(buffer))!=-1){//从哪读,固定代码
os.write(buffer,0,len);//信息都写入了os,os相当于载体
}
s.close();
fis.close();
os.close();
}
}
服务器:
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServerDemo2 {
public static void main(String[] args) throws Exception{
//1.首先服务器要有一个地址
ServerSocket serverSocket=null;
serverSocket=new ServerSocket(9999);
//2.等待客户端连接过来
Socket socket=null;
socket=serverSocket.accept();
//3.读取客户端发来的信息
InputStream is=socket.getInputStream();
//4,将收到的is放入缓冲区,并写入文件输出流fos
FileOutputStream fos=new FileOutputStream("receive1.jpg");//如果是输出信息用baos流,此题输出文件流用fos
byte[] buffer=new byte[1024];
int len;
while((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
//5.关闭资源
socket.close();
serverSocket.close();
is.close();
fos.close();
}
}
左侧文件夹多出了receive1.jpg说明传输成功了
解决问题2:方式二UDP通信
UDP应用1:单方面发送消息
客户端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
public class UdpClientDemo1 {
public static void main(String[] args) throws Exception{
//1.要知道对方的端口,ip
InetAddress serverIP=InetAddress.getByName("localhost");
int port=9999;
//2.要建立一个socket连接,这里与TCP用的不同类
DatagramSocket s=new DatagramSocket();
//3.建立包并打包好
String msg="你好啊,对面的朋友!";//信息
DatagramPacket packet=new DatagramPacket(msg.getBytes(),0,msg.getBytes().length,serverIP,port);//把信息打成包
//4.发送包
s.send(packet);
//5.关闭资源
s.close();
}
}
服务器端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UdpServerDemo1 {
public static void main(String[] args) throws Exception{
//1.开放端口,不用ip了,因为这个例子是用本机测试,client会把包发给localhost
DatagramSocket socket=new DatagramSocket(9999);
//2.接受包,并输出包裹内容
byte[] buffer=new byte[1024];//将包放到缓冲区
DatagramPacket packet=new DatagramPacket(buffer,0,buffer.length);//这里需要新建一个packet空间来接收
socket.receive(packet);//阻塞接收
System.out.println(packet.getAddress().getHostName());
System.out.println(new String(packet.getData(),0, packet.getLength()));//输出包裹内容
//3.关闭资源
socket.close();
}
}
UDP应用2:单方面不断发送消息
物皆对象,创建两个类,Sender类和Receiver类
Sender类:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
//UDP聊天实现
public class UdpSenderDemo1 {
public static void main(String[] args) throws Exception {
//1.要知道对方的端口,ip
InetAddress serverIP=InetAddress.getByName("localhost");
int port=6666;
//2.要建立一个socket连接,这里与TCP用的不同类
DatagramSocket s=new DatagramSocket();
//3.准备发送消息,控制台输入
BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
//4.不断读取数据,将数据打包
while (true){
String data=reader.readLine();//读取数据
DatagramPacket packet=new DatagramPacket(data.getBytes(),0,data.getBytes().length,serverIP,port);
s.send(packet);
if(data.equals("bye")){break;}
}
//5.关闭资源
s.close();
}
}
Receiver类
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UdpReceiverDemo1 {
public static void main(String[] args) throws Exception{
//1.开放端口
DatagramSocket socket=new DatagramSocket(6666);
//2.不断接受包,并输出包裹内容
while (true){
byte[] buffer=new byte[1024];//将包放到缓冲区
DatagramPacket packet=new DatagramPacket(buffer,0,buffer.length);//这里需要新建一个packet空间来接收
socket.receive(packet);//阻塞接收
String receiveData=new String(packet.getData(),0, packet.getLength());
System.out.println(receiveData);//输出包裹内容
if(receiveData.equals("bye")){break;}
}
//3.关闭资源
socket.close();
}
}
UDP应用3:双方都能发送和接受消息(即聊天实现)
万物皆对象:
双方都能够发送和接受信息,因此要有一个Send类和一个Receive类
双方都能同时操作,因此双方是两个线程
Send类
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
public class TalkSend1 implements Runnable {
//1.定义基本的信息
private int toPort;
private int localPort;
private String toIP;
//2.要建立一个socket连接
DatagramSocket s = null;
public TalkSend1(int localPort, String toIP, int toPort) {
this.toPort = toPort;
this.localPort = localPort;
this.toIP = toIP;
//2.向s传参数localPort
try {
s = new DatagramSocket(localPort);
} catch (SocketException e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
//3.准备发送消息,控制台输入
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while (true) {
try {
//4.创建一个包裹
String data = reader.readLine();//读取数据字符串
DatagramPacket packet = new DatagramPacket(data.getBytes(), 0, data.getBytes().length, new InetSocketAddress(this.toIP, this.toPort));
//5.发送包裹
s.send(packet);
if (data.equals("bye")) {
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
//6.关闭资源
s.close();
}
}
Receive类
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class TalkReceive1 implements Runnable {
//1.定义基本的信息
private int localPort2;
private String msgFrom;
//2.要建立一个socket连接
DatagramSocket s=null;
public TalkReceive1(int localPort2, String msgFrom) {
this.localPort2 = localPort2;
this.msgFrom = msgFrom;
try {//2.向s传参数localPort2
s = new DatagramSocket(localPort2);
} catch (SocketException e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
while (true) {
try {
//3.准备接受包裹
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
s.receive(packet);
//4.输出包裹内容(理解为拆包裹)
String receiveData = new String(packet.getData(), 0, packet.getData().length);
System.out.println(msgFrom + ":" + receiveData.trim());
if (receiveData.equals("bye")) {
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
//5.关闭资源!!!
s.close();
}
}
启动学生线程:
public class TalkStudent {
public static void main(String[] args) throws Exception {
//开启两个线程
new Thread(new TalkSend1(1111,"localhost",2222)).start();//学生作为发送人时,fromport是发件人的端口,toport是接收人的端口
new Thread(new TalkReceive1(3333,"老师")).start();//学生作为接收人时
}
}
启动老师线程:
public class TalkTeacher {
public static void main(String[] args) throws Exception {
//开启两个线程
new Thread(new TalkSend1(4444,"localhost",3333)).start();
new Thread(new TalkReceive1(2222,"学生")).start();
}
}
这里遇到一个错误是Address already in use:cannot find
原因是我开始写receive1类的时候忘记添加关闭资源语句,以及我在写send1类的时候开始是把 DatagramSocket s的定义放在run()方法里,为了防止程序找不到s,我把s.close()放在了while循环内catch语句后。
重新调整了之后就不会报错了,借这个错误认识到了关闭资源的重要性。