1、概述
(1)网络通信要素:
-找到对方IP
-用端口标识网络应用程序
-定义通信规则,即通信协议,如TCP/IP协议。
(2)网络参考模型
-OSI参考模型:
OSI参考模型将网络通信分为七层,从上到下包括应用层、表示层、会话层、传输层、网络层、数据链路层和物理层。
-TCP/IP参考模型:
TCP/IP参考模型将OSI参考模型的应用层、表示层和会话层理解为一层,将数据链路层和物理层理解为一层,所以TCP/IP模型包括应用层、传输层、网络层和链路层四层。
2、InetAddress类
(1)InetAddress类表示互联网协议 (IP)地址。IP 地址是 IP 使用的 32 位或 128 位无符号数字,它是一种低级协议,InetAddress 的实例包含 IP 地址,还可能包含相应的主机名(取决于它是否用主机名构造或者是否已执行反向主机名解析)。
(2)主要方法:
-byte[] getAddress():返回此 InetAddress
对象的原始 IP 地址。结果按网络字节顺序:地址的高位字节位于getAddress()[0]
中。
-static InetAddressgetByAddress(byte[] addr):在给定原始 IP 地址的情况下,返回InetAddress
对象。参数按网络字节顺序:地址的高位字节位于getAddress()[0]
中。
-static InetAddressgetByAddress(String host,byte[] addr):根据提供的主机名和 IP 地址创建 InetAddress。主机名可以是机器名,也可以是其 IP 地址的文本表示形式。
-static InetAddressgetByName(String host):在给定主机名的情况下确定主机的 IP 地址。主机名可以是机器名,也可以是其 IP 地址的文本表示形式。
-static InetAddressgetAllByName(String host):返回给定主机名的所有 IP 地址所组成的数组。
-static InetAddressgetLocalHost():返回本地主机。如果找不到host
的任何 IP 地址则抛出UnknownHostException
。
-StringgetHostAddress():返回 IP 地址字符串(以文本表现形式)。
-String getHostName():获取此 IP 地址的主机名。
(3)InetAddress有两个子类:Inet4Address和Inet6Address。
3、端口号
(1)端口号是用于标识进程的逻辑地址。
(2)有效使用端口为0-65535,其中0-1024为系统使用的保留端口。
4、传输协议
(1)UDP:即用户数据报协议,是一种面向无连接的传输层协议,提供不可靠的信息传送服务。其特点:
-将数据及源和目的封装在数据包中,不需要连接
-每个数据包的大小限制在64K内
-国面向无连接,是不可靠协议
-传输效率高。
(2)TCP:即传输控制协议,是一种面向连接的、可靠的传输层协议。其特点:
-建立连接,形成传输数据的通道
-在连接中进行大数据量传输
-通过三次握手完成连接,是可靠协议
-必须建立连接,传输效率较低。
5、Socket
Socket就是为网络服务提供的一种机制,通信的两端都有Socket,网络通信其实就是Socket的通信,数据在两个Sockt间通过IO传输。
6、UDP传输方式
(1)DatagramSocket
:DatagramSocket
类表示用来发送和接收数据报包的套接字。
(2)DatagramSocket:DatagramSocket类表示数据报包。用来实现无连接包投递服务。
(3)通过UDP传输方式(发送端),将一段文字数据发送出去:
-建立udpsocket服务
-提供数据,并将数据封装到数据包中
-通过socket服务的发送功能,将数据包发出去
-关闭资源
示例:
importjava.net.*;
class UdpSend
{
public static void main(String[]args)throws Exception
{
//建立UDP服务
DatagramSocket ds=newDatagramSocket();
//确定数据,并封装成数据包
byte[] buf="udpsend".getBytes();
DatagramPacket dp=newDatagramPacket(buf,buf.length,InetAddress.getByName("192.168.0.100"),10000);
//通过socket服务将数据包发送出去
ds.send(dp
//关闭资源
ds.close();
}
}
(4)通过UDP传输方式(接收端):
-定义udpsocket服务,通常会监听一个端口,其实就是给这个接收网络应用程序定义数字标识,方便于明确哪些数据过来该应用程序可以处理。
-定义一个数据包,用于存储接收到的字节数据,而数据包对象中有很多功能可以提取字节数据中的不同数据信息
-通过socket服务的receive方法将收到的数据存入已定义好的数据包中
-通过数据包对象的特有功能,将这些不同的数据取出,打印在控制台上
-关闭资源
示例:
class UdpRece
{
public static void main(String[]args)throws Exception
{
//建立UDP socket
DatagramSocket ds=newDatagramSocket(10000);
//定义数据包,用于存储数据
byte[] buf=new byte[1024];
DatagramPacket dp=newDatagramPacket(buf,buf.length);
//通过服务的receive方法将收到数据存入数据包中
ds.receive(dp);
//通过数据包的方法获取其中的数据
Stringip=dp.getAddress().getHostAddress();
String data=newString(dp.getData(),0,dp.getLength());
int port=dp.getPort();
System.out.println(ip+"::"+data+"::"+port);
//关闭资源
ds.close();
}
}
(4)示例:在UDP发送端通过键盘录入方式获取数据发给接收端。
importjava.io.*;
importjava.net.*;
//建立发送端
class UdpSend2
{
public static void main(String[]args)throws Exception
{
//建立UDP socket
DatagramSocket ds=newDatagramSocket();
//建立输入流读取键盘数据
BufferedReader br=newBufferedReader(new InputStreamReader(System.in));
String line=null;
while((line=br.readLine())!=null)
{
if("over".equals(line))
break;
byte[] buf=newbyte[1024];
buf=line.getBytes();
//建立数据包封装数据并发送到接收端
DatagramPacketdp=newDatagramPacket(buf,buf.length,InetAddress.getByName("192.168.0.100"),10000);
ds.send(dp);
}
ds.close();
}
}
//建立接收端
class UdpRece2
{
public static void main(String[]args)throws Exception
{
//建立UDP socket
DatagramSocket ds=newDatagramSocket(10000);
while(true)
{
//定义数据包,用于存储数据
byte[] buf=newbyte[1024];
DatagramPacketdp=new DatagramPacket(buf,buf.length);
//通过服务的receive方法将收到数据存入数据包中
ds.receive(dp);
Stringip=dp.getAddress().getHostAddress();
String data=newString(buf,0,dp.getLength());
System.out.println(ip+"::"+data);
}
}
}
(5)UDP练习:编写一个聊天程序,有收数据和发数据的部分,这两部分需要同时执行,那就需要用到多线程技术,一个纯种控制收,一个纯种控制发。因为收和发是不一致的,所以要定义两个run方法,而且这两个方法要封装到不同的类中。
importjava.io.*;
importjava.net.*;
public classUdpDemo3
{
public static void main(String[]args)throws Exception
{
DatagramSocket send=new DatagramSocket();
DatagramSocket rece=newDatagramSocket(10001);
new Thread(newSend(send)).start();
new Thread(newRece(rece)).start();
}
}
//定义发送线程
class Sendimplements Runnable
{
private DatagramSocket ds;
public Send(DatagramSocket ds)
{
this.ds=ds;
}
public void run()
{
try
{
BufferedReaderbr=new BufferedReader(new InputStreamReader(System.in));
String line=null;
while((line=br.readLine())!=null)
{
if("over".equals(line))
break;
byte[]buf=line.getBytes();
DatagramPacketdp=newDatagramPacket(buf,buf.length,InetAddress.getByName("192.168.0.100"),10001);
}
}
catch(Exception e)
{
System.out.println(e.toString());
}
}
}
//定义接收线程
class Receimplements Runnable
{
private DatagramSocket ds;
public Rece(DatagramSocket ds)
{
this.ds=ds;
}
public void run()
{
try
{
while(true)
{
byte[]buf=new byte[1024];
DatagramPacketdp=new DatagramPacket(buf,buf.length);
ds.receive(dp);
Stringip=dp.getAddress().getHostAddress();
Stringdata=new String(buf,0,dp.getLength());
System.out.println(ip+"::"+data);
}
}
catch(Exception e)
{
System.out.println(e.toString());
}
}
}
7、TCP传输
(1)TCP传输分客户端和服务端,客户端对应的对象是Socket,服务端对应的对象是ServerSocket。Socket实现客户端套接字,ServerSocket此类实现服务器套接字。
(2)Socket类主要方法:
-Socket(Stringhost,it port):创建一个流套接字并将其连接到指定主机上的指定端口号。
-InetAddressgetInetAddress():返回套接字连接的地址。
-InputStreamgetInputStream():返回此套接字的输入流。
-OutputStreamgetOutputStream():返回此套接字的输出流。
(3)ServerSocket主要方法:
-ServerSocket(intport):创建绑定到特定端口的服务器套接字。
-Socket accept():侦听并接受到此套接字的连接。
(4)创建TCP客户端步骤:
-创建socket服务,指定目的主机和端口
-为了发送数据,应获取socket流中的输出流。
示例:
importjava.io.*;
importjava.net.*;
//创建TCP客户端
class TcpClient
{
public static void main(String[]args)throws Exception
{
//建立客户端Socket并指定目的主机和端口
Socket s=newSocket("192.168.0.100",10002);
//获取socket中的输出流以发送数据
OutputStreamout=s.getOutputStream();
out.write("Tcpclient".getBytes());
//关闭资源
s.close();
}
}
(5)创建TCP服务端步骤
-建立服务端的socket服务,通过ServerSocket()方法,并监听一个端口
-获取连接过来的客户端对象,通过ServerSocket的accept()方法,若没有连接会等待,所以此方法是阻塞式的
-客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取该客户端对象的读取流来读取发过来的数据,并打印在控制台
-关闭服务端(可选)
importjava.io.*;
importjava.net.*;
class TcpServer
{
public static void main(String[]args)throws Exception
{
//建立服务端socket服务并监听一个端口
ServerSocket ss=newServerSocket(10002
//通过accept方法获取连接过来的客户端对象
Socket s=ss.accept();
Stringip=s.getInetAddress().getHostAddress();
System.out.println(ip+"...connected");
//获取客户端的读取流来读取客户端发过来的数据
InputStream in=s.getInputStream();
byte[] buf=new byte[1024];
int len=in.read(buf);
System.out.println(newString(buf,0,len));
//关闭资源
s.close();
ss.close();
}
}
(6)练习:客户端给服务端发数据,服务端收到后向客户端回数据
importjava.io.*;
importjava.net.*;
// 创建TCP客户端
class TcpClient1
{
public static void main(String[]args)throws Exception
{
//建立客户端socket服务
Socket s=newSocket("192.168.0.100",10004);
//获取socket输出流以向服务端发数据
OutputStreamout=s.getOutputStream();
out.write("Clientsend".getBytes());
//获取socket输入流以读取从服务器发回的数据
InputStreamin=s.getInputStream();
byte[] buf=new byte[1024];
int len=in.read(buf);
System.out.println(newString(buf,0,len));
//关闭资源
s.close();
}
}
//创建TCP服务端
class TcpServer1
{
public static void main(String[] args)throwsException
{
//建立服务端socket服务
ServerSocket ss=newServerSocket(10004);
//获取客户端socket对象
Socket s=ss.accept();
Stringip=s.getInetAddress().getHostAddress();
System.out.println(ip+"....connected");
//获取客户端socket输入流以读取客户端发来的数据
InputStreamin=s.getInputStream();
byte[] buf=new byte[1024];
int len=in.read(buf);
System.out.println(newString(buf,0,len));
//获取客户端socket输出流以向客户端回复数据
OutputStreamout=s.getOutputStream();
out.write("Serversend".getBytes());
//关闭资源
s.close();
ss.close();
}
}
(7)练习:建立一个文本转换服务器,客户端给服务器发关文本,服务器会将文本转换成大写再返回给客户端,而且客户端可以不断地进行文本转换。
importjava.io.*;
importjava.net.*;
//创建客户端
class Client
{
public static void main(String[]args)throws Exception
{
//建立客户端socket服务
Socket s=newSocket("192.168.0.100",10005);
//建立从键盘读取数据的字符缓冲区
BufferedReader br=newBufferedReader(new InputStreamReader(System.in));
//建立字符缓冲区用于向服务端发数据
BufferedWriter bwOut=newBufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//建立字符缓冲区用于读取从服务器发回的数据
BufferedReader brIn=newBufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
while((line=br.readLine())!=null)
{
if("over".equals(line))
break;
bwOut.write(line);
bwOut.newLine();
bwOut.flush();
Stringstr=brIn.readLine();
System.out.println("Server:"+str);
}
//关闭资源
s.close();
}
}
//创建服务端
class Server
{
public static void main(String[]args)throws Exception
{
//建立服务端socket服务
ServerSocket ss=newServerSocket(10005);
Socket s=ss.accept();
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+"....connected");
//建立字符缓冲区用读取客户端发来的数据
BufferedReader brIn=newBufferedReader(new InputStreamReader(s.getInputStream()));
//建立字符缓冲区用于向客户端发转换后的数据
BufferedWriter bwOut=newBufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line=null;
while((line=brIn.readLine())!=null)
{
bwOut.write(line.toUpperCase());
bwOut.newLine();
bwOut.flush();
}
//关闭资源
s.close();
ss.close();
}
}
这里要注意的是由于客户端和服务端中的readLine方法都是阻塞式方法,该方法如果没有读取到结束标记就一直会等待,从而导致两端都会在等待,所以客户端和服务端在向对方发数据的时候除了刷新缓冲区外还要在发送数据的末尾加入结束标记,这个可以用newLine方法实现。
(8)练习:将客户端的文件复制到服务器
importjava.io.*;
importjava.net.*;
//创建客户端
class Client1
{
public static void main(String[]args)throws Exception
{
//建立客户端socket服务
Socket s=newSocket("192.168.0.100",10006);
//建立字符缓冲区读取文件数据
BufferedReader br=newBufferedReader(new FileReader("D:/Test/Demo1.txt"));
//建立字符缓冲区以向服务器发数据
BufferedWriter bwOut=newBufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line=null;
while((line=br.readLine())!=null)
{
bwOut.write(line);
bwOut.flush();
bwOut.newLine();
}
//关闭socket的输出流,相当于向服务器发送一个结束标记
s.shutdownOutput();
//建立字符缓冲区以读取服务器发回的数据
BufferedReader brIn=newBufferedReader(new InputStreamReader(s.getInputStream()));
String str=brIn.readLine();
System.out.println(str);
//关闭资源
br.close();
s.close();
}
}
//建立服务端
class Server1
{
public static void main(String[]args)throws Exception
{
//建立服务端socket服务
ServerSocket ss=newServerSocket(10006);
Socket s=ss.accept();
//建立字符缓冲区以读取客户端发来的数据
BufferedReader brIn=newBufferedReader(new InputStreamReader(s.getInputStream()));
//建立字符缓冲区以将从客户端收到的数据写到文件中
BufferedWriter bw=newBufferedWriter(new FileWriter("D:/Test/Demo1_copy.txt"));
String line=null;
while((line=brIn.readLine())!=null)
{
System.out.println(line);
bw.write(line);
bw.flush();
}
//建立字符缓冲区以向客户端回复信息
BufferedWriter bwOut=newBufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bwOut.write("Uploadcompleted");
bwOut.flush();
//关闭资源
bw.close();
s.close();
ss.close();
}
}
这里需要注意的是,在客户端的while循环结束后并不会向服务端发送一个结束标记,导致服务端中的while循环因为没有读取到结束标记而一直在等待,所以在客户端中的while循环结束后要调用socket的showdownOuput方法关闭socket的流资源,这相当于结服务端发送一个结束标记。