应用程序可以与网络上其他设备中的应用程序进行数据交互。 网络编程的解决方案都是在java.net包下, 通信的基本架构主要有两种形式:一种是CS架构(Client 客户端/Server服务端)、一种是BS架构(Brower 浏览器/Server服务端)。
CS架构需要用户在自己的电脑或者手机上安装客户端软件,然后由客户端软件通过网络连接服务器程序,由服务器把数据发给客户端,客户端就可以在页面上看到各种数据了。
BS架构不需要开发客户端软件,用户只需要通过浏览器输入网址就可以直接从服务器获取数据,并由服务器将数据返回给浏览器,用户在页面上就可以看到各种数据了。
这两种结构不管是CS、还是BS都是需要用到网络编程的相关技术。我们学习Java的程序员,以后从事的工作方向主要还是BS架构的。
分别是IP地址、端口号、通信协议
IP地址:表示设备在网络中的地址,是网络中设备的唯一标识
端口号:应用程序在设备中唯一的标识
协议:连接和数据在网络中传输的规则。
IP(Ineternet Protocol)全称互联网协议地址,是分配给网络设备的唯一表示
本机的IP地址,可以在命令行窗口,输入ipconfig
命令查看,如下图所示
IPV6采用128位二进制数据来表示(16个字节),号称可以为地球上的每一粒沙子编一个IP地址,
IPV6比较长,为了方便阅读,每16位编成一组,每组采用十六进制数据表示,然后用冒号隔开(称为冒分十六进制表示法),如下图所示
Java中也有一个类用来表IP地址,这个类是InetAddress类。我们在开发网络通信程序的时候,可能有时候会获取本机的IP地址,以及测试与其他地址是否连通。
public class InetAddressTest {
public static void main(String[] args) throws Exception {
// 1、获取本机IP地址对象的
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());
// 2、获取指定IP或者域名的IP地址对象。
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
// ping www.baidu.com
System.out.println(ip2.isReachable(6000));
}
}
端口号:指的是计算机设备上运行的应用程序的标识,被规定为一个16位的二进制数据,范围(0~65535)
端口号分为一下几类(了解一下)
周知端口:0~1023,被预先定义的知名应用程序占用(如:HTTP占用80,FTP占用21)
注册端口:1024~49151,分配给用户经常或者某些应用程序
动态端口:49152~65536,之所以称为动态端口,是因为它一般不固定分配给某进程,而是动态分配的
网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。
为了让世界上各种上网设备能够互联互通,肯定需要有一个组织出来,指定一个规则,大家都遵守这个规则,才能进行数据通信。
三次握手如下图所示:目的是确认通信双方,手法消息都是正常没问题的
四次挥手如下图所示:目的是确保双方数据的收发已经完成,没有数据丢失
UDP是面向无连接的、不需要确认双方是否存在,所以它是不可靠的协议。Java提供了一个类叫DatagramSocket来完成基于UDP协议的收发数据。使用DatagramSocket收发数据时,数据要以数据包的形式体现,一个数据包限制在64KB以内 。
import java.io.IOException;
import java.net.*;
public class Client {
public static void main(String[] args) throws IOException {
//创建DatagramSocket对象
DatagramSocket ds= new DatagramSocket();
String meg = "我是客户端";
byte[] bytes = meg.getBytes();
//创建数据包对象并且封装要发送的数据;
DatagramPacket packet = new DatagramPacket(
bytes,
bytes.length,
InetAddress.getLocalHost(),
6666
);
ds.send(packet);//开始正式发送这个数据;
System.out.println("客户端数据发送完毕~~");
ds.close();//释放资源;
}
}
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("开启服务端");
//创建一个服务端接收对象注册端口;
DatagramSocket socket = new DatagramSocket(6666);
byte[] bytes = new byte[1024*64];//可接收最大数据64k
//创建接收数据的DatagramPacket对象
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
socket.receive(packet);
//从字节数组中把接收到的数据打印出来
int length = packet.getLength();
String s = new String(bytes, 0, length);
System.out.println(s);
}
}
public class Client {
public static void main(String[] args) throws IOException {
//创建DatagramSocket对象
DatagramSocket ds= new DatagramSocket();
Scanner scanner = new Scanner(System.in);
while (true){
System.out.println("请讲");
String meg = scanner.next();
if(meg.contains("886")){
System.out.println("欢迎下次光临~~~");
ds.close();
break;//跳出死循环;
}
byte[] bytes = meg.getBytes();
//创建数据包对象并且封装要发送的数据;
DatagramPacket packet = new DatagramPacket(
bytes,
bytes.length,
InetAddress.getLocalHost(),
6666
);
ds.send(packet);//开始正式发送这个数据;
System.out.println("客户端数据发送完毕~~");
ds.close();//释放资源;
}
}
}
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("开启服务端");
//创建一个服务端接收对象注册端口;
DatagramSocket socket = new DatagramSocket(6666);
byte[] bytes = new byte[1024*64];//可接收最大数据64k
//创建接收数据的DatagramPacket对象
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (true) {
socket.receive(packet);
//从字节数组中把接收到的数据打印出来
int length = packet.getLength();
String s = new String(bytes, 0, length);
System.out.println(s);
}
}
}
当创建Socket对象时,就会在客户端和服务端创建一个数据通信的管道,在客户端和服务端两边都会有一个Socket对象来访问这个通信管道。
现在假设客户端要发送一个“在一起”给服务端,客户端这边先需要通过Socket对象获取到一个字节输出流,通过字节输出流写数据到服务端
然后服务端这边通过Socket对象可以获取字节输入流,通过字节输入流就可以读取客户端写过来的数据,并对数据进行处理。
服务端处理完数据之后,假设需要把“没感觉”发给客户端端,那么服务端这边再通过Socket获取到一个字节输出流,将数据写给客户端
客户端这边再获取输入流,通过字节输入流来读取服务端写过来的数据。
public class Client {
public static void main(String[] args) throws IOException {
//创建socket对象;并请求与服务端的连接;
Socket socket = new Socket("localhost",6666);
//从socket通信中得到一个字节输出流,用来发数据给服务端;
OutputStream outputStream = socket.getOutputStream();
//把低级的字节输出流包装成数据输出流
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
//开始写数据出去;
dataOutputStream.writeUTF("我是客户端");
System.out.println(socket.getRemoteSocketAddress());
//释放资源
dataOutputStream.close();
socket.close();
}
}
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("启动服务端~~~");
//1、创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket ss = new ServerSocket(6666);
//2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = ss.accept();
//3、从socket通信管道中得到一个字节输入流。
InputStream inputStream = socket.getInputStream();
//4、把原始的字节输入流包装成数据输入流
DataInputStream dataInputStream = new DataInputStream(inputStream);
// 5、使用数据输入流读取客户端发送过来的消息
String s = dataInputStream.readUTF();
System.out.println(s);
//释放资源;
dataInputStream.close();
ss.close();
}
}
如果有多个客户端连接服务端,此时服务端是不支持的。
为了让服务端能够支持多个客户端通信,就需要用到多线程技术。具体的实现思路如下图所示:每当有一个客户端连接服务端,在服务端这边就为Socket开启一条线程取执行读取数据的操作,来多少个客户端,就有多少条线程。按照这样的设计,服务端就可以支持多个客户端连接了。
public class Client {
public static void main(String[] args) throws IOException {
//创建socket对象;并请求与服务端的连接;
Socket socket = new Socket("localhost",6666);
//从socket通信中得到一个字节输出流,用来发数据给服务端;
OutputStream outputStream = socket.getOutputStream();
//把低级的字节输出流包装成数据输出流
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请说");
String s = scanner.next();
if(s.contains("886")){
outputStream.close();
socket.close();
break;//出口;
}
//开始写数据出去;
dataOutputStream.writeUTF(s);
System.out.println(socket.getRemoteSocketAddress());
//释放资源
dataOutputStream.close();
socket.close();
}
}
}
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("启动服务端~~~");
//1、创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket ss = new ServerSocket(6666);
while (true) {
//2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = ss.accept();
System.out.println("有人上线了" + socket.getRemoteSocketAddress());
new ServerThread(socket).start();
}
}
}
public class ServerThread extends Thread{
private Socket socket;
public ServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//3、从socket通信管道中得到一个字节输入流。
InputStream inputStream = socket.getInputStream();
//4、把原始的字节输入流包装成数据输入流
DataInputStream dataInputStream = new DataInputStream(inputStream);
// 5、使用数据输入流读取客户端发送过来的消息
while (true) {
try {
String s = dataInputStream.readUTF();
System.out.println(s);
} catch (IOException e) {
//释放资源;
dataInputStream.close();
socket.close();
break;//出口
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
为了让服务端能够支持多个客户端通信,就需要用到多线程技术。具体的实现思路如下图所示:每当有一个客户端连接服务端,在服务端这边就为Socket开启一条线程取执行读取数据的操作,来多少个客户端,就有多少条线程。按照这样的设计,服务端就可以支持多个客户端连接了。
线程类:
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while (true){
try {
String msg = dis.readUTF();
System.out.println(msg);
} catch (Exception e) {
System.out.println("有人下线了:" + socket.getRemoteSocketAddress());
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端代码:
/**
* 目标:完成TCP通信快速入门-服务端开发:要求实现与多个客户端同时通信。
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-------");
// 1、创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
// 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
System.out.println("有人上线了:" + socket.getRemoteSocketAddress());
// 3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理。
new ServerReaderThread(socket).start();
}
}
}
在服务端创建一个存储Socket的集合,每当一个客户端连接服务端,就可以把客户端Socket存储起来;当一个客户端给服务端发消息时,再遍历集合通过每个Socket将消息再转发给其他客户端。
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while (true){
try {
String msg = dis.readUTF();
System.out.println(msg);
// 把这个消息分发给全部客户端进行接收。
sendMsgToAll(msg);
} catch (Exception e) {
System.out.println("有人下线了:" + socket.getRemoteSocketAddress());
Server.onLineSockets.remove(socket);
dis.close();
socket.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendMsgToAll(String msg) throws IOException {
// 发送给全部在线的socket管道接收。
for (Socket onLineSocket : Server.onLineSockets) {
OutputStream os = onLineSocket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF(msg);
dos.flush();
}
}
}