Java-网络编程总结

网络编程重在理论知识,我就直接转载大神笔记了,再加上课上的一些知识点和代码。希望对学习网络编程有所帮助。

1. 概述

  1. 计算机网络是通过传输介质、通信设施和网络通信协议,把分散在不同地点的计算机设备互连起来的,实现资源共享和数据传输的系统。网络编程就是编写程序使互联网的两个(或多个)设备(如计算机)之间进行数据传输。Java语言对网络编程提供了良好的支持。通过其提供的接口我们可以很方便地进行网络编程。

2. 网络分层

  1. 计算机网络20世纪60年代出现,经历了20世纪70年代、80年代和90年代的发展,进入21世纪后,计算机网络已经成为信息社会的基础设施,深入到人类社会的方方面面,与人们的工作、学习和生活息息相关。计算机网络分为网络协议和网络体系结构。

2.1 网络体系结构

  1. 通过网络发送数据是一项复杂的操作,必须仔细地协调网络的物理特性以及所发送数据的逻辑特征。通过网络将数据从一台主机发送到另外的主机,这个过程是通过计算机网络通信来完成。

  2. 网络通信的不同方面被分解为多个层,层与层之间用接口连接。通信的双方具有相同的层次,层次实现的功能由协议数据单元(PDU)来描述。不同系统中的同一层构成对等层,对等层之间通过对等层协议进行通信,理解批次定义好的规则和约定。每一层表示为物理硬件(即线缆和电流)与所传输信息之间的不同抽象层次。在理论上,每一层只与紧挨其上和其下的层对话。将网络分层,这样就可以修改甚至替换某一层的软件,只要层与层之间的接口保持不变,就不会影响到其他层。

  3. 计算机网络体系结构是计算机网络层次和协议的集合,网络体系结构对计算机网络实现的功能,以及网络协议、层次、接口和服务进行了描述,但并不涉及具体的实现。接口是同一节点内相邻层之间交换信息的连接处,也叫服务访问点(SAP)。
    Java-网络编程总结_第1张图片

  4. 世界上第一个网络体系结构由IBM公司提出(1974年,SNA),以后其他公司也相继提出自己的网络体系结构。为了促进计算机网络的发展,国际标准化组织ISO在现有网络的基础上,提出了不基于具体机型、操作系统或公司的网络体系结构,称为开放系统互连参考模型,即OSI/RM(Open System Interconnection Reference Model)。

  5. ISO制定的OSI参考模型过于庞大、复杂招致了许多批评。与此相对,美国国防部提出了TCP/IP协议栈参考模型,简化了OSI参考模型,由于TCP/IP协议栈的简单,获得了广泛的应用,并成为后续因特网使用的参考模型。

2.1.1 OSI参考模型

这里首先介绍OSI参考模型。OSI模型把网络通信的工作分为7层,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
Java-网络编程总结_第2张图片

  • 物理层
      物理层处于OSI的最底层,是整个开放系统的基础。物理层涉及通信信道上传输的原始比特流(bits),它的功能主要是为数据端设备提供传送数据的通路以及传输数据。
  • 数据链路层
      数据链路层的主要任务是实现计算机网络中相邻节点之间的可靠传输,把原始的、有差错的物理传输线加上数据链路协议以后,构成逻辑上可靠的数据链路。需要完成的功能有链路管理、成帧、差错控制以及流量控制等。其中成帧是对物理层的原始比特流进行界定,数据链路层也能够对帧的丢失进行处理。
  • 网络层
      网络层涉及源主机节点到目的主机节点之间可靠的网络传输,它需要完成的功能主要包括路由选择、网络寻址、流量控制、拥塞控制、网络互连等。
  • 传输层
      传输层起着承上启下的作用,涉及源端节点到目的端节点之间可靠的信息传输。传输层需要解决跨越网络连接的建立和释放,对底层不可靠的网络,建立连接时需要三次握手,释放连接时需要四次挥手。
  • 会话层和表示层
      会话层的主要功能是负责应用程序之间建立、维持和中断会话,同时也提供对设备和结点之间的会话控制,协调系统和服务之间的交流,并通过提供单工、半双工和全双工3种不同的通信方式,使系统和服务之间有序地进行通信。
      表示层关心所传输数据信息的格式定义,其主要功能是把应用层提供的信息变换为能够共同理解的形式,提供字符代码、数据格式、控制信息格式、加密等的统一表示。
  • 应用层
      应用层为OSI的最高层,是直接为应用进程提供服务的。其作用是在实现多个系统应用进程相互通信的同时,完成一系列业务处理所需的服务。

