网络编程

【Java程序可以非常方便的访问互联网上的 HTTP服务、FTP服务等,并可以直接取得互联网上的远程资源,还可以向远程资源发送 GET、POST请求】

一、网络编程

1.1 网络概念和分类
  • 所谓计算机网络就是把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。

  • 计算机网络有很多种,按照网络的传输介质划分为:双绞网线、同轴电缆网,光纤网、卫星网等。还有无线传输的,例如Wife的传输介质是红外线,蓝牙的传输介质是无线电波,都是电磁波的一种。

    如果按照网络的拓扑结构来划分,可以分为星型网络、总线网络、环线网络、树型网络、网状网络、混合型网络等。

    按照规模可分:局域网、城域网、广域网

1.2 互联网、因特网、万维网
  • 互联网:凡是能彼此通信的设备都叫做互联网,互联网有广域网、城域网及局域网之分。国际标准的互联网写法是internet,字母i小写!
  • 因特网:因特网是互联网的一种,该网络具有一定规模,因特网写法为Internet,字母I大写
  • 因特网是基于TCP/IP协议来实现的,只要是应用层使用的是HTTP协议,就成为万维网

二、网络协议

  • 不管处于那种网络,那么通信是网络最基本的要求,而计算机网络中实现通信必须有一些约定,即通信协议。对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。
2.1 计算机网络
  • 网络通信必须有硬件和软件方面的支持,由于世界上大型计算机厂商推出各自不同的网络体系结构,影响了网络通信的统一性,因此国际标准化组织ISO于1978年提出了著名的OSI(Open System Interconnection)开放系统互连参考模型。它把计算机网络分成物理层、数据链路层、网络层、传输层、会话层、表示层、应用层等七层。
    • 应用层:网络服务与最终用户的一个接口。协议有:HTTP、FTP、SMTP、DNS、TELNET、HTTPS、POP3等等。
    • 表示层:数据的表示、安全、压缩。格式有:JPEG、ASCll、DECOIC、加密格式等。
    • 会话层:建立、管理、终止会话。对应主机进程,指本地主机与远程主机正在进行的会话
    • 传输层:定义传输数据的协议端口号,以及流控和差错校验。协议有:TCP、UDP。
    • 网络层:进行逻辑地址寻址,实现不同网络之间的路径选择。协议有:ICMP、IGMP、IP(IPV4 IPV6)、ARP、RARP。
    • 数据链路层:建立逻辑连接、进行硬件地址寻址、差错校验等功能。将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。
    • 物理层:建立、维护、断开物理连接。
  • 而IP协议是一种非常重要的协议。IP(internet protocal)又称为互联网协议。IP的责任就是把数据从源传送到目的地。它在源地址和目的地址之间传送一种称之为数据包的东西,它还提供对数据大小的重新组装功能,以适应不同网络对包大小的要求。经常与IP协议放在一起的还有TCP(Transmission Control Protocol)协议,即传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。而通常我们说的TCP/IP协议,其实是指TCP/IP协议族,因为该协议家族的两个最核心协议:TCP(传输控制协议)和IP(网际协议),为该家族中最早通过的标准,所以简称为TCP/IP协议。
  • 按照TCP/IP协议模型,网络通常被分为四层:网络访问层、互联网层、传输层和应用层。

三、IP地址

3.1 概述
  • IP地址用于标识网络中的通信实体,而基于IP协议
  • IP地址是一个32位的整数,但为了便于记忆,通常把它分为4个8位的二进制数组成,每8位之间用圆点隔开,格式 X.X.X.X ,其中每个X表示地址中的8位,用十进制[0,255]之间值表示,因此我们看到的常是:222.222.88.102这种
  • Internet委员会定义了5种IP地址类型以适合不同容量的网络,即A类~E类。其中A、B、C这3类由Internet NIC在全球范围内统一分配,D、E类为特殊地址。

在这里插入图片描述

  • IP地址还分为IPV4和IPV6。由于IPv4最大的问题在于网络地址资源有限,严重制约了互联网的应用和发展。IPv6是IETF(Internet Engineering Task Force)设计的用于替代现行版本IP协议(IPv4)的下一代IP协议,号称可以为全世界的每一粒沙子编上一个网址。IPv4和IPv6地址格式不相同,因此在很长一段时间里,互联网中出现IPv4和IPv6长期共存的局面。2012年6月6日,国际互联网协会举行了世界IPv6启动纪念日,这一天,全球IPv6网络正式启动。多家知名网站,如Google、Facebook和Yahoo等,于当天全球标准时间0点(北京时间8点整)开始永久性支持IPv6访问。2018年6月,三大运营商联合阿里云宣布,将全面对外提供IPv6服务,并计划在2025年前助推中国互联网真正实现“IPv6 Only”。 7月,百度云制定了中国的IPv6改造方案。8月3日,工信部通信司在北京召开IPv6规模部署及专项督查工作全国电视电话会议,中国将分阶段有序推进规模建设IPv6网络,实现下一代互联网在经济社会各领域深度融合。

