1、TCP/IP网络参考模型包括五个层次:
应用层:大多数基于Internet的应用程序被看作TCP/IP网络的最上层。像http、https、ftp等协议
传输层:不同机器之间进行通信时,数据的传输是由传输层控制的,最常见的有tcp、udp
网络层:对硬件资源进行标识。如ip地址
(链路层):增加了一些必要的硬件和软件
物理层:确定与传输媒体接口有关的一些特性。主要做的是透明地传送比特流
2、ISO/OSI网络参考模型包括七个层次:
应用层、表示层、会话层、传输层、网络层、链路层、物理层
3、端口号是一个标记机器的逻辑通信信道的正整数,端口号不是物理实体,是标识应用的逻辑地址。IP地址和端口号组成了所谓的Socket,Socket是网络上运行的程序之间双向通信链路的最后终结点,它是TCP、UDP的基础。
1、传输控制协议TCP
是在端点与端点之间建立持续的连接而进行通信。
TCP传输控制协议过程:
1)服务端在某一端口对用户的连接请求进行监听(接电话的人要手机畅通不能关机)
2)客户端向服务端发送连接请求(拨号打电话)
3)经过三次挥手后建立持续的连接–信道(接通电话)
4)通过信道进行通信(开始打电话):发送端将发送的数据标记了序列号以字节流的方式发送出去,数据在需要时可以重新发送,整个字节流到达接收端时完好无缺
优点:面向连接的,可靠的,有序的,并且以字节流的方式发送数据,通常称为流通信协议
效率比较低
三次握手建立连接:(大概意思):
第一次握手–客户端对服务器说:我可以给你发送数据吗? (客户端无法确认服务端,服务端可以确认客户端能发送)
第二次握手–服务端对客户端说:可以,我也能给你发送数据吗? (客户端可以确认服务端能发送能接收)
第三次握手–客户端对服务端说:好,那我们可以互相发送数据—建立TCP连接 (服务端可以确认客户端能发送能接收)
只有三次才能确认彼此都能接收和发送消息–通信
四次挥手断开连接(大概意思)
第一次挥手—客户端对服务端说:我没有数据了,断开连接吧
第二次挥手—服务端对客户端说:好,但是我还有数据要发(此时属于半关闭状态–客户端不能再发送消息但可以接受服务端消息)
第三次挥手—服务端对客户端说:我也没有数据了,断开连接吧
第四次挥手—客户端说:好,那就断开连接 (到这里客户端确认服务端不再发送,TCP断开,连接断开)
2、用户数据报协议UDP
UDP是一种无连接的传输协议。
UDP传输过程:
1)首先需要将要传输的数据定义成数据报(将信放在信封里)
2)在数据报中指明所要到达的Socket–获取端口号和ip地址 (在信封上写好目的地)
3)然后再将数据报发送出去(邮局将信发出去)
这种传输方式是无序的,也不能确保绝对的安全可靠
先存储再转发
面向信报的,尽最全力交付
但是它很简单,效率也很高
TCP和UDP各有各的用处。当对所传输的数据具有时序性和可靠性等要求时,应使用TCP协议;当传输的数据比较简单、对时序无要求,则UDP协议可以发挥更好的作用,如ping、发送时间数据、语音、图像等。
InetAddress用于标识网络上的硬件资源。
InetAddress类没有构造函数,不能通过new创建它的实例。所以通过调用它提供的静态方法来获取对象
public class getLocalHostTest
{
public static void main(String args[])
{
InetAddress myIP = null;
try
{
//InetAddress没有提供构造函数,不能通过new创建实例。通过调用静态方法,获取InetAddress对象,可以直接通过类名调用
myIP = InetAddress.getLocalHost();//获取本机的IP地址
}
catch(UnknownHostException e){}
System.out.println(myIP);
}
}
http://www.baidu.com
http:// www.ztenc.com.cn /javaCourse/index.html
一个URL对象生成后,其属性是不能被改变的,但可以通过它给定的方法来获取这些属性:
public String getProtocol():获取该URL的协议名
public String getHost() :获取该URL的主机名
public String getPort() :获取该URL的端口号
public String getPath() :获取该URL的文件路径
public String getFile() :获取该URL的文件名
public String getRef() :获取该URL在文件中的相对位置
public String getQuery() :获取该URL的查询名
下载资源
public static void main (String args[])
{
try{
//URL gis = new URL("http://www.ztenc.com.cn/test.htm");
//获取资源所在位置---通过url标识资源
URL gis = new URL("file:/d:/1.txt");
//调用openStream方法,与url标识的资源建立一个网络字节输入流。转换为字符输入流
BufferedReader in = new BufferedReader( new InputStreamReader( gis.openStream() ) );
String line;
while( (line = in.readLine()) != null )
{
System.out.println("read:"+line);
}
in.close();
}catch(Exception e){
System.out.println(e);
}
}
下载的原理:
URL gis = new URL(“file:/d:/1.txt”);
创建url对象,通过url标识资源
BufferedReader in = new BufferedReader( new InputStreamReader( gis.openStream() ) );
然后调用openStream()方法,客户端与url标识的资源之间建立一个网络字节输入流,将字节输入流转换为字符输入流,再套接在Buffer流上(更快读取)
然后通过流进行读取资源中的数据。
下载的另一种方式
通过URL类提供的方法openConnection(),就可以获得一个URL连接对象,通过这个连接对象,可以创建网络输入字节流和网络输出字节流,从而在应用程序和URL之间进行交互
public InputStream getInputStream()
public OutputStream getOutputStream()
下载代码:
public class URLConnectionReader
{
public static void main (String args[])
{
try{
//URL gis = new URL("http://www.ztenc.com.cn/test.htm");
//获取要上传的目的地---到网络中的资源,通过url标识
URL gis = new URL("file:/d:/1.txt");
//获得一个url连接,从客户端和服务端(url资源)之间建立连接,可以互相交互
URLConnection uc = gis.openConnection();
//通过连接创建输入流---读取服务端资源,下载服务端资源
//通过连接创建输出流---写入到服务端资源,上传到服务端资源
BufferedReader in = new BufferedReader(
new InputStreamReader( uc.getInputStream() ) );//openStream()底层实现就是通过连接获取输入流
String line;
while( (line = in.readLine()) != null )
{
System.out.println(line);
}
in.close();
}catch(Exception e){
System.out.println(e);
}
}
}
原理和第一种是一样的,先获取url,通过url标识资源来获取资源的地址,然后得到一个url连接,这个连接实现了客户端与url资源之间的交互。通过连接创建输入流—读取服务端资源,下载服务端资源,通过连接创建输出流—写入到服务端资源,上传到服务端资源。
public class Server
{ public static void main(String args[])
{ ServerSocket server=null;
Socket you=null;String s=null;
DataOutputStream out=null;DataInputStream in=null;
//1、服务端在4331端口对用户的连接请求进行监听
try{ server=new ServerSocket(4331);}
catch(IOException e1){System.out.println("ERRO:"+e1);}
try{
//3、通过三次握手,建立tcp连接,建立一个公有的通信的信道。在server中叫you,在client中叫mysocket
//服务器处于阻塞状态,等待用户请求
you=server.accept();
//4、通过信道创建的网络字节输入流和网络字节输出流,再套接在数据流(以字符串的单位读和写)
in=new DataInputStream(you.getInputStream());
out=new DataOutputStream(you.getOutputStream());
while(true)
{
//服务端从信道里读的数据一定是客户端放进去的,客户端从信道里读出来的一定是服务端放进去的。
//他们会有序的放消息拿消息。例如,客户端往里放一条,服务端就会取一条,而不会丢失任何一条
//从信道中读消息,如果没有消息就阻塞状态
s=in.readUTF();// 通过使用in读取客户放入"线路"里的信息.堵塞状态,
//除非读取到信息.
out.writeUTF("你好:我是服务器");//通过 out向"线路"写入信息.
out.writeUTF("你说的数是:"+s);
System.out.println("服务器收到:"+s);
Thread.sleep(500);
}
}
catch(IOException e)
{ System.out.println("用户下线"+e);
//java.net.SocketException: Connection reset
}
catch(InterruptedException e){}
}
}
public class Client
{ public static void main(String args[])
{ String s=null;
Socket mysocket;
DataInputStream in=null;
DataOutputStream out=null;
try{
//2、客户端向127.0.0.1地址指向的服务器的4331端口发起连接请求---创建信道
mysocket=new Socket("localhost",4331);
//5、通过信道创建的网络字节输入流和网络字节输出流,再套接在数据流
in=new DataInputStream(mysocket.getInputStream());
out=new DataOutputStream(mysocket.getOutputStream());
// 服务端在信道中读消息,客户端也在信道中读消息,各自都想要对方的去放消息,争夺对方不可释放的消息,造成死锁
// s=in.readUTF();
out.writeUTF("你好 ");//通过 out向"线路"写入信息.
while(true)
{
s=in.readUTF();//通过使用in读取服务器放入"线路"里的信息.堵塞状态,
//除非读取到信息.
out.writeUTF(":"+Math.random());
System.out.println("客户收到:"+s);
Thread.sleep(500);
}
}
catch(IOException e)
{ System.out.println("无法连接"+e);
//java.net.ConnectException: Connection refused: connect
}
catch(InterruptedException e){}
}
}
实现原理:
1 :服务端在某一端口(4331)对用户的连接请求监听 server=new ServerSocket(4331)
2 :客户端向目标ip地址指向的服务器的这个端口(4331)发起请求连接 mysocket=new Socket(“localhost”,4331);
3 :通过三次握手建立tcp连接,创建一个共有的信道,在server端叫you,在client端叫mysocket
4 :客户端和服务端都要根据信道创建网络字节输入流和网络字节输出流,再套接在数据流中(以字符串单位读写)
in=new DataInputStream(you.getInputStream());
out=new DataOutputStream(you.getOutputStream());
in=new DataInputStream(mysocket.getInputStream());
out=new DataOutputStream(mysocket.getOutputStream());
5:通过输入流和输出流,读取消息和写入消息到信道。
in.readUTF();
out.writeUTF("xxxx");
当server和client都不往信道里放东西,而又要都往信道里读消息,就会造成死锁。两个线程各自申请对方不能释放的资源,造成阻塞。
无论一个Socket通信程序的功能多么齐全、程序多么复杂,其基本结构都是一样的,都包括以下四个基本步骤:
1 在客户方和服务器方创建Socket/ServerSocket。
2 打开连接到Socket的输入/输出流。
3 利用输入/输出流,按照一定的协议对Socket进行读/写操作。
4 关闭输入/输出流和Socket。
tcp过程的异常
无法连接java.net.ConnectException: Connection refused: connect
用户下线java.net.SocketException: Connection reset
UDP协议不是面向连接的,不像tcp一样你来我往,需要准备两个线程,主线程接发送消息,再创建一个线程来接受消息。
举例:上海和北京之间,使用udp协议传输。
shanghai.java
class Shanghai_Frame extends Frame implements Runnable,ActionListener
{ TextField out_message=new TextField("");
TextArea in_message=new TextArea();
Button b=new Button("发送数据包到北京");
Shanghai_Frame()
{ super("我是上海");
setSize(100,200);setVisible(true);
b.addActionListener(this);
add(out_message,"South");add(in_message,"Center");
add(b,"North");
Thread thread=new Thread(this);
//接收消息的线程
thread.start();//线程负责接收数据包
}
//点击按扭发送数据包。按钮绑定事件
@Override
public void actionPerformed(ActionEvent event)
{
//从输入框拿到消息并转化为字节,用一个字节数组接收
byte buffer[]=out_message.getText().trim().getBytes();
try{
//获得这个主机的ip地址
InetAddress address=InetAddress.getByName("localhost");
//数据包的目标端口是888(那么收方(北京)需在这个端口接收):
//消息内容,消息长度,目标方地址,目标方端口号。把信装到信封里
DatagramPacket data_pack= new DatagramPacket(buffer,buffer.length, address,888);
in_message.append("数据报目标主机地址:"+data_pack.getAddress()+"\n");
in_message.append("数据报目标端口是:"+data_pack.getPort()+"\n");
in_message.append("数据报长度:"+data_pack.getLength()+"\n");
//创建信道,将信放入信道送信
DatagramSocket mail_data=new DatagramSocket();
mail_data.send(data_pack);
}
catch(Exception e){e.getMessage();}
}
//接收数据包。新创建一个线程发送消息,主线程线程接收消息
@Override
public void run()
{ DatagramPacket pack=null;
DatagramSocket mail_data=null;
byte data[]=new byte[8192];//udp传输中数据报最大字节长度8192
try{
//1、创建一个空的数据报来接受消息
pack=new DatagramPacket(data,data.length);
//使用端口666来接收数据包(因为北京发来的数据报的目标端口是666).创建信道接收消息,端口号是对面发来时的目标端口
mail_data=new DatagramSocket(666);
}
catch(Exception e){}
while(true)
{ if(mail_data==null) {
break;
} else {
try{ mail_data.receive(pack);
int length=pack.getLength(); //获取收到的数据的实际长度.
InetAddress adress=pack.getAddress();//获取数据包的始发地址.
int port=pack.getPort();//获取数据包的始发端口.
//将数据报里的消息拿出来并转化为字符串对象
String message=new String(pack.getData(),0,length);
in_message.append("收到数据长度 "+length+"\n");
in_message.append("收到数据来自 "+adress+"端口 "+port+"\n");
in_message.append("收到数据是 "+message+"\n");
}
catch(Exception e){e.getMessage();}
}
}
}
}
public class Shanghai
{ public static void main(String args[])
{ Shanghai_Frame shanghai_win=new Shanghai_Frame();
shanghai_win.addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e)
{System.exit(0);
}
});
shanghai_win.pack();//自动调整组件位置
} }
beijing.java
class Beijing_Frame extends Frame implements Runnable,ActionListener
{ TextField out_message=new TextField();
TextArea in_message=new TextArea();
Button b=new Button("发送数据包到上海");
Beijing_Frame()
{ super("我是北京");
setSize(200,200);setVisible(true);
b.addActionListener(this);
add(out_message,"South");add(in_message,"Center");add(b,"North");
Thread thread=new Thread(this);
thread.start();//线程负责接收数据包
}
@Override
//点击按扭发送数据包
public void actionPerformed(ActionEvent event)
{ byte buffer[]=out_message.getText().trim().getBytes();
try{InetAddress address=InetAddress.getByName("localhost");
//数据包的目标端口是666(那么收方(上海)需在这个端口接收):
DatagramPacket data_pack=
new DatagramPacket(buffer,buffer.length, address,666);
DatagramSocket mail_data=new DatagramSocket();
in_message.append("数据报目标主机地址:"+data_pack.getAddress()+"\n");
in_message.append("数据报目标端口是:"+data_pack.getPort()+"\n");
in_message.append("数据报长度:"+data_pack.getLength()+"\n");
mail_data.send(data_pack);
}
catch(Exception e){e.getMessage();}
}
//接收数据包
@Override
public void run()
{ DatagramSocket mail_data=null;
byte[] data=new byte[8192];
DatagramPacket pack=null;
try{
pack=new DatagramPacket(data,data.length);
//使用端口888来接收数据包(因为上海发来的数据报的目标端口是888).
mail_data=new DatagramSocket(888);
}
catch(Exception e){}
while(true)
{ if(mail_data==null) {
break;
} else {
try{ mail_data.receive(pack);
int length=pack.getLength(); //获取收到的数据的实际长度.
InetAddress adress=pack.getAddress();//获取收到的数据包的始发地址.
int port=pack.getPort();//获取收到的数据包的始发端口.
String message=new String(pack.getData(),0,length);
in_message.append("收到数据长度 "+length+"\n");
in_message.append("收到数据来自 "+adress+"端口 "+port+"\n");
in_message.append("收到数据是 "+message+"\n");
}
catch(Exception e){e.getMessage();}
}
}
}
}
public class Beijing{
public static void main(String[] args){
Beijing_Frame beijing_win=new Beijing_Frame();
beijing_win.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
beijing_win.pack();//自动调整组件位置
}
}
UDP的方式传输,就不分服务端和客户端了,。并且接送双方/发送双方代码大同小异
原理如下:
主线程进行发送消息:
从输入框拿到要发送的消息并转化为字节,用一个字节数组接收
byte buffer[]=out_message.getText().trim().getBytes();
获得目标方的ip地址
InetAddress address=InetAddress.getByName("localhost");
将信息打包成数据报
//信息、信息的长度、目标ip地址、目标端口号
DatagramPacket data_pack= new DatagramPacket(buffer,buffer.length, address,888);
创建信道,将数据报发送
DatagramSocket mail_data=new DatagramSocket();
mail_data.send(data_pack);
线程2进行接收消息:
创建一个空的数据报来接收消息
pack=new DatagramPacket(data,data.length);
创建信道接收消息,端口号是对面发的消息的目标端口号(给我发那就是我的端口号)
mail_data=new DatagramSocket(666);
接收消息
mail_data.receive(pack);
基于UDP协议
广播组的IP地址是一类特殊的IP地址,它们没有分配给网上的硬件资源使用,而是专门保留下来作为广播通信使用的。这一类地址的范围是从224.0.0.0到239.255.255.255,其中地址224.0.0.0又被保留不能被一般应用程序所使用
许多防火墙和路由器可以配置为不允许UDP数据报进入。因此,如果想在这种环境下提供UDP网络服务,就需要请求系统管理员重新配置防火墙和路由器,以允许UDP数据报进入
代码实例
public class BroadCast extends Thread {
String s = "天气预报,最高温度32度,最低温度25度";
//组播组的端口
int port = 5858;
//组播组的地址
InetAddress group = null;
//建立多点广播套接字
MulticastSocket socket = null;
//多点广播套接字.
BroadCast() {
try {
group = InetAddress.getByName("239.255.8.0"); //设置广播组的地址为239.255.8.0.
//多点广播套接字将在port端口广播
socket = new MulticastSocket(port);
//多点广播套接字发送数据报范围为本地网络.
socket.setTimeToLive(1);
//加入广播组,加入group后,socket发送的数据报 ,可以被加入到group中的成员接收到.
socket.joinGroup(group);
} catch (Exception e) {
System.out.println("Error: " + e);
}
}
@Override
public void run() {
while (true) {//一直发送广播
try {
DatagramPacket packet = null; //待广播的数据包.
byte data[] = s.getBytes();
packet = new DatagramPacket(data, data.length, group, port);
System.out.println(new String(data));
socket.send(packet); //广播数据包.
sleep(2000);
} catch (Exception e) {
System.out.println("Error: " + e);
}
}
}
public static void main(String args[]) {
new BroadCast().start();
}
}
与普通的udp协议通信相比,加的内容是建立多点广播套接字,并加入广播组
group = InetAddress.getByName("239.255.8.0"); //设置广播组的地址为239.255.8.0.
//多点广播套接字将在port端口广播
socket = new MulticastSocket(port);
//多点广播套接字发送数据报范围为本地网络.
socket.setTimeToLive(1);
//加入广播组,加入group后,socket发送的数据报 ,可以被加入到group中的成员接收到.
socket.joinGroup(group);
public class Receive extends Frame implements Runnable, ActionListener {
int port; //组播的端口.
InetAddress group = null; //组播组的地址.
MulticastSocket socket = null; //多点广播套接字.
Button 开始接收, 停止接收;
TextArea 显示正在接收内容, 显示已接收的内容;
Thread thread; //负责接收信息的线程.
boolean 停止 = false;
public Receive() {
super("定时接收信息");
thread = new Thread(this);
开始接收 = new Button("开始接收");
停止接收 = new Button("停止接收");
停止接收.addActionListener(this);
开始接收.addActionListener(this);
显示正在接收内容 = new TextArea(10, 10);
显示正在接收内容.setForeground(Color.blue);
显示已接收的内容 = new TextArea(10, 10);
Panel north = new Panel();
north.add(开始接收);
north.add(停止接收);
add(north, BorderLayout.NORTH);
Panel center = new Panel();
center.setLayout(new GridLayout(1, 2));
center.add(显示正在接收内容);
center.add(显示已接收的内容);
add(center, BorderLayout.CENTER);
validate();//生效
port = 5858; //设置组播组的监听端口.
try {
group = InetAddress.getByName("239.255.8.0"); //设置广播组的地址为239.255.8.0.
socket = new MulticastSocket(port); //多点广播套接字将在port端口广播.
socket.joinGroup(group); //加入广播组,加入group后,socket发送的数据报,
//可以被加入到group中的成员接收到.
} catch (Exception e) {
}
setBounds(100, 50, 360, 380);
setVisible(true);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == 开始接收) {
开始接收.setBackground(Color.blue);
停止接收.setBackground(Color.gray);
if (!(thread.isAlive())) {
thread = new Thread(this);
}
try {
thread.start();
停止 = false;
} catch (Exception ee) {
}
}
if (e.getSource() == 停止接收) {
开始接收.setBackground(Color.gray);
停止接收.setBackground(Color.blue);
停止 = true;
}
}
@Override
public void run() {
while (true) {
byte data[] = new byte[8192];
DatagramPacket packet = null;
packet = new DatagramPacket(data, data.length, group, port); //待接收的数据包.
try {
socket.receive(packet);
String message = new String(packet.getData(), 0, packet.getLength());
显示正在接收内容.setText("正在接收的内容:\n" + message);
显示已接收的内容.append(message + "\n");
} catch (Exception e) {
System.out.println(e);
}
if (停止 == true) {//当按钮为停止接收,就退出while循环,不再接收。但是广播方还在广播消息
//当下一次按开始接收,会把这段时间广播的所有消息一次性接受
break;
}
}
}
public static void main(String args[]) {
new Receive();
}
}
设置监听广播的端口,创建多点广播套接字