网络编程
软件结构
C/S 结构: 全称为 Client/Server 结构, 是指客户端和服务器结构.
B/S 结构: 全称为 Browser/Server 结构, 是指浏览器和服务器结构.
网络通信协议
网络通信协议: 位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则, 它对数据的传输格式, 传输速率, 传输步骤等做了统一规定
TCP/IP 协议: 它定义了计算机如何连入因特网, 以及数据如何在它们之间传输的标准. 它的内部包含一系列的用于处理数据通信的协议, 每一层都呼叫它的下一层所提供的协议来完成自己的需求
应用层: 网络服务与最终用户的一个接口. 协议有: HTTP,FTP,SMTP,DNS,TELNET,HTTPS,POP3 等等.
表示层: 数据的表示, 安全, 压缩. 格式有: JPEG,ASCll,DECOIC, 加密格式等.
会话层: 建立, 管理, 终止会话. 对应主机进程, 指本地主机与远程主机正在进行的会话
传输层: 定义传输数据的协议端口号, 以及流控和差错校验. 协议有: TCP,UDP.
网络层: 进行逻辑地址寻址, 实现不同网络之间的路径选择. 协议有: ICMP,IGMP,IP(IPV4 IPV6),ARP,RARP.
数据链路层: 建立逻辑连接, 进行硬件地址寻址, 差错校验等功能. 将比特组合成字节进而组合成帧, 用 Mac 地址访问介质, 错误发现但不能纠正.
物理层: 建立, 维护, 断开物理连接.
IP(internet protocal)又称为互联网协议. IP 的责任就是把数据从源传送到目的地. 它在源地址和目的地址之间传送一种称之为数据包的东西, 它还提供对数据大小的重新组装功能, 以适应不同网络对包大小的要求. 经常与 IP 协议放在一起的还有 TCP(Transmission Control Protocol)协议
TCP 与 UDP 协议
UDP: 用户数据报协议(User Datagram Protocol).
非面向连接的, 不可靠的: 发送端不会确认接收端是否存在, 就会发出数据, 同样接收端在收到数据时, 也不会向发送端反馈是否收到数据.
大小限制的: 数据被限制在 64kb 以内, 超出这个范围就不能发送了.
数据报(Datagram): 网络传输的基本单位
TCP: 传输控制协议 (Transmission Control Protocol).
面向连接的, 可靠的: 在发送端和接收端建立逻辑连接, 然后再传输数据, 是一种面向连接的, 可靠的, 基于字节流的传输层的通信协议, 可以连续传输大量的数据. TCP 协议保证了数据包在传送中准确无误, TCP 协议使用重发机制, 需要收到另一个通信实体的确认信息
三次握手
四次挥手
网络编程三要素
1, 协议: 如上
2,IP 地址
IP 地址用来给一个网络中的计算机设备做唯一的编号,
IPv4:32 位整数, 8 位一组最多可以表示 42 亿个
IPv6: 采用 128 位地址长度, 每 16 个字节一组, 分成 8 组十六进制数
公网地址 ( 万维网使用) 和 私有地址( 局域网使用).192.168. 开头的就是私有址址, 范围即为 192.168.0.0--192.168.255.255, 专门为组织机构内部使用
特殊的 IP 地址:
本地回环地址(hostAddress):127.0.0.1
主机名(hostName):localhost
域名: 域名服务器 (DNS) 负责将域名转化成 IP 地址, 方便记忆.
3, 端口号
端口号可以找到唯一标识设备中的进程(应用程序)0~65535, 动态 / 私有端口: 49152~65535
如果端口号被另外一个服务或应用所占用, 会导致当前程序启动失败.
利用协议 + IP 地址 + 端口号三元组合, 就可以标识网络中的进程了, 那么进程间的通信就可以利用这个标识与其它进程进行交互.
InetAddress 类
InetAddress 类主要表示 IP 地址, 两个子类: Inet4Address,Inet6Address.
lInetAddress 类没有提供公共的构造器, 而是提供 了 如下几个 静态方法来获取 InetAddress 实例
public static InetAddress getLocalHost() [返回本地主机]
public static InetAddress getByName(String host) [在给定主机名的情况下确定主机的 IP 地址]
public static InetAddress getByAddress(byte[] addr) [在给定原始 IP 地址的情况下, 返回 InetAddress 对象]
例: byte[] addr = {(byte)192,(byte)168,24,56}; 其内部用一个 int 存储
InetAddress 提供了如下几个常用的方法
public String getHostAddress() : [返回 IP 地址字符串(以文本表现形式)]
public String getHostName() : [获取此 IP 地址的主机名]
public String getCanonicalHostName(): [获取此 IP 地址的完全限定域名]
boolean isReachable(int timeout) [测试是否可以达到该地址.]
Socket
socket 可理解为一个介于应用层与协议层之间的一个抽象层, 它屏蔽了各个协议的通信细节, 使得程序员无需关注协议本身, 直接使用 socket 提供的接口来进行互联的不同主机间的进程的通信.
就是提供了 tcp/ip 协议的抽象, 对外提供了一套接口, 通过这个接口就可以统一, 方便的使用 tcp/ip 协议的功能了
通信的两端都要有 Socket(也可以叫 "套接字"), 是两台机器间通信的端点. 网络通信其实就是 Socket 间的通信. 也是负责和网卡驱动程序沟通的对象. socket 工作流程
socket 是两个主机通信的关键, 先理解 IO 流的工作流程有助于理解网络间的通信, socket 数据的发送与接收也可简单的理解为:
客户端将要发送的数据通过 send()发送给客户端的 tcp/udp 协议的缓冲区, 由客户端协议发送给服务端的 tcp/udp 协议, 服务端的 receive()会读取服务端的协议缓冲区接收到的数据,
如需返回数据再经服务端的 send()发送给服务端的协议缓冲区, 服务端的协议再发送给客户端的协议, 客户端的 receive()会读取客户端协议缓冲区中的数据如此循环, 直到 close()
send()与 receive()函数
ServerSocket: 此类实现 TCP 服务器套接字. 服务器套接字等待请求通过网络传入. 流套接字
Socket: 此类实现客户端套接字(也可以就叫 "套接字"). 套接字是两台机器间通信的端点. 流套接字
DatagramSocket: 此类表示用来发送和接收 UDP 数据报包的套接字. 数据报套接字
TCP 网络编程
1, 服务器端
调用 ServerSocket(int port) : 创建一个服务器端套接字, 并绑定到指定端口上. 用于监听客户端的请求.
调用 accept() : 监听连接请求, 如果客户端请求连接, 则接受连接, 返回通信套接字对象.
调用 该 Socket 类对象的 getOutputStream() 和 getInputStream () : 获取输出流和输入流, 开始网络数据的发送和接收.
关闭 Socket 对象: 客户端访问结束, 关闭通信套接字.
2, 客户端
创建 Socket: 根据指定服务端的 IP 地址或端口号构造 Socket 类对象. 若服务器端响应, 则建立客户端到服务器的通信线路. 若连接失败, 会出现异常.
打开连接到 Socket 的输入 / 出流: 使用 getInputStream()方法获得输入流, 使用 getOutputStream()方法获得输出流, 进行数据传输
按照一定的协议对 Socket 进行读 / 写操作: 通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息), 通过输出流将信息写入线路.
关闭 Socket: 断开客户端到服务器的连接, 释放线路
API
ServerSocket 类的构造方法:
ServerSocket(int port) : [创建绑定到特定端口的服务器套接字]
ServerSocket 类的常用方法:
Socket accept() [侦听并接受到此套接字的连接.]
InetAddress getInetAddress() [返回此服务器套接字的本地地址.]
int getLocalPort() [返回此套接字在其上侦听的端口.]
void close(): [关闭此套接字.]
Socket 类的常用构造方法:
public Socket(InetAddress address,int port): [创建一个流套接字并将其连接到指定 IP 地址的指定端口号]
public Socket(String host,int port): [创建一个流套接字并将其连接到指定主机上的指定端口号]
Socket 类的常用方法:
public InputStream getInputStream(): [返回此套接字的输入流, 可以用于接收消息]
public OutputStream getOutputStream(): [返回此套接字的输出流, 可以用于发送消息]
public InetAddress getInetAddress(): [返回此套接字连接到的远程 IP 地址; 如果套接字是未连接的, 则返回 null]
public InetAddressgetLocalAddress(): [获取套接字绑定的本地地址]
public int getPort(): [返回此套接字连接到的远程端口号; 如果尚未连接套接字, 则返回 0]
public int getLocalPort(): [返回此套接字绑定到的本地端口. 如果尚未绑定套接字, 则返回 -1]
public void close(): [关闭套接字(即无法重新连接或重新绑定) 同时也将会关闭该套接字的 InputStream 和 OutputStream]
public void shutdownInput():
如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容, 则流将返回 EOF(文件结束符). 即不能在从此套接字的输入流中接收任何数据. 关闭输入流
public void shutdownOutput():
禁用此套接字的输出流. 对于 TCP 套接字, 任何以前写入的数据都将被发送, 并且后跟 TCP 的正常连接终止序列. 如果在套接字上调用 shutdownOutput() 后写入套接字输出流, 则该流将抛出 IOException. 即不能通过此套接字的输出流发送任何数据. 关闭输出流
boolean isInputShutdown() : [返回是否关闭套接字连接的半读状态 (read-half)]
boolean isOutputShutdown() : [返回是否关闭套接字连接的半写状态 (write-half)]
注意: 先后调用 Socket 的 shutdownInput()和 shutdownOutput()方法, 仅仅关闭了输入流和输出流, 并不等于调用 Socket 的 close()方法. 在通信结束后, 仍然要调用 Scoket 的 close()方法, 因为只有该方法才会释放 Socket 占用的资源, 比如占用的本地端口号等.
如果服务器端要 "同时" 处理多个客户端的请求, 因此服务器端需要为每一个客户端单独分配一个线程来处理, 否则无法实现 "同时".
UDP 网络编程
UDP(User Datagram Protocol, 用户数据报协议)特点:
在正式通信前不必与对方先建立连接, 至于对方是否可以接收到这些数据内容, UDP 协议无法控制, 无连接的好处就是快, 省内存空间和流量, 没有 TCP 的确认机制, 重传机制, 如果因为网络原因没有传送到对端, UDP 也不会给应用层返回错误信息.
UDP 协议是面向数据报文的信息传送服务. UDP 在发送端没有缓冲区, 对于应用层交付下来的报文在添加了首部之后就直接交付于 ip 层, 不会进行合并, 也不会进行拆分, 而是一次交付一个完整的报文.
UDP 协议没有拥塞控制, 所以当网络出现的拥塞不会导致主机发送数据的速率降低. 虽然 UDP 的接收端有缓冲区, 但是这个缓冲区只负责接收, 并不会保证 UDP 报文的到达顺序是否和发送的顺序一致.
因此 UDP 适用于一次只传送少量数据, 对可靠性要求不高的应用环境, 数据报大小限制在 64K 以下.
API
基于 UDP 协议的网络编程仍然需要在通信实例的两端各建立一个 Socket, 但这两个 Socket 之间并没有虚拟链路, 这两个 Socket 只是发送, 接收数据报的对象
DatagramSocket 类的常用方法:
public DatagramSocket(int port)
创建数据报套接字并将其绑定到本地主机上的指定端口. 套接字将被绑定到通配符地址, IP 地址由内核来选择.
public DatagramSocket(int port,InetAddress laddr)
创建数据报套接字, 将其绑定到指定的本地地址. 本地端口必须在 0 到 65535 之间(包括两者). 如果 IP 地址为 0.0.0.0, 套接字将被绑定到通配符地址, IP 地址由内核选择.
public void send(DatagramPacket p)
从此套接字发送数据报包. DatagramPacket 包含的信息指示: 将要发送的数据, 其长度, 远程主机的 IP 地址和远程主机的端口号.
public void receive(DatagramPacket p)
从此套接字接收数据报包. 当此方法返回时, DatagramPacket 的缓冲区填充了接收的数据. 数据报包也包含发送方的 IP 地址和发送方机器上的端口号. 此方法在接收到数据报前一直阻塞. 数据报包对象的 length 字段包含所接收信息的长度. 如果信息比包的长度长, 该信息将被截短.
public void close() [关闭此数据报套接字.]
DatagramPacket 类的常用方法:
public DatagramPacket(byte[] buf,int length)
构造 DatagramPacket, 用来接收长度为 length 的数据包. length 参数必须小于等于 buf.length.
public DatagramPacket(byte[] buf,int length,InetAddress address,int port)
构造数据报包, 用来将长度为 length 的包发送到指定主机上的指定端口号. length 参数必须小于等于 buf.length.
public int getLength() [返回将要发送或接收到的数据的长度]
多点广播
Datagram 只允许数据报发送给指定的目标地址, 而 MulticastSocket 可以将数据报以广播方式发送到数量不等的多个客户端.
IP 协议为多点广播提供了这批特殊的 IP 地址, 这些 IP 地址的范围是 224.0.0.0 至 239.255.255.255.
MulticastSocket 常用的方法:
MulticastSocket(int port) :
创建多播套接字并将其绑定到特定端口. 创建一个 MulticastSocket 对象后, 还需要将该 MulticastSocket 加入到指定的多点广播地址, 如果结束也需要脱离多点广播地址.
void joinGroup(InetAddress mcastaddr) :[加入多播组.]
void leaveGroup(InetAddress mcastaddr) :[离开多播组.]
void setLoopbackMode(boolean disable) :[启用 / 禁用多播数据报的本地回送. true 表示禁用 LoopbackMode.]
TCP 网络编程示例: 群聊
客户端未导包
- public class Client {
- public static void main(String[] args) throws UnknownHostException, IOException {
- // 1, 连接服务器
- Socket socket = new Socket("127.0.0.1", 9999);
- // 2, 开启两个线程, 一个收消息, 一个发消息
- SendThread st = new SendThread(socket);
- ReceiveThread rt = new ReceiveThread(socket);
- st.start();
- rt.start();
- // 等发送线程停下来再往下走
- try {
- st.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // 等接收线程停下来, 再往下走, 断开连接
- try {
- rt.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- socket.close();
- }
- static class SendThread extends Thread {
- private Socket socket;
- public SendThread(Socket socket) {
- super();
- this.socket = socket;
- }
- public void run() {
- try {
- // 键盘输入
- Scanner input = new Scanner(System.in);
- OutputStream out = socket.getOutputStream();
- PrintStream ps = new PrintStream(out);
- while (true) {
- // 从键盘输入
- System.out.print("请输入要发送的消息:");
- String content = input.nextLine();
- // 给服务器发送
- ps.println(content);
- // 如果 bye, 就结束发送
- if ("bye".equals(content)) {
- break;
- }
- }
- input.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- static class ReceiveThread extends Thread {
- private Socket socket;
- public ReceiveThread(Socket socket) {
- super();
- this.socket = socket;
- }
- public void run() {
- try {
- InputStream in = socket.getInputStream();
- InputStreamReader isr = new InputStreamReader(in);
- BufferedReader br = new BufferedReader(isr);
- while (true) {
- String line = br.readLine();
- if("bye".equals(line)){
- break;
- }
- System.out.println(line);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
服务器端
- public class Server {
- private static ArrayList<Socket> online = new ArrayList<Socket>();
- public static void main(String[] args) throws IOException {
- //1, 开启服务器
- ServerSocket server = new ServerSocket(9999);
- while(true){
- //2, 接收客户端的连接
- Socket socket = server.accept();
- // 把这个客户端加入到 online 中
- online.add(socket);
- // 每一个客户端独立的线程
- MessageHandler mh = new MessageHandler(socket);
- mh.start();
- }
- }
- private static class MessageHandler extends Thread{
- private Socket socket;
- private String ip;
- public MessageHandler(Socket socket) {
- super();
- this.socket = socket;
- this.ip = socket.getInetAddress().getHostAddress();
- }
- public void run(){
- // 这个客户端的一连接成功, 线程一启动, 就可以告诉其他人我上线了
- sendToOthers(ip+"上线了");
- //(1)接收当前的客户端发送的消息
- try {
- InputStream in = socket.getInputStream();
- InputStreamReader isr = new InputStreamReader(in);
- BufferedReader br = new BufferedReader(isr);
- String content;
- while((content = br.readLine()) !=null){
- // 收到一句, 转发一句
- sendToOthers(ip+"说:" + content);
- if("bye".equals(content)){
- // 给自己发一句 bye
- OutputStream out = socket.getOutputStream();
- PrintStream ps = new PrintStream(out);
- ps.println("bye");
- break;
- }
- }
- sendToOthers(ip+"下线了");
- } catch (IOException e) {
- sendToOthers(ip+"掉线了");
- }
- }
- // 因为转发的代码也很长, 独立为一个方法
- public void sendToOthers(String str){
- // 遍历所有 online 的客户端
- Iterator<Socket> iterator = online.iterator();
- while(iterator.hasNext()){
- Socket on = iterator.next();
- if(!on.equals(socket)){// 只给其他客户端转发
- try {
- OutputStream out = on.getOutputStream();
- PrintStream ps = new PrintStream(out);
- ps.println(str);
- } catch (IOException e) {
- // 说明 on 这个客户端要么下线了, 要么掉线了
- iterator.remove();
- }
- }
- }
- }
- }
- }
来源: https://www.cnblogs.com/Open-ing/p/12006106.html