精华内容
下载资源
问答
  • SOCKET 网络编程 计算机网络 作业 客户端 服务器端 client svever SOCKET 网络编程 计算机网络 作业 客户端 服务器端 client svever
  • 计算机网络编程

    2014-04-05 22:36:55
    计算机网络编程课程结课设计--网络邮件群发程序 内含实验报告。
  • 计算机网络高级编程

    2014-09-18 08:48:42
    本文件清晰明了地指导关于计算机网络的学习,通过学习可以在很深刻理解计算机网络结构概念的基础上,掌握必要的编程技巧
  • 该资源为网络编程的相关的ppt,对计算机网络感兴趣的同学可以下载来自己学习一些网络编程的相关技术,在这方面有一定的基础后再学习其他移动网络的东西就会相对容易些。
  • 描述了计算机网络编程的经典代码以及网络安全编程代码
  • 计算机网络高级软件编程技术(吴功宜版) 计算机网络高级软件编程技术.pdf
  • ftp客户端(计算机网络编程)ftp客户端(计算机网络编程)ftp客户端(计算机网络编程)ftp客户端(计算机网络编程)ftp客户端(计算机网络编程
  • ftp客户端(计算机网络编程)ftp客户端(计算机网络编程)ftp客户端(计算机网络编程)ftp客户端(计算机网络编程
  • 这是研究生高等计算机网络课程的编程习题,很不错
  • 计算机网络课程实际,基于soket编程的网络聊天室
  • 计算机网络高级软件编程技术.rar 计算机网络高级软件编程技术.rar
  • 计算机网络高级软件编程技术—电子版~ 计算机网络高级软件编程技术—电子版~
  • 计算机网络高级软件编程技术 吴功宜
  • 计算机网络高级软件编程技术 源代码
  • 计算机网络编程多人视频聊天,基于udp协议的多人网络视频聊天系统视频传输协议
  • 计算机网络高级软件编程技术电子书 需用超星阅览器打开
  • 网络组建与实施、SOCKET编程(包括MFC SOCKET编程和JAVA SOCKET编程)等编程实例
  • 计算机网络高级软件编程技术-代码~ 计算机网络高级软件编程技术-代码~
  • 计算机网络高级软件编程技术课件~~ 计算机网络高级软件编程技术课件~~
  • 计算机网络高级编程技术-源程序,和书本配套学习,效果很好。
  • 计算机网络高级编程技术附带光盘(书中全部源代码和生成的可运行exe文件,基于vc++6.0开发)
  • 吴功宜等编著,计算机网络高级软件编程技术(第2版),清华大学出版社,2011年9月 计算机网络编程必看 第二版 第2版
  • Java 计算机网络(网络编程总结)

    千次阅读 2018-10-05 20:31:52
    本文主要是自己在网络编程方面的学习总结,先主要介绍计算机网络方面的相关内容,包括计算机网络基础,OSI参考模型,TCP/IP协议簇,常见的网络协议等等,在此基础上,介绍Java中的网络编程。 一、概述 计算机网络是...

    本文主要是自己在网络编程方面的学习总结,先主要介绍计算机网络方面的相关内容,包括计算机网络基础,OSI参考模型,TCP/IP协议簇,常见的网络协议等等,在此基础上,介绍Java中的网络编程。

    一、概述

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

    二、计算机网络

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

    网络协议

    如同人与人之间相互交流是需要遵循一定的规矩一样,计算机之间能够进行相互通信是因为它们都共同遵守一定的规则,即网络协议。

    网络体系结构

    计算机网络是个复杂的系统,按照人们解决复杂问题的方法,把计算机网络实现的功能分到不同的层次上,层与层之间用接口连接。通信的双方具有相同的层次,层次实现的功能由协议数据单元(PDU)来描述。不同系统中的同一层构成对等层,对等层之间通过对等层协议进行通信,理解彼此定义好的规则和约定。

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

    计算机网络层次模型

    三、OSI参考模型

    前面我们介绍了计算机网络的体系结构,因为计算机网络是个复杂的系统,所以把计算机网络实现的功能分到不同的层次上,而计算机网络体系结构是计算机网络层次和协议的集合。那么,计算机网络如何进行分层呢?下面先介绍的是OSI参考模型。

    简介

    世界上第一个网络体系结构由IBM公司提出(1974年,SNA),以后其他公司也相继提出自己的网络体系结构如:Digital公司的DNA,美国国防部的TCP/IP等,多种网络体系结构并存,其结果是若采用IBM的结构,只能选用IBM的产品,只能与同种结构的网络互联。

    为了促进计算机网络的发展,国际标准化组织ISO于1977年成立了一个委员会,在现有网络的基础上,提出了不基于具体机型、操作系统或公司的网络体系结构,称为开放系统互连参考模型,即OSI/RM (Open System Interconnection Reference Model)。OSI模型把网络通信的工作分为7层,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。

    OSI参考模型的7个层次

    OSI模型层次功能

    • 物理层

    物理层处于OSI的最底层,是整个开放系统的基础。物理层涉及通信信道上传输的原始比特流(bits),它的功能主要是为数据端设备提供传送数据的通路以及传输数据。

    • 数据链路层

    数据链路层的主要任务是实现计算机网络中相邻节点之间的可靠传输,把原始的、有差错的物理传输线路加上数据链路协议以后,构成逻辑上可靠的数据链路。需要完成的功能有链路管理、成帧、差错控制以及流量控制等。其中成帧是对物理层的原始比特流进行界定,数据链路层也能够对帧的丢失进行处理。

    • 网络层

    网络层涉及源主机节点到目的主机节点之间可靠的网络传输,它需要完成的功能主要包括路由选择、网络寻址、流量控制、拥塞控制、网络互连等。

    • 传输层

    传输层起着承上启下的作用,涉及源端节点到目的端节点之间可靠的信息传输。传输层需要解决跨越网络连接的建立和释放,对底层不可靠的网络,建立连接时需要三次握手,释放连接时需要四次挥手。

    • 会话层和表示层

    会话层的主要功能是负责应用程序之间建立、维持和中断会话,同时也提供对设备和结点之间的会话控制,协调系统和服务之间的交流,并通过提供单工、半双工和全双工3种不同的通信方式,使系统和服务之间有序地进行通信。

    表示层关心所传输数据信息的格式定义,其主要功能是把应用层提供的信息变换为能够共同理解的形式,提供字符代码、数据格式、控制信息格式、加密等的统一表示。

    • 应用层

    应用层为OSI的最高层,是直接为应用进程提供服务的。其作用是在实现多个系统应用进程相互通信的同时,完成一系列业务处理所需的服务。

    四、TCP/IP参考模型

    OSI参考模型的初衷是提供全世界范围的计算机网络都要遵循的统一标准,但是由于存在模型和协议自身的缺陷,迟迟没有成熟的产品推出。TCP/IP协议在实践中不断完善和发展取得成功,作为网络的基础,Internet的语言,可以说没有TCP/IP协议就没有互联网的今天。

    简介

    TCP/IP,即Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,是Internet最基本的协议、Internet国际互联网络的基础。

    TCP/IP协议是一个开放的网络协议簇,它的名字主要取自最重要的网络层IP协议和传输层TCP协议。TCP/IP协议定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。TCP/IP参考模型采用4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求,这4个层次分别是:网络接口层、互联网层(IP层)、传输层(TCP层)、应用层。

    OSI 和 TCP/IP模型对比

    TCP/IP模型层次功能

    • 网络接口层

    TCP/IP协议对网络接口层没有给出具体的描述,网络接口层对应着物理层和数据链路层。

    • 互联网层 ( IP层 )

    互联网层是整个TCP/IP协议栈的核心。它的功能是把分组发往目标网络或主机。同时,为了尽快地发送分组,可能需要沿不同的路径同时进行分组传递。因此,分组到达的顺序和发送的顺序可能不同,这就需要上层必须对分组进行排序。互联网层除了需要完成路由的功能外,也可以完成将不同类型的网络(异构网)互连的任务。除此之外,互联网层还需要完成拥塞控制的功能。

    • 传输层 ( TCP层 )

    TCP层负责在应用进程之间建立端到端的连接和可靠通信,它只存在与端节点中。TCP层涉及两个协议,TCP和UDP。其中,TCP协议提供面向连接的服务,提供按字节流的有序、可靠传输,可以实现连接管理、差错控制、流量控制、拥塞控制等。UDP协议提供无连接的服务,用于不需要或无法实现面向连接的网络应用中。

    • 应用层

    应用层为Internet中的各种网络应用提供服务。

    五、常见网络协议

    上面主要介绍了OSI参考模型和TCP/IP模型的相关内容,从下面这张图可以看出TCP/IP协议簇中不同的层次中有着很多不同的网络协议,下面主要介绍传输层的TCP、UDP协议和应用层的HTTP协议。

    TCP协议

    简介

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

    TCP报文段格式

    TCP报文段包括协议首部和数据两部分,协议首部的固定部分有20个字节,首部的固定部分后面是选项部分。

    TCP报文段

    报文段首部各个字段的含义

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

    三次握手与四次挥手

    TCP是面向连接的协议,因此每个TCP连接都有3个阶段:连接建立、数据传送和连接释放。连接建立经历三个步骤,通常称为“三次握手”。

    TCP三次握手过程如下:

    TCP三次握手

    第一次握手

    客户机发送连接请求报文段到服务器,并进入SYN_SENT状态,等待服务器确认。(SYN = 1,seq=x)

    第二次握手

    服务器收到连接请求报文,如果同意建立连接,向客户机发回确认报文段,并为该TCP连接分配TCP缓存和变量。(SYN=1,ACK=1,seq=y,ack=x+1)。

    第三次握手

    客户机收到服务器的确认报文段后,向服务器给出确认报文段,并且也要给该连接分配缓存和变量。此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。(ACK=1,seq=x+1,ack=y+1)。

    TCP四次挥手过程如下:

    四次挥手

    由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

    1. TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。
    2. 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
    3. 服务器关闭客户端的连接,发送一个FIN给客户端。
    4. 客户端发回ACK报文确认,并将确认序号设置为收到序号加1。

    UDP协议

    • 简介

    UDP,用户数据报协议,英文全称是User Datagram Protocol,它是TCP/IP协议簇中无连接的运输层协议。

    • UDP协议格式

    UDP格式

    从图中可以看到,UDP协议十分简单,它由两部分组成:首部和数据。其中,首部仅有8个字节,包括源端口和目的端口,长度(UDP用于数据报的长度)、校验和。

    HTTP协议

    • 简介

    HTTP,超文本传输协议,英文全称是Hypertext Transfer Protocol,它是互联网上应用最为广泛的一种网络协议。HTTP是一种应用层协议,它是基于TCP协议之上的请求/响应式的协议,即一个客户端与服务器建立连接后,向服务器发送一个请求;服务器接到请求后,给予相应的响应信息。HTTP协议默认的端口号为80.

    现在使用的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连接就看服务器设置情况。

    • HTTP报文

    HTTP协议是基于TCP协议之上的请求/响应式协议,下面主要介绍HTTP报文的格式,HTTP报文主要有请求报文和响应报文两种。首先看请求报文的格式:

    • HTTP请求报文格式

    HTTP请求报文由请求行、首部行和实体主体组成,由浏览器发送给服务器。上面这张图中SP表示空格,cr lf表示回车和换行。

    • HTTP响应报文格式

    上面这张图是HTTP响应报文,它由状态行、首部行和实体主体组成。下面两张图是在谷歌浏览器内访问服务器查看的HTTP请求和响应。

    HTTP请求报文例子

    HTTP响应报文例子

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

    在上面的HTTP请求报文例子中,我们可以看到请求方法是GET,这表示请求读取由URL所标志的信息,除了GET,还有其它几种常用的方法。

    • HTTP请求报文的一些方法

    在HTTP响应报文的例子中,我们可以看到状态码是200,表示响应成功。下表是其它状态码,总共5大类,33种。

    HTTP响应报文的状态码

    HTTPS和HTTP的区别

    HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。https:URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间)。

    超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息。HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此HTTP协议不适合传输一些敏感信息,比如信用卡号、密码等。

    为了解决HTTP协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议HTTPS。为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。

    HTTPS和HTTP的区别主要为以下四点:1、https协议需要到ca申请证书,一般免费证书很少,需要交费。2、http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

    六、常见问题

    到这里,关于计算机网络部分的总结内容就结束了,下面是几个常见的问题,汇总在这里。

    • OSI参考模型的分为哪几层,每层的功能?
      OSI,开放系统互连参考模型,它的7个层次自顶到下依次为应用层,表示层,会话层,传输层,网络层,数据链路层和物理层。各层的功能见文章开始。

    • TCP协议和UDP协议的区别?
      TCP协议是传输控制协议,UDP协议是用户数据报协议,两者都是传输层的协议,主要区别在于前者是可靠的,面向连接的协议,后者是不可靠的,无连接的协议。其它的区别还有,TCP协议传输速度慢,UDP常用于一次性传输比较少量数据的网络应用。

    • TCP三次握手为什么不能是两次?
      主要是防止两次握手情况下已经失效的连接请求报文段突然又传送到服务端而产生错误。例如,客户机A向服务器B发送TCP连接请求,第一个连接请求报文在网络的某个节点长时间滞留,A超时后认为报文丢失,于是再重传一次连接请求,B收到后建立连接。数据传输完毕后双方断开连接,而这时之前滞留的连接请求到达了服务端B,而B认为A又发来连接请求。如果两次握手建立连接,A并无连接请求,造成B的资源浪费。

    • HTTP请求的GET方法和POST方法的区别?
      GET和POST是HTTP请求的两种方法,主要区别在于GET方法是请求读取由URL所标志的信息,POST是给服务器添加信息。点击查看更多

    • 在浏览器中输入网址到显示出页面的整个过程?
      (1) 输出包含域名的网址
      (2) 浏览器向DNS请求解析域名对应的IP地址
      (3) 域名系统DNS解析出域名对应的IP地址
      (4) 浏览器与该服务器建立TCP连接
      (5) 浏览器发送HTTP请求
      (6) 服务器通过HTTP响应把页面文件发送给浏览器
      (7) TCP连接释放
      (8) 浏览器解释文件,并显示

    七、Java网络编程

    Java的网络编程主要涉及到的内容是Socket编程,那么什么是Socket呢?简单地说,Socket,套接字,就是两台主机之间逻辑连接的端点。TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。Socket,本质上就是一组接口,是对TCP/IP协议的封装和应用(程序员层面上)。

    整体流程

    Socket编程主要涉及到客户端和服务器端两个方面,首先是在服务器端创建一个服务器套接字(ServerSocket),并把它附加到一个端口上,服务器从这个端口监听连接。端口号的范围是0到65536,但是0到1024是为特权服务保留的端口号,我们可以选择任意一个当前没有被其他进程使用的端口。

    客户端请求与服务器进行连接的时候,根据服务器的域名或者IP地址,加上端口号,打开一个套接字。当服务器接受连接后,服务器和客户端之间的通信就像输入输出流一样进行操作。

    • 实例一

    下面是一个客户端和服务器端进行数据交互的简单例子,客户端输入正方形的边长,服务器端接收到后计算面积并返回给客户端,通过这个例子可以初步对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();
                }
            }
        }
    }
    

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

    展开全文
  • Java 网络编程

    万次阅读 多人点赞 2019-05-30 23:07:18
    计算机网络: 把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大,功能强的网络系统,从而使众多的计算机可以方便的互相传递信息,共享硬件,软件,数据信息等资源。 计算机网络的主要功能:...

    首先理清一个概念:网络编程不等于网站编程,网络编程即使用套接字来达到进程间通信,现在一般称为TCP/IP编程。

    一、计算机网络

    (一)概念

    计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。

    (二)计算机网络的主要功能

    1. 资源共享
      资源共享包括计算机硬件资源、软件资源和数据资源的共享。硬件资源的共享提高了计算机硬件资源的利用率,由于受经济和其他因素的制约,这些硬件资派不可能所有用户都有,所以使用计算机网络不仅可以使用自身的硬件资源,也可共享网络上的资源。软件资源和数据资源的共享可以充分利用已有的信息资派.减少软件开发过程中的劳动,避免大型数据库的重复建设。
    2. 信息交换
      这是计算机网络最基本的功能.计算机网络中的计算机之间或计算机与终端之间,可以快速可靠地相互传递数据、程序或文件。例如,用户可以在网上传送电子邮件、交换数据,可以实现在商业部门或公司之间进行订单、发票等商业文件安全准确地交换。
    3. 均衡负荷与分布处理
      对于大型的任务或课题.如果都集中在一台计算机上.负荷太重,这时可以将任务分散到不同的计算机分别完成,或由网络中比较空闲的计算机分担负荷,各个计算机连成网络有利于共同协作进行重大科研课题的开发和研究。利用网络技术还可以将许多小型机或傲型机连成具有高性能的分布式计算机系统,使它具有解决复杂问题的能力,从而大大降低费用。
    4. 综合信息服务
      计算机网络可以向全社会提供各处经济信息、科研情报、商业信息和咨询服务,如Internet中的www就是如此。

    (三)计算机网络分类

    计算机网络按其覆盖的地理范围可分为如下3类:

    • 局域网(LAN)。局域网是一种在小区域内使用的,由多台计算机组成的网络,覆盖范围通常局限在10 千米范围之内,属于一个单位或部门组建的小范围网。
    • 城域网(MAN)。城域网是作用范围在广域网与局域网之间的网络,其网络覆盖范围通常可以延伸到整个城市,借助通信光纤将多个局域网联通公用城市网络形成大型网络,使得不仅局域网内的资源可以共享,局域网之间的资源也可以共享。
    • 广域网(WAN) 广城网是一种远程网,涉及长距离的通信,覆盖范围可以是个国家或多个国家,甚至整个世界。由于广域网地理上的距离可以超过几千千米,所以信息衰减非常严重,这种网络一般要租用专线,通过接口信息处理协议和线路连接起来,构成网状结构,解决寻径问题。

    二、网络通信协议及接口

    (一)网络通信协议

    计算机网络中实现通信必须有一些约定,即通信协议;包括对速率,传输代码,代码结构,传输控制步骤,出错控制等制定的标准。常见的网络通信协议有:TCP/IP协议、IPX/SPX协议、NetBEUI协议等。

    TCP/IP协议:传输控制协议/因特网互联协议(Transmission Control Protocol/Internet Protocal),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。

    (二)网络通信接口

    为了使两个节点之间能进行对话,必须在他们之间建立通信工具(即接口),使彼此之间,能进行信息交换。接口包括两部分:

    • 硬件装置:实现结点之间的信息传送
    • 软件装置:规定双方进行通信的约定协议

    (三)通信协议分层思想

    1.为什么要分层

    由于结点之间联系很复杂,在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式就是层次方式,及同层间可以通信,上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展。

    2.通信协议的分层规定

    把用户应用程序作为最高层,把物理通信线路作为最底层,将其间的协议处理分为若干层,规定每层处理的任务,也规定每层的接口标准。

    3.参考模型

    在这里插入图片描述
    上图中,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能。

    • 应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。
    • 传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
    • 网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
    • 数据链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。

    我们编写的程序位于应用层,因此我们的程序是和TCP/UDP打交道的。

    (四)协议分类

    通信的协议还是比较复杂的,java.net包中包含的类和接口,它们提供低层次的通信细节,我们可以直接使用这些类和接口,来专注于网络程序开发,而不用考虑通信的细节。

    java.net包中提供了两种常见的网络协议的支持:TCP和UDP

    • TCP是可靠的连接,TCP就像打电话,需要先打通对方电话,等待对方有回应后才会跟对方继续说话,也就是一定要确认可以发信息以后才会把信息发出去。TCP上传任何东西都是可靠的,只要两台机器上建立起了连接,在本机上发送的数据就一定能传到对方的机器上。
    • UDP就好比发电报,发出去就完事了,对方有没有接收到它都不管,所以UDP是不可靠的。
    • TCP传送数据虽然可靠,但传送得比较慢;UDP传送数据不可靠,但是传送得快。

    1.UDP

    • 用户数据报协议(User Datagram Protocol)。
    • 数据报(Datagram):网络传输的基本单位
    • UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
    • 由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
    • 但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时,不建议使用UDP协议。
    • 特点:数据被限制在64kb以内,超出这个范围就不能发送了。

    2.TCP

    • 传输控制协议(Transmission Control Protocol)。
    • TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
    • 在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。

    (1)三次握手

    TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。

    在这里插入图片描述

    • 第一次握手,客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
      (客户端向服务器端发出连接请求,等待服务器确认。)
    • 第二次握手,服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。(服务器端向客户端回送一个响应,通知客户端收到了连接请求。)
    • 第三次握手,客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。(客户端再次向服务器端发送确认信息,确认连接。)

    三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。

    (2)四次挥手

    其次,TCP的客户端和服务端断开连接,需要四次挥手
    在这里插入图片描述

    • 客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
    • 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSED_WAIT 状态。
    • 客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
    • 等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
    • 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
    • 服务器收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
    • 客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。

    你可以看到,每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。这里一点需要注意是:主动关闭连接的,才有 TIME_WAIT 状态。

    为什么挥手需要四次?
    关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。参考

    通俗理解:

    • 三次握手
      A:我要过来了!B:我知道你要过来了!A:我现在过来!
    • 四次挥手
      A:我们分手吧!B:真的分手吗?B:真的真的要分手吗?A:是的!

    由此,可以可靠地进行连接和断开。

    三、IP协议

    (一)概念

    IP协议:网络互连协议

    在这里插入图片描述
    每个人的电脑都有一个独一无二的IP地址,这样互相通信时就不会传错信息了。

    (二)分类

    IP地址根据版本可以分类为:IPv4和IPv6

    IPv4IPv6
    地址长度IPv4协议具有32位(4字节)地址长度IPv6协议具有128位(16字节)地址长度
    格式IPv4 地址的文本格式为 nnn.nnn.nnn.nnn,其中 0<=nnn<=255,而每个 n 都是十进制数。可省略前导零。IPv6 地址的文本格式为 xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx,其中每个 x 都是十六进制数可省略前导零。
    数量共有43亿,30亿在北美,4亿在亚洲,2011年就已经用尽多到每一粒沙子都可以分配一个IPv6地址

    在这里插入图片描述
    IPv4又可以分为五类:

    • A类:在IP地址的四段号码中,第一段号码为网络号码,剩下的三段号码为本地计算机的号码;A类IP地址中网络的标识长度为8位,主机标识的长度为24位,A类网络地址数量较少,有126个网络,每个网络可以容纳主机数达1600多万(256的三次方-2)台。

    • B类:在IP地址的四段号码中,前两段号码为网络号码。B类IP地址中网络的标识长度为16位,主机标识的长度为16位,B类网络地址适用于中等规模的网络,有16384个网络,每个网络所能容纳的计算机数为6万多(256的二次方-2)台

    • C类:在IP地址的四段号码中,前三段号码为网络号码,剩下的一段号码为本地计算机的号码;此类地址中网络的标识长度为24位,主机标识的长度为8位,C类网络地址数量较多,有209万余个网络。适用于小规模的局域网络,每个网络最多只能包含254(256-2)台计算机

    • D类:此类IP地址在历史上被叫做多播地址(multicast address),即组播地址;在以太网中,多播地址命名了一组应该在这个网络中应用接收到一个分组的站点;多播地址的最高位必须是“1110”,范围从224.0.0.0到239.255.255.255

    • E类: 此类地址也不分网络地址和主机地址,它的第1个字节的前五位固定为“11110”,为将来使用保留,地址范围从240.0.0.1到255.255.255.254

    (三)InetAddress类

    说到IP地址,就要引入一个类:InetAddress
    此类表示互联网协议 (IP) 地址
    在这里插入图片描述

    InetAddress类无构造方法

    1.常用方法摘要

    byte[] getAddress()
    返回此 InetAddress 对象的原始 IP 地址。
    static InetAddress getByName(String host)
    在给定主机名的情况下确定主机的 IP 地址。
    String getHostAddress()
    返回 IP 地址字符串(以文本表现形式)。
    String getHostName()
    获取此 IP 地址的主机名。
    static InetAddress getLocalHost()
    返回本地主机。
    127.0.0.1:本机地址,主要用于测试。别名:Localhost

    案例演示1

    import java.net.InetAddress;
    import java.net.UnknownHostException;
    
    public class TestIP {
        public static void main(String[] args) throws UnknownHostException {
            //InetAdress类表示IP地址
    
            //获取本机IP
            InetAddress ip = InetAddress.getLocalHost();// ADMINISTRATOR/192.xxx.xxx.xxx
            System.out.println(ip);
            //获得主机名
            System.out.println(ip.getHostName());// ADMINISTRATOR
            //获得IP地址
            System.out.println(ip.getHostAddress());// 192.xxx.xxx.xxx
            //getLocalHost=getHostName+getHostAddress
            System.out.println(InetAddress.getByName("localhost"));
        }
    }
    

    案例演示2

    import java.net.InetAddress;
    import java.net.UnknownHostException;
    
    public class TestIP2 {
        public static void main(String[] args) throws UnknownHostException {
            InetAddress inetAddress = InetAddress.getByName("www.baidu.com");
            // 获取此 IP 地址的主机名。
            System.out.println(inetAddress.getHostName());
            //返回 IP 地址字符串(以文本表现形式)。
            System.out.println(inetAddress.getHostAddress());
        }
    }
    

    在这里插入图片描述

    四、端口

    • IP地址用来标识一台计算机,但是一台计算机上可能提供多种网络应用程序,如何来区分这些不同的程序呢?这就要用到端口。端口号是用来区分一台机器上不同的应用程序的。
    • 我们使用IP地址加端口号,就可以保证数据准确无误地发送到对方计算机的指定软件上了。
    • 端口是虚拟的概念,是一个逻辑端口。
    • 当我们使用网络软件一打开,那么操作系统就会为网络软件分配一个随机的端口号,或者打开的时候向系统要指定的端口号。
    • 通过端口,可以在一个主机上运行多个网络应用程序。端口的表示是一个16位的二进制整数,2个字节,对应十进制的0~65535
    • 端口号在计算机内部是占2个字节。一台机器上最多有65536个端口号。一个应用程序可以占用多个端口号。端口号如果被一个应用程序占用了,那么其他的应用程序就无法再使用这个端口号了。
    • 记住一点,我们编写的程序要占用端口号的话占用1024以上的端口号,1024以下的端口号不要去占用,因为系统有可能会随时征用。端口号本身又分为TCP端口和UDP端口,TCP的8888端口和UDP的8888端口是完全不同的两个端口。TCP端口和UDP端口都有65536个

    (一)分类

    1. 公有端口:0~1023
      • HTTP:80
      • HTTPS:443
      • FTP:21
      • Telnet:23
    2. 程序注册端口(分配给用户或者程序):1024~49151
      • Tomcat:8080
      • MySQL:3306
      • Oracle:1521
    3. 动态、私有端口:49152~65535

    (二)DOS命令查看端口

    • 查看所有端口:netstat -ano
    • 查看指定端口:netstat -ano|findstr "端口号"
    • 查看指定端口的进程:tasklist|findstr "端口号"

    (三)InetSocketAddress类

    说到端口,则要引入一个类:InetSocketAddress

    此类实现 IP 套接字地址(IP 地址 + 端口号)。

    1.构造方法摘要

    InetSocketAddress(InetAddress addr, int port)
    根据 IP 地址和端口号创建套接字地址。
    InetSocketAddress(int port)
    创建套接字地址,其中 IP 地址为通配符地址,端口号为指定值。
    InetSocketAddress(String hostname, int port)
    根据主机名和端口号创建套接字地址。

    2.常用方法摘要

    InetAddress getAddress()
    获取 InetAddress。
    String getHostName()
    获取 hostname。
    int getPort()
    获取端口号。

    案例演示

    import java.net.InetAddress;
    import java.net.InetSocketAddress;
    
    public class TestPort {
        public static void main(String[] args) {
            InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",8082);
            System.out.println(inetSocketAddress);
            //返回主机名
            System.out.println(inetSocketAddress.getHostName());
            //获得InetSocketAddress的端口
            System.out.println(inetSocketAddress.getPort());
            //返回一个InetAddress对象(IP对象)
            InetAddress address = inetSocketAddress.getAddress();
            System.out.println(address);
        }
    }
    

    在这里插入图片描述

    五、TCP网络编程和UDP网络编程

    网络编程也叫做Socket编程,即套接字编程。套接字指的是两台设备之间通讯的端点。

    在这里插入图片描述

    (一)TCP网络编程

    1.概述

    TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。

    (1)两端通信时步骤:

    1. 服务端程序,需要事先启动,等待客户端的连接。
    2. 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。

    (2)在Java中,提供了两个类用于实现TCP通信程序:

    1. 客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
    2. 服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。

    2.Socket类

    Socket 类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。

    (1)构造方法摘要

    • public Socket(String host, int port) :创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址
      回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。

    案例演示

    Socket client = new Socket("127.0.0.1", 6666);
    

    (2)常用方法摘要

    • public InputStream getInputStream() : 返回此套接字的输入流。

      • 如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
      • 关闭生成的InputStream也将关闭相关的Socket。
    • public OutputStream getOutputStream() : 返回此套接字的输出流。

      • 如果此Socket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
      • 关闭生成的OutputStream也将关闭相关的Socket。
    • public void close() :关闭此套接字。

      • 一旦一个socket被关闭,它不可再使用。
      • 关闭此socket也将关闭相关的InputStream和OutputStream 。
    • public void shutdownOutput() : 禁用此套接字的输出流。

      • 任何先前写出的数据将被发送,随后终止输出流。

    3.ServerSocket类

    ServerSocket类:这个类实现了服务器套接字,该对象等待通过网络的请求。

    (1)构造方法摘要

    • public ServerSocket(int port) :使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。

    案例演示

    ServerSocket server = new ServerSocket(6666);
    

    (2)常用方法摘要

    • public Socket accept() :侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。

    4.代码实现

    案例演示1

    客户端:

    import java.io.IOException;
    import java.io.OutputStream;
    import java.net.InetAddress;
    import java.net.Socket;
    
    public class TCPClient {
        public static void main(String[] args){
            Socket socket = null;
            OutputStream os = null;
            try {
                //1、创建Socket对象,它的第一个参数需要的是服务端的IP,第二个参数是服务端的端口
                InetAddress inet = InetAddress.getByName("127.0.0.1");
                socket = new Socket(inet,8090);
                //2、获取一个输出流,用于写出要发送的数据
                os = socket.getOutputStream();
                //3、写出数据
                os.write("你好,我是客户端!".getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {//4、释放资源
                if(socket!=null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(os!=null){
                    try {
                        os.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    服务端:

    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class TCPServer {
        public static void main(String[] args) {
            ServerSocket serverSocket = null;
            Socket socket = null;
            InputStream is = null;
            ByteArrayOutputStream baos = null;
            try {
                //1、创建服务端的ServerSocket,指明自己的端口号
                serverSocket = new ServerSocket(8090);
                //2、调用accept接收到来自于客户端的socket
                socket = serverSocket.accept();//阻塞式监听,会一直等待客户端的接入
                //3、获取socket的输入流
                is = socket.getInputStream();
    
    //        不建议这样写:因为如果我们发送的数据有汉字,用String的方式输出可能会截取汉字,产生乱码
    //        int len=0;
    //        byte[] buffer = new byte[1024];
    //        while ((len=is.read(buffer))!=-1){
    //            String str = new String(buffer, 0, len);
    //            System.out.println(str);
    //        }
                
                //4、读取输入流中的数据
                //ByteArrayOutputStream的好处是它可以根据数据的大小自动扩充
                baos = new ByteArrayOutputStream();
                int len=0;
                byte[] buffer = new byte[1024];
                while ((len=is.read(buffer))!=-1){
                    baos.write(buffer,0,len);
                }
                System.out.println("收到了来自于客户端"+socket.getInetAddress().getHostName()
                        +"的消息:"+baos.toString());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {//5、关闭资源
                if(serverSocket!=null){
                    try {
                        serverSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(socket!=null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(is!=null){
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(baos!=null){
                    try {
                        baos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    

    在这里插入图片描述
    服务器是没有IO流的,服务器可以获取到请求的客户端对象socket
    使用每个客户端socket中提供的IO流和客户端进行交互
    服务器使用客户端的字节输入流读取客户端发送的数据
    服务器使用客户端的字节输出流给客户端回写数据

    案例演示2
    服务端向客户端回写数据

    客户端:

    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.InetAddress;
    import java.net.Socket;
    
    public class TCPClient {
        public static void main(String[] args){
            Socket socket = null;
            OutputStream os = null;
            ByteArrayOutputStream baos=null;
            InputStream is=null;
            try {
                //1、创建Socket对象,它的第一个参数需要的是服务端的IP,第二个参数是服务端的端口
                InetAddress inet = InetAddress.getByName("127.0.0.1");
                socket = new Socket(inet,8888);
                //2、获取一个输出流,用于写出要发送的数据
                os = socket.getOutputStream();
                //3、写出数据
                os.write("你好,我是客户端!".getBytes());
                //==========================解析回复==================================
                //4、首先必须通知服务器,我已经输出完毕了,不然服务端不知道什么时候输出完毕
                //服务端的while循环会一直执行,会阻塞
                socket.shutdownOutput();
                ///5、获取输入流,用于读取服务端回复的数据
                is = socket.getInputStream();
                baos = new ByteArrayOutputStream();
                int len=0;
                byte[] buffer = new byte[1024];
                while ((len=is.read(buffer))!=-1){
                    baos.write(buffer,0,len);
                }
                System.out.println("收到了来自服务端的消息:"+baos.toString());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {//6、释放资源
                if(socket!=null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(os!=null){
                    try {
                        os.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(is!=null){
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (baos!=null){
                    try {
                        baos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    服务端:

    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class TCPServer {
        public static void main(String[] args) {
            ServerSocket serverSocket = null;
            Socket socket = null;
            InputStream is = null;
            ByteArrayOutputStream baos = null;
            OutputStream os=null;
            try {
                //1、创建服务端的ServerSocket,指明自己的端口号
                serverSocket = new ServerSocket(8888);
                //2、调用accept接收到来自于客户端的socket
                socket = serverSocket.accept();//阻塞式监听,会一直等待客户端接入
                //3、获取socket的输入流
                is = socket.getInputStream();
    
    //        不建议这样写:因为如果我们发送的数据有汉字,用String的方式输出可能会截取汉字,产生乱码
    //        int len=0;
    //        byte[] buffer = new byte[1024];
    //        while ((len=is.read(buffer))!=-1){
    //            String str = new String(buffer, 0, len);
    //            System.out.println(str);
    //        }
    
                //4、读取输入流中的数据
                //ByteArrayOutputStream的好处是它可以根据数据的大小自动扩充
                baos = new ByteArrayOutputStream();
                int len=0;
                byte[] buffer = new byte[1024];
                while ((len=is.read(buffer))!=-1){
                    baos.write(buffer,0,len);
                }
                System.out.println("收到了来自于客户端"+socket.getInetAddress().getHostName()
                        +"的消息:"+baos.toString());
                //===========================回复==========================================
                //5、获取一个输出流,写出回复给客户端
                os = socket.getOutputStream();
                //6、写出数据
                os.write("你好,我是服务端".getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {//7、关闭资源
                if(serverSocket!=null){
                    try {
                        serverSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(socket!=null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(is!=null){
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(baos!=null){
                    try {
                        baos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(os!=null){
                    try {
                        os.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    在这里插入图片描述
    在这里插入图片描述
    案例演示3
    上传文件:客户端发送文件给服务端,服务端将文件保存在本地
    在这里插入图片描述

    客户端:

    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.net.InetAddress;
    import java.net.Socket;
    
    public class TCPClient {
        public static void main(String[] args) {
            Socket socket = null;
            FileInputStream fis = null;
            OutputStream os = null;
            try {
                //1、创建Socket对象,它的第一个参数需要的是服务端的IP,第二个参数是服务端的端口
                InetAddress inet = InetAddress.getByName("127.0.0.1");
                socket = new Socket(inet, 8888);
                //2、创建一个文件输入流,读取要上传的文件
                fis = new FileInputStream("D:/test/touxiang.jpg");
                //3、获取一个输出流,用于写出要发送的数据
                os = socket.getOutputStream();
                byte[] buffer = new byte[1024];
                int len=0;
                while((len=fis.read(buffer))!=-1){
                    //4、写出数据
                    os.write(buffer,0,len);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {//5、释放资源
                if(socket!=null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(fis!=null){
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(os!=null){
                    try {
                        os.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    服务端:

    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class TCPServer {
        public static void main(String[] args) {
            ServerSocket serverSocket = null;
            Socket socket = null;
            FileOutputStream fos = null;
            InputStream is = null;
            try {
                //1、创建服务端的ServerSocket,指明自己的端口号
                serverSocket = new ServerSocket(8888);
                //2、调用accept接收到来自于客户端的socket
                socket = serverSocket.accept();//阻塞式监听,会一直等待客户端的接入
                //3、创建一个文件输出流,用于将读取到的客户端上传的文件输出
                fos = new FileOutputStream("touxiang.jpg");
                //4、获取socket的输入流
                is = socket.getInputStream();
                byte[] buffer = new byte[1024];
                int len=0;
                while((len=is.read(buffer))!=-1){
                    fos.write(buffer,0,len);//5、写出文件
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {//6、释放资源
                if(serverSocket!=null){
                    try {
                        serverSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(socket!=null){
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(fos!=null){
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(is!=null){
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    假设大海里有许多岛屿,客户端和服务端相当于其中的两座岛屿,“客户端”岛屿生产了一种农作物要运到“服务端”岛屿,所以“客户端”要知道“服务端”确切的地址(IP),不然就运错地方了,socket就相当于运输的船只,port就相当于“服务端”岛屿的某个港口。

    (二)UDP网络编程

    从技术意义上来讲,只有TCP才会分Server和Client。对于UDP来说,严格意义上,并没有所谓的Server和Client。
    java.net包给我们提供了两个类DatagramSocket(此类表示用于发送和接收数据报的套接字)和DatagramPacket(该类表示数据报的数据包。 )

    1.DatagramSocket

    (1)构造方法摘要

    protected DatagramSocket()构造数据报套接字并将其绑定到本地主机上的任何可用端口。
    protected DatagramSocket(int port)构造数据报套接字并将其绑定到本地主机上的指定端口。
    protected DatagramSocket(int port, InetAddress laddr)创建一个数据报套接字,绑定到指定的本地地址。

    2.DatagramPacket

    (1)构造方法摘要

    DatagramPacket(byte[] buf, int offset, int length)构造一个 DatagramPacket用于接收指定长度的数据报包到缓冲区中。
    DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)构造用于发送指定长度的数据报包到指定主机的指定端口号上。

    (2)常用方法摘要

    byte[] getData() 返回数据报包中的数据。
    InetAddress getAddress() 返回该数据报发送或接收数据报的计算机的IP地址。
    int getLength() 返回要发送的数据的长度或接收到的数据的长度。

    3.代码实现

    案例演示1
    发送方:

    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    
    public class UDPSender {
        public static void main(String[] args) throws IOException {
            //1、创建一个socket
            DatagramSocket socket = new DatagramSocket();
            InetAddress inet = InetAddress.getLocalHost();
            String msg="你好,很高兴认识你!";
            byte[] buffer = msg.getBytes();
            //2、创建一个包(要发送给谁)
            DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length,inet,9090);
            //3、发送包
            socket.send(packet);
            //4、释放资源
            socket.close();
        }
    }
    

    接收方:

    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    
    public class UDPReceiver {
        public static void main(String[] args) throws IOException {
            //1、创建一个socket,开放端口
            DatagramSocket socket = new DatagramSocket(9090);
            byte[] buffer = new byte[1024];
            //2、创建一个包接收数据
            DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
            //3、接收数据
            socket.receive(packet);//阻塞式接收
            //将数据包转换为字符串输出
            String msg = new String(packet.getData(), 0, packet.getLength());
            System.out.println(msg);
            //4、释放资源
            socket.close();
        }
    }
    

    在这里插入图片描述
    注意
    如果是TCP中先启动客户端会报错:
    在这里插入图片描述
    而如果是UDP中先启动发送方不会报错,但会正常退出。

    案例演示2
    完成在线咨询功能,学生和老师在线一对一交流(多线程

    发送方:

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    import java.net.SocketException;
    
    public class UDPSender implements Runnable{
        //创建一个socket
        DatagramSocket socket=null;
        //创建一个流 用于录入键盘的数据
        BufferedReader bfr=null;
        //发送数据目的地的IP
        private String toIP;
        //发送数据目的地的端口
        private int toPort;
    
        public UDPSender(String toIP, int toPort) {
            this.toIP = toIP;
            this.toPort = toPort;
            try {
                socket=new DatagramSocket();//创建一个socket
            } catch (SocketException e) {
                e.printStackTrace();
            }
            bfr=new BufferedReader(new InputStreamReader(System.in));//从键盘录入数据到流中
        }
    
        @Override
        public void run() {
            while (true){//循环发送数据
                try {
                    String msg = bfr.readLine();//从流中读取数据
                    byte[] buffer = msg.getBytes();
                    InetAddress inet = InetAddress.getByName(toIP);
                    DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length, inet, toPort);
                    socket.send(packet);
                    //如果发送了拜拜,则退出发送
                    if(msg.equals("拜拜")){
                        break;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //释放资源
            if(socket!=null){
                socket.close();
            }
            if (bfr!=null){
                try {
                    bfr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    接收方:

    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.SocketException;
    
    public class UDPReceiver implements Runnable{
        //创建一个socket
        DatagramSocket socket=null;
        //接收方自己所在的端口
        private int fromPort;
        //数据发送者的姓名
        private String msgFrom;
    
        public UDPReceiver(int fromPort,String msgFrom) {
            this.fromPort = fromPort;
            this.msgFrom=msgFrom;
            try {
                socket=new DatagramSocket(fromPort);//创建一个socket
            } catch (SocketException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void run() {
            while(true){//循环接收
                try {
                    byte[] buffer = new byte[1024 * 8];
                    DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
                    socket.receive(packet);
                    String msg = new String(packet.getData(), 0, packet.getLength());
                    System.out.println(msgFrom+":"+msg);
                    if (msg.equals("拜拜")){//如果接收到的数据为拜拜,则退出接收
                        break;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //释放资源
            socket.close();
        }
    }
    

    学生线程:

    public class Student {
        public static void main(String[] args) {
            new Thread(new UDPSender("127.0.0.1",8888)).start();
            new Thread(new UDPReceiver(7777,"老师")).start();
        }
    }
    

    老师线程:

    public class Teacher {
        public static void main(String[] args) {
            new Thread(new UDPSender("127.0.0.1",7777)).start();
            new Thread(new UDPReceiver(8888,"学生")).start();
        }
    }
    

    学生的窗口:
    在这里插入图片描述
    老师的窗口:
    在这里插入图片描述

    六、URL

    (一)概念

    URL(Uniform Resource Locator)统一资源定位符,它表示Internet上某一资源的地址。
    通过URL我们可以访问Internet上的各种网络资源,比如最常见的www,ftp站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。

    URI=URL+URN

    • URI:Uniform Resource Identifier ,统一资源标志符。
    • URL:Uniform Resource Locator,统一资源定位符。
    • URN:Uniform Resource Name,统一资源命名。

    网络三大基石:HTML,HTTP,URL

    (二)格式

    URL的基本结构由5部分组成:
    <传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表

    • 片段名:即锚点,例如看小说,直接定位到章节
    • 参数列表格式:参数名=参数值&参数名=参数值......

    例如:
    http://localhost:8080/index.jsp#a?username=Tom&password=123456

    (三)URL类

    java.net包下

    1.构造方法摘要

    URL(String spec)根据 String 表示形式创建 URL 对象。
    URL(String protocol, String host, int port, String file) 根据指定协议名、主机名、端口号和文件名创建 URL 对象。
    URL(String protocol, String host, String file) 根据指定的协议名、主机名和文件名创建 URL。

    2.常用方法摘要

    String getProtocol()获取此 URL的协议名称。
    String getHost() 获取此 URL 的主机名。
    int getPort() 获取此 URL 的端口号。
    String getPath() 获取此 URL 的文件路径。
    String getFile()获取此 URL 的文件名。
    String getQuery()获取此 URL的查询部分。
    URLConnection openConnection() 返回一个URLConnection实例,表示与URL引用的远程对象的URL 。

    • URLConnection类中又有一个方法:
      InputStream getInputStream() 返回从此打开的连接读取的输入流。

    案例演示1

    import java.net.MalformedURLException;
    import java.net.URL;
    
    public class Test {
        public static void main(String[] args) throws MalformedURLException {
            URL url = new URL("http://localhost:8080/index.jsp?username=Tom&password=123456");
            System.out.println(url.getProtocol());//获取协议名
            System.out.println(url.getHost());//获取主机名
            System.out.println(url.getPort());//获取端口号
            System.out.println(url.getPath());//获取文件路径
            System.out.println(url.getFile());//获取文件名
            System.out.println(url.getQuery());//获取查询名
        }
    }
    

    在这里插入图片描述
    案例演示2
    URL下载网络资源

    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    public class Test {
        public static void main(String[] args) throws IOException {
            //下载地址
            URL url = new URL("https://img.t.sinajs.cn/t6/style/images/global_nav/WB_logo.png?id=1404211047727");
            //连接到这个资源 HTTP
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            InputStream is = urlConnection.getInputStream();
            FileOutputStream fos = new FileOutputStream("weibo.jpg");
            byte[] buffer = new byte[1024];
            int len=0;
            while ((len=is.read(buffer))!=-1){
                fos.write(buffer,0,len);
            }
            //释放资源
            urlConnection.disconnect();//断开连接
            is.close();
            fos.close();
        }
    }
    

    小结

    在这里插入图片描述

    展开全文
  • 计算机网络高级软件编程技术(第二版),基于c和c++的,网络编程必看
  • 计算机网络编程知识总结

    千次阅读 2017-12-17 21:36:24
    ...1. 网络层次划分 2. OSI七层网络模型 3. IP地址 4. 子网掩码及网络划分 5. ARP/RARP协议 6. 路由选择协议 7. TCP/IP协议 8. UDP协议  9. DNS协议 10. NAT协议 11. DHCP协议 12. HT
    博客出处:
    http://www.cnblogs.com/maybe2030/
     
    


      计算机网络学习的核心内容就是网络协议的学习。网络协议是为计算机网络中进行数据交换而建立的规则、标准或者说是约定的集合。因为不同用户的数据终端可能采取的字符集是不同的,两者需要进行通信,必须要在一定的标准上进行。一个很形象地比喻就是我们的语言,我们大天朝地广人多,地方性语言也非常丰富,而且方言之间差距巨大。A地区的方言可能B地区的人根本无法接受,所以我们要为全国人名进行沟通建立一个语言标准,这就是我们的普通话的作用。同样,放眼全球,我们与外国友人沟通的标准语言是英语,所以我们才要苦逼的学习英语。

      计算机网络协议同我们的语言一样,多种多样。而ARPA公司与1977年到1979年推出了一种名为ARPANET的网络协议受到了广泛的热捧,其中最主要的原因就是它推出了人尽皆知的TCP/IP标准网络协议。目前TCP/IP协议已经成为Internet中的“通用语言”,下图为不同计算机群之间利用TCP/IP进行通信的示意图。

    1. 网络层次划分

      为了使不同计算机厂家生产的计算机能够相互通信,以便在更大的范围内建立计算机网络,国际标准化组织(ISO)在1978年提出了“开放系统互联参考模型”,即著名的OSI/RM模型(Open System Interconnection/Reference Model)。它将计算机网络体系结构的通信协议划分为七层,自下而上依次为:物理层(Physics Layer)、数据链路层(Data Link Layer)、网络层(Network Layer)、传输层(Transport Layer)、会话层(Session Layer)、表示层(Presentation Layer)、应用层(Application Layer)。 其中第四层完成数据传送服务,上面三层面向用户。
      除了标准的OSI七层模型以外,常见的网络层次划分还有TCP/IP四层协议以及TCP/IP五层协议,它们之间的对应关系如下图所示:
     
     

    2. OSI七层网络模型

      TCP/IP协议毫无疑问是互联网的基础协议,没有它就根本不可能上网,任何和互联网有关的操作都离不开TCP/IP协议。不管是OSI七层模型还是TCP/IP的四层、五层模型,每一层中都要自己的专属协议,完成自己相应的工作以及与上下层级之间进行沟通。由于OSI七层模型为网络的标准层次划分,所以我们以OSI七层模型为例从下向上进行一一介绍。
     
      1)物理层(Physical Layer)

      激活、维持、关闭通信端点之间的机械特性、电气特性、功能特性以及过程特性。该层为上层协议提供了一个传输数据的可靠的物理媒体。简单的说,物理层确保原始的数据可在各种物理媒体上传输。物理层记住两个重要的设备名称,中继器(Repeater,也叫放大器)和集线器。

      2)数据链路层(Data Link Layer)

      数据链路层在物理层提供的服务的基础上向网络层提供服务,其最基本的服务是将源自网络层来的数据可靠地传输到相邻节点的目标机网络层。为达到这一目的,数据链路必须具备一系列相应的功能,主要有:如何将数据组合成数据块,在数据链路层中称这种数据块为帧(frame),帧是数据链路层的传送单位;如何控制帧在物理信道上的传输,包括如何处理传输差错,如何调节发送速率以使与接收方相匹配;以及在两个网络实体之间提供数据链路通路的建立、维持和释放的管理。数据链路层在不可靠的物理介质上提供可靠的传输。该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。

      有关数据链路层的重要知识点:

      1> 数据链路层为网络层提供可靠的数据传输;

      2> 基本数据单位为帧;

      3> 主要的协议:以太网协议;

      4> 两个重要设备名称:网桥和交换机。

      3)网络层(Network Layer)

      网络层的目的是实现两个端系统之间的数据透明传送,具体功能包括寻址和路由选择、连接的建立、保持和终止等。它提供的服务使传输层不需要了解网络中的数据传输和交换技术。如果您想用尽量少的词来记住网络层,那就是“路径选择、路由及逻辑寻址”。

      网络层中涉及众多的协议,其中包括最重要的协议,也是TCP/IP的核心协议——IP协议。IP协议非常简单,仅仅提供不可靠、无连接的传送服务。IP协议的主要功能有:无连接数据报传输、数据报路由选择和差错控制。与IP协议配套使用实现其功能的还有地址解析协议ARP、逆地址解析协议RARP、因特网报文协议ICMP、因特网组管理协议IGMP。具体的协议我们会在接下来的部分进行总结,有关网络层的重点为:

      1> 网络层负责对子网间的数据包进行路由选择。此外,网络层还可以实现拥塞控制、网际互连等功能;

      2> 基本数据单位为IP数据报;

      3> 包含的主要协议:

      IP协议(Internet Protocol,因特网互联协议);

      ICMP协议(Internet Control Message Protocol,因特网控制报文协议);

      ARP协议(Address Resolution Protocol,地址解析协议);

      RARP协议(Reverse Address Resolution Protocol,逆地址解析协议)。

      4> 重要的设备:路由器。

      4)传输层(Transport Layer)

      第一个端到端,即主机到主机的层次。传输层负责将上层数据分段并提供端到端的、可靠的或不可靠的传输。此外,传输层还要处理端到端的差错控制和流量控制问题。

      传输层的任务是根据通信子网的特性,最佳的利用网络资源,为两个端系统的会话层之间,提供建立、维护和取消传输连接的功能,负责端到端的可靠数据传输。在这一层,信息传送的协议数据单元称为段或报文。
      网络层只是根据网络地址将源结点发出的数据包传送到目的结点,而传输层则负责将数据可靠地传送到相应的端口。
      有关网络层的重点:
      1> 传输层负责将上层数据分段并提供端到端的、可靠的或不可靠的传输以及端到端的差错控制和流量控制问题;
      2> 包含的主要协议:TCP协议(Transmission Control Protocol,传输控制协议)、UDP协议(User Datagram Protocol,用户数据报协议);
      3> 重要设备:网关。

      5)会话层

      会话层管理主机之间的会话进程,即负责建立、管理、终止进程之间的会话。会话层还利用在数据中插入校验点来实现数据的同步。

      6)表示层

      表示层对上层数据或信息进行变换以保证一个主机应用层信息可以被另一个主机的应用程序理解。表示层的数据转换包括数据的加密、压缩、格式转换等。

      7)应用层

      为操作系统或网络应用程序提供访问网络服务的接口。

      会话层、表示层和应用层重点:

      1> 数据传输基本单位为报文;

      2> 包含的主要协议:FTP(文件传送协议)、Telnet(远程登录协议)、DNS(域名解析协议)、SMTP(邮件传送协议),POP3协议(邮局协议),HTTP协议(Hyper Text Transfer Protocol)。

    3. IP地址

      1)网络地址

      IP地址由网络号(包括子网号)和主机号组成,网络地址的主机号为全0,网络地址代表着整个网络。

      2)广播地址

      广播地址通常称为直接广播地址,是为了区分受限广播地址。

      广播地址与网络地址的主机号正好相反,广播地址中,主机号为全1。当向某个网络的广播地址发送消息时,该网络内的所有主机都能收到该广播消息。

      3)组播地址

      D类地址就是组播地址。

      先回忆下A,B,C,D类地址吧:

      A类地址以0开头,第一个字节作为网络号,地址范围为:0.0.0.0~127.255.255.255;(modified @2016.05.31)

      B类地址以10开头,前两个字节作为网络号,地址范围是:128.0.0.0~191.255.255.255;

      C类地址以110开头,前三个字节作为网络号,地址范围是:192.0.0.0~223.255.255.255。

      D类地址以1110开头,地址范围是224.0.0.0~239.255.255.255,D类地址作为组播地址(一对多的通信);

      E类地址以1111开头,地址范围是240.0.0.0~255.255.255.255,E类地址为保留地址,供以后使用。

      注:只有A,B,C有网络号和主机号之分,D类地址和E类地址没有划分网络号和主机号。

      4)255.255.255.255

      该IP地址指的是受限的广播地址。受限广播地址与一般广播地址(直接广播地址)的区别在于,受限广播地址只能用于本地网络,路由器不会转发以受限广播地址为目的地址的分组;一般广播地址既可在本地广播,也可跨网段广播。例如:主机192.168.1.1/30上的直接广播数据包后,另外一个网段192.168.1.5/30也能收到该数据报;若发送受限广播数据报,则不能收到。

      注:一般的广播地址(直接广播地址)能够通过某些路由器(当然不是所有的路由器),而受限的广播地址不能通过路由器。

      5)0.0.0.0

      常用于寻找自己的IP地址,例如在我们的RARP,BOOTP和DHCP协议中,若某个未知IP地址的无盘机想要知道自己的IP地址,它就以255.255.255.255为目的地址,向本地范围(具体而言是被各个路由器屏蔽的范围内)的服务器发送IP请求分组。

      6)回环地址

      127.0.0.0/8被用作回环地址,回环地址表示本机的地址,常用于对本机的测试,用的最多的是127.0.0.1。

      7)A、B、C类私有地址

      私有地址(private address)也叫专用地址,它们不会在全球使用,只具有本地意义。

      A类私有地址:10.0.0.0/8,范围是:10.0.0.0~10.255.255.255

      B类私有地址:172.16.0.0/12,范围是:172.16.0.0~172.31.255.255

      C类私有地址:192.168.0.0/16,范围是:192.168.0.0~192.168.255.255

    4. 子网掩码及网络划分

      随着互连网应用的不断扩大,原先的IPv4的弊端也逐渐暴露出来,即网络号占位太多,而主机号位太少,所以其能提供的主机地址也越来越稀缺,目前除了使用NAT在企业内部利用保留地址自行分配以外,通常都对一个高类别的IP地址进行再划分,以形成多个子网,提供给不同规模的用户群使用。

      这里主要是为了在网络分段情况下有效地利用IP地址,通过对主机号的高位部分取作为子网号,从通常的网络位界限中扩展或压缩子网掩码,用来创建某类地址的更多子网。但创建更多的子网时,在每个子网上的可用主机地址数目会比原先减少。

      什么是子网掩码?

      子网掩码是标志两个IP地址是否同属于一个子网的,也是32位二进制地址,其每一个为1代表该位是网络位,为0代表主机位。它和IP地址一样也是使用点式十进制来表示的。如果两个IP地址在子网掩码的按位与的计算下所得结果相同,即表明它们共属于同一子网中。

      在计算子网掩码时,我们要注意IP地址中的保留地址,即“ 0”地址和广播地址,它们是指主机地址或网络地址全为“ 0”或“ 1”时的IP地址,它们代表着本网络地址和广播地址,一般是不能被计算在内的。

      子网掩码的计算:

      对于无须再划分成子网的IP地址来说,其子网掩码非常简单,即按照其定义即可写出:如某B类IP地址为 10.12.3.0,无须再分割子网,则该IP地址的子网掩码255.255.0.0。如果它是一个C类地址,则其子网掩码为 255.255.255.0。其它类推,不再详述。下面我们关键要介绍的是一个IP地址,还需要将其高位主机位再作为划分出的子网网络号,剩下的是每个子网的主机号,这时该如何进行每个子网的掩码计算。

      下面总结一下有关子网掩码和网络划分常见的面试考题:

      1)利用子网数来计算

      在求子网掩码之前必须先搞清楚要划分的子网数目,以及每个子网内的所需主机数目。

      (1) 将子网数目转化为二进制来表示;

      如欲将B类IP地址168.195.0.0划分成27个子网:27=11011;

      (2) 取得该二进制的位数,为N;

      该二进制为五位数,N = 5

      (3) 取得该IP地址的类子网掩码,将其主机地址部分的的前N位置1即得出该IP地址划分子网的子网掩码。

      将B类地址的子网掩码255.255.0.0的主机地址前5位置 1,得到 255.255.248.0

      2)利用主机数来计算

      如欲将B类IP地址168.195.0.0划分成若干子网,每个子网内有主机700台:

      (1) 将主机数目转化为二进制来表示;

      700=1010111100;

      (2) 如果主机数小于或等于254(注意去掉保留的两个IP地址),则取得该主机的二进制位数,为N,这里肯定 N<8。如果大于254,则 N>8,这就是说主机地址将占据不止8位;

      该二进制为十位数,N=10;

      (3) 使用255.255.255.255来将该类IP地址的主机地址位数全部置1,然后从后向前的将N位全部置为 0,即为子网掩码值。

      将该B类地址的子网掩码255.255.0.0的主机地址全部置1,得到255.255.255.255,然后再从后向前将后 10位置0,即为:11111111.11111111.11111100.00000000,即255.255.252.0。这就是该欲划分成主机为700台的B类IP地址 168.195.0.0的子网掩码。

      3)还有一种题型,要你根据每个网络的主机数量进行子网地址的规划和计算子网掩码。这也可按上述原则进行计算。

      比如一个子网有10台主机,那么对于这个子网需要的IP地址是:

      10+1+1+1=13

      注意:加的第一个1是指这个网络连接时所需的网关地址,接着的两个1分别是指网络地址和广播地址。

      因为13小于16(16等于2的4次方),所以主机位为4位。而256-16=240,所以该子网掩码为255.255.255.240。

      如果一个子网有14台主机,不少人常犯的错误是:依然分配具有16个地址空间的子网,而忘记了给网关分配地址。这样就错误了,因为14+1+1+1=1717大于16,所以我们只能分配具有32个地址(32等于2的5次方)空间的子网。这时子网掩码为:255.255.255.224。

    5. ARP/RARP协议

      地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。地址解析协议是建立在网络中各个主机互相信任的基础上的,网络上的主机可以自主发送ARP应答消息,其他主机收到应答报文时不会检测该报文的真实性就会将其记入本机ARP缓存;由此攻击者就可以向某一主机发送伪ARP应答报文,使其发送的信息无法到达预期的主机或到达错误的主机,这就构成了一个ARP欺骗。ARP命令可用于查询本机ARP缓存中IP地址和MAC地址的对应关系、添加或删除静态对应关系等。

      ARP工作流程举例:

      主机A的IP地址为192.168.1.1,MAC地址为0A-11-22-33-44-01;
      主机B的IP地址为192.168.1.2,MAC地址为0A-11-22-33-44-02;
      当主机A要与主机B通信时,地址解析协议可以将主机B的IP地址(192.168.1.2)解析成主机B的MAC地址,以下为工作流程:
      (1)根据主机A上的路由表内容,IP确定用于访问主机B的转发IP地址是192.168.1.2。然后A主机在自己的本地ARP缓存中检查主机B的匹配MAC地址。
      (2)如果主机A在ARP缓存中没有找到映射,它将询问192.168.1.2的硬件地址,从而将ARP请求帧广播到本地网络上的所有主机。源主机A的IP地址和MAC地址都包括在ARP请求中。本地网络上的每台主机都接收到ARP请求并且检查是否与自己的IP地址匹配。如果主机发现请求的IP地址与自己的IP地址不匹配,它将丢弃ARP请求。
      (3)主机B确定ARP请求中的IP地址与自己的IP地址匹配,则将主机A的IP地址和MAC地址映射添加到本地ARP缓存中。
      (4)主机B将包含其MAC地址的ARP回复消息直接发送回主机A。
      (5)当主机A收到从主机B发来的ARP回复消息时,会用主机B的IP和MAC地址映射更新ARP缓存。本机缓存是有生存期的,生存期结束后,将再次重复上面的过程。主机B的MAC地址一旦确定,主机A就能向主机B发送IP通信了。

      逆地址解析协议,即RARP,功能和ARP协议相对,其将局域网中某个主机的物理地址转换为IP地址,比如局域网中有一台主机只知道物理地址而不知道IP地址,那么可以通过RARP协议发出征求自身IP地址的广播请求,然后由RARP服务器负责回答。

      RARP协议工作流程:

      (1)给主机发送一个本地的RARP广播,在此广播包中,声明自己的MAC地址并且请求任何收到此请求的RARP服务器分配一个IP地址;

      (2)本地网段上的RARP服务器收到此请求后,检查其RARP列表,查找该MAC地址对应的IP地址;

      (3)如果存在,RARP服务器就给源主机发送一个响应数据包并将此IP地址提供给对方主机使用;
      (4)如果不存在,RARP服务器对此不做任何的响应;
      (5)源主机收到从RARP服务器的响应信息,就利用得到的IP地址进行通讯;如果一直没有收到RARP服务器的响应信息,表示初始化失败。

    6. 路由选择协议

      常见的路由选择协议有:RIP协议、OSPF协议。

      RIP协议 :底层是贝尔曼福特算法,它选择路由的度量标准(metric)是跳数,最大跳数是15跳,如果大于15跳,它就会丢弃数据包。

      OSPF协议 :Open Shortest Path First开放式最短路径优先,底层是迪杰斯特拉算法,是链路状态路由选择协议,它选择路由的度量标准是带宽,延迟。

    7. TCP/IP协议

      TCP/IP协议是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台联网设备规定一个地址。
      IP层接收由更低层(网络接口层例如以太网设备驱动程序)发来的数据包,并把该数据包发送到更高层---TCP或UDP层;相反,IP层也把从TCP或UDP层接收来的数据包传送到更低层。IP数据包是不可靠的,因为IP并没有做任何事情来确认数据包是否按顺序发送的或者有没有被破坏,IP数据包中含有发送它的主机的地址(源地址)和接收它的主机的地址(目的地址)。

       TCP是面向连接的通信协议,通过三次握手建立连接,通讯完成时要拆除连接,由于TCP是面向连接的所以只能用于端到端的通讯。TCP提供的是一种可靠的数据流服务,采用“带重传的肯定确认”技术来实现传输的可靠性。TCP还采用一种称为“滑动窗口”的方式进行流量控制,所谓窗口实际表示接收能力,用以限制发送方的发送速度。

      TCP报文首部格式:

      TCP协议的三次握手和四次挥手:

     

      注:seq:"sequance"序列号;ack:"acknowledge"确认号;SYN:"synchronize"请求同步标志;;ACK:"acknowledge"确认标志";FIN:"Finally"结束标志。

      TCP连接建立过程:首先Client端发送连接请求报文,Server段接受连接后回复ACK报文,并为这次连接分配资源。Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源,这样TCP连接就建立了。

      TCP连接断开过程:假设Client端发起中断连接请求,也就是发送FIN报文。Server端接到FIN报文后,意思是说"我Client端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK,"告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息"。这个时候Client端就进入FIN_WAIT状态,继续等待Server端的FIN报文。当Server端确定数据已发送完成,则向Client端发送FIN报文,"告诉Client端,好了,我这边数据发完了,准备好关闭连接了"。Client端收到FIN报文后,"就知道可以关闭连接了,但是他还是不相信网络,怕Server端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。“,Server端收到ACK后,"就知道可以断开连接了"。Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。Ok,TCP连接就这样关闭了!

      为什么要三次挥手?

      在只有两次“握手”的情形下,假设Client想跟Server建立连接,但是却因为中途连接请求的数据报丢失了,故Client端不得不重新发送一遍;这个时候Server端仅收到一个连接请求,因此可以正常的建立连接。但是,有时候Client端重新发送请求不是因为数据报丢失了,而是有可能数据传输过程因为网络并发量很大在某结点被阻塞了,这种情形下Server端将先后收到2次请求,并持续等待两个Client请求向他发送数据...问题就在这里,Cient端实际上只有一次请求,而Server端却有2个响应,极端的情况可能由于Client端多次重新发送请求数据而导致Server端最后建立了N多个响应在等待,因而造成极大的资源浪费!所以,“三次握手”很有必要!

      为什么要四次挥手?

      试想一下,假如现在你是客户端你想断开跟Server的所有连接该怎么做?第一步,你自己先停止向Server端发送数据,并等待Server的回复。但事情还没有完,虽然你自身不往Server发送数据了,但是因为你们之前已经建立好平等的连接了,所以此时他也有主动权向你发送数据;故Server端还得终止主动向你发送数据,并等待你的确认。其实,说白了就是保证双方的一个合约的完整执行!

      使用TCP的协议:FTP(文件传输协议)、Telnet(远程登录协议)、SMTP(简单邮件传输协议)、POP3(和SMTP相对,用于接收邮件)、HTTP协议等。

    8. UDP协议 

      UDP用户数据报协议,是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送。 UDP通讯时不需要接收方确认,属于不可靠的传输,可能会出现丢包现象,实际应用中要求程序员编程验证。
       UDP与TCP位于同一层,但它不管数据包的顺序、错误或重发。因此,UDP不被应用于那些使用虚电路的面向连接的服务,UDP主要用于那些面向查询---应答的服务,例如NFS。相对于FTP或Telnet,这些服务需要交换的信息量较小。
      每个UDP报文分UDP报头和UDP数据区两部分。报头由四个16位长(2字节)字段组成,分别说明该报文的源端口、目的端口、报文长度以及校验值。UDP报头由4个域组成,其中每个域各占用2个字节,具体如下:
      (1)源端口号;
      (2)目标端口号;
      (3)数据报长度;
      (4)校验值。
      使用UDP协议包括:TFTP(简单文件传输协议)、SNMP(简单网络管理协议)、DNS(域名解析协议) NFS、 BOOTP。
       TCP 与 UDP 的区别:TCP是面向连接的,可靠的字节流服务;UDP是面向无连接的,不可靠的数据报服务。

    9. DNS协议

      DNS是域名系统(DomainNameSystem)的缩写,该系统用于命名组织到域层次结构中的计算机和网络服务,可以简单地理解为将URL转换为IP地址。域名是由圆点分开一串单词或缩写组成的,每一个域名都对应一个惟一的IP地址,在Internet上域名与IP地址之间是一一对应的,DNS就是进行域名解析的服务器。DNS命名用于Internet等TCP/IP网络中,通过用户友好的名称查找计算机和服务。

    10. NAT协议

      NAT网络地址转换(Network Address Translation)属接入广域网(WAN)技术,是一种将私有(保留)地址转化为合法IP地址的转换技术,它被广泛应用于各种类型Internet接入方式和各种类型的网络中。原因很简单,NAT不仅完美地解决了lP地址不足的问题,而且还能够有效地避免来自网络外部的攻击,隐藏并保护网络内部的计算机。

    11. DHCP协议

      DHCP动态主机设置协议(Dynamic Host Configuration Protocol)是一个局域网的网络协议,使用UDP协议工作,主要有两个用途:给内部网络或网络服务供应商自动分配IP地址,给用户或者内部网络管理员作为对所有计算机作中央管理的手段。

    12. HTTP协议

      超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的WWW文件都必须遵守这个标准。

      HTTP 协议包括哪些请求?

      GET:请求读取由URL所标志的信息。

      POST:给服务器添加信息(如注释)。

      PUT:在给定的URL下存储一个文档。

      DELETE:删除给定的URL所标志的资源。

      HTTP 中, POST 与 GET 的区别

      1)Get是从服务器上获取数据,Post是向服务器传送数据。

      2)Get是把参数数据队列加到提交表单的Action属性所指向的URL中,值和表单内各个字段一一对应,在URL中可以看到。

      3)Get传送的数据量小,不能大于2KB;Post传送的数据量较大,一般被默认为不受限制。

      4)根据HTTP规范,GET用于信息获取,而且应该是安全的和幂等的。

      I. 所谓 安全的 意味着该操作用于获取信息而非修改信息。换句话说,GET请求一般不应产生副作用。就是说,它仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会影响资源的状态。

      II. 幂等 的意味着对同一URL的多个请求应该返回同样的结果。

    13. 一个举例

      在浏览器中输入 www.baidu.com  后执行的全部过程

      现在假设如果我们在客户端(客户端)浏览器中输入http://www.baidu.com,而baidu.com为要访问的服务器(服务器),下面详细分析客户端为了访问服务器而执行的一系列关于协议的操作:

      1)客户端浏览器通过DNS解析到www.baidu.com的IP地址220.181.27.48,通过这个IP地址找到客户端到服务器的路径。客户端浏览器发起一个HTTP会话到220.161.27.48,然后通过TCP进行封装数据包,输入到网络层。

      2)在客户端的传输层,把HTTP会话请求分成报文段,添加源和目的端口,如服务器使用80端口监听客户端的请求,客户端由系统随机选择一个端口如5000,与服务器进行交换,服务器把相应的请求返回给客户端的5000端口。然后使用IP层的IP地址查找目的端。

      3)客户端的网络层不用关系应用层或者传输层的东西,主要做的是通过查找路由表确定如何到达服务器,期间可能经过多个路由器,这些都是由路由器来完成的工作,不作过多的描述,无非就是通过查找路由表决定通过那个路径到达服务器。

      4)客户端的链路层,包通过链路层发送到路由器,通过邻居协议查找给定IP地址的MAC地址,然后发送ARP请求查找目的地址,如果得到回应后就可以使用ARP的请求应答交换的IP数据包现在就可以传输了,然后发送IP数据包到达服务器的地址。


    展开全文
  • 计算机网络编程(转载)

    千次阅读 2018-04-18 15:27:57
     1.1计算机网络概述  网络编程的实质就是两个(或多个)设备(例如计算机)之间的数据传输。  按照计算机网络的定义,通过一定的物理设备将处于不同位置的计算机连接起来组成的网络,这个网络中包含的设备有:...

    1.网络编程
      1.1计算机网络概述
      网络编程的实质就是两个(或多个)设备(例如计算机)之间的数据传输。
      按照计算机网络的定义,通过一定的物理设备将处于不同位置的计算机连接起来组成的网络,这个网络中包含的设备有:计算机、路由器、交换机等等。
      其实从软件编程的角度来说,对于物理设备的理解不需要很深刻,就像你打电话时不需要很熟悉通信网络的底层实现是一样的,但是当深入到网络编程的底层时,这些基础知识是必须要补的。
      路由器和交换机组成了核心的计算机网络,计算机只是这个网络上的节点以及控制等,通过光纤、网线等连接将设备连接起来,从而形成了一张巨大的计算机网络。
      网络最主要的优势在于共享:共享设备和数据,现在共享设备最常见的是打印机,一个公司一般一个打印机即可,共享数据就是将大量的数据存储在一组机器中,其它的计算机通过网络访问这些数据,例如网站、银行服务器等等。
      如果需要了解更多的网络硬件基础知识,可以阅读《计算机网络》教材,对于基础进行强化,这个在基础学习阶段不是必须的,但是如果想在网络编程领域有所造诣,则是一个必须的基本功。
      对于网络编程来说,最主要的是计算机和计算机之间的通信,这样首要的问题就是如何找到网络上的计算机呢?这就需要了解IP地址的概念。
      为了能够方便的识别网络上的每个设备,网络中的每个设备都会有一个唯一的数字标识,这个就是IP地址。在计算机网络中,现在命名IP地址的规定是IPv4协议,该协议规定每个IP地址由4个0-255之间的数字组成,例如10.0.120.34。每个接入网络的计算机都拥有唯一的IP地址,这个IP地址可能是固定的,例如网络上各种各样的服务器,也可以是动态的,例如使用ADSL拨号上网的宽带用户,无论以何种方式获得或是否是固定的,每个计算机在联网以后都拥有一个唯一的合法IP地址,就像每个手机号码一样。
      但是由于IP地址不容易记忆,所以为了方便记忆,又创造了另外一个概念——域名(Domain Name),例如sohu.com等。一个IP地址可以对应多个域名,一个域名只能对应一个IP地址。域名的概念可以类比手机中的通讯簿,由于手机号码不方便记忆,所以添加一个姓名标识号码,在实际拨打电话时可以选择该姓名,然后拨打即可。
      在网络中传输的数据,全部是以IP地址作为地址标识,所以在实际传输数据以前需要将域名转换为IP地址,实现这种功能的服务器称之为DNS服务器,也就是通俗的说法叫做域名解析。例如当用户在浏览器输入域名时,浏览器首先请求DNS服务器,将域名转换为IP地址,然后将转换后的IP地址反馈给浏览器,然后再进行实际的数据传输。
      当DNS服务器正常工作时,使用IP地址或域名都可以很方便的找到计算机网络中的某个设备,例如服务器计算机。当DNS不正常工作时,只能通过IP地址访问该设备。所以IP地址的使用要比域名通用一些。
      IP地址和域名很好的解决了在网络中找到一个计算机的问题,但是为了让一个计算机可以同时运行多个网络程序,就引入了另外一个概念——端口(port)。
      在介绍端口的概念以前,首先来看一个例子,一般一个公司前台会有一个电话,每个员工会有一个分机,这样如果需要找到这个员工的话,需要首先拨打前台总机,然后转该分机号即可。这样减少了公司的开销,也方便了每个员工。在该示例中前台总机的电话号码就相当于IP地址,而每个员工的分机号就相当于端口。
      有了端口的概念以后,在同一个计算机中每个程序对应唯一的端口,这样一个计算机上就可以通过端口区分发送给每个端口的数据了,换句话说,也就是一个计算机上可以并发运行多个网络程序,而不会在互相之间产生干扰。
      在硬件上规定,端口的号码必须位于0-65535之间,每个端口唯一的对应一个网络程序,一个网络程序可以使用多个端口。这样一个网络程序运行在一台计算上时,不管是客户端还是服务器,都是至少占用一个端口进行网络通讯。在接收数据时,首先发送给对应的计算机,然后计算机根据端口把数据转发给对应的程序。
      有了IP地址和端口的概念以后,在进行网络通讯交换时,就可以通过IP地址查找到该台计算机,然后通过端口标识这台计算机上的一个唯一的程序。这样就可以进行网络数据的交换了。
      但是,进行网络编程时,只有IP地址和端口的概念还是不够的,下面就介绍一下基础的网络编程相关的软件基础知识。

      1.2网络编程概述
      网络编程中有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输。在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由(路由是指路由器从一个接口上收到数据包,根据数据包的目的地址进行定向并转发到另一个接口地址的过程),由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。
      按照前面的介绍,网络编程就是两个或多个设备之间的数据交换,其实更具体的说,网络编程就是两个或多个程序之间的数据交换,和普通的单机程序相比,网络程序最大的不同就是需要交换数据的程序运行在不同的计算机上,这样就造成了数据交换的复杂。虽然通过IP地址和端口号可以找到网络上运行的一个程序,但是如果需要进行网络编程,则需要了解网络通讯的过程。
      网络通讯基于“请求—响应”模型。在网络通讯中,第一次主动发起通讯的程序被称为客户端(client)程序,简称客户端,而第一次通讯中等待链接的程序被称为服务器端(Server)程序,简称服务器。一旦通讯建立,则客户端和服务器端完全一样,没有本质区别。
      由此,网络编程中的两种程序就分别是客户端和服务器端,例如QQ程序,每个QQ用户安装的都是QQ客户端程序,而QQ服务器端程序则在腾讯公司的机房中,为大量的QQ用户提供服务。这种网络编程的结构被称为客户端/服务器结构,也叫Client/Serverj结构,简称C/S结构。
      使用C/S结构的程序,在开发时需要分别开发客户端和服务器端,这种结构的优势在于客户端是专门开发的,所以根据需要实现各种效果,专业点的说就是表现力丰富,而服务器端也需要专门进行开发。但是这种结构也存在着很多不足,例如通用性差,几乎不能通用,也就是说一种程序的客户端只能和对应的服务器端通讯,而不能和其他服务器端通讯,在实际维护中,也需要维护专门的客户端和服务器端,维护的压力比较大。
      其实在运行很多程序时,没有必要使用专门的客户端,而需要使用通用的客户端,例如浏览器,使用浏览器作为客户端的结构称为浏览器/服务器结构,也叫做Browser/Server结构,简称B/S结构。
      使用B/S结构的程序,在开发时只需要开发服务器端即可,这种优势在于开发压力比较小,不需要维护客户端,但是这种结构也存在这很多不足,例如浏览器的限制比较大,表现力不强,不能进行系统级别的操作等。
      总之C/S结构和B/S结构是现在网络编程中常见的两种结构,B/S结构其实也就是一种特殊的C/S结构。
      另外简单的介绍一下P2P(Point to Point)程序,常见的如BT、电驴等。P2P程序是一种特殊的程序,应该一个P2P程序中既包含客户端程序,也包含服务器端程序,例如BT,使用客户端程序部分连接其它的种子(服务器端),而使用服务器端向其它的BT客户端传输数据。如果这个还不是很清楚,其实P2P程序和手机是一样的,当手机拨打电话时就是使用客户端的作用,而手机处于待机状态时,可以接收到其它用户拨打的电话则起的就是服务器端的功能,只是一般的手机不能同时使用拨打电话和接听电话的功能,而P2P程序实现了该功能。
      最后介绍一下网络编程中最重要的,也是最复杂的概念——协议(protocol)。按照前面的介绍,网络编程就是运行在不同计算机中两个程序之间的数据交换。在实际进行数据交换时,为了让接收端理解该数据,计算机比较笨,什么都不懂的,那么就需要规定该数据的格式,这个数据的格式就是协议
      如果没有理解协议的概念,那么再举一个例子,记得有个电影叫《永不消逝的电波》,讲述的是地下党通过电台发送情报的故事,这里我们不探讨电影的剧情,而只关 心电台发送的数据。在实际发报时,需要首先将需要发送的内容转换为电报编码,然后将电报编码发送出去,而接收端接收的是电报编码,如果需要理解电报的内容 则需要根据密码本翻译出该电报的内容。这里的密码本就规定了一种数据格式,这种对于网络中传输的数据格式在网络编程中就被称作协议。
      那么如何编写协议格式呢?答案是随意。只要按照这种协议格式能够生成唯一的编码,按照该编码可以唯一的解析出发送数据的内容即可。也正因为各个网络程序之间协议格式的不同,所以才导致了客户端程序都是专用的结构。
      在实际的网络编程中,最麻烦的内容不是数据的发送和接受,因为这个功能在几乎所有编程语言中都提供了封装好的API进行调用,最麻烦的内容就是协议的设计及协议的生产和解析,这个才是网络编程最核心的内容。
      1.3网络通讯方式
      在现有的网络中,网络通讯的方式主要有两种:
      1**.TCP**(传输控制协议)方式。
      2**.UDP**(用户数据协议)方式。
      为了方便理解这两种方式,还是先来看个例子。大家使用手机时,向别人传递信息时有两种方式:拨打电话和发送短信。使用拨打电话的方式可以保证该信息传递给别人,因为别人接电话时本身就确认收到了该信息。而发送短信的方式价格低廉,使用方便,但是接受人可能收不到。
      在网络通讯中,TCP方式就类似于拨打电话,使用该种方式进行网络通讯时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据,而UDP方式就类似于发送短信,使用这种方式进行网络通讯时,需要建立专门的虚拟连接,传输也是很可靠,如果发送失败则客户端无法获得。
      这两种传输方式都是实际的网络编程中进行使用,重要的数据一般使用TCP方式进行数据传输,而大量的非核心数据则都通过UDP方式进行传递,在一些程序中甚至结合使用这两种方式进行数据的传递。
      由于TCP需要建立专用的虚拟连接以及确认传输是否正确,所以使用TCP方式的速度稍微慢一些,而且传输时产生的数据量要比UDP稍微一些。
      关于网络编程的基础知识就介绍这么多,如果需要深入了解相关知识请阅读专门的计算机网络书籍,下面开始介绍Java语言中网络编程的相关技术。

      1.3网络编程步骤
      按照前面的基础知识介绍,无论使用TCP方式还是UDP方式进行网络通讯,网络编程都是由客户端和服务器端组成,所以,下面介绍网络编程的步骤时,均以C/S结构为基础进行介绍。
      1.3.1客户端网络编程步骤
      客户端是指网络编程中首先发起连接的程序,客户端一般实现程序界面和基本逻辑实现,在进行实际的客户端编程时,无论客户端复杂还是简单,以及客户端实现的方式,客户端的编程主要由三个步骤实现:
      1.建立网络连接
      客户端网络编程的第一步都是建立网络连接。在建立网络连接时需要指定连接的服务器的IP地址和端口号,建立完成以后,会形成一条虚拟的连接,后续的操作就可以通过该连接实现数据交换了。
      2.交换数据
      连接建立以后,就可以通过这个连接交换数据了,交换数据严格按照请求响应模型进行,由客户端发送一个请求数据到服务器,服务器反馈一个响应数据后给客户端,如果客户端不发送请求则服务器就不响应。
      根据逻辑需要,可以多次交换数据,但是还是必须遵循请求响应模型。
      3.关闭网络连接
      在数据交换完成后,关闭网络连接,释放程序占用的端口、内存等系统资源,结束网络编程。
      最基本的步骤一般都是这三个步骤,在实际实现时,步骤2会出现重复,在进行代码组织时,由于网络编程是比较耗时的操作,所以一般开启专门的现场进行网络通讯。

      1.4服务器端网络编程步骤
      服务器是指网络编程中被等待连接的程序,服务器端一般实现程序的核心逻辑以及数据存储等核心功能。服务器端的编程步骤和客户端不同,是由四个步骤实现,依次是:
      1.监听端口
      服务器端属于被动等待连接,所以服务器端启动以后,不需要发起连接,而只需要监听本地计算机的某个固定端口即可。这个端口就是服务器端开放给客户端的端口,服务器端程序运行的本地计算机的IP地址就是服务器端程序的IP地址。
      2.获得连接
      当客户端连接到服务器端时,服务器端就可以获得一个连接,这个连接包含客户端信息,例如客户端IP地址等,服务器端和客户端通过该连接进行数据交换。
      一般在服务器端编程中,当获得连接时,需要开启专门的线程处理该连接,每个连接都由独立的线程实现。
      3.交换数据
      服务器端通过获得的连接进行数据交换。服务器端的数据交换步骤是首先接收客户端发送过来的数据,然后进行逻辑处理,再把处理以后的结果数据发送给客户端。简单来说,就是先接收再发送,这个和客户端的数据交换顺序不同。
      其实,服务器端获得的连接和客户端的连接是一样的,只是数据交换的步骤不同。当然,服务器端的数据交换也是可以多次进行的。在数据交换完成以后,关闭和客户端的连接。
      4.关闭连接
      当服务器程序关闭时,需要关闭服务器端,通过关闭服务器端使得服务器监听的端口以及占用的内存可以释放出来,实现了连接的关闭。
      其实服务器端编程的模型和呼叫中心的实现是类似的,例如移动的客服电话10086就是典型的呼叫中心,当一个用户拨打10086时,转接给一个专门的客服人员,由该客服实现和该用户的问题解决,当另外一个用户拨打10086时,则转接给另一个客服,实现问题解决,依次类推。
      在服务器端编程时,10086这个电话号码就类似于服务器端的端口号码,每个用户就相当于一个客户端程序,每个客服人员就相当于服务器端启动的专门和客户端连接的线程,每个线程都是独立进行交互的。
      这就是服务器端编程的模型,只是TCP方式是需要建立连接的,对于服务器端的压力比较大,而UDP是不需要建立连接的,对于服务器端的压力比较小罢了
      总之,无论使用任何语言,任何方式进行基础的网络编程,都必须遵循固定的步骤进行操作,在熟悉了这些步骤以后,可以根据需要进行逻辑上的处理,但是还是必须遵循固定的步骤进行。
      其实,基础的网络编程本身不难,也不需要很多的基础网络知识,只是由于编程的基础功能都已经由API实现,而且需要按照固定的步骤进行,所以在入门时有一定的门槛,希望下面的内容能够将你快速的带入网络编程技术的大门。

    2.Java网络编程技术
      和网络编程有关的基本API位于Java.NET包中,该包中包含了基本的网络编程实现,该包是网络编程的基础。该包既包含基本的网络编程类,也包含封装后的专门处理WEB相关的处理类。
      首先来介绍一下基础的网络类-InetAddress类。该类的功能是代表一个IP地址,并且将IP地址和域名相关的操作方法包含在该类的内部。关于该类的使用,下面通过一个基础的代码演示该类的使用。

    import java.net.InetAddress;
    import java.net.UnknownHostException;
    
    public class InetAddressDemo {
        public static void main(String[] args) {
    
            try {
                InetAddress inet1 = InetAddress.getByName("www.163.com");
                System.out.println(inet1);
                InetAddress inet2=InetAddress.getByName("127.0.0.1");
                System.out.println(inet2);
                InetAddress inet3=InetAddress.getLocalHost();
                System.out.println(inet3);
                String host =inet3.getHostName();
                System.out.println("域名:"+host);
                String ip=inet3.getHostAddress();
                System.out.println("IP:"+ip);
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
    }

    在该示例代码中,演示了InetAddress类的基本使用,并使用了该类的几个常用方法,该代码的执行结果是:
    www.163.com/202.201.14.182
    /127.0.0.1
    DESKTOP-HRHF03J/192.168.1.196
    域名:DESKTOP-HRHF03J
    IP:192.168.1.196
    说明:由于该代码中包含一个互联网的网址,所以运行该程序时需要联网,否则将产生异常。
    在后续的使用中,经常包含需要使用InetAddress对象代表IP地址的构造方法,当然,该类的使用补水必须的,也可以使用字符串来代表IP地址。

    3.TCP编程
      在Java语言中,对于TCP方式的网络编程提供了良好的支持,在实际实现时,以java.net.socket类代表客户端连接,以java.net.ServerSocket类作为服务器端连接。在进行网络编程时,底层网络通讯的细节已经实现了比较高的封装,所以在程序员实际编程时,只需要指定IP地址和端口号就可以建立连接了。正是由于这种高度的封装,一方面,简化了Java语言网络编程的难度,另外也使得使用Java语言进行网络编程无法深入到网络的底层,所以使用Java语言进行网络底层系统编程很困难,具体点说,Java语言无法事先底层的网络嗅探以及获得IP包结构等消息。但是由于Java语言的网络编程比较简答,所以还是获得了广泛的使用。
      在使用TCP方式进行网络编程时,需要按照前面介绍的网络编程的步骤进行,下面分别介绍一下在Java语言中客户端和服务器端的实现步骤。在客户端网络编程中,首先需要建立连接,在Java API中以及java.net.socket类的对象代表网络连接,所以建立客户端网络连接,也就是创建Socket类型的对象,该对象代表网络连接,示例如下:
      Socket socket1=new Socket(“192.168.1.103”,10000);
      Socket socket2=new Socket(“www.sohu.com”,80);
      上面的代码中,socket1实现的是连接到IP地址是192.168.1.103的计算机的10000号端口,而socket2实现的是连接到域名是www.sohu.com的计算机的80号端口,至于底层网络如何实现建立连接,对于程序员来说是完全透明的。如果建立连接时,本机网络不通,或服务器端程序未开启,则会抛出异常。
      连接一旦建立,则完成了客户端编程的第一步,紧接着的步骤就是按照“请求-响应”模型进行网络数据交换,在Java语言中,数据传输功能由Java IO实现,也就是说只需要从连接中获得输入流和输出流即可,然后将需要发送的数据写入连接对象的输出流中,在发送完成后从输入流中读取数据即可。示例代码如下:
      OutputStream os=socket1.getOutputStream();
      InputStream is=socket1,getInputStream();
      上面的代码中,分别从socket1这个连接对象获得了输出流和输入流对象,在整个网络编程中,后续的数据交换就变成了IO操作,也就是遵循“请求-响应”模式的规定,先向输出流中写入数据,这些数据会被系统发送出去,然后再从输入流中读取服务器端的反馈信息,这样就完成了一次数据交换工作,当然这个数据交换可以多次进行。
      这里获得的只是最基本的输出流和输入流对象,还可以根据前面学习到的IO知识,使用流的嵌套将这些获得的基本流对象转换成需要的装饰流对象,从而方便数据的操作。
      最后当数据交换完成以后,关闭网络连接,释放网络连接占用的系统端口和内存等资源,完成网络操作,示例代码如下:
      socket1.close();
      这就是最基本的网络编程功能介绍,下面是一个简单的网络客户端程序示例,该程序的作用是向服务器发送一个字符串“Hello”,并将服务器端的反馈显示到控制台,数据交换只进行一次,当数据交换完成以后关闭网络连接,程序结束,实现的代码如下:

    package tcp;
    import java.io.*;
    import java.net.*;
    /**
     * 简单的Socket客户端
     * 功能为:发送字符串“Hello”到服务器端,并打印出服务器端的反馈
     */
    public class SimpleSocketClient {
             public static void main(String[] args) {
                       Socket socket = null;
                       InputStream is = null;
                       OutputStream os = null;
                       //服务器端IP地址
                       String serverIP = "127.0.0.1";
                       //服务器端端口号
                       int port = 10000;
                       //发送内容
                       String data = "Hello";
                       try {
                                //建立连接
                                socket = new Socket(serverIP,port);
                                //发送数据
                                os = socket.getOutputStream();
                                os.write(data.getBytes());
                                //接收数据
                                is = socket.getInputStream();
                                byte[] b = new byte[1024];
                                int n = is.read(b);
                                //输出反馈数据
                                System.out.println("服务器反馈:" + new String(b,0,n));
                       } catch (Exception e) {
                                e.printStackTrace(); //打印异常信息
                       }finally{
                                try {
                                         //关闭流和连接
                                         is.close();
                                         os.close();
                                         socket.close();
                                } catch (Exception e2) {}
                       }
             }
    }

    在该示例代码中建立了一个连接到IP地址为127.0.0.1,端口号为10000的TCP类型的网络连接,然后获得连接的输出流对象,将需要发送的字符串“Hello”转换为波耶特数组写入到输出流中,由系统自动完成将输出流中的数据发送出去,如果需要强制发送,可以调用输出流对象中的flush方法实现。在数据发送出去后,从连接对象的输入流中读取服务器端的反馈信息,读取时可以使用IO中的各种读取方法进行读取,这里使用最简单的方法进行读取。从输入流中读取到的内容就是服务器端的反馈,并将读取到的内容在客户端的控制台进行输出,最后依次关闭打开的流对象和网络连接对象。
      这是一个简单的功能示例,在该示例中演示了TCP类型的网络客户端基本方法的使用,该代码只起演示目的,还无法达到实用的级别。
      如果需要在控制台下面编译和运行该代码,需要首先在控制台下切换到源代码所在的目录,然后依次输入编译和运行命令:

     javac –d . SimpleSocketClient.java
    
     java tcp.SimpleSocketClient
    

    和下面将要介绍的SimpleSocketServer服务器端组合运行时,程序的输出结果为:

     服务器反馈:Hello
    

      介绍完一个简单的客户端编程的示例,下面接着介绍一下TCP类型的服务器端的编写。首先需要说明的是,客户端的步骤和服务器端的编写步骤不同,所以在学习服务器端编程时注意不要和客户端混淆起来。
      在服务器端程序编程中,由于服务器端实现的是被动等待连接,所以服务器端编程的第一个步骤是监听端口,也就是监听是否有客户端连接到达。实现服务器端监听的代码为:
      ServerSocket ss = new ServerSocket(10000);
      该代码实现的功能是监听当前计算机的10000号端口,如果在执行该代码时,10000号端口已经被别的程序占用,那么将抛出异常。否则将实现监听。
      服务器端编程的第二个步骤是获得连接。该步骤的作用是当有客户端连接到达时,建立一个和客户端连接对应的Socket连 接对象,从而释放客户端连接对于服务器端端口的占用。实现功能就像公司的前台一样,当一个客户到达公司时,会告诉前台我找某某某,然后前台就通知某某某, 然后就可以继续接待其它客户了。通过获得连接,使得客户端的连接在服务器端获得了保持,另外使得服务器端的端口释放出来,可以继续等待其它的客户端连接。 实现获得连接的代码是:
       Socket socket = ss.accept();
       该代码实现的功能是获得当前连接到服务器端的客户端连接。需要说明的是accept和前面IO部分介绍的read方法一样,都是一个阻塞方法,也就是当无连接时,该方法将阻塞程序的执行,直到连接到达时才执行该行代码。另外获得的连接会在服务器端的该端口注册,这样以后就可以通过在服务器端的注册信息直接通信,而注册以后服务器端的端口就被释放出来,又可以继续接受其它的连接了。
       连接获得以后,后续的编程就和客户端的网络编程类似了,这里获得的Socket类型的连接就和客户端的网络连接一样了,只是服务器端需要首先读取发送过来的数据,然后进行逻辑处理以后再发送给客户端,也就是交换数据的顺序和客户端交换数据的步骤刚好相反。这部分的内容和客户端很类似,所以就不重复了,如果还不熟悉,可以参看下面的示例代码。
      最后,在服务器端通信完成以后,关闭服务器端连接。实现的代码为:ss.close();
      这就是基本的TCP类型的服务器端编程步骤。下面以一个简单的echo服务实现为例子,介绍综合使用示例。echo的意思就是“回声”,echo服务器端实现的功能就是将客户端发送的内容再原封不动的反馈给客户端。实现的代码如下:

    package tcp;
    
    import java.io.*;
    import java.net.*;
    /**
     * echo服务器
     * 功能:将客户端发送的内容反馈给客户端
     */
    public class SimpleSocketServer {
             public static void main(String[] args) {
                       ServerSocket serverSocket = null;
                       Socket socket = null;
                       OutputStream os = null;
                       InputStream is = null;
                       //监听端口号
                       int port = 10000;
                       try {
                                //建立连接
                                serverSocket = new ServerSocket(port);
                                //获得连接
                                socket = serverSocket.accept();
                                //接收客户端发送内容
                                is = socket.getInputStream();
                                byte[] b = new byte[1024];
                                int n = is.read(b);
                                //输出
                                System.out.println("客户端发送内容为:" + new String(b,0,n));
                                //向客户端发送反馈内容
                                os = socket.getOutputStream();
                                os.write(b, 0, n);
                       } catch (Exception e) {
                                e.printStackTrace();
                       }finally{
                                try{
                                         //关闭流和连接
                                         os.close();
                                         is.close();
                                         socket.close();
                                         serverSocket.close();
                                }catch(Exception e){}
                       }
             }
    }

      在该示例代码中建立了一个监听当前计算机10000号端口的服务器端Socket连接,然后获得客户端发送过来的连接,如果有连接到达时,读取连接中发送过来的内容,并将发送的内容在控制台进行输出,输出完成以后将客户端发送的内容再反馈给客户端。最后关闭流和连接对象,结束程序。
      在控制台下面编译和运行该程序的命令和客户端部分的类似。
      这样,就以一个很简单的示例演示了TCP类型的网络编程在Java语言中的基本实现,这个示例只是演示了网络编程的基本步骤以及各个功能方法的基本使用,只是为网络编程打下了一个基础,下面将就几个问题来深入介绍网络编程深层次的一些知识。
      为了一步一步的掌握网络编程,下面再研究网络编程中的两个基本问题,通过解决这两个问题将对网络编程的认识深入一层。
      1、如何复用Socket连接?
      在前面的示例中,客户端中建立了一次连接,只发送一次数据就关闭了,这就相当于拨打电话时,电话打通了只对话一次就关闭了,其实更加常用的应该是拨通一次电话以后多次对话,这就是复用客户端连接。
      那么如何实现建立一次连接,进行多次数据交换呢?其实很简单,建立连接以后,将数据交换的逻辑写到一个循环中就可以了。这样只要循环不结束则连接就不会被关 闭。按照这种思路,可以改造一下上面的代码,让该程序可以在建立连接一次以后,发送三次数据,当然这里的次数也可以是多次,示例代码如下:

    package tcp;
    import java.io.*;
    import java.net.*;
    /**
     * 复用连接的Socket客户端
     * 功能为:发送字符串“Hello”到服务器端,并打印出服务器端的反馈
     */
    public class MulSocketClient {
             public static void main(String[] args) {
                       Socket socket = null;
                       InputStream is = null;
                       OutputStream os = null;
                       //服务器端IP地址
                       String serverIP = "127.0.0.1";
                       //服务器端端口号
                       int port = 10000;
                       //发送内容
                       String data[] ={"First","Second","Third"};
                       try {
                                //建立连接
                                socket = new Socket(serverIP,port);
                                //初始化流
                                os = socket.getOutputStream();
                                is = socket.getInputStream();
                                byte[] b = new byte[1024];
                                for(int i = 0;i < data.length;i++){
                                         //发送数据
                                         os.write(data[i].getBytes());
                                         //接收数据
                                         int n = is.read(b);
                                         //输出反馈数据
                                         System.out.println("服务器反馈:" + new String(b,0,n));
                                }
                       } catch (Exception e) {
                                e.printStackTrace(); //打印异常信息
                       }finally{
                                try {
                                         //关闭流和连接
                                         is.close();
                                         os.close();
                                         socket.close();
                                } catch (Exception e2) {}
                       }
             }
    }

      该示例程序和前面的代码相比,将数据交换部分的逻辑写在一个for循环的内容,这样就可以建立一次连接,依次将data数组中的数据按照顺序发送给服务器端了。
      如果还是使用前面示例代码中的服务器端程序运行该程序,则该程序的结果是:

    java.net.SocketException: Software caused connection abort: recv failed
    
                                         at java.net.SocketInputStream.socketRead0(Native Method)
    
                                         at java.net.SocketInputStream.read(SocketInputStream.java:129)
    
                                         at java.net.SocketInputStream.read(SocketInputStream.java:90)
    
                                         at tcp.MulSocketClient.main(MulSocketClient.java:30)

    服务器反馈:First

      显然,客户端在实际运行时出现了异常,出现异常的原因是什么呢?如果仔细阅读前面的代码,应该还记得前面示例代码中的服务器端是对话一次数据以后就关闭了连接,如果服务器端程序关闭了,客户端继续发送数据肯定会出现异常,这就是出现该问题的原因。
      按照客户端实现的逻辑,也可以复用服务器端的连接,实现的原理也是将服务器端的数据交换逻辑写在循环中即可,按照该种思路改造以后的服务器端代码为:

    package tcp;
    import java.io.*;
    import java.net.*;
    /**
     * 复用连接的echo服务器
     * 功能:将客户端发送的内容反馈给客户端
     */
    public class MulSocketServer {
             public static void main(String[] args) {
                       ServerSocket serverSocket = null;
                       Socket socket = null;
                       OutputStream os = null;
                       InputStream is = null;
                       //监听端口号
                       int port = 10000;
                       try {
                                //建立连接
                                serverSocket = new ServerSocket(port);
                                System.out.println("服务器已启动:");
                                //获得连接
                                socket = serverSocket.accept();
                                //初始化流
                                is = socket.getInputStream();
                                os = socket.getOutputStream();
                                byte[] b = new byte[1024];
                                for(int i = 0;i < 3;i++){
                                         int n = is.read(b);
                                         //输出
                                         System.out.println("客户端发送内容为:" + new String(b,0,n));
                                         //向客户端发送反馈内容
                                         os.write(b, 0, n);
                                }
                       } catch (Exception e) {
                                e.printStackTrace();
                       }finally{
                                try{
                                         //关闭流和连接
                                         os.close();
                                         is.close();
                                         socket.close();
                                         serverSocket.close();
                                }catch(Exception e){}
                       }
             }
    }

      在该示例代码中,也将数据发送和接收的逻辑写在了一个for循环内部,只是在实现时硬性的将循环次数规定成了3次,这样代码虽然比较简单,但是通用性比较差。
      以该服务器端代码实现为基础运行前面的客户端程序时,客户端的输出为:
    服务器反馈:First
    服务器反馈:Second
    服务器反馈:Third
    服务器端程序的输出结果为:
    服务器已启动:
    客户端发送内容为:First
    客户端发送内容为:Second
    客户端发送内容为:Third
      在该程序中,比较明显的体现出了“请求-响应”模型,也就是在客户端发起连接以后,首先发送字符串“First”给服务器端,服务器端输出客户端发送的内容“First”,然后将客户端发送的内容再反馈给客户端,这样客户端也输出服务器反馈“First”,这样就完成了客户端和服务器端的一次对话,紧接着客户端发送“Second”给服务器端,服务端输出“Second”,然后将“Second”再反馈给客户端,客户端再输出“Second”,从而完成第二次会话,第三次会话的过程和这个一样。在这个过程中,每次都是客户端程序首先发送数据给服务器端,服务器接收数据以后,将结果反馈给客户端,客户端接收到服务器端的反馈,从而完成一次通讯过程。
    在该示例中,虽然解决了多次发送的问题,但是客户端和服务器端的次数控制还不够灵活,如果客户端的次数不固定怎么办呢?是否可以使用某个特殊的字符串,例如quit,表示客户端退出呢,这就涉及到网络协议的内容了,会在后续的网络应用示例部分详细介绍。下面开始介绍另外一个网络编程的突出问题。
    2、如何使服务器端支持多个客户端同时工作?
    前面介绍的服务器端程序,只是实现了概念上的服务器端,离实际的服务器端程序结构距离还很遥远,如果需要让服务器端能够实际使用,那么最需要解决的问题就是——如何支持多个客户端同时工作。
    一个服务器端一般都需要同时为多个客户端提供通讯,如果需要同时支持多个客户端,则必须使用前面介绍的线程的概念。简单来说,也就是当服务器端接收到一个连接时,启动一个专门的线程处理和该客户端的通讯。
    按照这个思路改写的服务端示例程序将由两个部分组成,MulThreadSocketServer类实现服务器端控制,实现接收客户端连接,然后开启专门的逻辑线程处理该连接,LogicThread类实现对于一个客户端连接的逻辑处理,将处理的逻辑放置在该类的run方法中。该示例的代码实现为:

    package tcp;
    
    import java.net.ServerSocket;
    import java.net.Socket;
    /**
     * 支持多客户端的服务器端实现
     */
    public class MulThreadSocketServer {
             public static void main(String[] args) {
                       ServerSocket serverSocket = null;
                       Socket socket = null;
                       //监听端口号
                       int port = 10000;
                       try {
                                //建立连接
                                serverSocket = new ServerSocket(port);
                                System.out.println("服务器已启动:");
                                while(true){
                                         //获得连接
                                         socket = serverSocket.accept();
                                         //启动线程
                                         new LogicThread(socket);
                                }
                       } catch (Exception e) {
                                e.printStackTrace();
                       }finally{
                                try{
                                         //关闭连接
                                         serverSocket.close();
                                }catch(Exception e){}
                       }
             }
    }

      在该示例代码中,实现了一个while形式的死循环,由于accept方法是阻塞方法,所以当客户端连接未到达时,将阻塞该程序的执行,当客户端到达时接收该连接,并启动一个新的LogicThread线程处理该连接,然后按照循环的执行流程,继续等待下一个客户端连接。这样当任何一个客户端连接到达时,都开启一个专门的线程处理,通过多个线程支持多个客户端同时处理。
      下面再看一下LogicThread线程类的源代码实现:

    package tcp;
    
    import java.io.*;
    import java.net.*;
    /**
     * 服务器端逻辑线程
     */
    public class LogicThread extends Thread {
             Socket socket;
             InputStream is;
             OutputStream os;
             public LogicThread(Socket socket){
                       this.socket = socket;
                       start(); //启动线程
             }
    
             public void run(){
                       byte[] b = new byte[1024];
                       try{
                                //初始化流
                                os = socket.getOutputStream();
                                is = socket.getInputStream();
                                for(int i = 0;i < 3;i++){
                                         //读取数据
                                         int n = is.read(b);
                                         //逻辑处理
                                         byte[] response = logic(b,0,n);
                                         //反馈数据
                                         os.write(response);
                                }
                       }catch(Exception e){
                                e.printStackTrace();
                       }finally{
                                close();
                       }
             }
    
             /**
              * 关闭流和连接
              */
             private void close(){
                       try{
                                //关闭流和连接
                                os.close();
                                is.close();
                                socket.close();
                       }catch(Exception e){}
             }
    
             /**
              * 逻辑处理方法,实现echo逻辑
              * @param b 客户端发送数据缓冲区
              * @param off 起始下标
              * @param len 有效数据长度
              * @return
              */
             private byte[] logic(byte[] b,int off,int len){
                       byte[] response = new byte[len];
                       //将有效数据拷贝到数组response中
                       System.arraycopy(b, 0, response, 0, len);
                       return response;
             }
    }

     在该示例代码中,每次使用一个连接对象构造该线程,该连接对象就是该线程需要处理的连接,在线程构造完成以后,该线程就被启动起来了,然后在run方法内部对客户端连接进行处理,数据交换的逻辑和前面的示例代码一致,只是这里将接收到客户端发送过来的数据并进行处理的逻辑封装成了logic方法,按照前面介绍的IO编程的内容,客户端发送过来的内容存储在数组b的起始下标为0,长度为n个中,这些数据是客户端发送过来的有效数据,将有效的数据传递给logic方法,logic方法实现的是echo服务的逻辑,也就是将客户端发送的有效数据形成以后新的response数组,并作为返回值反馈。
    在线程中将logic方法的返回值反馈给客户端,这样就完成了服务器端的逻辑处理模拟,其他的实现和前面的介绍类似,这里就不在重复了。
    这里的示例还只是基础的服务器端实现,在实际的服务器端实现中,由于硬件和端口数的限制,所以不能无限制的创建线程对象,而且频繁的创建线程对象效率也比较低,所以程序中都实现了线程池来提高程序的执行效率。
    这里简单介绍一下线程池的概念,线程池(Thread pool)是池技术的一种,就是在程序启动时首先把需要个数的线程对象创建好,例如创建5000个线程对象,然后当客户端连接到达时从池中取出一个已经创建完成的线程对象使用即可。当客户端连接关闭以后,将该线程对象重新放入到线程池中供其它的客户端重复使用,这样可以提高程序的执行速度,优化程序对于内存的占用等。

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 347,981
精华内容 139,192
关键字:

编程计算机网络