IPv6的地址长度为128位,是IPv4地址长度的4倍,格式为X:X:X:X:X:X:X:X,其中每个X表示地址中的16位,以十六进制表示。

3.2 端口号

IP地址可以唯一的确定网络上的一个通信实体,但是一个通信实体可以有多个通信程序同时提供网络服务,此时还需要使用端口。IP地址就好比通信的街道和门牌号,我们通过IP地址可以找到房子,但是要具体找到某个人,还需要房间号或名字。

端口号是一个16位的整数,即在[0,65535]之间,通常它可以分为三类:

(1)公认端口(Well-Known Ports)范围从0到1023,这些端口号一般固定分配给一些服务。比如21端口分配给FTP(文件传输协议)服务,25端口分配给SMTP(简单邮件传输协议)服务,80端口分配给HTTP服务。

(2) 注册端口(Registered Ports):端口号从1024到49151。它们松散地绑定于一些服务。例如:Tomcat(8080),JBOSS(8080),Oracle(1521),MySQL(3306),SQL Server(1433),QQ(1080)。

(3)动态/私有端口(Dynamic and/Private Ports):端口的范围从49152到65535,这些端口号一般不固定分配给某个服务。只要运行的程序向系统提出访问网络的申请,那么系统就可以从这些端口号中分配一个供该程序使用。理论上,不应为服务分配这些端口。

然而实际生活中,要常人识记IP地址还是有些困难的,所以就有了域名。例如:网易的服务器IP地址是123.58.180.8,域名和IP地址是相对应的,网域名称系统(DNS,Domain Name System)是因特网的一项核心服务,它作为可以将域名和IP地址相互映射的一个分布式数据库,能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的IP地址数串。

四、基本的网络API

4.1 使用 InetAddress

此类表示互联网协议 (IP) 地址,它有两个子类Inet4Address和Inet6Address,分别对应IPV4和IPV6。InetAddress类没有提供公共的构造器,而是提供了如下几个静态方法来获取InetAddress实例。

  • public static InetAddress getLocalHost()
  • public static InetAddress getByAddress(byte[] addr)
  • public static InetAddress getByName(String host)

InetAddress提供了如下几个常用的方法:

  • public String getHostAddress():返回 IP 地址字符串(以文本表现形式)。
  • public String getHostName():获取此 IP 地址的主机名
  • public String getCanonicalHostName():获取此 IP 地址的完全限定域名
  • public boolean isReachable(int timeout):测试是否可以达到该地址。
package com.bdit.ip;

import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class TestInetAddress {
	public static void main(String[] args) throws Exception{
		InetAddress ip1 = InetAddress.getLocalHost();
		System.out.println(ip1);//Irene-PC/192.168.1.107
		
		InetAddress ip2 = InetAddress.getByName("www.bdit.com");
		System.out.println(ip2);//www.bdit.com/222.222.88.102
		
		byte[] ip = {(byte)222,(byte)222,88,102};
		InetAddress ip3 = InetAddress.getByAddress(ip);
		System.out.println(ip3);//输出ip而不是域名。如果这个IP地址不存在或DNS服务器不允许进行ip-->域名的映射,getHostName()方法就直接返回这个IP地址
	}
}

4.2 使用URL
  • URI(Uniform resource identifier):表示一个统一资源标识符 (URI) 引用,用来唯一的标识一个资源。
  • URL(Uniform Resource Locator):类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。URI不能用于定位任何资源,它的唯一作用是解析,而URL则包含一个可打开到达该资源的输入流。 URL的基本结构由5部分组成:
    • <传输协议>://<主机名>:<端口号>/<文件名>#片段名
    • <传输协议>://<主机名>:<端口号>/<文件名>?参数列表
  • 其中#片段名:即锚点,例如看小说,直接定位到章节
    • 例如:http://java.sun.com/index.html#chapter1
  • 参数列表格式:参数名=参数值&参数名=参数值…
    • 例如: http://192.168.1.100:8080/helloworld/index.jsp?username=chai&password=123
