13.1 网络概述
网络编程技术是当前一种主流的编程技术,随着联网趋势的逐步增强以及网络应用程序的大量出现,
所以在实际的开发中网络编程技术获得了大量的使用。本章中以浅 显的基础知识说明和实际的案例使广大初学者能够进入网络编程技术的大门,
至于以后的实际修行就要阅读进阶的书籍以及进行大量的实际练习。
13.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地址和端口的概念还是不够的,下面就介绍一下基础的网络编程相关的软件基础知识。
13.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地址、端口和协议等概念,将会极大的有助于后续知识的学习。
13.1.3 网络通讯方式
在现有的网络中,网络通讯的方式主要有两种:
1、 TCP(传输控制协议)方式
2、 UDP(用户数据报协议)方式
为了方便理解这两种方式,还是先来看一个例子。大家使用手机时,向别人传递信息时有两种方式:拨打电话和发送短信。使用拨打电话的方式可以保证将信息传递给别人,因为别人接听电话时本身就确认接收到了该信息。而发送短信的方式价格低廉,使用方便,但是接收人有可能接收不到。
在网络通讯中,TCP方式就类似于拨打电话,使用该种方式进行网络通讯时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据。而UDP方式就类似于发送短信,使用这种方式进行网络通讯时,不需要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则客户端无法获得。
这两种传输方式都是实际的网络编程中进行使用,重要的数据一般使用TCP方式进行数据传输,而大量的非核心数据则都通过UDP方式进行传递,在一些程序中甚至结合使用这两种方式进行数据的传递。
由于TCP需要建立专用的虚拟连接以及确认传输是否正确,所以使用TCP方式的速度稍微慢一些,而且传输时产生的数据量要比UDP稍微大一些。
关于网络编程的基础知识就介绍这么多,如果需要深入了解相关知识请阅读专门的计算机网络书籍,下面开始介绍Java语言中网络编程的相关技术。
13.2 网络编程技术
前面介绍了网络编程的相关基础知识,初步建立了网络编程的概念,但是实际学习网络编程还必须使用某种程序设计语言进行代码实现,下面就介绍一下网络编程的代码实现。
13.2.1 网络编程步骤
按照前面的基础知识介绍,无论使用TCP方式还是UDP方式进行网络通讯,网络编程都是由客户端和服务器端组成。当然,B/S结构的编程中只需要实现服务器端即可。所以,下面介绍网络编程的步骤时,均以C/S结构为基础进行介绍。
说明:这里的步骤实现和语言无关,也就是说,这个步骤适用于各种语言实现,不局限于Java语言。
13.2.1.1 客户端网络编程步骤
客户端(Client)是指网络编程中首先发起连接的程序,客户端一般实现程序界面和基本逻辑实现,在进行实际的客户端编程时,无论客户端复杂还是简单,以及客户端实现的方式,客户端的编程主要由三个步骤实现:
1、 建立网络连接
客户端网络编程的第一步都是建立网络连接。在建立网络连接时需要指定连接到的服务器的IP地址和端口号,建立完成以后,会形成一条虚拟的连接,后续的操作就可以通过该连接实现数据交换了。
2、 交换数据
连接建立以后,就可以通过这个连接交换数据了。交换数据严格按照请求响应模型进行,由客户端发送一个请求数据到服务器,服务器反馈一个响应数据给客户端,如果客户端不发送请求则服务器端就不响应。
根据逻辑需要,可以多次交换数据,但是还是必须遵循请求响应模型。
3、 关闭网络连接
在数据交换完成以后,关闭网络连接,释放程序占用的端口、内存等系统资源,结束网络编程。
最基本的步骤一般都是这三个步骤,在实际实现时,步骤2会出现重复,在进行代码组织时,由于网络编程是比较耗时的操作,所以一般开启专门的现场进行网络通讯。
13.2.1.2 服务器端网络编程步骤
服务器端(Server)是指在网络编程中被动等待连接的程序,服务器端一般实现程序的核心逻辑以及数据存储等核心功能。服务器端的编程步骤和客户端不同,是由四个步骤实现,依次是:
1、 监听端口
服务器端属于被动等待连接,所以服务器端启动以后,不需要发起连接,而只需要监听本地计算机的某个固定端口即可。
这个端口就是服务器端开放给客户端的端口,服务器端程序运行的本地计算机的IP地址就是服务器端程序的IP地址。
2、 获得连接
当客户端连接到服务器端时,服务器端就可以获得一个连接,这个连接包含客户端的信息,例如客户端IP地址等等,服务器端和客户端也通过该连接进行数据交换。
一般在服务器端编程中,当获得连接时,需要开启专门的线程处理该连接,每个连接都由独立的线程实现。
3、 交换数据
服务器端通过获得的连接进行数据交换。服务器端的数据交换步骤是首先接收客户端发送过来的数据,然后进行逻辑处理,再把处理以后的结果数据发送给客户端。简单来说,就是先接收再发送,这个和客户端的数据交换数序不同。
其实,服务器端获得的连接和客户端连接是一样的,只是数据交换的步骤不同。
当然,服务器端的数据交换也是可以多次进行的。
在数据交换完成以后,关闭和客户端的连接。
4、 关闭连接
当服务器程序关闭时,需要关闭服务器端,通过关闭服务器端使得服务器监听的端口以及占用的内存可以释放出来,实现了连接的关闭。
其实服务器端编程的模型和呼叫中心的实现是类似的,例如移动的客服电话10086就是典型的呼叫中心,当一个用户拨打10086时,转接给一个专门的客服人员,由该客服实现和该用户的问题解决,当另外一个用户拨打10086时,则转接给另一个客服,实现问题解决,依次类推。
在服务器端编程时,10086这个电话号码就类似于服务器端的端口号码,每个用户就相当于一个客户端程序,每个客服人员就相当于服务器端启动的专门和客户端连接的线程,每个线程都是独立进行交互的。
这就是服务器端编程的模型,只是TCP方式是需要建立连接的,对于服务器端的压力比较大,而UDP是不需要建立连接的,对于服务器端的压力比较小罢了。
13.2.1.3 小结
总之,无论使用任何语言,任何方式进行基础的网络编程,都必须遵循固定的步骤进行操作,在熟悉了这些步骤以后,可以根据需要进行逻辑上的处理,但是还是必须遵循固定的步骤进行。
其实,基础的网络编程本身不难,也不需要很多的基础网络知识,只是由于编程的基础功能都已经由API实现,而且需要按照固定的步骤进行,所以在入门时有一定的门槛,希望下面的内容能够将你快速的带入网络编程技术的大门。
13.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地址进行实现。
13.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){}
}
}
}
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