一. 前言
网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯。
网络编程中有两个主要的问题 :
- 一个是如何准确的定位网络上一台或多台主机[【TCP/IP】
- 一个就是找到主机后如何可靠高效的进行数据传输。【TCP/IP VS UDP】
在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。
目前较为流行的网络编程模型是客户机/服务器(C/S)结构。即通信双方一方作为服务器等待客户提出请求并予以响应。客户则在需要服务时向服务器提出申请。服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也能及时得到服务。
学习网络编程前还得先把计算机网络的基础知识复习一下,先能回答下面七个问题:
1. 什么是TCP/IP协议?
TCP是Tranfer Control Protocol的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。
2. TCP/IP有哪两种传输协议,各有什么特点?
尽管TCP/IP协议的名称中只有TCP这个协议名,但是在TCP/IP的传输层同时存在TCP和UDP两个协议。
UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
下面我们对这两种协议做简单比较:
使用UDP时,每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。对于TCP协议,由于它是一个面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中多了一个连接建立的时间。
使用UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。而TCP没有这方面的限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大量的数据。UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方。而TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。
总之,TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。相比之下UDP操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序。
既然有了保证可靠传输的TCP协议,为什么还要非可靠传输的UDP协议呢?主要的原因有两个。一是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,因此TCP传输的效率不如UDP高。二是在许多应用中并不需要保证严格的传输可靠性,比如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。
3. 什么是URL?
统一资源定位器URL
URL(Uniform Resource Locator)是统一资源定位器的简称,它表示Internet上某一资源的地址。通过URL我们可以访问Internet上的各种网络资源,比如最常见的WWW,FTP站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。
4. URL和IP地址有什么样的关系?
形象点说:
IP就是一个全球唯一的门牌号,每个上网的设备一旦接入网络,即会由网络分配给一个唯一的号码;
URL是一个全球唯一的企业或个人名称,相当于域名
URL必须通过DNS服务器把它转换成门牌号(即IP),才能找到放在哪架网上的服务器上。
5. 什么叫套接字(Socket)?
Socket通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。
6. 套接字(Socket)和TCP/IP协议的关系?
套接字的作用是网络编程的一个接口,底层使用的是TCP/IP协议
7. URL和套接字(Socket)的关系?
利用socket进行通信时,服务器端的程序可以打开多个线程与多个客户进行通信,还可以通过服务器使各个客户之间进行通信。这种方式比较灵活,适用于一些较复杂的通信,但是服务器端的程序必须始终处于运行状态以监听端口。
利用 URL进行通信时,服务器端的程序只能与一个客户进行通信,形式比较单一。但是它不需要服务器端的CGI程序一直处于运行状态,只是在有客户申请时才被激活。所以,这种方式比较适用于客户机的浏览器与服务器之间的通信。
二. 基于URL的Java网络编程
1.创建一个URL
为了表示URL, java.NET中实现了类URL。我们可以通过下面的构造方法来初始化一个URL对象:
(1) public URL (String spec);//通过一个表示URL地址的字符串可以构造一个URL对象
URL urlBase=new URL("http://www.data520.cn/")
(2) public URL(URL context, String spec);//通过基URL和相对URL构造一个URL对象。
URL net263=new URL ("http://www.data520.cn/");
URL index263=new URL(data520, "index.html")
(3) public URL(String protocol, String host, String file);
new URL("http", "www.gamelan.com", "/pages/Gamelan.net. html");
(4) public URL(String protocol, String host, int port, String file);
URL gamelan=new URL("http", "www.gamelan.com", 80, "Pages/Gamelan.network.html");
注意:类URL的构造方法都声明抛弃非运行时例外(MalformedURLException),因此生成URL对象时,我们必须要对这一例外进行处理,通常是用try-catch语句进行捕获。格式如下:
try{
URL myURL= new URL(…)
}
catch (MalformedURLException e){
}
2. 解析一个URL
一个URL对象生成后,其属性是不能被改变的,但是我们可以通过类URL所提供的方法来获取这些属性:
public String getProtocol() 获取该URL的协议名。
public String getHost() 获取该URL的主机名。
public int getPort() 获取该URL的端口号,如果没有设置端口,返回-1。
public String getFile() 获取该URL的文件名。
public String getRef() 获取该URL在文件中的相对位置。
public String getQuery() 获取该URL的查询信息。
public String getPath() 获取该URL的路径
public String getAuthority() 获取该URL的权限信息
public String getUserInfo() 获得使用者的信息
public String getRef()获得该URL的锚
3.从URL读取WWW网络资源
当我们得到一个URL对象后,就可以通过它读取指定的WWW资源。这时我们将使用URL的方法openStream(),其定义为:InputStream openStream();方法openSteam()与指定的URL建立连接并返回InputStream类的对象以从这一连接中读取数据。
package com.data520.test;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
public class URLReader {
public static void main(String[] args) throws Exception {
URL url = new URL("http://www.data520.cn");
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);
}
in.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
4.通过URLConnetction连接WWW
通过URL的方法openStream(),我们只能从网络上读取数据,如果我们同时还想输出数据,例如向服务器端的CGI程序发送一些数据,我们必须先与URL建立连接,然后才能对其进行读写,这时就要用到类URLConnection了。CGI是公共网关接口(Common Gateway Interface)的简称,它是用户浏览器和服务器端的应用程序进行连接的接口,有关CGI程序设计,请读者参考有关书籍。
类URLConnection也在包java.net中定义,它表示Java程序和URL在网络上的通信连接。当与一个URL建立连接时,首先要在一个URL对象上通过方法openConnection()生成对应的URLConnection对象。例如下面的程序段首先生成一个指向地址http://blog.csdn.net/shengmingqijiquan的对象,然后用openConnection()打开该URL对象上的一个连接,返回一个URLConnection对象。如果连接过程失败,将产生IOException
Try{
URL netchinaren = new URL ("http://blog.csdn.net/shengmingqijiquan");
URLConnectonn tc = netchinaren.openConnection();
}catch(MalformedURLException e){
…
}catch (IOException e){
…
}
类URLConnection提供了很多方法来设置或获取连接参数,程序设计时最常使用的是getInputStream()和getOurputStream(),其定义为:
InputSteram getInputSteram();
OutputSteram getOutputStream();
通过返回的输入/输出流我们可以与远程对象进行通信。看下面的例子:
package com.data520.test;
import java.io.DataInputStream;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLConnection;
public class URLReader {
public static void main(String[] args) throws Exception {
URL url=new URL("http://blog.csdn.net/shengmingqijiquan");
URLConnection conn=url.openConnection();
DataInputStream dis=new DataInputStream(conn.getInputStream());
PrintStream ps=new PrintStream(conn.getOutputStream());
String line=dis.readLine();
ps.println("client…");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
其中backwards为服务器端的CGI程序。实际上,类URL的方法openSteam()是通过URLConnection来实现的。它等价于openConnection().getInputStream();
基于URL的网络编程在底层其实还是基于下面要讲的Socket接口的。WWW,FTP等标准化的网络服务都是基于TCP协议的,所以本质上讲URL编程也是基于TCP的一种应用.
三.基于Socket的Java网络编程
网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket通常用来实现客户方和服务方的连接。一个Socket由一个IP地址和一个端口号唯一确定。在传统的UNIX环境下可以操作TCP/IP协议的接口不止Socket一个,Socket所支持的协议种类也不光TCP/IP一种,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。
1.Socket通讯的一般过程
使用Socket进行Client/Server程序设计的一般连接过程是这样的:
- Server端Listen(监听)某个端口是否有连接请求
- Client端向Server端发出Connect(连接)请求
- Server端向Client端发回Accept(接受)消息。
一个连接就建立起来了。Server端和Client端都可以通过Send,Write等方法与对方通信。
对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:
1. 创建ServerSocket和Socket
2. 打开连接Socket的输入/输出流
3. 按照协议(通常是TCP/UDP)对Socket进行读写操作
4. 关闭输入/输出流,关闭Socket
Socket通信模型如下图所示
2.Server服务器端:
a、创建ServerSocket对象,同时绑定监听端口
b、通过accept()方法监听客户端的请求
c、建立连接后,通过输入流读取客户端发送的请求信息
d、通过输出流向客户端发送响应信息
e、关闭相应资源
package com.data520;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
/**
* 基于TCP协议的服务端Socket通信
* 服务器端必须早于客户端启动
*
*/
public class ServerSocketDemo {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(5555);
System.out.println("===================服务器即将启动,等待客户端的连接===============");
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String data = null;
while(null != (data = br.readLine())){
System.out.println(new Date());
System.out.println("我是服务器端,客户端说:"+data);
}
socket.shutdownInput();
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("用户名和密码输入正确");
pw.flush();
socket.shutdownOutput();
pw.close();
os.close();
br.close();
isr.close();
is.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
运行结果:
服务器端:
3.Client客户端:
a、创建Socket对象,指明需要连接的服务器的地址和端口号
b、建立连接后,通过输出流向服务器端发送请求信息
c、通过输入流获取服务器的响应信息
d、关闭相应资源
package com.data520;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Date;
/**
* 基于TCP协议的客户端Socket通信
*
*/
public class ClientSocketDemo {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 5555);
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
StringBuffer bf = new StringBuffer();
bf.append("用户名:").append("admin");
bf.append("密码:").append("123");
pw.write(bf.toString());
pw.flush();
socket.shutdownOutput();
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String data = null;
while(null != (data=br.readLine())){
System.out.println(new Date());
System.out.println("我是客户端,服务器端说:"+data);
}
socket.shutdownInput();
br.close();
isr.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
运行结果:
客户端
四.Socket多线程编程
之前在前面已经介绍了Socket通信的一些基本原理,以及如何让客户端与服务器端建立通信,和实现通信的一些基本步骤(包括首先使得服务器端与客户端建立连接,建立连接之后,服务器端开始侦听客户端的请求,侦听到客户端的请求之后,通过输入输出流处理相关信息实现通信,最后通信完毕结束通信等一系列流程)。
但是之前只是单个客户端与服务器进行通信,而我们实际应用中单个客户端的情况几乎不存在,都是多个客户端同时与服务器进行交互(这里同时交互就会出现并发性的问题,对于并发性的问题暂时还不是很懂,只知道有这个概念),那就需要服务器端不停的侦听客户端的请求(写在while循环中,并且条件始终为true,死循环),每当侦听到一个客户端的请求时,都需要创建一个Socket与之建立通信(通过线程实现,每当侦听到一个客户端的请求,服务端都要单独开辟一条线程与之进行通信)。
1、服务端Socket,(这里面通过死循环让服务器端一直循环侦听来自客户端的请求)
需要注意的是,服务端必须要先于客户端启动,因为要启动之后才能侦听客户端的请求
package com.socket;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务器端Socket
*
*/
public class ServerSocketMany {
/**
* main测试方法
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException{
System.out.println("服务器已经启动,等待客户端的连接....");
ServerSocket server = new ServerSocket(5555);
int count = 0;
Socket socket = null;
while(true){
socket = server.accept();
Thread serverThread = new ServerThread(socket);
serverThread.start();
count++;
System.out.println("当前链接的客户端的数量为:"+count+"个....");
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
2.服务器端的线程实现类,线程中主要是实现与客户端的通信(通过输入输出流接收并响应数据信息)
package com.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
/**
* 创建一个服务端线程,循环侦听客户端的请求,多个客户端
*
* /
public class ServerThread extends Thread{
public Socket socket = null;
public ServerThread(){}
public ServerThread(Socket socket){
this.socket = socket;
}
public void run(){
InputStream in = null;
InputStreamReader isr = null;
BufferedReader br = null;
OutputStream out = null;
PrintWriter pw = null;
try {
in = socket.getInputStream();
isr = new InputStreamReader(in);
br = new BufferedReader(isr);
String data = null;
while((data = br.readLine()) != null){
System.out.println("我是服务器,客户端说:"+data);
}
socket.shutdownInput();
out = socket.getOutputStream();
pw = new PrintWriter(out);
pw.write("用户名和密码正确");
pw.flush();
socket.shutdownOutput();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(null != pw)
pw.close();
if(null != out)
out.close();
if(null != br)
br.close();
if(null != isr)
isr.close();
if(null != in)
in.close();
if(null != socket)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
3.客户端Socket,这个和单客户端的情况一样
package com.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 客户端Socket
*
*/
public class ClientSocketDemo {
/**
* main测试方法
* @param args
* @throws UnknownHostException
* @throws IOException
*/
public static void main(String[] args) throws UnknownHostException, IOException{
String address = "localhost";
int port = 5555;
Socket socket = new Socket(address, port);
OutputStream out = socket.getOutputStream();
PrintWriter pw = new PrintWriter(out);
pw.print("用户名:green,密码:222");
pw.flush();
socket.shutdownOutput();
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
String data = null;
while((data = br.readLine()) != null){
System.out.println("我是客户端,服务器说:"+data);
}
socket.shutdownInput();
br.close();
isr.close();
in.close();
pw.close();
out.close();
socket.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
运行结果:
服务器端:
客户端:
参考:
http://blog.csdn.net/hu1991die/article/details/41210957
http://blog.csdn.net/csh624366188/article/details/7331716