网络编程 订阅
网络编程从大的方面说就是对信息的发送到接收,中间传输为物理线路的作用。网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的。中间最主要的就是数据包的组装,数据包的过滤,数据包的捕获,数据包的分析,当然最后再做一些处理,代码、开发工具、数据库、服务器架设和网页设计这5部分你都要接触。 展开全文
网络编程从大的方面说就是对信息的发送到接收,中间传输为物理线路的作用。网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的。中间最主要的就是数据包的组装,数据包的过滤,数据包的捕获,数据包的分析,当然最后再做一些处理,代码、开发工具、数据库、服务器架设和网页设计这5部分你都要接触。
信息
性    质
编程方式
外文名
WINSOCK; LINUX
代码分类
静态代码、动态代码等
含    义
使用套接字来达到进程间通信
中文名
网络编程
网络编程代码分类
静态代码是服务器不解析直接发送给客户端的部分,用做布局效果,一般不用于数据库操作静态代码分html,javascript,css等,其中 [1]  html语言是基础,要学网络编程就先学html语言.javascript用于实现某些特效,css是样式语言.这3个语言组合起来,可以设计出美妙的网页效果动态代码是服务器需要解析的部分,用作数据库连接操作等.有php,jsp,asp这几种语言你只用先学一种就可.如果是想快速入门,建议先学asp,如果想学了找工作等,建议学php或jsp,开发工具有很多种,我推荐一种,网络3剑客,其中dw是开发代码的,fw是做图的.flash是做动画的.数据库要结合你学的动态语言来选择,asp系列的,你可以使用access,大型点使用mySQL.php和mySQL是很好的搭档.服务器架设也是结合你学的动态语言的,windows下安装iis很方便,iis可以运行asp,安装.net框架后能运行,这两者架设相对简单,也是我推荐你入门学asp的原因.php一般安装apache服务器,jsp一般安装tomcat服务器.只有架设好服务器,才能浏览动态语言编写的程序.虽然是编程,但是总会涉及到网页设计部分,还是要去学学怎么简单的作图和动画。
收起全文
精华内容
下载资源
问答
  • Java 网络编程

    万次阅读 多人点赞 2019-05-30 23:07:18
    网络编程 一、网络编程基础概念 首先理清一个概念:网络编程不等于网站编程,网络编程即使用套接字来达到进程间通信,现在一般称为TCP/IP编程。 计算机网络: 把分布在不同地理区域的计算机与专门的外部设备用通信...

    首先理清一个概念:网络编程不等于网站编程,网络编程即使用套接字来达到进程间通信,现在一般称为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();
        }
    }
    

    小结

    在这里插入图片描述

    展开全文
  • 网络编程面试题(2020最新版)

    万次阅读 多人点赞 2020-03-16 17:36:28
    文章目录计算机网络体系结构网络协议是什么?为什么要对网络协议分层?TCP/IP 协议族应用层运输层网络层数据链路层物理层TCP/IP 协议族TCP的三次握手四次挥手TCP报文的头部结构三次握手四次挥手常见面试题为什么TCP...

    Java面试总结(2021优化版)已发布在个人微信公众号【技术人成长之路】,优化版首先修正了读者反馈的部分答案存在的错误,同时根据最新面试总结,删除了低频问题,添加了一些常见面试题,对文章进行了精简优化,欢迎大家关注!😊😊

    【技术人成长之路】,助力技术人成长!更多精彩文章第一时间在公众号发布哦!

    计算机网络体系结构

    在计算机网络的基本概念中,分层次的体系结构是最基本的。计算机网络体系结构的抽象概念较多,在学习时要多思考。这些概念对后面的学习很有帮助。

    网络协议是什么?

    在计算机网络要做到有条不紊地交换数据,就必须遵守一些事先约定好的规则,比如交换数据的格式、是否需要发送一个应答信息。这些规则被称为网络协议。

    为什么要对网络协议分层?

    • 简化问题难度和复杂度。由于各层之间独立,我们可以分割大问题为小问题。
    • 灵活性好。当其中一层的技术变化时,只要层间接口关系保持不变,其他层不受影响。
    • 易于实现和维护。
    • 促进标准化工作。分开后,每层功能可以相对简单地被描述。

    网络协议分层的缺点: 功能可能出现在多个层里,产生了额外开销。

    为了使不同体系结构的计算机网络都能互联,国际标准化组织 ISO 于1977年提出了一个试图使各种计算机在世界范围内互联成网的标准框架,即著名的开放系统互联基本参考模型 OSI/RM,简称为OSI。

    OSI 的七层协议体系结构的概念清楚,理论也较完整,但它既复杂又不实用,TCP/IP 体系结构则不同,但它现在却得到了非常广泛的应用。TCP/IP 是一个四层体系结构,它包含应用层,运输层,网际层和网络接口层(用网际层这个名字是强调这一层是为了解决不同网络的互连问题),不过从实质上讲,TCP/IP 只有最上面的三层,因为最下面的网络接口层并没有什么具体内容,因此在学习计算机网络的原理时往往采用折中的办法,即综合 OSI 和 TCP/IP 的优点,采用一种只有五层协议的体系结构,这样既简洁又能将概念阐述清楚,有时为了方便,也可把最底下两层称为网络接口层。

    四层协议,五层协议和七层协议的关系如下:

    • TCP/IP是一个四层的体系结构,主要包括:应用层、运输层、网际层和网络接口层。
    • 五层协议的体系结构主要包括:应用层、运输层、网络层,数据链路层和物理层。
    • OSI七层协议模型主要包括是:应用层(Application)、表示层(Presentation)、会话层(Session)、运输层(Transport)、网络层(Network)、数据链路层(Data Link)、物理层(Physical)。

    在这里插入图片描述

    注:五层协议的体系结构只是为了介绍网络原理而设计的,实际应用还是 TCP/IP 四层体系结构。

    TCP/IP 协议族

    应用层

    应用层( application-layer )的任务是通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程(进程:主机中正在运行的程序)间的通信和交互的规则。

    对于不同的网络应用需要不同的应用层协议。在互联网中应用层协议很多,如域名系统 DNS,支持万维网应用的 HTTP 协议,支持电子邮件的 SMTP 协议等等。

    运输层

    运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务。应用进程利用该服务传送应用层报文。

    运输层主要使用一下两种协议

    1. 传输控制协议-TCP:提供面向连接的,可靠的数据传输服务。
    2. 用户数据协议-UDP:提供无连接的,尽最大努力的数据传输服务(不保证数据传输的可靠性)。
    UDPTCP
    是否连接无连接面向连接
    是否可靠不可靠传输,不使用流量控制和拥塞控制可靠传输,使用流量控制和拥塞控制
    连接对象个数支持一对一,一对多,多对一和多对多交互通信只能是一对一通信
    传输方式面向报文面向字节流
    首部开销首部开销小,仅8字节首部最小20字节,最大60字节
    场景适用于实时应用(IP电话、视频会议、直播等)适用于要求可靠传输的应用,例如文件传输

    每一个应用层(TCP/IP参考模型的最高层)协议一般都会使用到两个传输层协议之一:

    运行在TCP协议上的协议:

    • HTTP(Hypertext Transfer Protocol,超文本传输协议),主要用于普通浏览。
    • HTTPS(HTTP over SSL,安全超文本传输协议),HTTP协议的安全版本。
    • FTP(File Transfer Protocol,文件传输协议),用于文件传输。
    • POP3(Post Office Protocol, version 3,邮局协议),收邮件用。
    • SMTP(Simple Mail Transfer Protocol,简单邮件传输协议),用来发送电子邮件。
    • TELNET(Teletype over the Network,网络电传),通过一个终端(terminal)登陆到网络。
    • SSH(Secure Shell,用于替代安全性差的TELNET),用于加密安全登陆用。

    运行在UDP协议上的协议:

    • BOOTP(Boot Protocol,启动协议),应用于无盘设备。
    • NTP(Network Time Protocol,网络时间协议),用于网络同步。
    • DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),动态配置IP地址。

    运行在TCPUDP协议上:

    • DNS(Domain Name Service,域名服务),用于完成地址查找,邮件转发等工作。

    网络层

    网络层的任务就是选择合适的网间路由和交换结点,确保计算机通信的数据及时传送。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 IP 协议,因此分组也叫 IP 数据报 ,简称数据报。

    互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Intert Prococol)和许多路由选择协议,因此互联网的网络层也叫做网际层或 IP 层。

    数据链路层

    数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。

    在两个相邻节点之间传送数据时,数据链路层将网络层交下来的 IP 数据报组装成帧,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。

    在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。

    一般的web应用的通信传输流是这样的:

    img

    发送端在层与层之间传输数据时,每经过一层时会被打上一个该层所属的首部信息。反之,接收端在层与层之间传输数据时,每经过一层时会把对应的首部信息去除。

    物理层

    在物理层上所传送的数据单位是比特。 物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。

    TCP/IP 协议族

    在互联网使用的各种协议中最重要和最著名的就是 TCP/IP 两个协议。现在人们经常提到的 TCP/IP 并不一定是单指 TCP 和 IP 这两个具体的协议,而往往是表示互联网所使用的整个 TCP/IP 协议族。

    img

    互联网协议套件(英语:Internet Protocol Suite,缩写IPS)是一个网络通讯模型,以及一整个网络传输协议家族,为网际网络的基础通讯架构。它常被通称为TCP/IP协议族(英语:TCP/IP Protocol Suite,或TCP/IP Protocols),简称TCP/IP。因为该协定家族的两个核心协定:TCP(传输控制协议)和IP(网际协议),为该家族中最早通过的标准。

    划重点:

    TCP(传输控制协议)和IP(网际协议) 是最先定义的两个核心协议,所以才统称为TCP/IP协议族

    TCP的三次握手四次挥手

    TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,在发送数据前,通信双方必须在彼此间建立一条连接。所谓的“连接”,其实是客户端和服务端保存的一份关于对方的信息,如ip地址、端口号等。

    TCP可以看成是一种字节流,它会处理IP层或以下的层的丢包、重复以及错误问题。在连接的建立过程中,双方需要交换一些连接的参数。这些参数可以放在TCP头部。

    一个TCP连接由一个4元组构成,分别是两个IP地址和两个端口号。一个TCP连接通常分为三个阶段:连接、数据传输、退出(关闭)。通过三次握手建立一个链接,通过四次挥手来关闭一个连接

    当一个连接被建立或被终止时,交换的报文段只包含TCP头部,而没有数据

    TCP报文的头部结构

    在了解TCP连接之前先来了解一下TCP报文的头部结构。

    TCPHeader.png

    上图中有几个字段需要重点介绍下:

    (1)序号:seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。

    (2)确认序号:ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,ack=seq+1。

    (3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:

    • ACK:确认序号有效。
    • FIN:释放一个连接。
    • PSH:接收方应该尽快将这个报文交给应用层。
    • RST:重置连接。
    • SYN:发起一个新连接。
    • URG:紧急指针(urgent pointer)有效。

    需要注意的是:

    • 不要将确认序号ack与标志位中的ACK搞混了。
    • 确认方ack=发起方seq+1,两端配对。

    三次握手

    三次握手的本质是确认通信双方收发数据的能力

    首先,我让信使运输一份信件给对方,对方收到了,那么他就知道了我的发件能力和他的收件能力是可以的

    于是他给我回信,我若收到了,我便知我的发件能力和他的收件能力是可以的,并且他的发件能力和我的收件能力是可以

    然而此时他还不知道他的发件能力和我的收件能力到底可不可以,于是我最后回馈一次,他若收到了,他便清楚了他的发件能力和我的收件能力是可以的

    这,就是三次握手,这样说,你理解了吗?

    三次握手.png

    • 第一次握手:客户端要向服务端发起连接请求,首先客户端随机生成一个起始序列号ISN(比如是100),那客户端向服务端发送的报文段包含SYN标志位(也就是SYN=1),序列号seq=100。
    • 第二次握手:服务端收到客户端发过来的报文后,发现SYN=1,知道这是一个连接请求,于是将客户端的起始序列号100存起来,并且随机生成一个服务端的起始序列号(比如是300)。然后给客户端回复一段报文,回复报文包含SYN和ACK标志(也就是SYN=1,ACK=1)、序列号seq=300、确认号ack=101(客户端发过来的序列号+1)。
    • 第三次握手:客户端收到服务端的回复后发现ACK=1并且ack=101,于是知道服务端已经收到了序列号为100的那段报文;同时发现SYN=1,知道了服务端同意了这次连接,于是就将服务端的序列号300给存下来。然后客户端再回复一段报文给服务端,报文包含ACK标志位(ACK=1)、ack=301(服务端序列号+1)、seq=101(第一次握手时发送报文是占据一个序列号的,所以这次seq就从101开始,需要注意的是不携带数据的ACK报文是不占据序列号的,所以后面第一次正式发送数据时seq还是101)。当服务端收到报文后发现ACK=1并且ack=301,就知道客户端收到序列号为300的报文了,就这样客户端和服务端通过TCP建立了连接。

    四次挥手

    四次挥手的目的是关闭一个连接

    四次挥手.jpeg

    比如客户端初始化的序列号ISA=100,服务端初始化的序列号ISA=300。TCP连接成功后客户端总共发送了1000个字节的数据,服务端在客户端发FIN报文前总共回复了2000个字节的数据。

    • 第一次挥手:当客户端的数据都传输完成后,客户端向服务端发出连接释放报文(当然数据没发完时也可以发送连接释放报文并停止发送数据),释放连接报文包含FIN标志位(FIN=1)、序列号seq=1101(100+1+1000,其中的1是建立连接时占的一个序列号)。需要注意的是客户端发出FIN报文段后只是不能发数据了,但是还可以正常收数据;另外FIN报文段即使不携带数据也要占据一个序列号。
    • 第二次挥手:服务端收到客户端发的FIN报文后给客户端回复确认报文,确认报文包含ACK标志位(ACK=1)、确认号ack=1102(客户端FIN报文序列号1101+1)、序列号seq=2300(300+2000)。此时服务端处于关闭等待状态,而不是立马给客户端发FIN报文,这个状态还要持续一段时间,因为服务端可能还有数据没发完。
    • 第三次挥手:服务端将最后数据(比如50个字节)发送完毕后就向客户端发出连接释放报文,报文包含FIN和ACK标志位(FIN=1,ACK=1)、确认号和第二次挥手一样ack=1102、序列号seq=2350(2300+50)。
    • 第四次挥手:客户端收到服务端发的FIN报文后,向服务端发出确认报文,确认报文包含ACK标志位(ACK=1)、确认号ack=2351、序列号seq=1102。注意客户端发出确认报文后不是立马释放TCP连接,而是要经过2MSL(最长报文段寿命的2倍时长)后才释放TCP连接。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接,所以服务端结束TCP连接的时间要比客户端早一些。

    常见面试题

    为什么TCP连接的时候是3次?2次不可以吗?

    因为需要考虑连接时丢包的问题,如果只握手2次,第二次握手时如果服务端发给客户端的确认报文段丢失,此时服务端已经准备好了收发数(可以理解服务端已经连接成功)据,而客户端一直没收到服务端的确认报文,所以客户端就不知道服务端是否已经准备好了(可以理解为客户端未连接成功),这种情况下客户端不会给服务端发数据,也会忽略服务端发过来的数据。

    如果是三次握手,即便发生丢包也不会有问题,比如如果第三次握手客户端发的确认ack报文丢失,服务端在一段时间内没有收到确认ack报文的话就会重新进行第二次握手,也就是服务端会重发SYN报文段,客户端收到重发的报文段后会再次给服务端发送确认ack报文。

    为什么TCP连接的时候是3次,关闭的时候却是4次?

    因为只有在客户端和服务端都没有数据要发送的时候才能断开TCP。而客户端发出FIN报文时只能保证客户端没有数据发了,服务端还有没有数据发客户端是不知道的。而服务端收到客户端的FIN报文后只能先回复客户端一个确认报文来告诉客户端我服务端已经收到你的FIN报文了,但我服务端还有一些数据没发完,等这些数据发完了服务端才能给客户端发FIN报文(所以不能一次性将确认报文和FIN报文发给客户端,就是这里多出来了一次)。

    为什么客户端发出第四次挥手的确认报文后要等2MSL的时间才能释放TCP连接?

    这里同样是要考虑丢包的问题,如果第四次挥手的报文丢失,服务端没收到确认ack报文就会重发第三次挥手的报文,这样报文一去一回最长时间就是2MSL,所以需要等这么长时间来确认服务端确实已经收到了。

    如果已经建立了连接,但是客户端突然出现故障了怎么办?

    TCP设有一个保活计时器,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

    什么是HTTP,HTTP 与 HTTPS 的区别

    HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范

    区别HTTPHTTPS
    协议运行在 TCP 之上,明文传输,客户端与服务器端都无法验证对方的身份身披 SSL( Secure Socket Layer )外壳的 HTTP,运行于 SSL 上,SSL 运行于 TCP 之上, 是添加了加密和认证机制的 HTTP
    端口80443
    资源消耗较少由于加解密处理,会消耗更多的 CPU 和内存资源
    开销无需证书需要证书,而证书一般需要向认证机构购买
    加密机制共享密钥加密和公开密钥加密并用的混合加密机制
    安全性由于加密机制,安全性强

    常用HTTP状态码

    HTTP状态码表示客户端HTTP请求的返回结果、标识服务器处理是否正常、表明请求出现的错误等。

    状态码的类别:

    类别原因短语
    1XXInformational(信息性状态码) 接受的请求正在处理
    2XXSuccess(成功状态码) 请求正常处理完毕
    3XXRedirection(重定向状态码) 需要进行附加操作以完成请求
    4XXClient Error(客户端错误状态码) 服务器无法处理请求
    5XXServer Error(服务器错误状态码) 服务器处理请求出错

    常用HTTP状态码:

    2XX成功(这系列表明请求被正常处理了)
    200OK,表示从客户端发来的请求在服务器端被正确处理
    204No content,表示请求成功,但响应报文不含实体的主体部分
    206Partial Content,进行范围请求成功
    3XX重定向(表明浏览器要执行特殊处理)
    301moved permanently,永久性重定向,表示资源已被分配了新的 URL
    302found,临时性重定向,表示资源临时被分配了新的 URL
    303see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源(对于301/302/303响应,几乎所有浏览器都会删除报文主体并自动用GET重新请求)
    304not modified,表示服务器允许访问资源,但请求未满足条件的情况(与重定向无关)
    307temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求
    4XX客户端错误
    400bad request,请求报文存在语法错误
    401unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息
    403forbidden,表示对请求资源的访问被服务器拒绝,可在实体主体部分返回原因描述
    404not found,表示在服务器上没有找到请求的资源
    5XX服务器错误
    500internal sever error,表示服务器端在执行请求时发生了错误
    501Not Implemented,表示服务器不支持当前请求所需要的某个功能
    503service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求

    GET和POST区别

    说道GET和POST,就不得不提HTTP协议,因为浏览器和服务器的交互是通过HTTP协议执行的,而GET和POST也是HTTP协议中的两种方法。

    HTTP全称为Hyper Text Transfer Protocol,中文翻译为超文本传输协议,目的是保证浏览器与服务器之间的通信。HTTP的工作方式是客户端与服务器之间的请求-应答协议。

    HTTP协议中定义了浏览器和服务器进行交互的不同方法,基本方法有4种,分别是GET,POST,PUT,DELETE。这四种方法可以理解为,对服务器资源的查,改,增,删。

    • GET:从服务器上获取数据,也就是所谓的查,仅仅是获取服务器资源,不进行修改。
    • POST:向服务器提交数据,这就涉及到了数据的更新,也就是更改服务器的数据。
    • PUT:英文含义是放置,也就是向服务器新添加数据,就是所谓的增。
    • DELETE:从字面意思也能看出,这种方式就是删除服务器数据的过程。

    GET和POST区别

    1. Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。 但是这种做法也不时绝对的,大部分人的做法也是按照上面的说法来的,但是也可以在get请求加上 request body,给 post请求带上 URL 参数。

    2. Get请求提交的url中的数据最多只能是2048字节,这个限制是浏览器或者服务器给添加的,http协议并没有对url长度进行限制,目的是为了保证服务器和浏览器能够正常运行,防止有人恶意发送请求。Post请求则没有大小限制。

    3. Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。

    4. Get执行效率却比Post方法好。Get是form提交的默认方法。

    5. GET产生一个TCP数据包;POST产生两个TCP数据包。

      对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);

      而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

    什么是对称加密与非对称加密

    对称密钥加密是指加密和解密使用同一个密钥的方式,这种方式存在的最大问题就是密钥发送问题,即如何安全地将密钥发给对方;

    而非对称加密是指使用一对非对称密钥,即公钥和私钥,公钥可以随意发布,但私钥只有自己知道。发送密文的一方使用对方的公钥进行加密处理,对方接收到加密信息后,使用自己的私钥进行解密。
    由于非对称加密的方式不需要发送用来解密的私钥,所以可以保证安全性;但是和对称加密比起来,非常的慢

    什么是HTTP2

    HTTP2 可以提高了网页的性能。

    在 HTTP1 中浏览器限制了同一个域名下的请求数量(Chrome 下一般是六个),当在请求很多资源的时候,由于队头阻塞当浏览器达到最大请求数量时,剩余的资源需等待当前的六个请求完成后才能发起请求。

    HTTP2 中引入了多路复用的技术,这个技术可以只通过一个 TCP 连接就可以传输所有的请求数据。多路复用可以绕过浏览器限制同一个域名下的请求数量的问题,进而提高了网页的性能。

    Session、Cookie和Token的主要区别

    HTTP协议本身是无状态的。什么是无状态呢,即服务器无法判断用户身份。

    什么是cookie

    cookie是由Web服务器保存在用户浏览器上的小文件(key-value格式),包含用户相关的信息。客户端向服务器发起请求,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户身份。

    什么是session

    session是依赖Cookie实现的。session是服务器端对象

    session 是浏览器和服务器会话过程中,服务器分配的一块储存空间。服务器默认为浏览器在cookie中设置 sessionid,浏览器在向服务器请求过程中传输 cookie 包含 sessionid ,服务器根据 sessionid 获取出会话中存储的信息,然后确定会话的身份信息。

    cookie与session区别

    • 存储位置与安全性:cookie数据存放在客户端上,安全性较差,session数据放在服务器上,安全性相对更高;
    • 存储空间:单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,session无此限制
    • 占用服务器资源:session一定时间内保存在服务器上,当访问增多,占用服务器性能,考虑到服务器性能方面,应当使用cookie。

    什么是Token

    Token的引入:Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。

    Token的定义:Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。

    使用Token的目的:Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。

    Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位

    session与token区别

    • session机制存在服务器压力增大,CSRF跨站伪造请求攻击,扩展性不强等问题;
    • session存储在服务器端,token存储在客户端
    • token提供认证和授权功能,作为身份认证,token安全性比session好;
    • session这种会话存储方式方式只适用于客户端代码和服务端代码运行在同一台服务器上,token适用于项目级的前后端分离(前后端代码运行在不同的服务器下)

    Servlet是线程安全的吗

    Servlet不是线程安全的,多线程并发的读写会导致数据不同步的问题。

    解决的办法是尽量不要定义name属性,而是要把name变量分别定义在doGet()和doPost()方法内。虽然使用synchronized(name){}语句块可以解决问题,但是会造成线程的等待,不是很科学的办法。

    注意:多线程的并发的读写Servlet类属性会导致数据不同步。但是如果只是并发地读取属性而不写入,则不存在数据不同步的问题。因此Servlet里的只读属性最好定义为final类型的。

    Servlet接口中有哪些方法及Servlet生命周期探秘

    在Java Web程序中,Servlet主要负责接收用户请求HttpServletRequest,在doGet()doPost()中做相应的处理,并将回应HttpServletResponse反馈给用户。Servlet可以设置初始化参数,供Servlet内部使用。

    Servlet接口定义了5个方法,其中前三个方法与Servlet生命周期相关

    • void init(ServletConfig config) throws ServletException
    • void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException
    • void destory()
    • java.lang.String getServletInfo()
    • ServletConfig getServletConfig()

    生命周期:

    Web容器加载Servlet并将其实例化后,Servlet生命周期开始,容器运行其init()方法进行Servlet的初始化;

    请求到达时调用Servlet的service()方法,service()方法会根据需要调用与请求对应的doGet或doPost等方法;

    当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的destroy()方法

    init方法和destory方法只会执行一次,service方法客户端每次请求Servlet都会执行。Servlet中有时会用到一些需要初始化与销毁的资源,因此可以把初始化资源的代码放入init方法中,销毁资源的代码放入destroy方法中,这样就不需要每次处理客户端的请求都要初始化与销毁资源。

    如果客户端禁止 cookie 能实现 session 还能用吗?

    Cookie 与 Session,一般认为是两个独立的东西,Session采用的是在服务器端保持状态的方案,而Cookie采用的是在客户端保持状态的方案。

    但为什么禁用Cookie就不能得到Session呢?因为Session是用Session ID来确定当前对话所对应的服务器Session,而Session ID是通过Cookie来传递的,禁用Cookie相当于失去了Session ID,也就得不到Session了。

    假定用户关闭Cookie的情况下使用Session,其实现途径有以下几种:

    1. 手动通过URL传值、隐藏表单传递Session ID。
    2. 用文件、数据库等形式保存Session ID,在跨页过程中手动调用。
    展开全文
  • 网络编程

    万次阅读 2017-02-13 15:36:33
    QT网络编程需要在pro文件中添加QT += network 一、QHostInfo 网络信息 //获得本机主机名 QString localHostName = QHostInfo::localHostName(); //通过主机名获取主机信息 QHostInfo hostInfo = QHostInfo::from...

    QT网络编程需要在pro文件中添加QT += network

    一、QHostInfo

    网络信息

    //获得本机主机名
    QString localHostName = QHostInfo::localHostName();
    //通过主机名获取主机信息
    QHostInfo hostInfo = QHostInfo::fromName(localHostName);
    //获取所有的IP地址列表
    QList<QHostAddress> listAddress = hostInfo.addresses();

    二、QNetworkInterface

    一个主机IP地址和网络接口列表

        QString detail="";
        //获取所有的网络硬件接口
        QList<QNetworkInterface> list=QNetworkInterface::allInterfaces();
        for(int i=0;i<list.count();i++)
        {
            QNetworkInterface interface=list.at(i);
            detail=detail+tr("设备:")+interface.name()+"\n";
            detail=detail+tr("硬件地址:")+interface.hardwareAddress()+"\n";
            //每个网络接口包含0个或多个IP地址
            QList<QNetworkAddressEntry> entryList=interface.addressEntries();
            for(int j=0;j<entryList.count();j++)
            {
                QNetworkAddressEntry entry=entryList.at(j);
                detail=detail+"\t"+tr("IP 地址:")+entry.ip().toString()+"\n";
                detail=detail+"\t"+tr("子网掩码:")+entry.netmask().toString()+"\n";
                detail=detail+"\t"+tr("广播地址:")+entry.broadcast().toString()+"\n";
            }
        }
        QMessageBox::information(this,tr("Detail"),detail);

    三、UDP编程

    //server
    QUdpSocket *udpSocket = new QUdpSocket(this);
    //QHostAddress::Broadcast:广播到本机的所有IP,当非本机时,使用接收端主机IP
    udpSocket->writeDatagram(msg.toLatin1(),msg.length(),QHostAddress::Broadcast,5555);
    //client
    QUdpSocket *udpSocket = new QUdpSocket(this);
    //当有数据到来时,发出readyread信号
    connect(udpSocket,SIGNAL(readyRead()),this,SLOT(dataReceived()));
    bool result=udpSocket->bind(5555);  //绑定端口
    
    void UdpClient::dataReceived()
    {
        while(udpSocket->hasPendingDatagrams())
        {
            QByteArray datagram;
            //根据接收的数据大小来重新定义QByteArray的大小
            datagram.resize(udpSocket->pendingDatagramSize());
            //接收数据包
            udpSocket->readDatagram(datagram.data(),datagram.size());
            //获取具体数据
            QString msg=datagram.data();
        }
    }

    四、TCP编程

    //server
    //只是截取了重要代码,不要直接拷贝,要根据自己的程序结构添加到对应的文件
     QList<QTcpSocket *> tcpClientSocketList;
     QTcpSocket * socket;
     QTcpServer *tcpServer = new QTcpServer(this);
     //监听指定端口
     tcpServer->listen(QHostAddress::Any,6666);
     //有新的连接时会发出newConnection信号
     connect(tcpServer,SIGNAL(newConnection()),this,SLOT(acceptConnection()));
    
     void MainWindow::acceptConnection()
    {
        socket = tcpServer->nextPendingConnection();
        //将新连接的Socket放到List中进行管理
        tcpClientSocketList.append(socket);
        connect(socket,SIGNAL(readyread()),this , SLOT(dataReceive()));
    }
    
    void MainWindow::dataReceive()
    {
        QString datas = tcpSocket->readAll();
        ui->lineEdit->setText(datas);
    }
    //client
    QTcpSocket * tcpSocket ;
    this->tcpSocket = new QTcpSocket(this);
    //连接到服务器
    tcpSocket->connectToHost("127.0.0.1",6666);
    connect(tcpSocket,SIGNAL(connected()),this,SLOT(slotConnected()));
    connect(tcpSocket,SIGNAL(disconnected()),this,SLOT(slotDisconnected()));
    connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(dataReceived()));
    //连接到服务器后处理函数
    void TcpClient::slotConnected()
    {
    
    }
    //发送数据
    void TcpClient::slotSend()
    {
        if(sendLineEdit->text()=="")
        {
            return ;
        }
    
        QString msg=userName+":"+sendLineEdit->text();
    
        tcpSocket->write(msg.toLatin1(),msg.length());
        sendLineEdit->clear();
    }
    //断开连接
    void TcpClient::slotDisconnected()
    {
    
    }
    //接收数据
    void TcpClient::dataReceived()
    {
        while(tcpSocket->bytesAvailable()>0)
        {
            QByteArray datagram;
            datagram.resize(tcpSocket->bytesAvailable());
    
            tcpSocket->read(datagram.data(),datagram.size());
    
            QString msg=datagram.data();
            contentListWidget->addItem(msg.left(datagram.size()));
        }
    }
    展开全文
  • 【带你入门】java网络编程

    万次阅读 多人点赞 2018-02-18 12:10:41
    网络编程网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习。在 学习网络编程以前,很多初学者可能觉得网络编程是...

    网络编程

    网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习。

    在 学习网络编程以前,很多初学者可能觉得网络编程是比较复杂的系统工程,需要了解很多和网络相关的基础知识,其实这些都不是很必需的。首先来问一个问题:你 会打手机吗?很多人可能说肯定会啊,不就是按按电话号码,拨打电话嘛,很简单的事情啊!其实初学者如果入门网络编程的话也可以做到这么简单!

    网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据。程序员所作的事情就是把数据发送到指定的位置,或者接收到指定的数据,这个就是狭义的网络编程范畴。在发送和接收数据时,大部分的程序设计语言都设计了专门的API实现这些功能,程序员只需要调用即可。所以,基础的网络编程可以和打电话一样简单。

    下面就开始Java语言的网络编程技术学习吧。

    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.1.2 网络编程概述

    按照前面的介绍,网络编程就是两个或多个设备之间的数据交换,其实更具体的说,网络编程就是两个或多个程序之间的数据交换,和普通的单机程序相比,网络程序最大的不同就是需要交换数据的程序运行在不同的计算机上,这样就造成了数据交换的复杂。虽然通过IP地址和端口可以找到网络上运行的一个程序,但是如果需要进行网络编程,则还需要了解网络通讯的过程。

    网络通讯基于“请求-响应”模型。为了理解这个模型,先来看一个例子,经常看电视的人肯定见过审讯的场面吧,一般是这样的:

           警察:姓名
    
           嫌疑犯:XXX
    
           警察:性别
    
           嫌疑犯:男
    
           警察:年龄
    
           嫌疑犯:29
    
           ……
    

    在这个例子中,警察问一句,嫌疑犯回答一句,如果警察不问,则嫌疑犯保持沉默。这种一问一答的形式就是网络中的“请求-响应”模型。也就是通讯的一端发送数据,另外一端反馈数据,网络通讯都基于该模型。

    在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序,简称客户端,而在第一次通讯中等待连接的程序被称作服务器端(Server)程序,简称服务器。一旦通讯建立,则客户端和服务器端完全一样,没有本质的区别。

    由此,网络编程中的两种程序就分别是客户端和服务器端,例如QQ程序,每个QQ用户安装的都是QQ客户端程序,而QQ服务器端程序则运行在腾讯公司的机房中,为大量的QQ用户提供服务。这种网络编程的结构被称作客户端/服务器结构,也叫做Client/Server结构,简称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进行调用,最麻烦的内容就是协议的设计以及协议的生产和解析,这个才是网络编程中最核心的内容。

    关于网络编程的基础知识,就介绍这里,深刻理解IP地址、端口和协议等概念,将会极大的有助于后续知识的学习。

    1.1.3 网络通讯方式

    在现有的网络中,网络通讯的方式主要有两种:

    • TCP(传输控制协议)方式

    • UDP(用户数据报协议)方式

    为 了方便理解这两种方式,还是先来看一个例子。大家使用手机时,向别人传递信息时有两种方式:拨打电话和发送短信。使用拨打电话的方式可以保证将信息传递给 别人,因为别人接听电话时本身就确认接收到了该信息。而发送短信的方式价格低廉,使用方便,但是接收人有可能接收不到。

    在网络通讯中,TCP方式就类似于拨打电话,使用该种方式进行网络通讯时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据。而UDP方式就类似于发送短信,使用这种方式进行网络通讯时,不需要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则客户端无法获得。

    这两种传输方式都是实际的网络编程中进行使用,重要的数据一般使用TCP方式进行数据传输,而大量的非核心数据则都通过UDP方式进行传递,在一些程序中甚至结合使用这两种方式进行数据的传递。

    由于TCP需要建立专用的虚拟连接以及确认传输是否正确,所以使用TCP方式的速度稍微慢一些,而且传输时产生的数据量要比UDP稍微大一些。

    关于网络编程的基础知识就介绍这么多,如果需要深入了解相关知识请阅读专门的计算机网络书籍,下面开始介绍Java语言中网络编程的相关技术。

    1.2 网络编程技术

    前面介绍了网络编程的相关基础知识,初步建立了网络编程的概念,但是实际学习网络编程还必须使用某种程序设计语言进行代码实现,下面就介绍一下网络编程的代码实现。

    1.2.1 网络编程步骤

    按照前面的基础知识介绍,无论使用TCP方式还是UDP方式进行网络通讯,网络编程都是由客户端和服务器端组成。当然,B/S结构的编程中只需要实现服务器端即可。所以,下面介绍网络编程的步骤时,均以C/S结构为基础进行介绍。

    说明: 这里的步骤实现和语言无关,也就是说,这个步骤适用于各种语言实现,不局限于Java语言。

    1.2.1.1 客户端网络编程步骤

    客户端(Client)是指网络编程中首先发起连接的程序,客户端一般实现程序界面和基本逻辑实现,在进行实际的客户端编程时,无论客户端复杂还是简单,以及客户端实现的方式,客户端的编程主要由三个步骤实现:

    1、 建立网络连接

    客户端网络编程的第一步都是建立网络连接。在建立网络连接时需要指定连接到的服务器的IP地址和端口号,建立完成以后,会形成一条虚拟的连接,后续的操作就可以通过该连接实现数据交换了。

    2、 交换数据

    连接建立以后,就可以通过这个连接交换数据了。交换数据严格按照请求响应模型进行,由客户端发送一个请求数据到服务器,服务器反馈一个响应数据给客户端,如果客户端不发送请求则服务器端就不响应。

    根据逻辑需要,可以多次交换数据,但是还是必须遵循请求响应模型。

    3、 关闭网络连接

    在数据交换完成以后,关闭网络连接,释放程序占用的端口、内存等系统资源,结束网络编程。

    最基本的步骤一般都是这三个步骤,在实际实现时,步骤2会出现重复,在进行代码组织时,由于网络编程是比较耗时的操作,所以一般开启专门的现场进行网络通讯。

    1.2.1.2 服务器端网络编程步骤

    服务器端(Server)是指在网络编程中被动等待连接的程序,服务器端一般实现程序的核心逻辑以及数据存储等核心功能。服务器端的编程步骤和客户端不同,是由四个步骤实现,依次是:

    1、 监听端口

    服务器端属于被动等待连接,所以服务器端启动以后,不需要发起连接,而只需要监听本地计算机的某个固定端口即可。

    这个端口就是服务器端开放给客户端的端口,服务器端程序运行的本地计算机的IP地址就是服务器端程序的IP地址。

    2、 获得连接

    当客户端连接到服务器端时,服务器端就可以获得一个连接,这个连接包含客户端的信息,例如客户端IP地址等等,服务器端和客户端也通过该连接进行数据交换。

    一般在服务器端编程中,当获得连接时,需要开启专门的线程处理该连接,每个连接都由独立的线程实现。

    3、 交换数据

    服务器端通过获得的连接进行数据交换。服务器端的数据交换步骤是首先接收客户端发送过来的数据,然后进行逻辑处理,再把处理以后的结果数据发送给客户端。简单来说,就是先接收再发送,这个和客户端的数据交换数序不同。

    其实,服务器端获得的连接和客户端连接是一样的,只是数据交换的步骤不同。

    当然,服务器端的数据交换也是可以多次进行的。

    在数据交换完成以后,关闭和客户端的连接。

    4、 关闭连接

    当服务器程序关闭时,需要关闭服务器端,通过关闭服务器端使得服务器监听的端口以及占用的内存可以释放出来,实现了连接的关闭。

    其实服务器端编程的模型和呼叫中心的实现是类似的,例如移动的客服电话10086就是典型的呼叫中心,当一个用户拨打10086时,转接给一个专门的客服人员,由该客服实现和该用户的问题解决,当另外一个用户拨打10086时,则转接给另一个客服,实现问题解决,依次类推。

    在服务器端编程时,10086这个电话号码就类似于服务器端的端口号码,每个用户就相当于一个客户端程序,每个客服人员就相当于服务器端启动的专门和客户端连接的线程,每个线程都是独立进行交互的。

    这就是服务器端编程的模型,只是TCP方式是需要建立连接的,对于服务器端的压力比较大,而UDP是不需要建立连接的,对于服务器端的压力比较小罢了。

    1.2.1.3 小结

    总之,无论使用任何语言,任何方式进行基础的网络编程,都必须遵循固定的步骤进行操作,在熟悉了这些步骤以后,可以根据需要进行逻辑上的处理,但是还是必须遵循固定的步骤进行。

    其实,基础的网络编程本身不难,也不需要很多的基础网络知识,只是由于编程的基础功能都已经由API实现,而且需要按照固定的步骤进行,所以在入门时有一定的门槛,希望下面的内容能够将你快速的带入网络编程技术的大门。

    1.2.2 Java网络编程技术

    Java语言是在网络环境下诞生的,所以Java语言虽然不能说是对于网络编程的支持最好的语言,但是必须说是一种对于网络编程提供良好支持的语言,使用Java语言进行网络编程将是一件比较轻松的工作。

    和网络编程有关的基本API位于java.net包中,该包中包含了基本的网络编程实现,该包是网络编程的基础。该包中既包含基础的网络编程类,也包含封装后的专门处理WEB相关的处理类。在本章中,将只介绍基础的网络编程类。

    首先来介绍一个基础的网络类——InetAddress类。该类的功能是代表一个IP地址,并且将IP地址和域名相关的操作方法包含在该类的内部。

    关于该类的使用,下面通过一个基础的代码示例演示该类的使用,代码如下:

     package inetaddressdemo;
    
    import java.net.*;
    
    /**
    
     * 演示InetAddress类的基本使用
    
     */
    
    public class InetAddressDemo {
    
             public static void main(String[] args) {
    
                       try{
    
                                //使用域名创建对象
    
                                InetAddress inet1 = InetAddress.getByName("www.163.com");
    
                                System.out.println(inet1);
    
                                //使用IP创建对象
    
                                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);
    
                                //获得对象中存储的IP
    
                                String ip = inet3.getHostAddress();
    
                                System.out.println("IP:" + ip);
    
                       }catch(Exception e){}
    
             }
    
    }
    

    在该示例代码中,演示了InetAddress类的基本使用,并使用了该类中的几个常用方法,该代码的执行结果是:

    www.163.com/220.181.28.50
    
    /127.0.0.1
    
    chen/192.168.1.100
    
    域名:chen
    
    IP:192.168.1.100
    

    说明: 由于该代码中包含一个互联网的网址,所以运行该程序时需要联网,否则将产生异常。

    在后续的使用中,经常包含需要使用InetAddress对象代表IP地址的构造方法,当然,该类的使用不是必须的,也可以使用字符串来代表IP地址进行实现。

    1.2.3 TCP编程

    按照前面的介绍,网络通讯的方式有TCP和UDP两种,其中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”转换为byte数组写入到输出流中,由系统自动完成将输出流中的数据发送出去,如果需要强制发送,可以调用输出流对象中的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个线程对象,然后当客户端连接到达时从池中取出一个已经创建完成的线程对象使用即可。当客户端连接关闭以后,将该线程对象重新放入到线程池中供其它的客户端重复使用,这样可以提高程序的执行速度,优化程序对于内存的占用等。

    关于基础的TCP方式的网络编程就介绍这么多,下面介绍UDP方式的网络编程在Java语言中的实现。

    网络通讯的方式除了TCP方式以外,还有一种实现的方式就是UDP方式。UDP(User Datagram Protocol),中文意思是用户数据报协议,方式类似于发短信息,是一种物美价廉的通讯方式,使用该种方式无需建立专用的虚拟连接,由于无需建立专用的连接,所以对于服务器的压力要比TCP小很多,所以也是一种常见的网络编程方式。但是使用该种方式最大的不足是传输不可靠,当然也不是说经常丢失,就像大家发短信息一样,理论上存在收不到的可能,这种可能性可能是1%,反正比较小,但是由于这种可能的存在,所以平时我们都觉得重要的事情还是打个电话吧(类似TCP方式),一般的事情才发短信息(类似UDP方式)。网络编程中也是这样,必须要求可靠传输的信息一般使用TCP方式实现,一般的数据才使用UDP方式实现。

    UDP方式的网络编程也在Java语言中获得了良好的支持,由于其在传输数据的过程中不需要建立专用的连接等特点,所以在Java API中设计的实现结构和TCP方式不太一样。当然,需要使用的类还是包含在java.net包中。

    在Java API中,实现UDP方式的编程,包含客户端网络编程和服务器端网络编程,主要由两个类实现,分别是:

    DatagramSocket

    DatagramSocket类实现“网络连接”,包括客户端网络连接和服务器端网络连接。虽然UDP方式的网络通讯不需要建立专用的网络连接,但是毕竟还是需要发送和接收数据,DatagramSocket实现的就是发送数据时的发射器,以及接收数据时的监听器的角色。类比于TCP中的网络连接,该类既可以用于实现客户端连接,也可以用于实现服务器端连接。

    DatagramPacket

    DatagramPacket类实现对于网络中传输的数据封装,也就是说,该类的对象代表网络中交换的数据。在UDP方式的网络编程中,无论是需要发送的数据还是需要接收的数据,都必须被处理成DatagramPacket类型的对象,该对象中包含发送到的地址、发送到的端口号以及发送的内容等。其实DatagramPacket类的作用类似于现实中的信件,在信件中包含信件发送到的地址以及接收人,还有发送的内容等,邮局只需要按照地址传递即可。在接收数据时,接收到的数据也必须被处理成DatagramPacket类型的对象,在该对象中包含发送方的地址、端口号等信息,也包含数据的内容。和TCP方式的网络传输相比,IO编程在UDP方式的网络编程中变得不是必须的内容,结构也要比TCP方式的网络编程简单一些。

    下面介绍一下UDP方式的网络编程中,客户端和服务器端的实现步骤,以及通过基础的示例演示UDP方式的网络编程在Java语言中的实现方式。

    UDP方式的网络编程,编程的步骤和TCP方式类似,只是使用的类和方法存在比较大的区别,下面首先介绍一下UDP方式的网络编程客户端实现过程。

    UDP客户端编程涉及的步骤也是4个部分:建立连接、发送数据、接收数据和关闭连接。

    首先介绍UDP方式的网络编程中建立连接的实现。其中UDP方式的建立连接和TCP方式不同,只需要建立一个连接对象即可,不需要指定服务器的IP和端口号码。实现的代码为:

         DatagramSocket ds = new DatagramSocket();
    

    这样就建立了一个客户端连接,该客户端连接使用系统随机分配的一个本地计算机的未用端口号。在该连接中,不指定服务器端的IP和端口,所以UDP方式的网络连接更像一个发射器,而不是一个具体的连接。

    当然,可以通过制定连接使用的端口号来创建客户端连接。

                       DatagramSocket ds = new DatagramSocket(5000);
    

    这样就是使用本地计算机的5000号端口建立了一个连接。一般在建立客户端连接时没有必要指定端口号码。

    接着,介绍一下UDP客户端编程中发送数据的实现。在UDP方式的网络编程中,IO技术不是必须的,在发送数据时,需要将需要发送的数据内容首先转换为byte数组,然后将数据内容、服务器IP和服务器端口号一起构造成一个DatagramPacket类型的对象,这样数据的准备就完成了,发送时调用网络连接对象中的send方法发送该对象即可。例如将字符串“Hello”发送到IP是127.0.0.1,端口号是10001的服务器,则实现发送数据的代码如下:

          String s = “Hello”;
    
                       String host = “127.0.0.1”;
    
                       int port = 10001;
    
                      //将发送的内容转换为byte数组
    
                       byte[] b = s.getBytes();
    
                       //将服务器IP转换为InetAddress对象
    
                       InetAddress server = InetAddress.getByName(host);
    
                       //构造发送的数据包对象
    
                       DatagramPacket sendDp = new DatagramPacket(b,b.length,server,port);
    
                       //发送数据
    
                       ds.send(sendDp);
    

    在该示例代码中,不管发送的数据内容是什么,都需要转换为byte数组,然后将服务器端的IP地址构造成InetAddress类型的对象,在准备完成以后,将这些信息构造成一个DatagramPacket类型的对象,在UDP编程中,发送的数据内容、服务器端的IP和端口号,都包含在DatagramPacket对象中。在准备完成以后,调用连接对象ds的send方法把DatagramPacket对象发送出去即可。

    按照UDP协议的约定,在进行数据传输时,系统只是尽全力传输数据,但是并不保证数据一定被正确传输,如果数据在传输过程中丢失,那就丢失了。

    UDP方式在进行网络通讯时,也遵循“请求-响应”模型,在发送数据完成以后,就可以接收服务器端的反馈数据了。

    下面介绍一下UDP客户端编程中接收数据的实现。当数据发送出去以后,就可以接收服务器端的反馈信息了。接收数据在Java语言中的实现是这样的:首先构造一个数据缓冲数组,该数组用于存储接收的服务器端反馈数据,该数组的长度必须大于或等于服务器端反馈的实际有效数据的长度。然后以该缓冲数组为基础构造一个DatagramPacket数据包对象,最后调用连接对象的receive方法接收数据即可。接收到的服务器端反馈数据存储在DatagramPacket类型的对象内部。实现接收数据以及显示服务器端反馈内容的示例代码如下:

         //构造缓冲数组
    
                       byte[] data = new byte[1024];
    
                       //构造数据包对象
    
                       DatagramPacket received = new DatagramPacket(data,data.length);
    
                       //接收数据
    
                       ds.receive(receiveDp);
    
                       //输出数据内容
    
                       byte[] b = receiveDp.getData(); //获得缓冲数组
    
                       int len = receiveDp.getLength(); //获得有效数据长度
    
                       String s = new String(b,0,len);
    
                       System.out.println(s);
    

    在该代码中,首先构造缓冲数组data,这里设置的长度1024是预估的接收到的数据长度,要求该长度必须大于或等于接收到的数据长度,然后以该缓冲数组为基础,构造数据包对象,使用连接对象ds的receive方法接收反馈数据,由于在Java语言中,除String以外的其它对象都是按照地址传递,所以在receive方法内部可以改变数据包对象receiveDp的内容,这里的receiveDp的功能和返回值类似。数据接收到以后,只需要从数据包对象中读取出来就可以了,使用DatagramPacket对象中的getData方法可以获得数据包对象的缓冲区数组,但是缓冲区数组的长度一般大于有效数据的长度,换句话说,也就是缓冲区数组中只有一部分数据是反馈数据,所以需要使用DatagramPacket对象中的getLength方法获得有效数据的长度,则有效数据就是缓冲数组中的前有效数据长度个内容,这些才是真正的服务器端反馈的数据的内容。

    UDP方式客户端网络编程的最后一个步骤就是关闭连接。虽然UDP方式不建立专用的虚拟连接,但是连接对象还是需要占用系统资源,所以在使用完成以后必须关闭连接。关闭连接使用连接对象中的close方法即可,实现的代码如下:

                       ds.close();
    

    需要说明的是,和TCP建立连接的方式不同,UDP方式的同一个网络连接对象,可以发送到达不同服务器端IP或端口的数据包,这点是TCP方式无法做到的。

    介绍完了UDP方式客户端网络编程的基础知识以后,下面再来介绍一下UDP方式服务器端网络编程的基础知识。

    UDP方式网络编程的服务器端实现和TCP方式的服务器端实现类似,也是服务器端监听某个端口,然后获得数据包,进行逻辑处理以后将处理以后的结果反馈给客户端,最后关闭网络连接,下面依次进行介绍。

    首先UDP方式服务器端网络编程需要建立一个连接,该连接监听某个端口,实现的代码为:

        DatagramSocket ds = new DatagramSocket(10010);
    

    由于服务器端的端口需要固定,所以一般在建立服务器端连接时,都指定端口号。例如该示例代码中指定10010端口为服务器端使用的端口号,客户端端在连接服务器端时连接该端口号即可。

    接着服务器端就开始接收客户端发送过来的数据,其接收的方法和客户端接收的方法一直,其中receive方法的作用类似于TCP方式中accept方法的作用,该方法也是一个阻塞方法,其作用是接收数据。

    接收到客户端发送过来的数据以后,服务器端对该数据进行逻辑处理,然后将处理以后的结果再发送给客户端,在这里发送时就比客户端要麻烦一些,因为服务器端需要获得客户端的IP和客户端使用的端口号,这个都可以从接收到的数据包中获得。示例代码如下:

        //获得客户端的IP
    
         InetAddress clientIP = receiveDp.getAddress();
    
             //获得客户端的端口号
    
             Int clientPort = receiveDp.getPort();
    

    使用以上代码,就可以从接收到的数据包对象receiveDp中获得客户端的IP地址和客户端的端口号,这样就可以在服务器端中将处理以后的数据构造成数据包对象,然后将处理以后的数据内容反馈给客户端了。

    最后,当服务器端实现完成以后,关闭服务器端连接,实现的方式为调用连接对象的close方法,示例代码如下:

          ds.close();
    

    介绍完了UDP方式下的客户端编程和服务器端编程的基础知识以后,下面通过一个简单的示例演示UDP网络编程的基本使用。

    该示例的功能是实现将客户端程序的系统时间发送给服务器端,服务器端接收到时间以后,向客户端反馈字符串“OK”。实现该功能的客户端代码如下所示:

    package udp;
    
    import java.net.*;
    
    import java.util.*;
    
    /**
    
     * 简单的UDP客户端,实现向服务器端发生系统时间功能
    
     */
    
    public class SimpleUDPClient {
    
                public static void main(String[] args) {
    
                         DatagramSocket ds = null; //连接对象
    
                         DatagramPacket sendDp; //发送数据包对象
    
                    DatagramPacket receiveDp; //接收数据包对象
    
                         String serverHost = "127.0.0.1"; //服务器IP
    
                    int serverPort = 10010; //服务器端口号
    
                         try{
    
                            //建立连接
    
                            ds = new DatagramSocket();
    
                            //初始化发送数据
    
                            Date d = new Date(); //当前时间
    
                            String content = d.toString(); //转换为字符串
    
                            byte[] data = content.getBytes();
    
                            //初始化发送包对象
    
                            InetAddress address = InetAddress.getByName(serverHost);
    
                            sendDp = new DatagramPacket(data,data.length,address,serverPort);
    
                            //发送
    
                            ds.send(sendDp);
    
                                                                              
    
                            //初始化接收数据
    
                            byte[] b = new byte[1024];
    
                            receiveDp = new DatagramPacket(b,b.length);
    
                            //接收
    
                            ds.receive(receiveDp);
    
                            //读取反馈内容,并输出
    
                            byte[] response = receiveDp.getData();
    
                            int len = receiveDp.getLength();
    
                            String s = new String(response,0,len);
    
                            System.out.println("服务器端反馈为:" + s);
    
                    }catch(Exception e){
    
                            e.printStackTrace();
    
                    }finally{
    
                            try{
    
                               //关闭连接
    
                               ds.close();
    
                            }catch(Exception e){}
    
                    }
    
                }
    
            }
    

    在该示例代码中,首先建立UDP方式的网络连接,然后获得当前系统时间,这里获得的系统时间是客户端程序运行的本地计算机的时间,然后将时间字符串以及服务器端的IP和端口,构造成发送数据包对象,调用连接对象ds的send方法发送出去。在数据发送出去以后,构造接收数据的数据包对象,调用连接对象ds的receive方法接收服务器端的反馈,并输出在控制台。最后在finally语句块中关闭客户端网络连接。

    和下面将要介绍的服务器端一起运行时,客户端程序的输出结果为:

      服务器端反馈为:OK
    

    下面是该示例程序的服务器端代码实现:

          package udp;
    
            import java.net.*;
    
            /**
    
             * 简单UDP服务器端,实现功能是输出客户端发送数据,
    
               并反馈字符串“OK"给客户端
    
             */
    
            public class SimpleUDPServer {
    
                public static void main(String[] args) {
    
                         DatagramSocket ds = null; //连接对象
    
                         DatagramPacket sendDp; //发送数据包对象
    
                         DatagramPacket receiveDp; //接收数据包对象
    
                         final int PORT = 10010; //端口
    
                                                   try{
    
                            //建立连接,监听端口
    
                            ds = new DatagramSocket(PORT);
    
                           System.out.println("服务器端已启动:");
    
                            //初始化接收数据
    
                            byte[] b = new byte[1024];
    
                            receiveDp = new DatagramPacket(b,b.length);
    
                            //接收
    
                            ds.receive(receiveDp);
    
                            //读取反馈内容,并输出
    
                            InetAddress clientIP = receiveDp.getAddress();
    
                            int clientPort = receiveDp.getPort();
    
                            byte[] data = receiveDp.getData();
    
                            int len = receiveDp.getLength();
    
                            System.out.println("客户端IP:" + clientIP.getHostAddress());
    
                            System.out.println("客户端端口:" + clientPort);
    
                            System.out.println("客户端发送内容:" + new String(data,0,len));
    
                                                                              
    
                            //发送反馈
    
                            String response = "OK";
    
                            byte[] bData = response.getBytes();
    
                            sendDp = new DatagramPacket(bData,bData.length,clientIP,clientPort);
    
                            //发送
    
                            ds.send(sendDp);
    
                                                   }catch(Exception e){
    
                            e.printStackTrace();
    
                                                   }finally{
    
                            try{
    
                               //关闭连接
    
                               ds.close();
    
                            }catch(Exception e){}
    
                                                   }
    
                }
    
            }
    

    在该服务器端实现中,首先监听10010号端口,和TCP方式的网络编程类似,服务器端的receive方法是阻塞方法,如果客户端不发送数据,则程序会在该方法处阻塞。当客户端发送数据到达服务器端时,则接收客户端发送过来的数据,然后将客户端发送的数据内容读取出来,并在服务器端程序中打印客户端的相关信息,从客户端发送过来的数据包中可以读取出客户端的IP以及客户端端口号,将反馈数据字符串“OK”发送给客户端,最后关闭服务器端连接,释放占用的系统资源,完成程序功能示例。

    和前面TCP方式中的网络编程类似,这个示例也仅仅是网络编程的功能示例,也存在前面介绍的客户端无法进行多次数据交换,以及服务器端不支持多个客户端的问题,这两个问题也需要对于代码进行处理才可以很方便的进行解决。

    在解决该问题以前,需要特别指出的是UDP方式的网络编程由于不建立虚拟的连接,所以在实际使用时和TCP方式存在很多的不同,最大的一个不同就是“无状态”。该特点指每次服务器端都收到信息,但是这些信息和连接无关,换句话说,也就是服务器端只是从信息是无法识别出是谁发送的,这样就要求发送信息时的内容需要多一些,这个在后续的示例中可以看到。

    下面是实现客户端多次发送以及服务器端支持多个数据包同时处理的程序结构,实现的原理和TCP方式类似,在客户端将数据的发送和接收放入循环中,而服务器端则将接收到的每个数据包启动一个专门的线程进行处理。实现的代码如下:

     package udp;
    
        import java.net.*;
    
        import java.util.*;
    
        /**
    
         * 简单的UDP客户端,实现向服务器端发生系统时间功能
    
         * 该程序发送3次数据到服务器端
    
         */
    
        public class MulUDPClient {
    
                      public static void main(String[] args) {
    
                     DatagramSocket ds = null; //连接对象
    
                                         DatagramPacket sendDp; //发送数据包对象
    
                                         DatagramPacket receiveDp; //接收数据包对象
    
                                         String serverHost = "127.0.0.1"; //服务器IP
    
                                         int serverPort = 10012; //服务器端口号
    
                                         try{
    
                        //建立连接
    
                        ds = new DatagramSocket();
    
                        //初始化
    
                                  InetAddress address = InetAddress.getByName(serverHost);
    
                        byte[] b = new byte[1024];
    
                        receiveDp = new DatagramPacket(b,b.length);
    
                        System.out.println("客户端准备完成");
    
                        //循环30次,每次间隔0.01秒
    
                        for(int i = 0;i < 30;i++){
    
                                                            //初始化发送数据
    
                                                            Date d = new Date(); //当前时间
    
                                                            String content = d.toString(); //转换为字符串
    
                                                            byte[] data = content.getBytes();
    
                                                            //初始化发送包对象
    
                                                            sendDp = new DatagramPacket(data,data.length,address, serverPort);
    
                                                            //发送
    
                                                            ds.send(sendDp);
    
                                                            //延迟
    
                                                            Thread.sleep(10);
    
                                                            //接收
    
                                                            ds.receive(receiveDp);
    
                                                            //读取反馈内容,并输出
    
                                                            byte[] response = receiveDp.getData();
    
                                                            int len = receiveDp.getLength();
    
                                                            String s = new String(response,0,len);
    
                                                            System.out.println("服务器端反馈为:" + s);
    
                         }
    
                     }catch(Exception e){
    
                         e.printStackTrace();
    
                     }finally{
    
                         try{
    
                                                            //关闭连接
    
                                                            ds.close();
    
                         }catch(Exception e){}
    
                     }
    
             }
    
         }
    

    在该示例中,将和服务器端进行数据交换的逻辑写在一个for循环的内部,这样就可以实现和服务器端的多次交换了,考虑到服务器端的响应速度,在每次发送之间加入0.01秒的时间间隔。最后当数据交换完成以后关闭连接,结束程序。

    实现该逻辑的服务器端程序代码如下:

    package udp;
    
    import java.net.*;
    
    /**
    
    * 可以并发处理数据包的服务器端
    
    * 功能为:显示客户端发送的内容,并向客户端反馈字符串“OK”
    
    */
    
    public class MulUDPServer {
    
    public static void main(String[] args) {
    
    DatagramSocket ds = null; //连接对象
    
    DatagramPacket receiveDp; //接收数据包对象
    
    final int PORT = 10012; //端口
    
    byte[] b = new byte[1024];
    
    receiveDp = new DatagramPacket(b,b.length);
    
    try{
    
    //建立连接,监听端口
    
    ds = new DatagramSocket(PORT);
    
    System.out.println("服务器端已启动:");
    
    while(true){
    
    //接收
    
    ds.receive(receiveDp);
    
    //启动线程处理数据包
    
    new LogicThread(ds,receiveDp);
    
    }
    
    }catch(Exception e){
    
             e.printStackTrace();
    
    }finally{
    
    try{
    
    //关闭连接
    
    ds.close();
    
    }catch(Exception e){}
    
    }
    
    }
    
    }
    

    该代码实现了服务器端的接收逻辑,使用一个循环来接收客户端发送过来的数据包,当接收到数据包以后启动一个LogicThread线程处理该数据包。这样服务器端就可以实现同时处理多个数据包了。

    实现逻辑处理的线程代码如下:

    package udp;
    
    import java.net.*;
    
    /**
    
     * 逻辑处理线程
    
     */
    
    public class LogicThread extends Thread {
    
    /**连接对象*/
    
    DatagramSocket ds;
    
    /**接收到的数据包*/
    
    DatagramPacket dp;
    
     
    
    public LogicThread(DatagramSocket ds,DatagramPacket dp){
    
    this.ds = ds;
    
    this.dp = dp;
    
    start(); //启动线程
    
    }
    
     
    
    public void run(){
    
    try{
    
    //获得缓冲数组
    
    byte[] data = dp.getData();
    
    //获得有效数据长度
    
    int len = dp.getLength();
    
    //客户端IP
    
    InetAddress clientAddress = dp.getAddress();
    
    //客户端端口
    
    int clientPort = dp.getPort();
    
    //输出
    
    System.out.println("客户端IP:" + clientAddress.getHostAddress());
    
    System.out.println("客户端端口号:" + clientPort);
    
    System.out.println("客户端发送内容:" + new String(data,0,len));
    
    //反馈到客户端
    
    byte[] b = "OK".getBytes();
    
    DatagramPacket sendDp = new DatagramPacket(b,b.length,clientAddress,clientPort);
    
    //发送
    
    ds.send(sendDp);
    
    }catch(Exception e){
    
    e.printStackTrace();
    
    }
    
    }
    
    }
    

    在该线程中,只处理一次UDP通讯,当通讯结束以后线程死亡,在线程内部,每次获得客户端发送过来的信息,将获得的信息输出到服务器端程序的控制台,然后向客户端反馈字符串“OK”。

    由于UDP数据传输过程中可能存在丢失,所以在运行该程序时可能会出现程序阻塞的情况。如果需要避免该问题,可以将客户端的网络发送部分也修改成线程实现。

    关于基础的UDP网络编程就介绍这么多了,下面将介绍一下网络协议的概念。

    网络协议

    对于需要从事网络编程的程序员来说,网络协议是一个需要深刻理解的概念。那么什么是网络协议呢?

    网络协议是指对于网络中传输的数据格式的规定。对于网络编程初学者来说,没有必要深入了解TCP/IP协议簇,所以对于初学者来说去读大部头的《TCP/IP协议》也不是一件很合适的事情,因为深入了解TCP/IP协议是网络编程提高阶段,也是深入网络编程底层时才需要做的事情。

    对于一般的网络编程来说,更多的是关心网络上传输的逻辑数据内容,也就是更多的是应用层上的网络协议,所以后续的内容均以实际应用的数据为基础来介绍网络协议的概念。

    那么什么是网络协议呢,下面看一个简单的例子。春节晚会上“小沈阳”和赵本山合作的小品《不差钱》中,小沈阳和赵本山之间就设计了一个协议,协议的内容为:

           如果点的菜价钱比较贵是,就说没有。
    

    按照该协议的规定,就有了下面的对话:

           赵本山:4斤的龙虾
    
           小沈阳:(经过判断,得出价格比较高),没有
    
           赵本山:鲍鱼
    
           小沈阳:(经过判断,得出价格比较高),没有
    

    这就是一种双方达成的一种协议约定,其实这种约定的实质和网络协议的实质是一样的。网络协议的实质也是客户端程序和服务器端程序对于数据的一种约定,只是由于以计算机为基础,所以更多的是使用数字来代表内容,这样就显得比较抽象一些。

    下 面再举一个简单的例子,介绍一些基础的网络协议设计的知识。例如需要设计一个简单的网络程序:网络计算器。也就是在客户端输入需要计算的数字和运算符,在 服务器端实现计算,并将计算的结果反馈给客户端。在这个例子中,就需要约定两个数据格式:客户端发送给服务器端的数据格式,以及服务器端反馈给客户端的数 据格式。

    可能你觉得这个比较简单,例如客户端输入的数字依次是12和432,输入的运算符是加号,可能最容易想到的数据格式是形成字符串“12+432”,这样格式的确比较容易阅读,但是服务器端在进行计算时,逻辑就比较麻烦,因为需要首先拆分该字符串,然后才能进行计算,所以可用的数据格式就有了一下几种:

              “12,432,+”     格式为:第一个数字,第二个数字,运算符
    
              “12,+,432”     格式为:第一个数字,运算符,第二个数字
    

    其实以上两种数据格式很接近,比较容易阅读,在服务器端收到该数据格式以后,使用“,”为分隔符分割字符串即可。

    假设对于运算符再进行一次约定,例如约定数字0代表+,1代表减,2代表乘,3代表除,整体格式遵循以上第一种格式,则上面的数字生产的协议数据为:

          “12,432,0”
    

    这就是一种基本的发送的协议约定了。

    另 外一个需要设计的协议格式就是服务器端反馈的数据格式,其实服务器端主要反馈计算结果,但是在实际接受数据时,有可能存在格式错误的情况,这样就需要简单 的设计一下服务器端反馈的数据格式了。例如规定,如果发送的数据格式正确,则反馈结果,否则反馈字符串“错误”。这样就有了以下的数据格式:

         客户端:“1,111,1”         服务器端:”-110”
    
         客户端:“123,23,0”    服务器端:“146”
    
         客户端:“1,2,5”       服务器端:“错误”
    

    这样就设计出了一种最最基本的网络协议格式,从该示例中可以看出,网络协议就是一种格式上的约定,可以根据逻辑的需要约定出各种数据格式,在进行设计时一般遵循“简单、通用、容易解析”的原则进行。

    而对于复杂的网络程序来说,需要传输的数据种类和数据量都比较大,这样只需要依次设计出每种情况下的数据格式即可,例如QQ程序,在该程序中需要进行传输的网络数据种类很多,那么在设计时就可以遵循:登录格式、注册格式、发送消息格式等等,一一进行设计即可。所以对于复杂的网络程序来说,只是增加了更多的命令格式,在实际设计时的工作量增加不是太大。

    不管怎么说,在网络编程中,对于同一个网络程序来说,一般都会涉及到两个网络协议格式:客户端发送数据格式和服务器端反馈数据格式,在实际设计时,需要一一对应。这就是最基本的网络协议的知识。

    网络协议设计完成以后,在进行网络编程时,就需要根据设计好的协议格式,在程序中进行对应的编码了,客户端程序和服务器端程序需要进行协议处理的代码分别如下。

    客户端程序需要完成的处理为:

    • 客户端发送协议格式的生成

    • 服务器端反馈数据格式的解析

    服务器端程序需要完成的处理为:

    • 服务器端反馈协议格式的生成

    • 客户端发送协议格式的解析

    这里的生成是指将计算好的数据,转换成规定的数据格式,这里的解析指,从反馈的数据格式中拆分出需要的数据。在进行对应的代码编写时,严格遵循协议约定即可。

    所以,对于程序员来说,在进行网络程序编写时,需要首先根据逻辑的需要设计网络协议格式,然后遵循协议格式约定进行协议生成和解析代码的编写,最后使用网络编程技术实现整个网络编程的功能。

    由于各种网络程序使用不同的协议格式,所以不同网络程序的客户端之间无法通用。

    而对于常见协议的格式,例如HTTP(Hyper Text Transfer Protocol,超文本传输协议)、FTP(File Transfer Protocol,文件传输协议),SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)等等,都有通用的规定,具体可以查阅相关的RFC文档。

    最后,对于一种网络程序来说,网络协议格式是该程序最核心的技术秘密,因为一旦协议格式泄漏,则任何一个人都可以根据该格式进行客户端的编写,这样将影响服务器端的实现,也容易出现一些其它的影响。

    1.2.6 小结

    关于网络编程基本的技术就介绍这么多,该部分介绍了网络编程的基础知识,以及Java语言对于网络编程的支持,网络编程的步骤等,并详细介绍了TCP方式网络编程和UDP方式网络编程在Java语言中的实现。

    网络协议也是网络程序的核心,所以在实际开始进行网络编程时,设计一个良好的协议格式也是必须进行的工作。

    网络编程示例

    “实践出真知”,所以在进行技术学习时,还是需要进行很多的练习,才可以体会技术的奥妙,下面通过两个简单的示例,演示网络编程的实际使用。

    1.3.1质数判别示例

    该示例实现的功能是质数判断,程序实现的功能为客户端程序接收用户输入的数字,然后将用户输入的内容发送给服务器端,服务器端判断客户端发送的数字是否是质数,并将判断的结果反馈给客户端,客户端根据服务器端的反馈显示判断结果。

    质数的规则是:最小的质数是2,只能被1和自身整除的自然数。当用户输入小于2的数字,以及输入的内容不是自然数时,都属于非法输入。

    网络程序的功能都分为客户端程序和服务器端程序实现,下面先描述一下每个程序分别实现的功能:

    1、 客户端程序功能:

    • 接收用户控制台输入

    • 判断输入内容是否合法

    • 按照协议格式生成发送数据

    • 发送数据

    • 接收服务器端反馈

    • 解析服务器端反馈信息,并输出

    2、 服务器端程序功能:

    • 接收客户端发送数据

    • 按照协议格式解析数据

    • 判断数字是否是质数

    • 根据判断结果,生成协议数据

    • 将数据反馈给客户端

    分解好了网络程序的功能以后,就可以设计网络协议格式了,如果该程序的功能比较简单,所以设计出的协议格式也不复杂。

    客户端发送协议格式:

    • 将用户输入的数字转换为字符串,再将字符串转换为byte数组即可。

    • 例如用户输入16,则转换为字符串“16”,使用getBytes转换为byte数组。

    • 客户端发送“quit”字符串代表结束连接

    服务器端发送协议格式:

    • 反馈数据长度为1个字节。数字0代表是质数,1代表不是质数,2代表协议格式错误。

    • 例如客户端发送数字12,则反馈1,发送13则反馈0,发送0则反馈2。

    功能设计完成以后,就可以分别进行客户端和服务器端程序的编写了,在编写完成以后联合起来进行调试即可。

    下面分别以TCP方式和UDP方式实现该程序,注意其实现上的差异。不管使用哪种方式实现,客户端都可以多次输入数据进行判断。对于UDP方式来说,不需要向服务器端发送quit字符串。

    以TCP方式实现的客户端程序代码如下:

    package example1;
    
    import java.io.*;
    
    import java.net.*;
    
    /**
    
     * 以TCP方式实现的质数判断客户端程序
    
     */
    
    public class TCPPrimeClient {
    
             static BufferedReader br;
    
             static Socket socket;
    
             static InputStream is;
    
             static OutputStream os;
    
             /**服务器IP*/
    
             final static String HOST = "127.0.0.1";
    
             /**服务器端端口*/
    
             final static int PORT = 10005;
    
            
    
             public static void main(String[] args) {
    
                       init(); //初始化
    
                       while(true){
    
                                System.out.println("请输入数字:");
    
                                String input = readInput(); //读取输入
    
                                if(isQuit(input)){ //判读是否结束
    
                                         byte[] b = "quit".getBytes();
    
                                         send(b);
    
                                         break; //结束程序
    
                                }
    
                                if(checkInput(input)){ //校验合法
    
                                         //发送数据
    
                                         send(input.getBytes());
    
                                         //接收数据
    
                                         byte[] data = receive();
    
                                         //解析反馈数据
    
                                         parse(data);
    
                                }else{
    
                                         System.out.println("输入不合法,请重新输入!");
    
                                }
    
                       }
    
                       close(); //关闭流和连接
    
             }
    
            
    
             /**
    
              * 初始化
    
              */
    
             private static void init(){
    
                       try {
    
                                br = new BufferedReader(
    
                                                   new InputStreamReader(System.in));
    
                                socket = new Socket(HOST,PORT);
    
                                is = socket.getInputStream();
    
                                os = socket.getOutputStream();
    
                       } catch (Exception e) {}
    
             }
    
            
    
             /**
    
              * 读取客户端输入
    
              */
    
             private static String readInput(){
    
                       try {
    
                                return br.readLine();
    
                       } catch (Exception e) {
    
                                return null;
    
                       }
    
             }
    
            
    
             /**
    
              * 判断是否输入quit
    
              * @param input 输入内容
    
              * @return true代表结束,false代表不结束
    
              */
    
             private static boolean isQuit(String input){
    
                       if(input == null){
    
                                return false;
    
                       }else{
    
                                if("quit".equalsIgnoreCase(input)){
    
                                         return true;
    
                                }else{
    
                                         return false;
    
                                }
    
                       }
    
             }
    
            
    
             /**
    
              * 校验输入
    
              * @param input 用户输入内容
    
              * @return true代表输入符合要求,false代表不符合
    
              */
    
             private static boolean checkInput(String input){
    
                       if(input == null){
    
                                return false;
    
                       }
    
                       try{
    
                                int n = Integer.parseInt(input);
    
                                if(n >= 2){
    
                                         return true;
    
                                }else{
    
                                         return false;
    
                                }
    
                       }catch(Exception e){
    
                                return false; //输入不是整数
    
                       }
    
             }
    
            
    
             /**
    
              * 向服务器端发送数据
    
              * @param data 数据内容
    
              */
    
             private static void send(byte[] data){
    
                       try{
    
                                os.write(data);
    
                       }catch(Exception e){}
    
             }
    
            
    
             /**
    
              * 接收服务器端反馈
    
              * @return 反馈数据
    
              */
    
             private static byte[] receive(){
    
                       byte[] b = new byte[1024];
    
                       try {
    
                                int n = is.read(b);
    
                                byte[] data = new byte[n];
    
                                //复制有效数据
    
                                System.arraycopy(b, 0, data, 0, n);
    
                                return data;
    
                       } catch (Exception e){}
    
                       return null;
    
             }
    
            
    
             /**
    
              * 解析协议数据
    
              * @param data 协议数据
    
              */
    
             private static void parse(byte[] data){
    
                       if(data == null){
    
                                System.out.println("服务器端反馈数据不正确!");
    
                                return;
    
                       }
    
                       byte value = data[0]; //取第一个byte
    
                       //按照协议格式解析
    
                       switch(value){
    
                       case 0:
    
                                System.out.println("质数");
    
                                break;
    
                       case 1:
    
                                System.out.println("不是质数");
    
                                break;
    
                       case 2:
    
                                System.out.println("协议格式错误");
    
                                break;
    
                       }
    
             }
    
            
    
             /**
    
              * 关闭流和连接
    
              */
    
             private static void close(){
    
                       try{
    
                                br.close();
    
                                is.close();
    
                                os.close();
    
                                socket.close();
    
                       }catch(Exception e){
    
                                e.printStackTrace();
    
                       }
    
             }
    
    }
    

    在该代码中,将程序的功能使用方法进行组织,使得结构比较清晰,核心的逻辑流程在main方法中实现。

    以TCP方式实现的服务器端的代码如下:

    package example1;
    
    import java.net.*;
    
    /**
    
     * 以TCP方式实现的质数判别服务器端
    
     */
    
    public class TCPPrimeServer {
    
             public static void main(String[] args) {
    
                       final int PORT = 10005;
    
                       ServerSocket ss = null;
    
                       try {
    
                                ss = new ServerSocket(PORT);
    
                                System.out.println("服务器端已启动:");
    
                                while(true){
    
                                         Socket s = ss.accept();
    
                                         new PrimeLogicThread(s);
    
                                }
    
                       } catch (Exception e) {}
    
                       finally{
    
                                try {
    
                                         ss.close();
    
                                } catch (Exception e2) {}
    
                       }
    
                      
    
             }
    
    }
    
    package example1;
    
    import java.io.*;
    
    import java.net.*;
    
    /**
    
     * 实现质数判别逻辑的线程
    
     */
    
    public class PrimeLogicThread extends Thread {
    
             Socket socket;
    
             InputStream is;
    
             OutputStream os;
    
            
    
             public PrimeLogicThread(Socket socket){
    
                       this.socket = socket;
    
                       init();
    
                       start();
    
             }
    
             /**
    
              * 初始化
    
              */
    
             private void init(){
    
                       try{
    
                                is = socket.getInputStream();
    
                                os = socket.getOutputStream();
    
                       }catch(Exception e){}
    
             }
    
            
    
             public void run(){
    
                       while(true){
    
                                //接收客户端反馈
    
                                byte[] data = receive();
    
                                //判断是否是退出
    
                                if(isQuit(data)){
    
                                         break; //结束循环
    
                                }
    
                                //逻辑处理
    
                                byte[] b = logic(data);
    
                                //反馈数据
    
                                send(b);
    
                       }
    
                       close();
    
             }
    
            
    
             /**
    
              * 接收客户端数据
    
              * @return 客户端发送的数据
    
              */
    
             private byte[] receive(){
    
                       byte[] b = new byte[1024];
    
                       try {
    
                                int n = is.read(b);
    
                                byte[] data = new byte[n];
    
                                //复制有效数据
    
                                System.arraycopy(b, 0, data, 0, n);
    
                                return data;
    
                       } catch (Exception e){}
    
                       return null;
    
             }
    
            
    
             /**
    
              * 向客户端发送数据
    
              * @param data 数据内容
    
              */
    
             private void send(byte[] data){
    
                       try{
    
                                os.write(data);
    
                       }catch(Exception e){}
    
             }
    
            
    
             /**
    
              * 判断是否是quit
    
              * @return 是返回true,否则返回false
    
              */
    
             private boolean isQuit(byte[] data){
    
                       if(data == null){
    
                                return false;
    
                       }else{
    
                                String s = new String(data);
    
                                if(s.equalsIgnoreCase("quit")){
    
                                         return true;
    
                                }else{
    
                                         return false;
    
                                }
    
                       }
    
             }
    
            
    
             private byte[] logic(byte[] data){
    
                       //反馈数组
    
                       byte[] b = new byte[1];
    
                       //校验参数
    
                       if(data == null){
    
                                b[0] = 2;
    
                                return b;
    
                       }
    
                       try{
    
                                //转换为数字
    
                                String s = new String(data);
    
                                int n = Integer.parseInt(s);
    
                                //判断是否是质数
    
                                if(n >= 2){
    
                                         boolean flag = isPrime(n);
    
                                         if(flag){
    
                                                   b[0] = 0;
    
                                         }else{
    
                                                   b[0] = 1;
    
                                         }
    
                                }else{
    
                                         b[0] = 2; //格式错误
    
                                         System.out.println(n);
    
                                }
    
                       }catch(Exception e){
    
                                e.printStackTrace();
    
                                b[0] = 2;
    
                       }
    
                       return b;
    
             }
    
            
    
             /**
    
              *
    
              * @param n
    
              * @return
    
              */
    
             private boolean isPrime(int n){
    
                       boolean b = true;
    
                       for(int i = 2;i <= Math.sqrt(n);i++){
    
                                if(n % i == 0){
    
                                         b = false;
    
                                         break;
    
                                }
    
                       }
    
                       return b;
    
             }
    
            
    
             /**
    
              * 关闭连接
    
              */
    
             private void close(){
    
                       try {
    
                                is.close();
    
                                os.close();
    
                                socket.close();
    
                       } catch (Exception e){}
    
             }
    
    }
    

    本示例使用的服务器端的结构和前面示例中的结构一致,只是逻辑线程的实现相对来说要复杂一些,在线程类中的logic方法中实现了服务器端逻辑,根据客户端发送过来的数据,判断是否是质数,然后根据判断结果按照协议格式要求,生成客户端反馈数据,实现服务器端要求的功能。

    猜数字小游戏

    下面这个示例是一个猜数字的控制台小游戏。该游戏的规则是:当客户端第一次连接到服务器端时,服务器端生产一个【0,50】之间的随机数字,然后客户端输入数字来猜该数字,每次客户端输入数字以后,发送给服务器端,服务器端判断该客户端发送的数字和随机数字的关系,并反馈比较结果,客户端总共有5次猜的机会,猜中时提示猜中,当输入”quit”时结束程序。

    和前面的示例类似,在进行网络程序开发时,首先需要分解一下功能的实现,觉得功能是在客户端程序中实现还是在服务器端程序中实现。区分的规则一般是:客户端 程序实现接收用户输入等界面功能,并实现一些基础的校验降低服务器端的压力,而将程序核心的逻辑以及数据存储等功能放在服务器端进行实现。遵循该原则划分 的客户端和服务器端功能如下所示。

    客户端程序功能列表:

    • 接收用户控制台输入

    • 判断输入内容是否合法

    • 按照协议格式发送数据

    • 根据服务器端的反馈给出相应提示

      服务器端程序功能列表:

    • 接收客户端发送数据

    • 按照协议格式解析数据

    • 判断发送过来的数字和随机数字的关系

    • 根据判断结果生产协议数据

    • 将生产的数据反馈给客户端

    在该示例中,实际使用的网络命令也只有两条,所以显得协议的格式比较简单。

    其中客户端程序协议格式如下:

    • 将用户输入的数字转换为字符串,然后转换为byte数组

    • 发送“quit”字符串代表退出

    其中服务器端程序协议格式如下:

    • 反馈长度为1个字节,数字0代表相等(猜中),1代表大了,2代表小了,其它数字代表错误。

    实现该程序的代码比较多,下面分为客户端程序实现和服务器端程序实现分别进行列举。

    客户端程序实现代码如下:

    package guess;
    
    import java.net.*;
    
    import java.io.*;
    
    /**
    
     * 猜数字客户端
    
     */
    
    public class TCPClient {
    
     public static void main(String[] args) {
    
             Socket socket = null;
    
             OutputStream os = null;
    
             InputStream is = null;
    
             BufferedReader br = null;
    
             byte[] data = new byte[2];
    
             try{
    
                       //建立连接
    
                       socket = new Socket(
    
                                         "127.0.0.1",10001);
    
                      
    
                       //发送数据
    
                       os= socket.getOutputStream();
    
                      
    
                       //读取反馈数据
    
                       is = socket.getInputStream();
    
                      
    
                       //键盘输入流
    
                       br = new BufferedReader(
    
                                         new InputStreamReader(System.in));
    
                      
    
                       //多次输入
    
                       while(true){
    
                                System.out.println("请输入数字:");
    
                                //接收输入
    
                                String s = br.readLine();
    
                                //结束条件
    
                                if(s.equals("quit")){
    
                                         os.write("quit".getBytes());
    
                                         break;
    
                                }
    
                                //校验输入是否合法
    
                                boolean b = true;
    
                                try{
    
                                         Integer.parseInt(s);
    
                                }catch(Exception e){
    
                                         b = false;
    
                                }
    
                                if(b){ //输入合法
    
                                         //发送数据
    
                                         os.write(s.getBytes());
    
                                         //接收反馈
    
                                         is.read(data);
    
                                         //判断
    
                                         switch(data[0]){
    
                                         case 0:
    
                                                   System.out.println("相等!祝贺你!");
    
                                                   break;
    
                                         case 1:
    
                                                   System.out.println("大了!");
    
                                                   break;
    
                                         case 2:
    
                                                   System.out.println("小了!");
    
                                                   break;
    
                                         default:
    
                                                   System.out.println("其它错误!");
    
                                         }
    
                                         //提示猜的次数
    
                                         System.out.println("你已经猜了" + data[1] + "次!");
    
                                         //判断次数是否达到5次
    
                                         if(data[1] >= 5){
    
                                                   System.out.println("你挂了!");
    
                                                   //给服务器端线程关闭的机会
    
                                                   os.write("quit".getBytes());
    
                                                   //结束客户端程序
    
                                                   break;
    
                                         }
    
                                }else{ //输入错误
    
                                         System.out.println("输入错误!");
    
                                }
    
                       }
    
             }catch(Exception e){
    
                       e.printStackTrace();
    
             }finally{
    
                       try{
    
                                //关闭连接
    
                                br.close();
    
                                is.close();
    
                                os.close();
    
                                socket.close();
    
                       }catch(Exception e){
    
                                e.printStackTrace();
    
                       }
    
             }
    
     }
    
          }
    

    在该示例中,首先建立一个到IP地址为127.0.0.1的端口为10001的连接,然后进行各个流的初始化工作,将逻辑控制的代码放入在一个while循环中,这样可以在客户端多次进行输入。在循环内部,首先判断用户输入的是否为quit字符串,如果是则结束程序,如果输入不是quit,则首先校验输入的是否是数字,如果不是数字则直接输出“输入错误!”并继续接收用户输入,如果是数字则发送给服务器端,并根据服务器端的反馈显示相应的提示信息。最后关闭流和连接,结束客户端程序。

    服务器端程序的实现还是分为服务器控制程序和逻辑线程,实现的代码分别如下:

    package guess;
    
    import java.net.*;
    
    /**
    
     * TCP连接方式的服务器端
    
     * 实现功能:接收客户端的数据,判断数字关系
    
     */
    
    public class TCPServer {
    
     public static void main(String[] args) {
    
             try{
    
                       //监听端口
    
                       ServerSocket ss = new ServerSocket(10001);
    
                       System.out.println("服务器已启动:");
    
                       //逻辑处理
    
                       while(true){
    
                                //获得连接
    
                                Socket s = ss.accept();
    
                                //启动线程处理
    
                                new LogicThread(s);
    
                       }
    
                      
    
             }catch(Exception e){
    
                       e.printStackTrace();
    
             }
    
     }
    
         }
    
          package guess;
    
    import java.net.*;
    
    import java.io.*;
    
    import java.util.*;
    
    /**
    
     * 逻辑处理线程
    
     */
    
    public class LogicThread extends Thread {
    
          Socket s;
    
         
    
          static Random r = new Random();
    
         
    
          public LogicThread(Socket s){
    
                  this.s = s;
    
                  start(); //启动线程
    
          }
    
         
    
          public void run(){
    
                  //生成一个[0,50]的随机数
    
                  int randomNumber = Math.abs(r.nextInt() % 51);
    
                  //用户猜的次数
    
                  int guessNumber = 0;
    
                  InputStream is = null;
    
                  OutputStream os = null;
    
                  byte[] data = new byte[2];
    
                  try{
    
                           //获得输入流
    
                           is = s.getInputStream();
    
                           //获得输出流
    
                           os = s.getOutputStream();
    
                           while(true){ //多次处理
    
                                     //读取客户端发送的数据
    
                                     byte[] b = new byte[1024];
    
                                     int n = is.read(b);
    
                                     String send = new String(b,0,n);
    
                                     //结束判别
    
                                     if(send.equals("quit")){
    
                                              break;
    
                                     }
    
                                     //解析、判断
    
                                     try{
    
                                              int num = Integer.parseInt(send);
    
                                              //处理
    
                                              guessNumber++; //猜的次数增加1
    
                                              data[1] = (byte)guessNumber;
    
                                              //判断
    
                                              if(num > randomNumber){
    
                                                       data[0] = 1;
    
                                              }else if(num < randomNumber){
    
                                                       data[0] = 2;
    
                                              }else{
    
                                                       data[0] = 0;
    
                                                       //如果猜对
    
                                                       guessNumber = 0; //清零
    
                                                       randomNumber = Math.abs(r.nextInt() % 51);
    
                                              }
    
                                              //反馈给客户端
    
                                              os.write(data);                                    
    
                                             
    
                                     }catch(Exception e){ //数据格式错误
    
                                              data[0] = 3;
    
                                              data[1] = (byte)guessNumber;
    
                                              os.write(data); //发送错误标识
    
                                              break;
    
                                     }
    
                                     os.flush();   //强制发送
    
                           }
    
                          
    
                  }catch(Exception e){
    
                           e.printStackTrace();
    
                  }finally{
    
                           try{
    
                                     is.close();
    
                                     os.close();
    
                                     s.close();
    
                           }catch(Exception e){}
    
                  }
    
          }
    
    }
    

    在 该示例中,服务器端控制部分和前面的示例中一样。也是等待客户端连接,如果有客户端连接到达时,则启动新的线程去处理客户端连接。在逻辑线程中实现程序的 核心逻辑,首先当线程执行时生产一个随机数字,然后根据客户端发送过来的数据,判断客户端发送数字和随机数字的关系,然后反馈相应的数字的值,并记忆客户 端已经猜过的次数,当客户端猜中以后清零猜过的次数,使得客户端程序可以继续进行游戏。

    总体来说,该程序示例的结构以及功能都与上一个程序比较类似,希望通过比较这两个程序,加深对于网络编程的认识,早日步入网络编程的大门。

    转自:http://www.cnblogs.com/springcsc/archive/2009/12/03/1616413.html
    文章有不当之处,欢迎指正,你也可以关注我的微信公众号:好好学java,获取优质资源。

    展开全文
  • 课程内容: 本机IPC与网络IPC、回顾网络基础知识、网络通信协议与网络编程接口、TCP协议的特点、TCP通信编程举例、udp协议的特点以及代码实现、如何基于UDP实现服务器、如何使用UDP实现点对点直通、域套接字、广播/...
  • 本课程是网络编程实践部分,带大家使用socket接口及其相关函数,从头编写一个服务器和客户端的通信程序,并且引出了应用层协议和业务逻辑的概念,本课程的目的是带领大家进入网络编程的世界,为大家后续的持续学习...
  • Java网络编程精讲

    2020-08-17 20:36:33
    本课程是《零基础学Java》系列课程中的网络编程部分知识点的课程,该课程详讲述了:计算机网络原理、ip协议、socket编程、tcp网络编程、udp网络编程相关知识点! 课程风格细腻、柔和,知识点讲解由浅入深!全程手写...
  • [Linux系统编程/网络编程] 笔记目录

    千次阅读 多人点赞 2021-04-19 17:10:51
    Linux系统编程 [Linux系统编程]文件IO(一) [Linux系统编程]进程(二) [Linux系统编程]信号(三) ...[Linux系统编程]守护进程/线程...[Linux网络编程]高并发-Select模型 [Linux网络编程]高并发-Epoll模型 .
  • 嵌入式LInux网络编程

    千人学习 2015-12-02 14:22:39
    本课程讲解网络编程基础知识,UDP与TCP网络编程步骤,基于多线程的网络聊天程序。
  • C#网络编程(Socket编程)

    千次阅读 多人点赞 2020-04-02 09:45:27
    一、Socket网络编程 1.Socket是什么? 在计算机通信领域,Socket被译为“套接字”。它是计算机之间进行通信的一种约定或一种方式。通过Socket这种约定可以接收到其他计算机的数据,也可以向其他计算机发送数据。 2....
  • 本课程是网络编程部分的前奏,主要讲解了网络相关的一些基础知识,譬如网络编程的架构,网卡、路由器、集线器、交换机、DHCP、NAT等概念,学习这些的目的是对网络通信原理有一定了解方便下个课程继续学习socket编程...
  • unix网络编程源码

    千次下载 热门讨论 2014-07-30 10:31:09
    unix网络编程源码,包括unpipc.h头文件
  • C++网络编程进阶

    千人学习 2017-09-20 00:06:32
    C/S模型在企业级的软件开发中非常常见,高并发网络通讯更是很多项目的核心模块,学好socket 网络编程显得尤为重要,本课程分为TCP/IP 协议、socket基础与进阶的异步通讯模型三个部分,通过浅显易懂的代码与讲解,让...
  • C++网络编程

    万次阅读 多人点赞 2017-01-24 00:10:11
    C++网络编程
  • C++ Socket网络编程

    2016-03-17 10:02:00
    本来搜集到的一些 关于C++ 网络编程 Socket套接字的一些资源
  • Java网络编程第三版.pdf

    千次下载 热门讨论 2010-11-28 20:08:56
    Java网络编程第三版.pdf
  • 陈硕 网络编程实战 视频

    热门讨论 2017-04-13 14:37:54
    陈硕于博览网所讲授的网络编程课程 别人录屏而来的 只有某一讲他录错了 其他没有任何问题
  • 《3.linux应用编程和网络编程》总计9个课程(79节、41小时),结构上分为应用编程和网络编程2部分。本课程主要讲解linux的各种API接口及其实践编程训练,如文件读写、异步IO、多路复用IO、进程、进程间通信、线程、...
  • Windows Sockets网络编程pdf

    千次下载 热门讨论 2013-04-29 22:26:37
    Windows Sockets网络编程,Bob著,徐磊等译,扫描版。Windows Sockets网络编程经典著作!Windows Sockets 2规范解释小组负责人亲自执笔,对Windows Sockets规范进行深刻解读!pdf格式!在网上搜集的!在这分享下!
  • Java网络编程学习汇总

    万次阅读 2021-03-02 11:32:13
    Java网络编程(一)概念 未完待续!!!!! 写作不易,如果您觉得写的不错,欢迎给博主点赞、收藏、评论、收藏来一波~让博主更有动力吧! 路漫漫其修远兮,吾必将上下求索~
  • Java网络编程(一)网络的基本认知

    万次阅读 2021-03-02 10:07:59
    ① 收件人: 相当于网络编程的ip地址+端口号,定位到某一个具体的地方!只有找对地方才可以成功发送对吧? ②内容: 相当于网络编程中的数据包。也就是具体发送的内容。 ③内容语言: 相当于是网络协议,比方说我发...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,023,281
精华内容 409,312
关键字:

网络编程