引子:
当前,互联网 体系结构的参考模型主要有两种,一种是OSI参考模型,另一种是TCP/IP参考模型。
一、OSI参考模型,即开放式通信系统互联参考模型(OSI/RM,Open Systems Interconnection Reference Model),是国际标准化组织(ISO)提出的一个试图使各种计算机在世界范围内互连为网络的标准框架,简称OSI。
OSI参考模型将实现网络互连的通信协议分为7层,自上而下分别是:
第7层应用层:OSI中的最高层,为用户提供各项互联网应用,如公司老板通过浏览器上网、发送电子邮件等。 常见的协议有:HTTP,HTTPS,FTP,TELNET,SSH,SMTP,POP3等。
第6层表示层:相当公司中替老板写信的助理。
第5层会话层:相当于公司中收寄信、写信封与拆信封的秘书。
第4层传输层:提供终端到终端的可靠连接,相当于公司中跑邮局的送信职员。
第3层网络层: 确保信件通过一系列路由到达目的地。
第2层数据链路层: 决定访问网络介质的方式,并处理流控制。
第1层物理层:处于OSI参考模型的最底层,物理层的主要功能是利用物理传输介质为数据链路层提供物理连接,以便透明地传输比特流;该层的常用设备有网卡、集线器、中继器、调制解调器、网线、双绞线、同轴电缆等各种物理设备。
数据发送时,从第七层传到第一层,接收数据则相反。
上三层总称为“应用层”,用来控制软件方面;下四层总称为“数据流层”,用来管理硬件。除了物理层之外,其他层都是用软件实现的。
二、TCP/IP参考模型。
│ ││N│I│H│T│T│O│E│R│M│S│其│
│第四层,应用层 ││S│N│O│P│T│P│L│C│T│E│ │
│ ││ │G│I│ │P│H│N│ │P│N│ │
│ ││ │E│S│ │ │E│E│ │ │E│它│
│ ││ │R│ │ │ │R│T│ │ │T│ │
└───────------─┘└─┴─┴─-┴─┴─-┴─┴─-┴─┴─-┴─┴-─┘
┌───────-----─┐┌─────────-------┬──--------─────────┐
│第三层,传输层 ││ TCP │ UDP │
└───────-----─┘└────────-------─┴──────────--------─┘
┌───────-----─┐┌───----──┬───---─┬────────-------──┐
│ ││ │ICMP│ │
│第二层,网间层 ││ └──---──┘ │
│ ││ IP │
└────────-----┘└────────────────────-------------─-┘
┌────────-----┐┌─────────-------┬──────--------─────┐
│第一层,网络接口││ARP/RARP │ 其它 │
└────────------┘└─────────------┴─────--------──────┘
我们在对上述两种参考模型有些了解后,接下来主要看TCP和UDP。我们先来看二者的区别:
1.TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接;
2.TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付;
3.TCP面向字节流,实际上是TCP把数据看成是一连串无结构的字节流;UDP是面向报文的,它没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(如IP电话,实时视频会议等)
4.每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信;
5.TCP首部开销20字节;UDP的首部开销小,只有8个字节;
6.TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
在Java中,对遵守TCP协议的类有ServerSocket和Socket,遵守UDP协议的类有DatagramSocket。我这里提供的聊天室项目,是基于TCP协议的。
该项目分为4个包,分别是utils(提供工具),ui(提供窗体界面),server(服务器子线程),client(客户端子线程)。
utils包下有两个类,分别是HostInfo和Release。HostInfo.java代码如下:
import java.net.InetAddress;import java.net.UnknownHostException;public abstract class HostInfo {public static String IP=getIP();public static final int PORT=10086;public static final int NUM=50;public static final String NEW_LINE="\r\n";private static String getIP(){try {return InetAddress.getLocalHost().getHostAddress();} catch (UnknownHostException e) {e.printStackTrace();return null;}}}
Release.java的代码如下:
import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;public abstract class Release {public static void release(Socket socket,BufferedWriter bw){release(null,socket,null,bw);}public static void release(Socket socket,BufferedReader br){release(null,socket,br,null);}public static void release(Socket socket){release(null,socket,null,null);}public static void release(ServerSocket server){release(server,null,null,null);}public static void release(ServerSocket server,Socket socket,BufferedReader br,BufferedWriter bw){if(server!=null){try {server.close();} catch (IOException e) {e.printStackTrace();}}if(socket!=null){try {socket.close();} catch (IOException e) {e.printStackTrace();}}if(br!=null){try {br.close();} catch (IOException e) {e.printStackTrace();}}if(bw!=null){try {bw.close();} catch (IOException e) {e.printStackTrace();}}}}
ui包下有两个类,分别是UIserver和UIclient。UIserver.java的代码如下:
import java.awt.Dimension;import java.awt.FlowLayout;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.JPanel;import javax.swing.JScrollPane;import javax.swing.JTextArea;import javax.swing.JTextField;import 聊天室.server.Server;import 聊天室.utils.HostInfo;public class UIserver extends JFrame {private JPanel jp=new JPanel();private JLabel jl_ipTips=new JLabel("服务器ip:");public static JTextField jtf_ip=new JTextField(HostInfo.IP);private JLabel jl_portTips=new JLabel("服务器端口:");public static JTextField jtf_port=new JTextField(HostInfo.PORT+"");public static JButton bt_open=new JButton("启动服务器");public static JTextArea jta_log=new JTextArea();private JScrollPane jsp_log=new JScrollPane(jta_log);public UIserver(){jp.setLayout(new FlowLayout());jp.add(jl_ipTips);jp.add(jtf_ip);jp.add(jl_portTips);jp.add(jtf_port);jp.add(bt_open);jp.add(jsp_log);jl_ipTips.setPreferredSize(new Dimension(100, 50));jtf_ip.setPreferredSize(new Dimension(150, 50));jl_portTips.setPreferredSize(new Dimension(100, 50));jtf_port.setPreferredSize(new Dimension(150, 50));bt_open.setPreferredSize(new Dimension(260, 50));jsp_log.setPreferredSize(new Dimension(260, 192));jta_log.setLineWrap(true);jta_log.setEditable(false);bt_open.addActionListener(new ActionListener(){@Overridepublic void actionPerformed(ActionEvent arg0) {new Server().start();}});add(jp);setTitle("聊天应用控制服务器");setBounds(100, 50, 300, 400);setResizable(false);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);setVisible(true);}public static void main(String[] args) {new UIserver();}}
UIclient.java的代码如下:
import java.awt.BorderLayout;import java.awt.Dimension;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.awt.event.FocusAdapter;import java.awt.event.FocusEvent;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JPanel;import javax.swing.JScrollPane;import javax.swing.JTextArea;import javax.swing.JTextField;import 聊天室.client.Client;public class UIclient extends JFrame {private JPanel jp_chat=new JPanel();public static JTextArea jta_chat=new JTextArea();private JScrollPane jsp_chat=new JScrollPane(jta_chat);private JPanel jp_send=new JPanel();public static JTextField jtf_desip=new JTextField("请输入对方Ip");public static JTextArea jta_message=new JTextArea();private JScrollPane jsp_message=new JScrollPane(jta_message);private JButton bt_send=new JButton("发送");private Client client;public UIclient(){jp_chat.add(jsp_chat);jta_chat.setLineWrap(true);jta_chat.setEditable(false);jsp_chat.setPreferredSize(new Dimension(550, 400));jp_send.add(jtf_desip);jp_send.add(jsp_message);jta_message.setLineWrap(true);jp_send.add(bt_send);jtf_desip.setPreferredSize(new Dimension(100, 50));jsp_message.setPreferredSize(new Dimension(250, 50));bt_send.setPreferredSize(new Dimension(100, 50));jtf_desip.addFocusListener(new FocusAdapter(){@Overridepublic void focusGained(FocusEvent e) {jtf_desip.setText("");}});jta_message.addFocusListener(new FocusAdapter(){@Overridepublic void focusGained(FocusEvent e) {jta_message.setText("");}});bt_send.addActionListener(new ActionListener(){@Overridepublic void actionPerformed(ActionEvent arg0) {client.send();}});add(jp_chat, BorderLayout.CENTER);add(jp_send, BorderLayout.SOUTH);setTitle("群聊窗口");setBounds(300, 100, 600, 500);setResizable(false);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);setVisible(true);client=new Client();client.start();}public static void main(String[] args) {new UIclient();}}
server包下有两个类,分别是Server和Transport。Server.java的代码如下:
import java.io.IOException;
import java.net.ServerSocket;import java.net.Socket;import java.util.ArrayList;import java.util.List;import 聊天室.ui.UIserver;import 聊天室.utils.HostInfo;import 聊天室.utils.Release;public class Server extends Thread {private ServerSocket server;public static List<Transport> clients=new ArrayList<>();@Overridepublic void run() {try {server=new ServerSocket(Integer.parseInt(UIserver.jtf_port.getText().trim()));} catch (IOException e) {Release.release(server);throw new RuntimeException("服务器端口被占!");}UIserver.bt_open.setText("已启动服务器");UIserver.bt_open.setEnabled(false);UIserver.jta_log.append("服务器成功启动!"+HostInfo.NEW_LINE);new Accept().start();}class Accept extends Thread{private Socket socket;@Overridepublic void run() {int num=0;while(num<HostInfo.NUM){try {socket=server.accept();} catch (IOException e) {Release.release(socket);throw new RuntimeException("客户端连接失败!");}num++;String str="第 "+num+" 个客户端连接成功!==>"+socket.getInetAddress().getHostAddress()+" :"+socket.getPort()+HostInfo.NEW_LINE;UIserver.jta_log.append(str);clients.add(new Transport(socket));}UIserver.jta_log.append("超出服务器负荷!");Release.release(server);}}}
Transport.java的代码如下:
import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.net.Socket;import 聊天室.utils.Release;public class Transport extends Thread {private Socket socket;private String ip;public Transport(Socket socket){this.socket=socket;this.ip=socket.getInetAddress().getHostAddress();this.start();}@Overridepublic void run() {BufferedReader br=null;BufferedWriter ownbw=null;try {br=new BufferedReader(new InputStreamReader(socket.getInputStream()));ownbw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));} catch (IOException e) {Server.clients.remove(this);Release.release(socket);throw new RuntimeException("获取流失败!");}String str=null;try {while((str=br.readLine())!=null){String[] split = str.split(":", 2);if(split.length<=1){ownbw.write("数据格式错误!");ownbw.newLine();ownbw.flush();}String desip=split[0];String content=split[1];BufferedWriter desbw=null;boolean isOnLine=false;for(Transport des:Server.clients){if(desip.equals(des.ip)){isOnLine=true;desbw=new BufferedWriter(new OutputStreamWriter(des.socket.getOutputStream()));desbw.write(str);desbw.newLine();desbw.flush();}}if(!isOnLine){ownbw.write("对方不在线!");ownbw.newLine();ownbw.flush();}}} catch (IOException e) {Server.clients.remove(this);Release.release(socket);throw new RuntimeException("获取流失败!");}}}
client包下有一个类Client.java,该类的代码如下:
import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.net.Socket;import 聊天室.ui.UIclient;import 聊天室.ui.UIserver;import 聊天室.utils.HostInfo;import 聊天室.utils.Release;public class Client extends Thread {private Socket socket;@Overridepublic void run() {try {socket=new Socket(UIserver.jtf_ip.getText().toLowerCase(), Integer.parseInt(UIserver.jtf_port.getText().trim()));} catch (IOException e) {Release.release(socket);throw new RuntimeException("客户端创建失败!");}BufferedReader br=null;try {br=new BufferedReader(new InputStreamReader(socket.getInputStream()));} catch (IOException e) {Release.release(socket);throw new RuntimeException("获取流失败!");}String str=null;try {while((str=br.readLine())!=null){UIclient.jta_chat.append(str+HostInfo.NEW_LINE);}} catch (IOException e) {Release.release(socket);throw new RuntimeException("获取流失败!");}}public void send(){new Send().start();}class Send extends Thread{@Overridepublic void run() {BufferedWriter bw=null;try {bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));} catch (IOException e) {Release.release(socket,bw);throw new RuntimeException("获取流失败!");}String str=UIclient.jtf_desip.getText().trim()+":"+UIclient.jta_message.getText().trim();try {bw.write(str);bw.newLine();bw.flush();} catch (IOException e) {Release.release(socket,bw);throw new RuntimeException("获取流失败!");}}}}