2.1.2 TCP/IP参考模型

1、TCP/IP,即Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,是Internet最基本的协议,Internet国际互联网络的基础。
2、TCP/IP协议是一个开放的网络协议簇,它的名字主要取自最重要的网络层IP协议和传输层TCP协议。TCP/IP协议定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。TCP/IP参考模型采用4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求,这4个层次分别是:网络接口层、网络层(IP层)、传输层(TCP层)、应用层。
Java-网络编程总结_第3张图片

  • 网络接口层
      TCP/IP协议对网络接口层没有给出具体的描述,网络接口层对应着OSI参考模型的物理层和数据链路层
  • 网络层(IP层)
      网络层是整个TCP/IP协议栈的核心。它的功能是把分组发往目标网络或主机。同时,为了尽快地发送分组,可能需要沿不同的路径同时进行分组传递。因此,分组到达的顺序和发送的顺序可能不同,这就需要上层必须对分组进行排序。网络层除了需要完成路由的功能外,也可以完成将不同类型的网络(异构网)互连的任务。除此之外,互联网层还需要完成拥塞控制的功能。
  • 传输层(TCP层)
      TCP层负责在应用进程之间建立端到端的连接和可靠通信,它只存在与端节点中。TCP层涉及两个协议,TCP和UDP。其中,TCP协议提供面向连接的服务,提供按字节流的有序、可靠传输,可以实现连接管理、差错控制、流量控制、拥塞控制等。UDP协议提供无连接的服务,用于不需要或无法实现面向连接的网络应用中。
  • 应用层
      应用层为Internet中的各种网络应用提供服务。

2.2 网络协议

1、如同人与人之间相互交流是需要遵循一定的规则(如语言)一样,计算机之间能够进行相互通信是因为它们都共同遵守一定的规则,即网络协议。
2、OSI参考模型和TCP/IP模型在不同的层次中有许多不同的网络协议,如图所示:
Java-网络编程总结_第4张图片
网络协议之间的关系图如下:
Java-网络编程总结_第5张图片

2.2.1 IP协议(Internet protocol)

IP协议的作用在于把各种数据包准备无误的传递给对方,其中两个重要的条件是IP地址和MAC地址。由于IP地址是稀有资源,不可能每个人都拥有一个IP地址,所以我们通常的IP地址是路由器给我们生成的IP地址,路由器里面会记录我们的MAC地址。而MAC地址是全球唯一的。举例,IP地址就如同是我们居住小区的地址,而MAC地址就是我们住的那栋楼那个房间那个人。IP地址采用的IPv4格式,目前正在向IPv6过渡。

2.2.2 TCP协议(Transmission Control Protocol)

TCP(传输控制协议)是面向连接的传输层协议。TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。TCP协议采用字节流传输数据。

2.2.2.1 TCP的报文格式

TCP报文段包括协议首部和数据两部分,协议首部的固定部分是20个字节,首部的固定部分后面是选项部分。
  Java-网络编程总结_第6张图片
下面是报文段首部各个字段的含义:

  1. 源端口号以及目的端口号:各占2个字节,端口是传输层和应用层的服务接口,用于寻找发送端和接收端的进程,一般来讲,通过端口号和IP地址,可以唯一确定一个TCP连接,在网络编程中,通常被称为一个socket接口。
  2. 序号:Seq序号,占4个字节、32位。用来标识从TCP发送端向TCP接收端发送的数据字节流。发起方发送数据时对此进行标记。
  3. 确认序号:Ack序号,占4个字节、32位。包含发送确认的一端所期望收到的下一个序号。只有ACK标记位为1时,确认序号字段才有效,因此,确认序号应该是上次已经成功收到数据字节序号加1,即Ack=Seq+ 1。
  4. 数据偏移:占4个字节,用于指出TCP首部长度,若不存在选项,则这个值为20字节,数据偏移的最大值为60字节。
  5. 保留字段占6位,暂时可忽略,值全为0。
  6. 标志位,6个
    URG(紧急):为1时表明紧急指针字段有效
    ACK(确认):为1时表明确认号字段有效
    PSH(推送):为1时接收方应尽快将这个报文段交给应用层
    RST(复位):为1时表明TCP连接出现故障必须重建连接
    SYN(同步):在连接建立时用来同步序号
    FIN(终止):为1时表明发送端数据发送完毕要求释放连接
  7. 接收窗口:占2个字节,用于流量控制和拥塞控制,表示当前接收缓冲区的大小。在计算机网络中,通常是用接收方的接收能力的大小来控制发送方的数据发送量。TCP连接的一端根据缓冲区大小确定自己的接收窗口值,告诉对方,使对方可以确定发送数据的字节数。
  8. 校验和:占2个字节,范围包括首部和数据两部分。
  9. 选项是可选的,默认情况是不选。
