一、网络:将不同区域的计算机连接到一起就组成了一个网络,根据区域大小可以将其分为局域网、广域网、城域网等。
二、地址:IP地址,确定网络上的一个绝对位置 ,相当于房子的地址
三、端口号:区分计算机软件的 ,每一个应用都需要绑定一个端口,在这个端口下运行。相当于房子的房门,包含两个字节 0~65535 一共65535个端口
1、在同一个协议下,端口号不能重复。
2、1024以下的端口不要使用。操作系统预留的端口,供知名厂商使用。如80 -->http 21 -->ftp
四、资源定位:URL统一资源定位符 URI:统一资源
五、数据的传输
1、协议:TCP和UDP协议
(1)TCP:电话 类似于三次握手,面向连接 安全可靠 效率低下
(2)UDP:非面向连接 效率高 。类似短信
2、数据传输
1)先封装
2)后拆分
关注的重点:传输层+网络层
我们在进行网络编程的时候,按照步骤一步步来,其实是比较简单的。
在用java进行网络编程时,常用的的传输协议其实只有两个,TCP协议和UDP协议。
包含的类有
1、InetAddress、InetSocketAddress
2、URL
3、TCP
4、UDP
InetAddress :此类表示互联网协议(ip)地址。InetAddress的实例包含IP地址,还可能包含相应的 主机名称取决于它是否用主机名构造或者是否已执行反向主机名解析),InetAddress不能访问构造器,只能获取静态实例,即InetAddress.方法
InetAddress inetAddress=InetAddress.getLocalHost(); System.out.println(inetAddress.getHostAddress()); InetAddress inetAddress1= InetAddress.getByName("www.google.com"); System.out.println(inetAddress1.getHostName()); System.out.println(inetAddress1.getHostAddress());
上述代码可以获取主机名称和主机地址,获取谷歌的主机名称和主机地址。
方法:InetAddress.getLocalHost()获得本机地址
getHostName()返回域名,本机为计算机名
getHostAddress()返回ip地址
InetAddress.getByName("ip地址|域名")这里注意下,在获得网络上的ip地址时,若ip地址dns可以解析,则会返回解析之后的域名,如果dns解析不了,则只能返回ip地址。
2、InetSocketAddress address = new InetSocketAddress("127.0.0.1",999);
介绍了基础的,然后我们首先看一下UDP协议。。。
UDP:以数据为中心,非面向连接、不安全,效率高。我们在使用UDP连接的时候,服务器和客户端之间不需要连接也可以发送消息。
使用UDP连接的步骤:
1、客户端:(1)创建客户端,准备连接。建立Socket+指定端口(2)准备数据 数据为字节数组(3)打包数据DatagramPacket+服务器端口及地址(4)发送(5)释放资源
简单代码如下:
//注意:服务器无需开启也可以运行。这是和TCP不同的一点,TCP协议必须是服务器和客户端两者连接起来才能运行
public static void main(String[] args) throws IOException {
// 创建客户端,建立Socket类+端口
DatagramSocket client = new DatagramSocket(999);
try {
//准备数据 字节数组
while (true){
System.out.println("请输入要发送的信息:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String msg = br.readLine();
byte[] data= msg.getBytes();
// 打包
DatagramPacket datagramPacket =new DatagramPacket(data,data.length,new InetSocketAddress("localhost",9000));
// 发送
client.send(datagramPacket);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
Utils.close();
}
}
2、服务器端
(1)创建服务器,DatagramSocket类+指定端口(2)准备容器接收从客户端传来的数据,字节数组,封装DatagramPacket(3)封装成包,接收数据 (4)对数据进行分析 (5)释放资源。
简单代码如下:
public static void main(String[] args) throws IOException {
// 1)创建服务器端,DatagramSocket 类+指定端口
DatagramSocket server = null;
try {
server = new DatagramSocket(9000);
// 2)准备接收容器 字节数组 封装 DatagramPacket
while (true){
byte[] data = new byte[100];
// 3)封装成包 接收数据
DatagramPacket packet = new DatagramPacket(data,data.length);
server.receive(packet);
// 4) 分析数据
byte[] res= packet.getData();
System.out.println(new String(res,0,res.length));
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
Utils.close(server);
}
}
TCP:Socket通信,面向连接 特点是安全、可靠、效率低 ,类似于打电话
采用套接字编程。必须要等服务器开启才能开启客户端,且接收数据之后,两者之间可以相互通信,其应用有聊天室,私聊等。
比如说我们打电话给10086,10086就相当于一个服务器,服务器接收到请求之后,会把我们的请求交给一个客服处理,与此同时又有另一个客户打电话,服务器转发到另一个客服进行处理。这个都是通过多线程实现的,各个线程之间彼此独立,互不干扰。
由于我们的Java底层都封装好了,因此我们直接用Socket连接,监听端口即可实现网络编程。
步骤:
1、服务器端(1)首先建立Socket连接,监听端口。(2)当有客户端连接之后,获得客户端的ip地址和其他信息。(3)和客户端交换数据(发送/接收消息)等 (4)关闭连接
2、客户端 客户端的步骤也是一样的。
简单示例:(单线程、一个客户一个服务器)
客户端:
/** * 1、创建客户端,必须指定服务器和端口 此时就在连接 * 2、接收服务器端连接 * 3、发送数据+接收数据 */ public class MyClient { public static void main(String[] args) throws IOException { Socket socket =null; DataInputStream dis =null; DataOutputStream dos=null; while (true){ //创建socket连接 socket = new Socket("localhost",9001); // System.out.println("请输入姓名:"); //接收服务器消息 // br = new BufferedReader(new InputStreamReader(socket.getInputStream())); // String msg = br.readLine();//注意,用这个方法需要有行结束符 // System.out.println(msg); dis = new DataInputStream(socket.getInputStream()); String msg = dis.readUTF(); System.out.println(msg); //发送消息给服务器 Scanner sc = new Scanner(System.in); //创建输出管道 dos = new DataOutputStream(socket.getOutputStream()); dos.writeUTF(sc.nextLine()); dos.flush(); Utils.close(dos,dis,socket); } } }
服务器端:
public static void main(String[] args) throws IOException { ServerSocket server =null; BufferedWriter bw = null; Socket socket=null; //创建socket连接 server =new ServerSocket(9001); while (true){ socket = server.accept(); System.out.println("一个客户端建立连接!"); //给客户端发送消息 getOutputstream方法把数据输出到管道 DataOutputStream data = new DataOutputStream(socket.getOutputStream()); String msg ="欢迎您!"; data.writeUTF(msg); //服务器接收数据 getInputStream接收从客户端发来的管道中的数据 DataInputStream dis = new DataInputStream(socket.getInputStream()); String msg1 = dis.readUTF(); System.out.println("客户端说:"+msg1); Utils.close(dis,data,socket); } }
这个例子实现的是一个客户端对应一个服务器,我们采用getOutputstream和getInputStream两个方法分别输出和接收数据。该示例服务器端在本机创建了一个Socket连接,监听9001端口,服务器端首先发送一个“欢迎您”的消息,将这消息写入输出流管道中,待客户端连接之后,用getInputStream接收管道中的数据并打印(注意:此处是打印到控制台,如果有界面可以输出至界面)。下一步,客户端在控制台获取用户输入,再写入管道中,最后发送给服务器。服务器接收并打印出来,这就实现了一个简单的TCP通信。
当然,上述的例子都是很简单的,看似实现了一个私聊的功能,实则不然。在实际的应用中,我们不可能发送和接收都是同一条管道,服务器只有一个,而客户端都是存在多个的,因此我们需要有多条管道。在上述例子中,发送和接收消息都是在同一条管道的,这显然不现实。这意味着客户端发送接收消息只能按顺序进行。因此我们需要把发送接收都独立出来,这就需要用到多线程了。我们把发送接收独立成两个线程,两者互不干扰。以这种思路,我们可以写一个简单的聊天室,实现群聊的功能。
代码如下:
1、发送数据线程:
package com.zr.net.chat; import com.zr.Utils; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; /** * 发送数据线程 */ public class Send implements Runnable { //控制台输入流 private BufferedReader console; //输出流 private DataOutputStream dos; //控制线程运行与否 private boolean isRunning = true; public Send(){ console = new BufferedReader(new InputStreamReader(System.in)); } //初始化 public Send(Socket socket) throws IOException { this(); try { dos =new DataOutputStream(socket.getOutputStream()); } catch (IOException e) { isRunning = false;//出异常则关闭 Utils.close(dos); // e.printStackTrace(); } } //从控制台接收读取数据 private String recevieMsgFromConsole(){ try { return console.readLine(); } catch (IOException e) { //e.printStackTrace(); //读取失败返回空串 } return ""; } //发送数据 public void send(){ String msg = recevieMsgFromConsole(); if (null!=msg&&!msg.equals("")){ try { dos.writeUTF(msg); dos.flush(); } catch (IOException e) { isRunning = false; // e.printStackTrace(); } } } @Override public void run() { while (isRunning){ send(); } } }
接收数据线程:
package com.zr.net.chat; import com.zr.Utils; import java.io.DataInputStream; import java.io.IOException; import java.net.Socket; /** * 接收数据线程 */ public class Receive implements Runnable { //接收数据,读取,输入流 private DataInputStream dis ; //线程标识符 private boolean isRunning =true; public Receive(){ } public Receive(Socket client){ this(); try { dis = new DataInputStream(client.getInputStream()); } catch (IOException e) { // e.printStackTrace(); isRunning = false; try { Utils.close(dis); } catch (IOException e1) { e1.printStackTrace(); } } } //接收数据 private String getMsg(){ String msg = null; try { msg = dis.readUTF(); } catch (IOException e) { isRunning = false; try { Utils.close(dis); } catch (IOException e1) { e1.printStackTrace(); } // e.printStackTrace(); } return msg; } @Override public void run() { while (isRunning){ System.out.println(getMsg()); } } }
服务器多个管道,接收发送数据,转发数据给其他客户端:
/** * 一个客户端一条通道 * 1、输入流 * 2、输出流 * 3、发送数据 * 4、接收数据 */ private class MyChannel implements Runnable{ DataInputStream input = null; DataOutputStream out = null; private boolean isRunning = true; public MyChannel(Socket client){ try { input = new DataInputStream(client.getInputStream()); out = new DataOutputStream(client.getOutputStream()); } catch (IOException e) { // e.printStackTrace(); try { Utils.close(out,input); isRunning = false; } catch (IOException e1) { e1.printStackTrace(); } } } //发送数据 private void send(String msg){ try { out.writeUTF(msg); out.flush(); } catch (IOException e) { e.printStackTrace(); isRunning = false; } } //接收数据 private String receive() throws IOException { String msg= ""; try { msg=input.readUTF(); System.out.println(msg); } catch (IOException e) { Utils.close(input); isRunning = false; list.remove(this);//移除自身 } return msg; } //发送给其他客户端 private void sendOthers(){ try { String msg =receive(); for (MyChannel c:list){ if (c ==this){ continue; } //发送给其他客户端 c.send(msg); } } catch (IOException e) { // e.printStackTrace(); } } @Override public void run() { while (isRunning){ sendOthers(); } } }
服务器主线程:这里用list创建多个客户端与服务器通信的管道
public class MultiServer { //多条道路 private List<MyChannel> list=new ArrayList<MyChannel>(); public static void main(String[] args) throws IOException { new MultiServer().start(); } public void start() throws IOException { ServerSocket server= new ServerSocket(9999); while (true){ Socket socket = server.accept(); MyChannel myChannel = new MyChannel(socket); list.add(myChannel); new Thread(myChannel).start(); } }
客户端:发送接收数据线程独立运行,互不干扰。
public class Client2 { public static void main(String[] args) { try { Socket client = new Socket("localhost",9999); System.out.println("请输入用户名:"); new Thread(new Send(client)).start(); new Thread(new Receive(client)).start(); } catch (IOException e) { e.printStackTrace(); } } }
当然这个程序还有很多可以扩展 的地方。
总结如下:
网络编程我们主要要掌握的其实就是Socket套接字编程,在数据传输层其实用到的就是前面所学的IO流,唯一的不同就是这里的数据是通过管道进行传输的。简单的网络通信很简单,通过套接字,监听端口,连接,IO流交换数据,即可实现,实际上我们还需要加入多线程、循环队列等技术来进行控制,实现多发多收,消息转发的功能。我们需要在服务器端建立多个channel,多个通道,确保数据正确无误的转发。此外还可以根据自己的需要进行封装。。