应用层:应用程序间沟通的层,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等。
传输层:在此层中,它提供了节点间的数据传送,应用程序之间的通信服务,主要功能是数据格式化、数据确认和丢失重传等。如传输控制协议(TCP)、用户数据报协议(UDP)等,TCP和UDP给数据包加入传输数据并把它传输到下一层中,这一层负责传送数据,并且确定数据已被送达并接收。
连网络层:负责提供基本的数据封包传送功能,让每一块数据包都能够到达目的主机(但不检查是否被正确接收),如网际协议(IP)
TCP/IP协议(Transmission Control Protocol/Internet Protocol)叫做传输控制/网际协议.
TCP/IP 的工作原理 -->本文采用TCP/IP协议传送文件为例,说明TCP/IP的工作原理,其中应用层传输文件采用文件传输协议(FTP)。
TCP/IP协议的工作流程如下:
●在源主机上,应用层将一串应用数据流传送给传输层。
●传输层将应用层的数据流截成分组,并加上TCP报头形成TCP段,送交网络层。
●在网络层给TCP段加上包括源、目的主机IP地址的IP报头,生成一个IP数据包,并将IP数据包送交链路层。
●链路层在其MAC帧的数据部分装上IP数据包,再加上源、目的主机的MAC地址和帧头,并根据其目的MAC地址,将MAC帧发往目的主机或IP路由器。
●在目的主机,链路层将MAC帧的帧头去掉,并将IP数据包送交网络层。
●网络层检查IP报头,如果报头中校验和与计算结果不一致,则丢弃该IP数据包;若校验和与计算结果一致,则去掉IP报头,将TCP段送交传输层。
●传输层检查顺序号,判断是否是正确的TCP分组,然后检查TCP报头数据。若正确,则向源主机发确认信息;若不正确或丢包,则向源主机要求重发信息。
●在目的主机,传输层去掉TCP报头,将排好顺序的分组组成应用数据流送给应用程序。这样目的主机接收到的来自源主机的字节流,就像是直接接收来自源主机的字节流一样。
/**
* 127.0.0.1没有于网卡绑定,当网卡坏了时,仍可测试使用本地网络程序
* IP地址保证数据传送到计算机,但是不能确定是哪个网络程序,需要使用PORT,两个字节的整数
* PORT 0~1023用于知名的网络服务和应用,端口号在0~65535之间
*
* 高级协议 UDP TCP
* TCP:传输控制协议,面向连接的通信协议,打电话
* UDP:用户数据报协议,一个主机向另一个主机发送,它不管另个主机是否启动,另一个主机接收到信息后不会回应,像是传呼台,一般发送几遍
*
* 数据帧格式
* 协议类型 | 源IP | 目标IP | 源端口 | 目标端口 | 帧序号 | 帧数据
* 1 hello
* 2 world
* Socket是网络驱动层提供给应用程序编程的接口和一种机制
* 类似港口码头,应用程序 把 货物 放到港口码头,完成了货物的运货
* 应用程序 只需等待 货物 到达码头,将 货物取走
*
*
* 1 产生Socket
* 应用程序-----------------------------
* | ^ |
* 2 | | |
* | | |
* 调用bind将 | | 4应用程序从Socket中取数据 |
* Socket的信息 | --------------------------- Socket
* 通知给驱动程序 | -------------------------->
* | | 3 驱动程序根据从网卡传送来的
* | | 数据包中指定目标端口号,将处理后的数据传送到
* | | 相应的socket中
* | |
* 驱动程序
*
* ServerSocket 用于TCP通信的服务器
* DatagramDocket类用于UDP通信
* Scoket类用于TCP通信的服务器和客户端
*
*/
public class InetAddressExample {
public static void main(String[] args){
try{
//该主机每一个接口所以对应的NetworkInterface类实例
Enumeration<NetworkInterface> interfaceList = NetworkInterface.getNetworkInterfaces();
if(interfaceList == null){
System.out.println("--NO interfaces found--");
}else{
while(interfaceList.hasMoreElements()){
NetworkInterface iface = interfaceList.nextElement();
//返回一个本地名称
System.out.println("Interface " + iface.getName() + ":");
//获取与接口关联的地址
Enumeration<InetAddress> addrList = iface.getInetAddresses();
if(!addrList.hasMoreElements()){
System.out.println("\t(No addresses for this interface)");
}
while(addrList.hasMoreElements()){
InetAddress address = addrList.nextElement();
System.out.println("\tAddress"
+ ((address instanceof Inet4Address? "(v4)"
: (address instanceof Inet6Address? "(v6)" : "?"))));
System.out.println("\t\t:" + address.getHostName());
System.out.println("\t\t:" + address.getHostAddress());
}
}
}
}catch(SocketException se){
System.out.println("Error getting network interfaces:" + se.getMessage());
}
}
}
/**
*
* TCP网络程序的工作原理
* UDP么有主从之分,两个可以是完全应用实例
*
* 为Client创建的Socket
* 两个Socket之间建立专线连接 服务器接受请求并创建Socket
* | ^
* | 客户端发出连接 |
* Client Socket [simple phone] -------------------------------> ServerSockt [114]
*
* ServerSocket类
* public ServerSocket()
* public ServerSocket(int port)
* public ServerSocket(int port, int backlog) //保持连接请求的客户数量
* public ServerSocket(int port, int backlog, InetAddress bindAddr)
* close()
* accpet() //返回建立专线连接的对象
*
*
* Socket类
* public Socket() //轮循
* public Socket(String host, int port)
* public Socket(InetAddress address, int port)
* public Socket(String host, int port, InetAddress address, int localport)
* //客户机有多快网卡,通过哪块网卡连接服务器
*
* 网络字节流形式,数据交换
* getInputStream和getOutputStream
*
* 简单的TCP服务器程序
* TCP服务器程序先运行,客户端才能连接上
* 使用Windows提供的telnet程序测试
* 使用BufferedReader包装类,从网络输入流中一次读取一行文本
* 如何打开telent程序的本地回显文本
*
* @author ShenJie
*
*/
public class TCPDemo1 {
public static void main(String[]args) throws Exception{
ServerSocket ss = new ServerSocket(8001);
Socket s = ss.accept();
InputStream ips = s.getInputStream();
OutputStream ops = s.getOutputStream();
ops.write("welcome to SHENJIE".getBytes());
// byte[] buf = new byte[1024];
// int len = ips.read(buf);
// System.out.println(new String(buf, 0 , len));
BufferedReader br = new BufferedReader(new InputStreamReader(ips));
System.out.println(br.readLine());
br.close();
//关闭顺序
ips.close();
ops.close();
s.close();
ss.close();
//用telnet实验
//telnet localhost 8001
//telnet > ?
//telnet > set ?
}
}
/**
*
*
* 完善的TCP服务器程序模型
* 同时与多个客户端会话,
* 需要循环调用accept方法,会话不鞥你影响,需要独立运行
* 客户端每次向服务器发送一行字符文本,服务器将这行字符文本中的所有字符反向排列送回客户端
* 当客户端发送“quit”时,服务器结束与客户端的会话
*
* TCP客户端
*
* @author ShenJie
*
*/
public class TCPDemo2 {
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(8001);
boolean bRunning = true;
//会话过程
while(bRunning){
Socket s = ss.accept();
//每个会话启动一个线程
new Thread(new Servicer(s)).start();
}
ss.close();
}
}
class Servicer implements Runnable{
Socket s;
public Servicer(Socket s){
this.s = s;
}
@Override
public void run() {
InputStream ips;
try {
ips = s.getInputStream();
OutputStream ops = s.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(ips));
//true自动刷新缓冲区
PrintWriter pw = new PrintWriter(new OutputStreamWriter(ops),true);
//PrintStream 都是产生\n
while(true){
String strLine = br.readLine();
if(strLine.equalsIgnoreCase("quit")){
break;
}
System.out.println(strLine + strLine.length());
//abd{backspace}c
//c{backspace}dba
//abc --- > abd
String strEcho = new StringBuffer(strLine).reverse().toString();
pw.println(strLine + " ----> " + strEcho);
}
br.close();
pw.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
*
* 如何检测和解决端口冲突问题
* netstat
* 通过TCP程序在网络上传送对象
* @author ShenJie
*/
public class TCPDemo3 {
public static void main(String[] args) throws Exception, IOException{
Socket s = new Socket("localhost", 9999);
InputStream ips = s.getInputStream();
OutputStream ops = s.getOutputStream();
BufferedReader brNet = new BufferedReader(new InputStreamReader(ips));
//true自动刷新缓冲区
PrintWriter pw = new PrintWriter(ops,true);
BufferedReader brKeyBoard = new BufferedReader(new InputStreamReader(System.in));
while(true){
String strWord = brKeyBoard.readLine();
pw.println(strWord);
if(strWord.equalsIgnoreCase("quit")){
break;
}
System.out.println(brNet.readLine());
}
pw.close();
brNet.close();
ops.close();
ips.close();
s.close();
}
}
/**
* ObjectInputStream 和 ObjectOutputStream从底层输入流中读取对象类型,以及写入
*
* 应用通信协议和网络通信协议的关系
*
* 电话系统tcp 语言ftp
*
* @author ShenJie
*
*/
public class TCPDemo4 {
public static void main(String[] args) throws Exception{
ServerSocket ss =new ServerSocket(9999);
Socket s = ss.accept();
OutputStream ops = s.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(ops);
Student stu = new Student(19,"wangwu", 22 ,"hunan");
oos.writeObject(stu);
oos.close();
ops.close();
s.close();
ss.close();
}
}
class ObjectClient{
public ObjectClient() throws Exception{
Socket s = new Socket("localhost", 9999);
InputStream ips = s.getInputStream();
ObjectInputStream ois = new ObjectInputStream(ips);
Student stu = (Student)ois.readObject();
System.out.print("id is =" + stu.id);
System.out.print("name is =" + stu.name);
System.out.print("age is =" + stu.age);
System.out.print("department is =" + stu.department);
ois.close();
ips.close();
s.close();
}
}
class Student implements Serializable{
int id;
String name;
int age;
String department;
public Student(int id, String name, int age, String department){
this.id =id; this.name =name; this.age= age; this.department=department;
}
}
****************************************
int recvMsgSize;
byte[] reveiveBuf = new byte[BUFSIZE];
//接收并复制数据,直到客户端关闭
while((recvMsgSize = in.read(reveiveBuf)) != -1){
out.write(reveiveBuf,0,recvMsgSize);
}
***************************************************
/**
* 简易 TCPEchoServerPool
*/
public class TCPEchoServerPool {
private static final int BUFSIZE =32;
public static void main(String[] args) throws IOException{
int servPort = 9999;
int threadPoolSize = 20;
final ServerSocket servSock = new ServerSocket(servPort);
for(int i=0;i<threadPoolSize;i++){
Thread thread = new Thread(){
public void run(){
while(true){
try{
Socket clntSock = servSock.accept();
//handleEchoClient
}catch(IOException e){
}
}
}
};
thread.start();
}
}
}
/**
* 港口码头
* public DatagramSocket() 系统自动分配端口号:例如给别人打电话,自己的号码不固定
* public DatagramSocket(int port) :例如要朋友给自己打电话
* public DatagramSocket(int port, InetAddress Iaddr) 多个ip地址上运行,明确指定发送接收的IP地址
*
* 接收和发送数据的集装箱
* public DatagramPacket(byte[] buf,int length) 接收数据
* public DatagramPacket(byte[] buf,int length, InetAddress address,int port) 发送数据,需要有接收方的地址信息
*
* getInetAddress 和 getPort方法可以获得发送方的地址信息
* getData 和 getLength方法 == packet中字节数组缓冲区,packet中实际收到的数据长度
*
* 数据包定位1024个字节,发送不能多于这个
*
* InetAddress是表示计算机ip地址的一个类,一般计算机地址用"192.168.0.1"和"www.it315.org"表示
* getByName方法返回一个InetAddress实例对象
* getHostAddress返回 类似"192.168.0.1"的地址
*
* 字符串与字节数组之间双向转换
* UDP接收程序先启动运行,才能接收UDP发送程序发送的数据
* CMD用start命令打开新命令行窗口的好处,继承环境
* 解决发送中文字符串的问题
*
* @author ShenJie
*
*/
public class UDPDemo1 {
public static void main(String[] args){
}
}
//数据一发完就结束了
class UdpSend{
public static void main(String[] args){
DatagramSocket ds = null;
try {
ds = new DatagramSocket();
String strInfo = "Hello, this is Jesse!";
// String strInfo = "用中文的字符!";
try {
ds.send(new DatagramPacket(strInfo.getBytes(),strInfo.length(),3000));
// 中文时
// ds.send(new DatagramPacket(strInfo.getBytes(),strInfo.getBytes().length(),3000));
} catch (IOException e) {
e.printStackTrace();
}
ds.close();
} catch (SocketException e) {
e.printStackTrace();
}
}
}
//接收程序开着,一直阻塞
class UdpRecv{
public static void main(String[] args){
DatagramSocket ds = null;
try {
ds = new DatagramSocket(3000);
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, 1024);
ds.receive(dp);
//接收实际收到的数据个数
dp.getAddress();//获得对方的发送ip地址
System.out.println(new String(dp.getData(),0,dp.getLength())
+ "from " + dp.getAddress().getHostAddress() + ":" + dp.getPort());
ds.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 多线程和GUI
* 1. 编写图形用户界面
* 2. 编写网络消息发送功能
* 3. 编写网络消息接收功能
*
* 只有udp才能发送和接收广播地址
* 广播地址需要计算
* 可以放在INternet上使用,每台计算机需要有合法的IP地址,本来就能合法访问
*
* *******************************************************
* 使用私有IP并通过网关代理上网的计算机不能
*
* 192.168.0.2
* 192.168.0.3 [内部网络没有合法地址]
* 192.168.0.4
* 192.168.0.5
* ----------------------------交换机----------------------------
* 电话,isdn,cable,光纤专线连接
* 获得合法有效的IP地址 166.111.111.10
* 可以让Internet计算机访问到他
* 如果打开IP网络转发功能,内部网络其他计算机网关地址设置为这台机器的内部网络地址
* |
* |
* |
* |
* internet-------------------------> 221.201.121.57
*
* 192.168.0.2 发送到 221.201.121.57, 先将数据发送到网关上
* 数据格式为
* 1. 192.168.0.2 221.201.121.57 3000 3000 hello
* 2. 166.111.111.10 221.201.121.57 1027 3000 hello
* 3. 221.201.121.57 166.111.111.10 3000 1027 world
* 4. 221.201.121.57 192.168.0.2 3000 3000 world
*
*
* 转发映射记录表
* 192.168.0.2/3000 166.111.111.10/1027
* 192.168.0.3/3000 166.111.111.10/1028
*
* *******************************************************
*/
public class UDPDemo2 extends JFrame{
List lst = new List();
JTextField tfIP = new JTextField(15);
JTextField tfData = new JTextField(20);
DatagramSocket ds = null;
public UDPDemo2(){
try {
ds =new DatagramSocket(3000);
} catch (SocketException e) {
e.printStackTrace();
}
this.add(lst, "Center");
Panel p =new Panel();
add(p, "South");
p.setLayout(new BorderLayout());
p.add(tfIP,"West");
p.add(tfData,"East");
//线程类接收网络上发送的消息
new Thread(new Runnable(){
@Override
public void run() {
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, 1024);
while(true){
try {
ds.receive(dp);
} catch (IOException e) {
/**
* 当关闭的时候不发生异常
*/
if(!ds.isClosed())
e.printStackTrace();
}
//lst一般显示在最后一行,希望显示在第一行
// lst.add("");
lst.add(new String(/*dp.getData()*/buf, 0 , dp.getLength())
+ " from " + dp.getAddress().getHostAddress() + ":" + dp.getPort(),0);
}
}
}).start();
tfData.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
byte[] buf = tfData.getText().getBytes();
DatagramPacket dp;
try {
dp = new DatagramPacket(buf, buf.length,
InetAddress.getByName(tfIP.getText()), 3000);
try {
ds.send(dp);
} catch (IOException ex) {
ex.printStackTrace();
}
} catch (UnknownHostException ex) {
ex.printStackTrace();
}
tfData.setText("");
}
});
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
ds.close();
dispose();
System.exit(0);
}
});
setTitle("UDP\u5355\u673a\u7f51\u7edc\u804a\u5929\u5de5\u5177");
}
public static void main(String[] args){
UDPDemo2 ud = new UDPDemo2();
ud.setSize(380,500);
ud.setVisible(true);
}
}