2.2.2.2 三次握手与四次挥手

TCP是面向连接的协议,因此每个TCP连接都有3个阶段:连接建立、数据传送和连接释放。连接建立经历三个步骤,通常称为“三次握手”。
  TCP三次握手过程如下:
Java-网络编程总结_第7张图片

  1. 第一次握手(客户端发送请求)
      客户机发送连接请求报文段到服务器,并进入SYN_SENT状态,等待服务器确认。发送连接请求报文段内容:SYN=1,seq=x;SYN=1意思是一个TCP的SYN标志位置为1的包,指明客户端打算连接的服务器的端口;seq=x表示客户端初始序号x,保存在包头的序列号(Sequence Number)字段里。
  2. 第二次握手(服务端回传确认)
      服务器收到客户端连接请求报文,如果同意建立连接,向客户机发回确认报文段(ACK)应答,并为该TCP连接分配TCP缓存和变量。服务器发回确认报文段内容:SYN=1,ACK=1,seq=y,ack=x+1;SYN标志位和ACK标志位均为1,同时将确认序号(Acknowledgement Number)设置为客户的ISN加1,即x+1;seq=y为服务端初始序号y。
  3. 第三次握手(客户端回传确认)
      客户机收到服务器的确认报文段后,向服务器给出确认报文段(ACK),并且也要给该连接分配缓存和变量。此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。客户端发回确认报文段内容:ACK=1,seq=x+1,ack=y+1;ACK=1为确认报文段;seq=x+1为客户端序号加1;ack=y+1,为服务器发来的ACK的初始序号字段+1。
      注意:握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。
    TCP四次挥手过程如下:
    Java-网络编程总结_第8张图片
    由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
  4. TCP客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入FIN_WAIT_1状态。发送报文段内容:FIN=1,seq=u;FIN=1表示请求切断连接;seq=u为客户端请求初始序号。
  5. 服务端收到这个FIN,它发回一个ACK给客户端,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号;服务端进入CLOSE_WAIT状态。发送报文段内容:ACK=1,seq=v,ack=u+1;ACK=1为确认报文;seq=v为服务器确认初始序号;ack=u+1为客户端初始序号加1。
  6. 服务器关闭客户端的连接后,发送一个FIN给客户端,服务端进入LAST_ACK状态。发送报文段内容:FIN=1,ACK=1,seq=w,ack=u+1;FIN=1为请求切断连接,ACK=1为确认报文,seq=w为服务端请求切断初始序号。
  7. 客户端收到FIN后,客户端进入TIME_WAIT状态,接着发回一个ACK报文给服务端确认,并将确认序号设置为收到序号加1,服务端进入CLOSED状态,完成四次挥手。发送报文内容:ACK=1,seq=u+1,ack=w+1;ACK=1为确认报文,seq=u+1为客户端初始序号加1,ack=w+1为服务器初始序号加1。
      注意:为什么连接的时候是三次握手,关闭的时候却是四次挥手?
      因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭socket,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文,我收到了”。只有等到服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送,故需要四步挥手。

2.2.3 UDP协议(User Datagram Protocol)

UDP,用户数据报协议,它是TCP/IP协议簇中无连接的运输层协议。

  1. UDP是一个非连接的协议,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。
  2. 由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务器可同时向多个客户端传输相同的消息。
  3. UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包的额外开销很小。
  4. 吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。
  5. UDP使用尽量最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态表。
  6. UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部受就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。
2.2.3.1 UDP协议格式

Java-网络编程总结_第9张图片
 UDP协议由两部分组成:首部和数据。其中,首部仅有8个字节,包括源端口和目的端口、长度(UDP用于数据报的长度)、校验和。

  • 发送端
//创建DatagramSocket对象,绑定端口3456
DatagramSocket sendSocket = new DatagramSocket(3456);
//准备好要发送的数据,类型为byte[]
String string = "Hello,I come form ICSS!";
byte[] databyte = new byte[100];
databyte = string.getBytes();
//创建数据报,封装了要发送的数据,数据长度,服务器地址,以及服务器端口为5000
DatagramPacket sendPacket = new DatagramPacket(databyte,
string.length(), InetAddress.getByName("127.0.0.1"), 5000);
//使用DatagramSocket对象将数据报sendPacket发送到服务器
sendSocket.send(sendPacket);
System.out.println("发送数据:" + string);
  • 接收端
