
学习内容总结
网络编程
网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据。程序员所作的事情就是把数据发送到指定的位置,或者接收到指定的数据,这个就是狭义的网络编程范畴。在发送和接收数据时,大部分的程序设计语言都设计了专门的API实现这些功能,程序员只需要调用即可。所以,基础的网络编程可以和打电话一样简单。
13.1 网络概述
网 络编程技术是当前一种主流的编程技术,随着联网趋势的逐步增强以及网络应用程序的大量出现,所以在实际的开发中网络编程技术获得了大量的使用。
13.1.1 计算机网络概述
网络编程的实质就是两个(或多个)设备(例如计算机)之间的数据传输。
按照计算机网络的定义,通过一定的物理设备将处于不同位置的计算机连接起来组成的网络,这个网络中包含的设备有:计算机、路由器、交换机等等。
为了能够方便的识别网络上的每个设备,网络中的每个设备都会有一个唯一的数字标识,这个就是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)。
13.1. 2 网络编程概述
按照前面的介绍,网络编程就是两个或多个设备之间的数据交换,其实更具体的说,网络编程就是两个或多个程序之间的数据交换,和普通的单机程序相比,网络程序最大的不同就是需要交换数据的程序运行在不同的计算机上,这样就造成了数据交换的复杂。虽然通过IP地址和端口可以找到网络上运行的一个程序,但是如果需要进行网络编程,则还需要了解网络通讯的过程。
在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序,简称客户端,而在第一次通讯中等待连接的程序被称作服务器端(Server)程序,简称服务器。一旦通讯建立,则客户端和服务器端完全一样,没有本质的区别。
由此,网络编程中的两种程序就分别是客户端和服务器端,例如QQ程序,每个QQ用户安装的都是QQ客户端程序,而QQ服务器端程序则运行在腾讯公司的机房中,为大量的QQ用户提供服务。这种网络编程的结构被称作客户端/服务器结构,也叫做Client/Server结构,简称C/S结构。
使用C/S结 构的程序,在开发时需要分别开发客户端和服务器端,这种结构的优势在于由于客户端是专门开发的,所以根据需要实现各种效果,专业点说就是表现力丰富,而服 务器端也需要专门进行开发。但是这种结构也存在着很多不足,例如通用性差,几乎不能通用等,也就是说一种程序的客户端只能和对应的服务器端通讯,而不能和 其它服务器端通讯,在实际维护时,也需要维护专门的客户端和服务器端,维护的压力比较大。
其实在运行很多程序时,没有必要使用专用的客户端,而需要使用通用的客户端,例如浏览器,使用浏览器作为客户端的结构被称作浏览器/服务器结构,也叫做Browser/Server结构,简称为B/S结构。
13.1.3 网络通讯方式
在现有的网络中,网络通讯的方式主要有两种:
1、 TCP(传输控制协议)方式
2、 UDP(用户数据报协议)方式
13.2 网络编程技术
13.2.1 网络编程步骤
13.2.1.1 客户端网络编程步骤
1、 建立网络连接
2、 交换数据
3、 关闭网络连接
最基本的步骤一般都是这三个步骤,在实际实现时,步骤2会出现重复,在进行代码组织时,由于网络编程是比较耗时的操作,所以一般开启专门的现场进行网络通讯。
13.2.1.2 服务器端网络编程步骤
1、 监听端口
2、 获得连接
3、 交换数据
4、 关闭连接
13.2.1.3 小结
13.2.2 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类的基本使用,并使用了该类中的几个常用方法,该代码的执行结果是:
13.2.3 TCP编程
在客户端网络编程中,首先需要建立连接,在Java API中以java.net.Socket类的对象代表网络连接,所以建立客户端网络连接,也就是创建Socket类型的对象,该对象代表网络连接,示例如下:
Socket socket1 = new Socket(“192.168.1.103”,10000);
Socket socket2 = new Socket(“www.sohu.com”,80);
连接一旦建立,则完成了客户端编程的第一步,紧接着的步骤就是按照“请求-响应”模型进行网络数据交换,在Java语言中,数据传输功能由Java IO实现,也就是说只需要从连接中获得输入流和输出流即可,然后将需要发送的数据写入连接对象的输出流中,在发送完成以后从输入流中读取数据即可。示例代码如下:
OutputStream os = socket1.getOutputStream(); //获得输出流
InputStream is = socket1.getInputStream(); //获得输入流
最后当数据交换完成以后,关闭网络连接,释放网络连接占用的系统端口和内存等资源,完成网络操作,示例代码如下:
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) {}
}
}
}
如果需要在控制台下面编译和运行该代码,需要首先在控制台下切换到源代码所在的目录,然后依次输入编译和运行命令:
javac –d . SimpleSocketClient.java
java tcp.SimpleSocketClient
在服务器端程序编程中,由于服务器端实现的是被动等待连接,所以服务器端编程的第一个步骤是监听端口,也就是监听是否有客户端连接到达。实现服务器端监听的代码为:
ServerSocket ss = new ServerSocket(10000);
服务器端编程的第二个步骤是获得连接。该步骤的作用是当有客户端连接到达时,建立一个和客户端连接对应的Socket连 接对象,从而释放客户端连接对于服务器端端口的占用。实现功能就像公司的前台一样,当一个客户到达公司时,会告诉前台我找某某某,然后前台就通知某某某, 然后就可以继续接待其它客户了。通过获得连接,使得客户端的连接在服务器端获得了保持,另外使得服务器端的端口释放出来,可以继续等待其它的客户端连接。 实现获得连接的代码是:
Socket socket = ss.accept();
最后,在服务器端通信完成以后,关闭服务器端连接。实现的代码为:
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) {}
}
}
}
如果还是使用前面示例代码中的服务器端程序运行该程序,则该程序的结果是:
按照客户端实现的逻辑,也可以复用服务器端的连接,实现的原理也是将服务器端的数据交换逻辑写在循环中即可,按照该种思路改造以后的服务器端代码为:
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){}
}
}
}
以该服务器端代码实现为基础运行前面的客户端程序时,客户端的输出为:
服务器反馈:First
服务器反馈:Second
服务器反馈:Third
服务器端程序的输出结果为:
服务器已启动:
客户端发送内容为:First
客户端发送内容为:Second
客户端发送内容为:Third
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){}
}
}
}
下面再看一下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;
}
}
在Java API中,实现UDP方式的编程,包含客户端网络编程和服务器端网络编程,主要由两个类实现,分别是:
DatagramSocket
DatagramPacket
首先介绍UDP方式的网络编程中建立连接的实现。其中UDP方式的建立连接和TCP方式不同,只需要建立一个连接对象即可,不需要指定服务器的IP和端口号码。实现的代码为:
DatagramSocket ds = new DatagramSocket();
当然,可以通过制定连接使用的端口号来创建客户端连接。
DatagramSocket ds = new DatagramSocket(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);
下面介绍一下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);
UDP方式客户端网络编程的最后一个步骤就是关闭连接。虽然UDP方式不建立专用的虚拟连接,但是连接对象还是需要占用系统资源,所以在使用完成以后必须关闭连接。关闭连接使用连接对象中的close方法即可,实现的代码如下:
ds.close();
首先UDP方式服务器端网络编程需要建立一个连接,该连接监听某个端口,实现的代码为:
DatagramSocket ds = new DatagramSocket(10010);
接收到客户端发送过来的数据以后,服务器端对该数据进行逻辑处理,然后将处理以后的结果再发送给客户端,在这里发送时就比客户端要麻烦一些,因为服务器端需要获得客户端的IP和客户端使用的端口号,这个都可以从接收到的数据包中获得。示例代码如下:
//获得客户端的IP
InetAddress clientIP = receiveDp.getAddress();
//获得客户端的端口号
Int clientPort = receiveDp.getPort();
最后,当服务器端实现完成以后,关闭服务器端连接,实现的方式为调用连接对象的close方法,示例代码如下:
ds.close();
该示例的功能是实现将客户端程序的系统时间发送给服务器端,服务器端接收到时间以后,向客户端反馈字符串“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){}
}
}
}
下面是该示例程序的服务器端代码实现:
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){}
}
}
}
下面是实现客户端多次发送以及服务器端支持多个数据包同时处理的程序结构,实现的原理和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){}
}
}
}
实现该逻辑的服务器端程序代码如下:
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){}
}
}
}
实现逻辑处理的线程代码如下:
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();
}
}
}
网络协议
客户端程序需要完成的处理为:
1、 客户端发送协议格式的生成
2、 服务器端反馈数据格式的解析
服务器端程序需要完成的处理为:
1、 服务器端反馈协议格式的生成
2、 客户端发送协议格式的解析
13.2.6小结
网络编程示例
13.3.1质数判别示例
网络程序的功能都分为客户端程序和服务器端程序实现,下面先描述一下每个程序分别实现的功能:
1、 客户端程序功能:
a) 接收用户控制台输入
b) 判断输入内容是否合法
c) 按照协议格式生成发送数据
d) 发送数据
e) 接收服务器端反馈
f) 解析服务器端反馈信息,并输出
2、 服务器端程序功能:
a) 接收客户端发送数据
b) 按照协议格式解析数据
c) 判断数字是否是质数
d) 根据判断结果,生成协议数据
e) 将数据反馈给客户端
以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();
}
}
}
以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){}
}
}
猜数字小游戏
客户端程序功能列表:
1、 接收用户控制台输入
2、 判断输入内容是否合法
3、 按照协议格式发送数据
4、 根据服务器端的反馈给出相应提示
服务器端程序功能列表:
1、 接收客户端发送数据
2、 按照协议格式解析数据
3、 判断发送过来的数字和随机数字的关系
4、 根据判断结果生产协议数据
5、 将生产的数据反馈给客户端
其中客户端程序协议格式如下:
1、 将用户输入的数字转换为字符串,然后转换为byte数组
2、 发送“quit”字符串代表退出
其中服务器端程序协议格式如下:
1、 反馈长度为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();
}
}
}
}
服务器端程序的实现还是分为服务器控制程序和逻辑线程,实现的代码分别如下:
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){}
}
}
}
代码调试中的问题和解决过程
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){}
}
}
}
运行了这个程序之后,为什么我的结果中没有OK
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();
}
}
}
为什么程序明明是一样的,结果却显示不出来
本周代码截图
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | 基本掌握JAVA |
第一周 | 200/200 | 1/2 | 20/20 | 了解基本的JAVA |
第二周 | 250/450 | 2/4 | 20/40 | 了解基础语法 |
第三周 | 500/950 | 1/5 | 20/60 | 了解JAVA语法核心 |
第四周 | 869/1819 | 1/6 | 30/90 | 了解继承、接口、多态的知识 |
第五周 | 918/2737 | 1/7 | 25/115 | 了解了很多异常处理的知识 |
第六周 | 1474/4211 | 2/9 | 30/ 145 | 了解输入输出、线程 |
第七周 | 586/4797 | 2/11 | 35/180 | 认识了JAVA中的时间与日期 |
第八周 | 442/5239 | 2/13 | 35/215 | 了解了NIO和通用API |
第九周 | 1597/6836 | 2/15 | 35/250 | 主要了解数据库 |
第十周 | 0/6836 | 2/17 | 35/285 | 了解JAVA的网络编程 |