黑马程序员—网络编程
------- android培训、java培训、期待与您交流! ----------
1.网络编程基础知识
1.1 OSI模型
OSI(Open System Interconnection)即开放系统互连参考模型。此模型力求将网络简化,并以模块化的方式来设计网络。
1.2 TCP/IP分层模型
TCP/IP分层模型是网络中最常用的基础协议。
1.3 IP地址和端口号
IP地址:是网络中设备的标识,即用于唯一标识网络上的一个通信实体。IP地址是数字型的,通常把它分成4个8位的二进制数,每8位之间用圆点隔开,每个8位整数可以转换成一个0-255的十进制整数,因此我们看到的IP地址常常是这种形式:192.168.2.14.
端口号:用于标识进程的逻辑地址,不同进程的标识不同。不同的应用程序处理不同端口上的数据,同一台机器上不能有两个程序使用同一个端口,端口号可以从0-65535.其中0-1024是公认端口,默认绑定一些特定的服务。
总之,如果把IP地址理解为某个人所在地方的地址包括街道和门牌号,但仅有地址还是找不到这个人,还需要知道他所在的房号(端口号)才可以找到这个人。因此如果我们认为应用程序是人,而计算机网络是邮递员,当一个程序需要发送数据时,需要指定目的地的IP地址和端口号,如果指定了正确的IP和端口号,计算机网络就可以将数据发送给所对应的程序。
1.4 InetAddress类
此类代表IP地址。
代码示例:
package com.mine.net;
import java.net.*;
public class InetAddressTest
{
public static void main(String[] args)
{
// 根据主机名来获取对应的InetAddress实例
InetAddress ip;
try {
ip = InetAddress.getByName("www.baidu.com");
// 判断是否可达
System.out.println("baidu是否可达:" + ip.isReachable(2000));
// 获取该InetAddress实例的IP字符串
System.out.println(ip.getHostAddress());
// 根据原始IP地址来获取对应的InetAddress实例
InetAddress local = InetAddress.getByAddress(new byte[]{127,0,0,1});
System.out.println("本机是否可达:" + local.isReachable(5000));
// 获取该InetAddress实例对应的全限定域名
System.out.println(local.getCanonicalHostName());
} catch (Exception e) {
throw new RuntimeException();
}
}
}
2. 基于TCP协议的网络编程
TCP协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。
TCP协议使用的是三方握手机制:当一个通信实体发送一个消息给另一个通信实体后,需要收到另 一个通信实体的确认信息,如果没有收到另一个通信实体的确认信息,则会再次重发刚才发送的信息。通过这种机制,TCP协议向应用程序提供了可靠的通信连接。
TCP通信中,能够接收其他通信实体连接请求的类是ServerSocket,ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状态。
import java.net.*;
import java.io.*;
public class Server
{
public static void main(String[] args)
{
// 创建一个ServerSocket,用于监听客户端Socket的连接请求
ServerSocket ss;
try {
ss = new ServerSocket(30000);
// 采用循环不断接受来自客户端的请求
while (true)
{
// 每当接受到客户端Socket的请求,服务器端也对应产生一个Socket
Socket s = ss.accept();
// 将Socket对应的输出流包装成PrintStream
PrintStream ps = new PrintStream(s.getOutputStream());
// 进行普通IO操作
ps.println("您好,您收到了服务器的信息!");
// 关闭输出流,关闭Socket
ps.close();
s.close();
}
} catch (IOException e) {
throw new RuntimeException();
}
}
}
客户端使用Socket来连接到指定服务器。
package com.mine.net;
import java.net.*;
import java.io.*;
public class Client
{
public static void main(String[] args)
{
Socket socket=null;
BufferedReader br=null;
try {
socket = new Socket("127.0.0.1" , 30000);
// 将Socket对应的输入流包装成BufferedReader
br= new BufferedReader(
new InputStreamReader(socket.getInputStream()));
// 进行普通IO操作
String line = br.readLine();
System.out.println("来自服务器的数据:" + line);
} catch (UnknownHostException e) {
throw new RuntimeException();
} catch (IOException e) {
throw new RuntimeException();
}finally{
// 关闭输入流、socket
try {
if(br!=null)
br.close();
} catch (IOException e) {
throw new RuntimeException();
}
try {
if(socket!=null)
socket.close();
} catch (IOException e) {
throw new RuntimeException();
}
}
}
}
TCP与多线程向结合:
以上的服务器端与客户端直接只是进行简单的通信,通过加入多线程之后,服务器端可以不断的读取客户端发送过来的数据并向客户端写入数据,客户端也需要不断地读取服务器端数据,并向服务器端写入数据,从而实现简单的聊天功能。
服务器端:
MyServer类用来监听客户端的连接请求。
import java.net.*;
import java.io.*;
import java.util.*;
public class MyServer
{
//定义保存所有Socket的ArrayList
public static ArrayList socketList= new ArrayList<>();
public static void main(String[] args)
{
ServerSocket ss;
try {
ss = new ServerSocket(30000);
while(true)
{
// 此行代码会阻塞,将一直等待别人的连接
Socket s = ss.accept();
socketList.add(s);
// 每当客户端连接后启动一条ServerThread线程为该客户端服务
new Thread(new ServerThread(s)).start();
}
} catch (IOException e) {
throw new RuntimeException();
}
}
}
ServerThread负责处理每个客户端与服务器端的通信。
import java.io.*;
import java.net.*;
// 负责处理每个线程通信的线程类
public class ServerThread implements Runnable
{
// 定义当前线程所处理的Socket
Socket s = null;
// 该线程所处理的Socket所对应的输入流
BufferedReader br = null;
public ServerThread(Socket s)
throws IOException
{
this.s = s;
// 初始化该Socket对应的输入流
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
}
public void run()
{
try
{
String content = null;
// 采用循环不断从Socket中读取客户端发送过来的数据
while ((content = readFromClient()) != null)
{
// 遍历socketList中的每个Socket,
// 将读到的内容向每个Socket发送一次
for (Socket s : MyServer.socketList)
{
PrintStream ps = new PrintStream(s.getOutputStream());
ps.println(content);
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
// 定义读取客户端数据的方法
private String readFromClient()
{
try
{
return br.readLine();
}
// 如果捕捉到异常,表明该Socket对应的客户端已经关闭
catch (IOException e)
{
// 删除该Socket。
MyServer.socketList.remove(s);
}
return null;
}
}
MyClient负责接收键盘输入,并将输入的数据写入到Socket对应的输出流中。
import java.io.*;
import java.net.*;
public class MyClient
{
public static void main(String[] args)
{
Socket s;
try {
s = new Socket("127.0.0.1" , 30000);
// 客户端启动ClientThread线程不断读取来自服务器的数据
new Thread(new ClientThread(s)).start(); // ①
// 获取该Socket对应的输出流
PrintStream ps = new PrintStream(s.getOutputStream());
String line = null;
// 不断读取键盘输入
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
while ((line = br.readLine()) != null)
{
// 将用户的键盘输入内容写入Socket对应的输出流
ps.println(line);
}
} catch (UnknownHostException e) {
throw new RuntimeException("地址有误!");
} catch (IOException e) {
throw new RuntimeException("数据读取失败!");
}
}
}
ClientThread负责读取Socket输出流中的内容,并将这些内容打印在控制台上。
import java.io.*;
import java.net.*;
public class ClientThread implements Runnable
{
// 该线程负责处理的Socket
private Socket s;
// 该线程所处理的Socket所对应的输入流
BufferedReader br = null;
public ClientThread(Socket s)
{
this.s = s;
try {
br = new BufferedReader(
new InputStreamReader(s.getInputStream()));
} catch (IOException e) {
throw new RuntimeException("数据读取失败!");
}
}
public void run()
{
try
{
String content = null;
// 不断读取Socket输入流中的内容,并将这些内容打印输出
while ((content = br.readLine()) != null)
{
System.out.println(content);
}
}
catch (Exception e)
{
throw new RuntimeException("数据输出失败!");
}
}
}
以上程序可以实现在任何一个客户端输入一些内容之后按回车,就可以再所有客户端的控制台上收到刚刚发送的内容,实现了一个简单的聊天室功能。
3. 基于UDP协议的网络编程
UDP协议是一种不可靠的网络协议,它在通信实体的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,只是发送、接收数据的对象。
UDP协议是一种面向非连接的协议,是在正式通信前不必与对方先建立连接,不管对方状态就直接发送。至于对方是否可以接收到数据,此协议无法控制。
UDP协议适用于一次只传送少量数据、对可靠性要求不高的应用环境。
UDP协议中使用DatagramSocket来发送、接收数据。
UDP协议结合多线程来实现收发数据:
import java.net.*;
import java.io.*;
//发送端通过接收键盘输入,并将数据发送给接收端
class Send implements Runnable {
private DatagramSocket ds;
public Send(DatagramSocket ds) {
this.ds = ds;
}
public void run() {
//获取键盘输入
BufferedReader br=null;
try {
br=new BufferedReader(new InputStreamReader(System.in));
String line=null;
while ((line=br.readLine())!=null) {
if("byebye".equals(line))
break;
byte[] bytes=line.getBytes();
//将数据封装成包
DatagramPacket dp=new DatagramPacket(bytes, bytes.length, InetAddress.getByName("127.0.0.1"), 10000);
//发送数据
ds.send(dp);
}
} catch (Exception e) {
throw new RuntimeException("发送端失败!");
}
}
}
import java.net.*;
import java.io.*;
//接收端负责接收数据,并将数据打印在控制台上
public class Receive implements Runnable {
private DatagramSocket ds;
public Receive(DatagramSocket ds) {
this.ds = ds;
}
public void run() {
try {
//循环接收
while(true)
{
byte[] bytes=new byte[1024];
DatagramPacket dp=new DatagramPacket(bytes, bytes.length);
//接收数据
ds.receive(dp);
//获取IP
String ip=dp.getAddress().getHostAddress();
//获取数据内容
String data=new String(dp.getData(),0,dp.getLength());
//将内容打印在控制台上
System.out.println(ip+":"+data);
}
} catch (Exception e) {
throw new RuntimeException("接收失败!");
}
}
}
public class ChatDemo {
public static void main(String[] args) {
try {
DatagramSocket sendSocket=new DatagramSocket();
DatagramSocket receSocket=new DatagramSocket(10000);
new Thread(new Send(sendSocket)).start();
new Thread(new Send(receSocket)).start();
} catch (SocketException e) {
e.printStackTrace();
}
}
}
总结:
TCP与UDP的区别:
(1)TCP是面向连接的通信协议;而UDP是面向无连接的。
(2)TCP可以大数据传输;而UDP传输数据的大小是有限制的,在64K内。
(3)TCP是可靠的,安全的;而UDP是不可靠的,相对不安全。
(4)TCP因为必须建立连接,所以效率较低;而UDP的效率则较高。
------- android培训、java培训、期待与您交流! ----------