//创建DatagramSocket对象,用来接收数据,端口为5000
DatagramSocket receiveSocket = new DatagramSocket(5000);
byte buf[] = new byte[1000];
DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
System.out.println("startinig to receive packet");
while (true) {
//使用DatagramSocket接收数据报
receiveSocket.receive(receivePacket);
//解析数据报中的信息,获得主机名及端口、数据等
String name = receivePacket.getAddress().toString();
System.out.println("来自主机:" + name + "端口:"+ receivePacket.getPort());
String s = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("接收数据: " + s);
}
2.2.3.2 TCP与UDP的区别
  1. TCP基于连接,UDP是无连接的;
  2. 对系统资源的要求,TCP较多,UDP较少;
  3. UDP程序结构较简单;
  4. TCP是流模式,而UDP是数据报模式;
  5. TCP保证数据正确性,而UDP可能丢包;TCP保证数据顺序,而UDP不保证;
    TCP通讯线程特征
  • 基于TCP协议的通讯,客户端和服务器端都使用Socket对象获取输入流和输出流;使用IO流对象读写数据进行通讯;
  • 如果需要服务器端为多个客户端服务,必须为每个客户端启动一个新的线程;
  • 让我们编写一个存在问题的例子,以便说明该问题:
private static ArrayList socketspool=new ArrayList();
 //启动线程,在线程中处理请求