4.2.1 URL构造方法:
  • public URL (String spec):通过一个表示URL地址的字符串可以构造一个URL对象。例如:URL url = new URL (“http://www. bdit.com/”);
  • public URL(URL context, String spec):通过基 URL 和相对 URL 构造一个 URL 对象。例如:URL downloadUrl = new URL(url, “download.html”)
  • public URL(String protocol, String host, String file); 例如:new URL(“http”, “www.bdit.com”, “download. html");
  • public URL(String protocol, String host, int port, String file); 例如: URL gamelan = new URL(“http”, “www.bdit.com”, 80, “download.html");
4.2.2 URL的常用方法:
  • public String getProtocol():获取该URL的协议名
  • public String getHost():获取该URL的主机名
  • public String getPort():获取该URL的端口号
  • public String getPath():获取该URL的文件路径,即tomcat的webapps目录下级目录名也就是资源路径名
  • public String getFile():获取该URL的文件名
  • public String getRef():获取该URL在文件中的相对位置
  • public String getQuery():获取该URL的查询名
  • public final InputStream openStream():返回一个用于从该连接读入的 InputStream。
package com.bdit.instance;

import java.net.MalformedURLException;
import java.net.URL;

public class Stance02 {
    public static void main(String[] args) {
        try {
            URL url = new URL("http://www.baidu.com");
            System.out.println(url);
            System.out.println(url.getHost());
            System.out.println(url.getPort());
            URL url1 = new URL("https://hao.lenovo.com.cn/?c=lenovo_broswer");
            System.out.println(url1.getFile());
            String str = url1.toString();
            System.out.println(str+"----");
            URL url2 = new URL("https","www.baidu.com","ff");
            System.out.println(url2);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

    }
}
4.3 使用 URLConnection
  • URL 的方法openStream() 能从网络上读取数据,但是无法给服务器端发送数据,若希望给服务器端发送数据,则需要URLConnection
  • 它代表应用程序和URL之间的通信连接,此类的实例可以用于读取和写入此URL引用资源
  • 连接步骤
    • 通过 URL对象调用 openConnection方法创建URLConnection连接对象。
    • 处理设置参数和一般请求属性。
    • 使用 connect 方法建立到远程对象的实际连接。
    • 远程对象变为可用。远程对象的头字段和内容变为可访问。
  • URLConnection的常用方法:
    • public void setDoOutput(boolean dooutput)如果打算使用 URL 连接进行输出,例如给服务器传递请求参数,则将 DoOutput 标志设置为 true;如果不打算使用,则设置为 false。默认值为 false。 必须在所有getXXX()和connect()方法之前。
    • public String getContentEncoding():返回 content-encoding 头字段的值。
    • public int getContentLength():返回 content-length 头字段的值。
    • public String getContentType():返回 content-type 头字段的值。
    • public long getDate():返回 date 头字段的值。
    • public long getLastModified():返回 last-modified 头字段的值。结果为距离格林威治标准时间 1970 年 1 月 1 日的毫秒数。
    • public String getHeaderField(String name):返回指定的头字段的值。
    • public InputStream getInputStream()返回从此打开的连接读取的输入流。
    • public OutputStream getOutputStream()返回写入到此连接的输出流
package com.bdit.url;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

public class TestURLConnection {
	public static void main(String[] args) throws Exception{
		URL url = new URL("http://localhost/web1/denglu");
		//通过 URL对象调用 openConnection 方法创建URLConnection连接对象
		URLConnection uc = url.openConnection();
		//处理设置参数
		uc.setDoOutput(true);
		//给服务器发送请求参数
		uc.getOutputStream().write("username=admin&password=123".getBytes());
		//使用 connect 方法建立到远程对象的实际连接。
		uc.connect();
		//获取资源
		InputStream is = uc.getInputStream();
		BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
		String str;
		while((str=br.readLine())!=null){
			System.out.println(str);
		}
		br.close();
	}
}
4.4 案例 获取网易首页的资源
package com.bdit.instance;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class Stance03 {
    public static void main(String[] args) {
        try {
            URL url = new URL("http://www.163.com");
            URLConnection uct = url.openConnection();
            InputStream ins = uct.getInputStream();
            BufferedReader bue = new BufferedReader(new InputStreamReader(ins,"gbk"));
            String str = bue.readLine();
            while(str!=null){
                System.out.println(str);
                str = bue.readLine();
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

五、Socket

通信的两端都要有Socket(也可以叫“套接字”),是两台机器间通信的端点。网络通信其实就是Socket间的通信。Socket可以分为:

  • 流套接字(stream socket):使用TCP提供可依赖的字节流服务
  • 数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务

【基于TCP的网络编程都是采用的流套接字,它是面向连接,提供可靠无差错的网络通信】

【基于UDP的网络编程是面向无连接,不可靠的网络通信,大多数网络会议软件都是采用的UDP协议】

5.1 Socket类的常用构造方法:
  • public Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
  • public Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口号。
5.2 Socket类的常用方法:
  • public InputStream getInputStream():返回此套接字的输入流,可以用于接收消息
  • public OutputStream getOutputStream():返回此套接字的输出流,可以用于发送消息
  • public InetAddress getInetAddress():此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。
  • public InetAddress getLocalAddress():获取套接字绑定的本地地址。
  • 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。 即不能通过此套接字的输出流发送任何数据。

注意:先后调用Socket的shutdownInput()和shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等于调用Socket的close()方法。在通信结束后,仍然要调用Scoket的close()方法,因为只有该方法才会释放Socket占用的资源,比如占用的本地端口号等。

六、基于TCP协议的网络编程

  • TCP(Transmission Control Protocol,传输控制协议)被称作一种端对端协议。是一种面向连接的、可靠的、基于字节流的传输层的通信协议,可以连续传输大量的数据。类似于打电话的效果。
  • 这是因为它为当一台计算机需要与另一台远程计算机连接时,TCP协议会采用“三次握手”方式让它们建立一个连接,用于发送和接收数据的虚拟链路。数据传输完毕TCP协议会采用“四次挥手”方式断开连接。
  • TCP协议负责收集这些信息包,并将其按适当的次序放好传送,在接收端收到后再将其正确的还原。TCP协议保证了数据包在传送中准确无误。TCP协议使用重发机制,当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体确认信息,如果没有收到另一个通信实体确认信息,则会再次重复刚才发送的消息。

七、基于TCP协议的网络通信程序结构

7.1 服务器程序的工作过程包含以下五个基本的步骤:

(1) 使用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。

(2) 调用 accept()方法:监听连接请求,如果客户端请求连接,则接受连接,创建与该客户端的通信套接字对象。否则该方法将一直处于等待状态。

(3) 调用 该Socket对象的 getOutputStream() 和 getInputStream ():获取输出流和输入流,开始网络数据的发送和接收。

(4) 关闭Socket对象:某客户端访问结束,关闭与之通信的套接字。

(5) 关闭ServerSocket:如果不再接收任何客户端的连接的话,调用close()进行关闭。

7.2 客户端Socket的工作过程包含以下四个基本的步骤:

(1) 创建 Socket:根据指定服务端的 IP 地址或端口号构造 Socket 类对象,创建的同时会自动向服务器方发起连接。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。

(2) 打开连接到Socket 的输入/出流:使用 getInputStream()方法获得输入流,使用 getOutputStream()方法获得输出流,进行数据传输。

(3) 进行读/写操作:通过输入流读取服务器发送的信息,通过输出流将信息发送给服务器。

(4) 关闭 Socket:断开客户端到服务器的连接

注意:客户端和服务器端在获取输入流和输出流时要对应,否则容易死锁。例如:客户端先获取字节输出流(即先写),那么服务器端就先获取字节输入流(即先读);反过来客户端先获取字节输入流(即先读),那么服务器端就先获取字节输出流(即先写)。

7.3 示例
7.3.1一个客户端与服务器单次通信
  • 需求:客户端连接服务器,连接成功后给服务发送信息,服务器收到消息后,给客户端返回信息
package com.bdit.instance;
/*
* 服务器端和客户端  一对一样式
* */
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ServertDom {
    public static void main(String[] args) {
        Date date = new Date();
        SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd  hh:mm:ss");
        try {
            ServerSocket serverSocket = new ServerSocket(8888);
            System.out.println("等待用户端连接。。。。");
            Socket socket = serverSocket.accept();
            System.out.println("连接成功");


            InputStream ins = socket.getInputStream();
            byte[] by01= new byte[1024];
            StringBuilder str01 = new StringBuilder();
            int len = ins.read(by01);
            while(len!=-1){
                str01.append(new String(by01,0,len));
                len=ins.read(by01);
            }
            System.out.println(str01);

            OutputStream ost = socket.getOutputStream();
            String str = dateFormat.format(date);
            ost.write(("当前时间:"+str).getBytes());

            socket.close();
            serverSocket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package com.bdit.instance;
/*
 * 服务器端和客户端  一对一样式
 * */
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.Socket;

import java.net.UnknownHostException;
//用户端
public class ServertClient {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1",8888);

            OutputStream ost = socket.getOutputStream();
            ost.write("用户端AAAAAAA".getBytes());
            socket.shutdownOutput();

            InputStream ins = socket.getInputStream();
            byte[] by01= new byte[1024];
            StringBuilder stbu = new StringBuilder();
            int len;
            while((len = ins.read(by01))!=-1){
                stbu.append(new String(by01,0,len));
            }
            System.out.println("服务器返回的信息:"+stbu);

            socket.close();

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

【运行程序时,必须先启动服务器端程序,主动接收客户端程序的连接请求。 】

7.3.2 多个客户端和服务器之间的多次通信

通常情况下,服务器不应该只接受一个客户端请求,而应该不断地接受来自客户端的所有请求,所以Java程序通常会通过循环,不断地调用ServerSocket的accept()方法。

如果服务器端要“同时”处理多个客户端的请求,因此服务器端需要为每一个客户端单独分配一个线程来处理,否则无法实现“同时”。

案例需求:多个客户端连接服务器,并进行多次通信

  • 每一个客户端连接成功后,从键盘输入英文单词或中国成语,并发送给服务器
  • 服务器收到客户端的消息后,把词语“反转”后返回给客户端
  • 客户端接收服务器返回的“词语”,打印显示
  • 当客户端输入“stop”时断开与服务器的连接

多个客户端可以同时给服务器发送“词语”,服务器可以“同时”处理多个客户端的请求

客户端程序示例代码:

package com.bdit.tcp.echo;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {

	public static void main(String[] args) throws Exception {
		// 1、准备Socket,连接服务器,需要指定服务器的IP地址和端口号
		Socket socket = new Socket("127.0.0.1", 8888);

		// 2、获取输出流,用来发送数据给服务器
		OutputStream out = socket.getOutputStream();
		PrintStream ps = new PrintStream(out);
		
		// 3、获取输入流,用来接收服务器发送给该客户端的数据
		InputStream input = socket.getInputStream();
		BufferedReader br = new BufferedReader(new InputStreamReader(input));
		
		Scanner scanner = new Scanner(System.in);
		while(true){
			System.out.println("输入发送给服务器的单词或成语:");
			String message = scanner.nextLine();
			if(message.equals("stop")){
				socket.shutdownOutput();
				break;
			}
			
			// 4、 发送数据
			ps.println(message);
			// 接收数据
			String feedback  = br.readLine();
			System.out.println("从服务器收到的反馈是:" + feedback);
		}
		
		//5、关闭socket,断开与服务器的连接
		scanner.close();
		socket.close();
	}
}

服务器端程序示例代码:

package com.bdit.tcp.echo;

import java.net.ServerSocket;
import java.net.Socket;

public class Server {

	public static void main(String[] args)throws Exception {
		// 1、准备一个ServerSocket
		ServerSocket server = new ServerSocket(8888);
		System.out.println("等待连接...");
		
		int count = 0;
		while(true){
			// 2、监听一个客户端的连接
			Socket socket = server.accept();
			System.out.println("第" + ++count + "个客户端连接成功!!");
			
			ClientHandlerThread ct = new ClientHandlerThread(socket);
			ct.start();
		}
		
		//这里没有关闭server,永远监听
	}
}

服务器端处理客户端请求的线程类示例代码:

package com.bdit.tcp.echo;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

public class ClientHandlerThread extends Thread{
	private Socket socket;

	public ClientHandlerThread(Socket socket) {
		super();
		this.socket = socket;
	}
	
	public void run(){
		
		try{
			//(1)获取输入流,用来接收该客户端发送给服务器的数据
			BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			//(2)获取输出流,用来发送数据给该客户端
			PrintStream ps = new PrintStream(socket.getOutputStream());
			String str;
			// (3)接收数据
			while ((str = br.readLine()) != null) {
				//(4)反转
				StringBuilder word = new StringBuilder(str);
				word.reverse();
				
				//(5)返回给客户端
				ps.println(word);
			}
		}catch(Exception  e){
			e.printStackTrace();
		}finally{
			try {
				//(6)断开连接
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

八、基于UDP协议的网络编程

UDP(User Datagram Protocol,用户数据报协议):是一个无连接的传输层协议、提供面向事务的简单不可靠的信息传送服务,类似于短信。

UDP协议是一种面向非连接的协议,面向非连接指的是在正式通信前不必与对方先建立连接,不管对方状态就直接发送,至于对方是否可以接收到这些数据内容,UDP协议无法控制,因此说,UDP协议是一种不可靠的协议。无连接的好处就是快,省内存空间和流量,因为维护连接需要创建大量的数据结构。UDP会尽最大努力交付数据,但不保证可靠交付,没有TCP的确认机制、重传机制,如果因为网络原因没有传送到对端,UDP也不会给应用层返回错误信息。

UDP协议是面向数据报文的信息传送服务。UDP在发送端没有缓冲区,对于应用层交付下来的报文在添加了首部之后就直接交付于ip层,不会进行合并,也不会进行拆分,而是一次交付一个完整的报文。比如我们要发送100个字节的报文,我们调用一次send()方法就会发送100字节,接收方也需要用receive()方法一次性接收100字节,不能使用循环每次获取10个字节,获取十次这样的做法。

UDP协议没有拥塞控制,所以当网络出现的拥塞不会导致主机发送数据的速率降低。虽然UDP的接收端有缓冲区,但是这个缓冲区只负责接收,并不会保证UDP报文的到达顺序是否和发送的顺序一致。因为网络传输的时候,由于网络拥塞的存在是很大的可能导致先发的报文比后发的报文晚到达。如果此时缓冲区满了,后面到达的报文将直接被丢弃。这个对实时应用来说很重要,比如:视频通话、直播等应用。

因此UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境,数据报大小限制在64K以下。

8.1 基于UDP协议的网络编程

基于UDP协议的网络编程仍然需要在通信实例的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送、接收数据报的对象,Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送、接收的数据报。

DatagramSocket 类的常用方法:

  • public DatagramSocket(int port)创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。
  • public DatagramSocket(int port,InetAddress laddr)创建数据报套接字,将其绑定到指定的本地地址。本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地址,IP 地址由内核选择。
  • public void close()关闭此数据报套接字。
  • public void send(DatagramPacket p)从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。
  • public void receive(DatagramPacket p)从此套接字接收数据报包。当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。

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()返回将要发送或接收到的数据的长度。
8.2“发送端”

步骤流程:

1、建立发送端的DatagramSocket,需要指定本端的端口号

2、建立数据包DatagramPacket

  • 数据
  • 接收端的IP地址
  • 接收端的端口号

3、调用DatagramSocket的发送方法

4、关闭DatagramSocket

8.3 “接收端”

步骤流程:

1、建立接收端的DatagramSocket,需要指定本端的IP地址和端口号

2、建立数据包DatagramPacket

  • 需要指定装数据的数组

3、调用Socket的接收方法

4、拆封数据

5、关闭Socket

package com.bdit.instance;
//基于UDP协议的网络编程
import java.io.IOException;
import java.net.*;
import java.util.ArrayList;
//1、“发送端”
public class MessDom {
    public static void main(String[] args) {
        DatagramSocket ds = null;
        try {
             ds = new DatagramSocket();

            ArrayList<String> arrayList = new ArrayList<String>();
            arrayList.add("健康是一所学校");
            arrayList.add("人应该学会");
            arrayList.add("关爱自己");
            arrayList.add("1233333");

            InetAddress ina =InetAddress.getByName("127.0.0.1");

            int dkh = 7777;

            for (int i = 0; i < arrayList.size(); i++) {
                byte[] by01 = arrayList.get(i).getBytes();
                DatagramPacket dp = new DatagramPacket(by01, by01.length,ina,dkh);
                ds.send(dp);
            }
            System.out.println(ds);
            ds.close();
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package com.bdit.instance;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
//2、“接收端”
public class MessCilent {
    public static void main(String[] args) {
        try {
            DatagramSocket ds = new DatagramSocket(7777);

            while(true){
                byte[] by01 = new byte[1024*64];
                DatagramPacket dp = new DatagramPacket(by01, by01.length);

                ds.receive(dp);

                String str = new String(by01,0,dp.getLength());
                System.out.println(str);
            }
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}




package com.bdit.instance;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
//2、“接收端”
public class MessCilent {
    public static void main(String[] args) {
        try {
            DatagramSocket ds = new DatagramSocket(7777);

            while(true){
                byte[] by01 = new byte[1024*64];
                DatagramPacket dp = new DatagramPacket(by01, by01.length);

                ds.receive(dp);

                String str = new String(by01,0,dp.getLength());
                System.out.println(str);
            }
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值