new Thread(new ThreadServer()).start();
//调用ServerSocket的accept方法,可以接受客户端的请求,并返回当前的Socket对象,加入到集合列表中
while(true){
socket = server.accept();	
socketspool.add(socket);	

如上所示,TCPServer02类,启动一个线程对每个客户端连接进行服务,同时在一个无限循环中接收客户端的请求,并把获得的socket对象存储在集合列表中;

  • ThreadServer类负责处理客户端的请求:
while (true) {
//迭代列表中所有socket
for (int i=0;i

如上所示,ThreadServer类中,在一个无限循环中,迭代集合列表中的所有socket对象,读取客户端发过来的信息,并进行打印输出。

  • TCPClient02类向服务器端发送消息:
try {
//创建Socket对象
Socket socket = new Socket("127.0.0.1", 5700);
//获得键盘输入流
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
//获得与socket对象有关的输入输出流
DataOutputStream os = new DataOutputStream(socket.getOutputStream());
DataInputStream is = new DataInputStream(socket.getInputStream());
String readline;
// 向服务器写数据
while (true) {
readline = sin.readLine();
System.out.println(readline);
//向服务器写字符串
os.writeUTF(readline);

如上所示,TCPClient02类中,将键盘输入写到服务器端;

单一服务器对多客户端提供网络服务

  • 服务类TCPServer03,用来接收客户端的请求,并创建线程启动线程
ServerSocket server = null;
try {
	server = new ServerSocket(6700);
	System.out.println("服务器启动成功");
} catch (Exception e) {
	System.out.println("服务器启动出错");
}
Socket socket = null;
try {
while (true) {
	socket = server.accept();
	MultiThreadServer st=new MultiThreadServer(socket);
	Thread thread=new Thread(st);
	clients.add(st);
	thread.start();
	}

如上所示,TCPServer03类中,在无限循环中接收请求,创建线程并启动线程;

  • 线程类MultiThreadServer,用来对客户端提供服务:
is = new DataInputStream(socket.getInputStream());
os = new DataOutputStream(socket.getOutputStream());
while (true) {
line = is.readUTF();
System.out.println("Client "+socket.hashCode()+"说:" + line);
TCPServer03.sendToAll("Client "+socket.hashCode()+"说:"+line);
if (line.equals("exit")) {
	break;
}
}

如上所示,MultiThreadServer类中,读取客户端发过来的信息,再群发到所有的客户端。

  • 运行两次客户端TCPClient03类,模拟两个客户端对服务器端发送请求:
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
try {
String readline = is.readUTF();
System.out.println(readline);
if (readline.equals("exit")) {
	break;
}
} catch (Exception ex) {
ex
}}}};
new Thread(runnable).start();
while (true) {
readline = sin.readLine();
os.writeUTF(readline);
}

Java-网络编程总结_第10张图片
Java-网络编程总结_第11张图片
Java-网络编程总结_第12张图片

2.2.4 HTTP协议(Hypertext Transfer Protocol)

HTTP,超文本传输协议,它是互联网上应用最为广泛的一种网络协议。HTTP是一种应用层协议,它是基于TCP协议之上的请求/响应式的协议。HTTP协议是Web浏览器和Web服务器之间通信的标准协议。HTTP指定客户端与服务器如何建立连接、客户端如何从服务器请求数据,服务器如何响应请求,以及最后如何关闭连接。HTTP连接使用TCP/IP来传输数据。
  对于从客户端到服务器的每一个请求,都有4个步骤:

  1. 默认情况下,客户端在端口80打开与服务器的一个TCP连接,URL中还可以指定其他端口。
  2. 客户端向服务器发送消息,请求指定路径上的资源。这个资源包括一个首部,可选地(取决于请求的性质)还可以有一个空行,后面是这个请求的数据。
  3. 服务器向客户端发送响应。响应以响应码开头,后面是包含数据的首部、一个空行以及所请求的文档或错误消息。
  4. 服务器关闭连接。
      现在使用的HTTP协议是HTTP/1.1版本,1997年之前采用的是HTTP1.0版本。HTTP连接在1.0版本中采用非持续连接工作方式,1.1版本采用的是持续连接工作方式,持续连接是指服务器在发送响应后仍然在一段时间内保持这条由TCP运输层协议建立起来的连接,使客户端和服务器可以继续在这条连接上传输HTTP报文。
      是否采用持续连接工作方式,1.0中默认是关闭的,需要在HTTP头加入“Connection:Keep-Alive”,才能启用Keep-Alive。HTTP1.1中默认启用Keep-Alive,如果加入“Connection:close”,才关闭。目前大部分浏览器都是用HTTP1.1协议,也就是说默认都会发起Keep-Alive的连接请求了,所以是否能完成一个完整的Keep-Alive连接就看服务器设置情况。
2.2.4.1 HTTP报文

HTTP协议是基于TCP协议之上的请求/响应式协议,下面主要介绍HTTP报文的格式,HTTP报文主要有请求报文和响应报文两种。
  首先看HTTP请求报文的格式
  Java-网络编程总结_第13张图片
  HTTP请求报文由请求行、首部行和实体主体组成,由浏览器发送给服务器。上面这张图中SP表示空格,cr lf表示回车和换行。下图是谷歌浏览器内访问服务器查看的HTTP请求例子:
Java-网络编程总结_第14张图片
 HTTP响应报文格式:
Java-网络编程总结_第15张图片
上面这张图是HTTP响应报文,它由状态行、首部行和实体主体组成。下图为HTTP响应报文例子:
Java-网络编程总结_第16张图片

2.2.4.2 HTTP请求方法和响应状态码

在上面的HTTP请求报文例子中,我们可以看到请求方法是GET,这表示请求读取由URL所标志的信息,除了GET,还有其他几种常用的方法。
  Java-网络编程总结_第17张图片
在HTTP响应报文的例子中,我们可以看到状态码是200,表示响应成功。下表是其他状态码,总共5大类,33种。
Java-网络编程总结_第18张图片

2.2.4.3 HTTP和HTTPS的区别
  1. HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单来说就是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。它是一个URL scheme(抽象标识符体系),句法类同http:体系,用于安全的HTTP数据传输。https:URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间)。
  2. 超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息。HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此HTTP协议不适合传输一些敏感信息,比如信用开号、密码等。
  3. 为了解决HTTP协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议HTTPS。为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。
  4. HTTPS和HTTP的区别主要为以下四点:
    • https协议需要到ca申请证书,一般免费证书很少,需要缴费。
    • http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
    • http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
    • http的连接很简单,是无状态的;https协议是有ssl+http协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
2.2.4.4 HTTP和TCP/IP协议的关系
  1. 网络中有一段比较容易理解的介绍:
  2. “我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如 果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP、FTP、TELNET等,也 可以自己定义应用层协议。WEB使用HTTP协议作应用层协议,以封装HTTP文本信息,然后使用TCP/IP做传输层协议将它发到网络上。”

3. Java Socket网络编程

3.1 Socket概述

  1. Java的网络编程主要涉及到的内容是Socket编程。Socket,套接字,就是两台主机之间逻辑连接的端点。TCP/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。Socket是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议、本地主机的IP地址、本地进程的协议端口、远程主机的IP地址、远程进程的协议端口。
  2. 应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
  3. Socket,实际上是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。实际上,Socket跟TCP/IP协议没有必然的关系,Socket编程接口在设计的时候,就希望也能适应其他的网络协议。所以说,Socket的出现,只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口,比如create、listen、accept、send、read和write等等。网络有一段关于socket和TCP/IP协议关系的说法比较容易理解:
  4. “TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。这个就像操作系统会提供标准的编程接口,比如win32编程接口一样,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。”
  5. 实际上,传输层的TCP是基于网络层的IP协议的,而应用层的HTTP协议又是基于传输层的TCP协议的,而Socket本身不算是协议,就像上面所说,它只是提供了一个针对TCP或者UDP编程的接口。socket是对端口通信开发的工具,它要更底层一些。
    服务器建立 ServerSocket 对象
  • ServerSocket 对象负责等待客户端请求建立套接字连接,类似邮局某个窗口中的业务员。也就是说,服务器必须事先建立一个等待客户请求建立套接字连接的ServerSocket对象。
  • 所谓“接收”客户的套接字请求,就是accept()方法会返回一个 Socket 对象
ServerSocket ss = new ServerSocket(9999);
Socket s = ss.accept ();
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int num = in.read(buf);
String str = new String(buf,0,num);
System.out.println(s.getInetAddress().toString()+”:”+str);
s.close();
ss.close();

示例一客户端
Java-网络编程总结_第19张图片
示例一服务端
Java-网络编程总结_第20张图片
示例二客户端
Java-网络编程总结_第21张图片
示例二服务端
Java-网络编程总结_第22张图片
示例三(交互)客户端
Java-网络编程总结_第23张图片
示例三(交互)服务端
Java-网络编程总结_第24张图片

3.2 Socket整体流程

  1. Socket编程主要涉及到客户端和服务端两个方面,首先是在服务器端创建一个服务器套接字(ServerSocket),并把它附加到一个端口上,服务器从这个端口监听连接。端口号的范围是0到65536,但是0到1024是为特权服务保留的端口号,我们可以选择任意一个当前没有被其他进程使用的端口。
  2. 客户端请求与服务器进行连接的时候,根据服务器的域名或者IP地址,加上端口号,打开一个套接字。当服务器接受连接后,服务器和客户端之间的通信就像输入输出流一样进行操作。
    Java-网络编程总结_第25张图片
    ServerSocket类的常用方法
    Java-网络编程总结_第26张图片
    一直连接并发送信息客户端
// 1 实例化客户端并连接服务端
Socket client = null;
try {
client = new Socket("127.0.0.1", 1245);
System.out.println("连接服务器成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("连接服务器失败!");
return;
}
// 2 客户端给服务端发送信息
// 利用DataOutputStream向服务端写入信息
DataOutputStream dos = new DataOutputStream(client.getOutputStream());
DataInputStream dis = new DataInputStream(client.getInputStream());
int id = 0;
while (true) // 不断发送信息。间隔2秒钟。
{
dos.writeUTF("我是好学生" + id);
id++;
Thread.sleep(2000);
String str = dis.readUTF();
System.out.println("收到来自服务端的信息:" + str);
if (id == 10) // 只发10个包
break;
}
dos.close();
client.close();

一直等待一直下发消息服务端功能

ServerSocket server = null;
try {
// 1 启动监听
server = new ServerSocket(port);
System.out.println("在" + port + "端口启动监听成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("启动监听失败,端口可能被占用!");
return;
}
// 2 不断等待客户端连接
while (true) {
try {
Socket client = server.accept();
System.out.println("来自客户端" + client.getRemoteSocketAddress() + "的连接!");

// 3 读取来自客户端的消息
DataInputStream dis = new DataInputStream(client.getInputStream());
DataOutputStream dos = new DataOutputStream(client.getOutputStream());
while (true) {
String str = dis.readUTF(); // 读取客户端的信息
System.out.println("收到客户端" + client.getRemoteSocketAddress() + "消息:" + str);
str = "响应-" + str;//
dos.writeUTF(str);// 服务器将信息发回给客户端
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("读取客户端信息失败,客户端可能已经断开!");
}

接受控制台输入的客户端-1

//连接成功后,先创建线程,不断从服务端读信息
class MyClientReceiveThread extends Thread
{
private Socket client; //本客户端的对象
public MyClientReceiveThread(Socket client) {
	this.client = client;
}
@Override
public void run() 
{
DataInputStream dis = null;
try {
dis = new DataInputStream(client.getInputStream());
while (true) {
String str = dis.readUTF();
System.out.println("收到来自服务端的响应:" + str);
}
} catch (Exception e) 
{
}
finally
{
try {
	dis.close();
} catch (Exception e2) {
}
}
}
}

接受控制台输入的客户端-2

System.out.println("这是接受控制台输入的客户端!!");
//1 实例化客户端并连接服务端
Socket client = null;
try {
client = new Socket("127.0.0.1",1245);
System.out.println("连接服务器成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("连接服务器失败!");
return;
}
//2 连接成功后,先创建线程,不断从服务端读信息
MyClientReceiveThread rt = new MyClientReceiveThread(client);
rt.start();//启动线程,不断从服务端读取信息
//3不断读取控制台内容
DataOutputStream dos = null;
try {
dos = new DataOutputStream(client.getOutputStream());
System.out.println("请输入:");
Scanner sc = new Scanner(System.in);
String readstr = sc.nextLine();//从控制读取内容
while (!readstr.toUpperCase().equals("QUIT")) //只要读取的内容不为QUIT,则???
{
dos.writeUTF(readstr); //往服务器写
sc.reset();
readstr = sc.nextLine();//写完后,继续从控制台读
}
} catch (Exception e) 

支持多个客户端的Tcp服务端-1

//服务端使用的,专门与1个客户端对话的线程
class MyClientThread extends Thread
{
private Socket client; //对话的客户端端 
public MyClientThread(Socket client) 
{
	this.client = client;
}
@Override
public void run() 
{
System.out.println("来自客户端"+client.getRemoteSocketAddress()+"的连接!");
try {
//3 读取来自客户端的消息
DataInputStream dis = new DataInputStream(client.getInputStream());
DataOutputStream dos = new DataOutputStream(client
.getOutputStream());
while (true) {
String str = dis.readUTF(); //读取客户端的信息
System.out.println("收到客户端" + client.getRemoteSocketAddress()
+ "消息:" + str);
str = "响应-" + str;//
dos.writeUTF(str);//服务器将信息发回给客户端
}

支持多个客户端的Tcp服务端 -2

System.out.println("这是多线程的服务端!!");
int port = 1245;
ServerSocket server = null;
try {
// 1 启动监听
server = new ServerSocket(port);
System.out.println("在" + port + "端口启动监听成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("启动监听失败,端口可能被占用!");
return;
}
// 2 不断等待客户端连接
while (true) {
try {
Socket client = server.accept();
// 创建线程对象,并启动
MyClientThread ct = new MyClientThread(client);
ct.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

实例一
下面是一个客户端和服务器端进行数据交互的简单例子,客户端输入正方形的边长,服务器端接收到后计算面积并返回给客户端,通过这个例子可以初步对Socket编程有个把握。

  • 服务器端
public class SocketServer {
    public static void main(String[] args) throws IOException {
        // 端口号
        int port = 7000;
        // 在端口上创建一个服务器套接字
        ServerSocket serverSocket = new ServerSocket(port);
        // 监听来自客户端的连接
        Socket socket = serverSocket.accept();
        DataInputStream dis = new DataInputStream(
                new BufferedInputStream(socket.getInputStream()));
        DataOutputStream dos = new DataOutputStream(
                new BufferedOutputStream(socket.getOutputStream()));
        do {
            double length = dis.readDouble();
            System.out.println("服务器端收到的边长数据为:" + length);
            double result = length * length;
            dos.writeDouble(result);
            dos.flush();
        } while (dis.readInt() != 0);
        socket.close();
        serverSocket.close();
    }
}
  • 客户端
public class SocketClient {
    public static void main(String[] args) throws UnknownHostException, IOException {
        int port = 7000;
        String host = "localhost";
        // 创建一个套接字并将其连接到指定端口号
        Socket socket = new Socket(host, port);
        DataInputStream dis = new DataInputStream(
                new BufferedInputStream(socket.getInputStream()));
        DataOutputStream dos = new DataOutputStream(
                new BufferedOutputStream(socket.getOutputStream()));
        Scanner sc = new Scanner(System.in);
        boolean flag = false;
        while (!flag) {
            System.out.println("请输入正方形的边长:");
            double length = sc.nextDouble();
            dos.writeDouble(length);
            dos.flush();
            double area = dis.readDouble();
            System.out.println("服务器返回的计算面积为:" + area);
           while (true) {
                System.out.println("继续计算?(Y/N)");
                String str = sc.next();
                if (str.equalsIgnoreCase("N")) {
                    dos.writeInt(0);
                    dos.flush();
                    flag = true;
                    break;
                } else if (str.equalsIgnoreCase("Y")) {
                    dos.writeInt(1);
                    dos.flush();
                    break;
                }
            }
        }
        socket.close();
    }
}

实例二
可以看到上面的服务器端程序和客户端程序是一对一的关系,为了能让一个服务器端程序能同时为多个客户提供服务,可以使用多线程机制,每个客户端的请求都由一个独立的线程进行处理。下面是改写后的服务器端程序。

public class SocketServerM {
    public static void main(String[] args) throws IOException {
        int port = 7000;
        int clientNo = 1;
        ServerSocket serverSocket = new ServerSocket(port);
        // 创建线程池
        ExecutorService exec = Executors.newCachedThreadPool();
        try {
            while (true) {
                Socket socket = serverSocket.accept();
                exec.execute(new SingleServer(socket, clientNo));
                clientNo++;
            }
        } finally {
            serverSocket.close();
        }
    }
}
class SingleServer implements Runnable {
    private Socket socket;
    private int clientNo;
    public SingleServer(Socket socket, int clientNo) {
        this.socket = socket;
        this.clientNo = clientNo;
    }
    @Override
    public void run() {
        try {
            DataInputStream dis = new DataInputStream(
                    new BufferedInputStream(socket.getInputStream()));
            DataOutputStream dos = new DataOutputStream(
                    new BufferedOutputStream(socket.getOutputStream()));
            do {
                double length = dis.readDouble();
                System.out.println("从客户端" + clientNo + "接收到的边长数据为:" + length);
                double result = length * length;
                dos.writeDouble(result);
                dos.flush();
            } while (dis.readInt() != 0);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("与客户端" + clientNo + "通信结束");
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

上面改进后的服务器端代码可以支持不断地并发响应网络中的客户请求。关键的地方在于多线程机制的运用,同时利用线程池可以改善服务器程序的性能。
实例三

  • 客户端部分代码
//创建Socket,指定ip,port
Socket socket = new Socket("127.0.0.1", 8989);		
//获得键盘输入
BufferedReader sin = new BufferedReader(new InputStreamReader(
System.in));		
//获得基于Socket的输入流和输出流
PrintWriter os = new PrintWriter(socket.getOutputStream());
BufferedReader is = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
String readline;
readline = sin.readLine();			
//向服务器写数据
while (!readline.equals("exit")) {
os.println(readline);
os.flush();
readline = sin.readLine();
}
  • 服务端部分代码
try {
ServerSocket server = null;
try {
//创建ServerSocket对象,指定端口是8989
server = new ServerSocket(8989);
System.out.println("服务器启动成功");
} catch (Exception e) {
System.out.println("服务器启动出错");
}
Socket socket = null;
try {
//调用ServerSocket的accept方法,可以接受客户端的请求
socket = server.accept();
} catch (Exception e) {
e.printStackTrace();
}

小结

  • 位于网络中的计算机具有唯一的IP地址,这样不同的主机可以互相区分。
  • 客户端-服务器是一种最常见的网络应用程序模型。服务器是一个为其客户端提供某种特定服务的硬件或软件。客户机是一个用户应用程序,用于访问某台服务器提供的服务。端口号是对一个服务的访问场所,它用于区分同一物理计算机上的多个服务。套接字用于连接客户端和服务器,客户端和服务器之间的每个通信会话使用一个不同的套接字。TCP协议用于实现面向连接的会话。
  • Java 中有关网络方面的功能都定义在 java.net 程序包中。Java 用 InetAddress 对象表示 IP
    地址,该对象里有两个字段:主机名(String) 和 IP 地址(int)。
  • 类 Socket 和 ServerSocket实现了基于TCP协议的客户端-服务器程序。Socket是客户端和服务器之间的一个连接,连接创建的细节被隐藏了。这个连接提供了一个安全的数据传输通道,这是因为TCP 协议可以解决数据在传送过程中的丢失、损坏、重复、乱序以及网络拥挤等问题,它保证数据可靠的传送。
  • 类 URL 和 URLConnection 提供了最高级网络应用。URL 的网络资源的位置来同一表示 Internet上各种网络资源。通过URL对象可以创建当前应用程序和 URL 表示的网络资源之间的连接,这样当前程序就可以读取网络资源数据,或者把自己的数据传送到网络上去。

你可能感兴趣的:(笔记,java,网络)