Java网络编程

Java网络编程

Java的基本网络支持

使用InetAddress

Java提供了InteAddress类来代表IP地址,没有提供构造器,而是提供了如下两个静态方法来获取InetAddress实例。

  • getByName(String host)
  • getByAddress(byte[] addr)
  • getLocalHost,获取本机IP地址对应的实例。

获取信息:

  • String getCanonicalHostName()
  • String getHostAddress()
  • String getHostName()
  • boolean isReachable()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class InetAddressTest
{
public static void main(String[] args)
throws Exception
{
// 根据主机名来获取对应的InetAddress实例
InetAddress ip = InetAddress.getByName("www.crazyit.org");
// 判断是否可达
System.out.println("crazyit是否可达:" + ip.isReachable(2000));
// 获取该InetAddress实例的IP字符串
System.out.println(ip.getHostAddress());
// 根据原始IP地址来获取对应的InetAddress实例
InetAddress local = InetAddress.getByAddress(
new byte[]{127,0,0,1});
System.out.println("本机是否可达:" + local.isReachable(5000));
// 获取该InetAddress实例对应的全限定域名
System.out.println(local.getCanonicalHostName());
}
}

使用URLDecoder和URLEncoder

普通字符串和application/x-www-form-urlencode MIME字符串的转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class URLDecoderTest
{
public static void main(String[] args)
throws Exception
{

String keyWord = URLDecoder.decode(
"%B7%E8%BF%F1java", "GBK");
System.out.println(keyWord);

String urlStr = URLEncoder.encode(
"疯狂java讲义" , "GBK");
System.out.println(urlStr);
}
}

URL,URLConnection和URLPermission

URL对象代表统一资源定位器,是指向互联网资源指针。资源可以是简单的文件,也可是更加复杂的引用。通常URL有协议名、主机、端口和资源组成。
protocol:://host:port/resourceName

  • String getFile()
  • String getHost()
  • String getPath()
  • int getPort()
  • String getProtocol()
  • String getQuery()
  • URLConnection openConnection()
  • InputStream openStream()

一个简单的多线程下载工具类:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
public class DownUtil
{
// 定义下载资源的路径
private String path;
// 指定所下载的文件的保存位置
private String targetFile;
// 定义需要使用多少线程下载资源
private int threadNum;
// 定义下载的线程对象
private DownThread[] threads;
// 定义下载的文件的总大小
private int fileSize;

public DownUtil(String path, String targetFile, int threadNum)
{
this.path = path;
this.threadNum = threadNum;
// 初始化threads数组
threads = new DownThread[threadNum];
this.targetFile = targetFile;
}

public void download() throws Exception
{
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("GET");
conn.setRequestProperty(
"Accept",
"image/gif, image/jpeg, image/pjpeg, image/pjpeg, "
+ "application/x-shockwave-flash, application/xaml+xml, "
+ "application/vnd.ms-xpsdocument, application/x-ms-xbap, "
+ "application/x-ms-application, application/vnd.ms-excel, "
+ "application/vnd.ms-powerpoint, application/msword, */*");
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("Connection", "Keep-Alive");
// 得到文件大小
fileSize = conn.getContentLength();
conn.disconnect();
int currentPartSize = fileSize / threadNum + 1;
RandomAccessFile file = new RandomAccessFile(targetFile, "rw");
// 设置本地文件的大小
file.setLength(fileSize);
file.close();
for (int i = 0; i < threadNum; i++)
{
// 计算每条线程的下载的开始位置
int startPos = i * currentPartSize;
// 每个线程使用一个RandomAccessFile进行下载
RandomAccessFile currentPart = new RandomAccessFile(targetFile,
"rw");
// 定位该线程的下载位置
currentPart.seek(startPos);
// 创建下载线程
threads[i] = new DownThread(startPos, currentPartSize,
currentPart);
// 启动下载线程
threads[i].start();
}
}

// 获取下载的完成百分比
public double getCompleteRate()
{
// 统计多条线程已经下载的总大小
int sumSize = 0;
for (int i = 0; i < threadNum; i++)
{
sumSize += threads[i].length;
}
// 返回已经完成的百分比
return sumSize * 1.0 / fileSize;
}

private class DownThread extends Thread
{
// 当前线程的下载位置
private int startPos;
// 定义当前线程负责下载的文件大小
private int currentPartSize;
// 当前线程需要下载的文件块
private RandomAccessFile currentPart;
// 定义已经该线程已下载的字节数
public int length;

public DownThread(int startPos, int currentPartSize,
RandomAccessFile currentPart)
{
this.startPos = startPos;
this.currentPartSize = currentPartSize;
this.currentPart = currentPart;
}

@Override
public void run()
{
try
{
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url
.openConnection();
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("GET");
conn.setRequestProperty(
"Accept",
"image/gif, image/jpeg, image/pjpeg, image/pjpeg, "
+ "application/x-shockwave-flash, application/xaml+xml, "
+ "application/vnd.ms-xpsdocument, application/x-ms-xbap, "
+ "application/x-ms-application, application/vnd.ms-excel, "
+ "application/vnd.ms-powerpoint, application/msword, */*");
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("Charset", "UTF-8");
InputStream inStream = conn.getInputStream();
// 跳过startPos个字节,表明该线程只下载自己负责哪部分文件。
inStream.skip(this.startPos);
byte[] buffer = new byte[1024];
int hasRead = 0;
// 读取网络数据,并写入本地文件
while (length < currentPartSize
&& (hasRead = inStream.read(buffer)) != -1)
{
currentPart.write(buffer, 0, hasRead);
// 累计该线程下载的总大小
length += hasRead;
}
currentPart.close();
inStream.close();
}
catch (Exception 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
public class MultiThreadDown
{
public static void main(String[] args) throws Exception
{
// 初始化DownUtil对象
final DownUtil downUtil = new DownUtil("http://www.crazyit.org/"
+ "attachment.php?aid=MTY0NXxjNjBiYzNjN3wxMzE1NTQ2MjU5fGNhO"
+ "DlKVmpXVmhpNGlkWmVzR2JZbnluZWpqSllOd3JzckdodXJOMUpOWWt0aTJz,"
, "oracelsql.rar", 4);
// 开始下载
downUtil.download();
new Thread()
{
public void run()
{
while(downUtil.getCompleteRate() < 1)
{
// 每隔0.1秒查询一次任务的完成进度,
// GUI程序中可根据该进度来绘制进度条
System.out.println("已完成:"
+ downUtil.getCompleteRate());
try
{
Thread.sleep(1000);
}
catch (Exception ex){}
}
}
}.start();
}
}

上面的程序还用到URLConnection和HttpURLConnection对象,其中前者表示应用程序和URL之间的通信连接,后者表示与URL之间的HTTP连接。程序可以功过URLConnection实例向该URL发送请求,读取URL引用的资源。

上面是直接获取输入流然后写到本地,连接操作都隐藏在openStream中了。

通常创建一个和URL的连接,并发送请求、读取此URL的资源需要如下几个步骤:

  1. 调用URL对象的openConnection()方法来创建URLConnection对象。
  2. 设置URLConnection的参数和普通请求属性
  3. 如果只是发送GET方式请求,则使用connect()方法建立和远程资源之间的实际连接即可;如果要发送POST方式的请求,则需要获取URLConnection实例对应的输出流来发送请求参数。
  4. 远程资源变为可用,程序可以访问远程资源的头字段或通过输入流读取远程资源的数据。在建立和远程资源的实际连接之前。程序可以通过如下方法来设置字段的值:
  • setAllowUserInteraction()
  • setDoInput()
  • setDoOutput()
  • setRequestProperty(String key, String value)

远程资源可用后:

  • Object getConnect()
  • String getHeaderField(String name)
  • getInputStream()
  • getOutputStream
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
ublic class GetPostTest
{
/**
* 向指定URL发送GET方法的请求
* @param url 发送请求的URL
* @param param 请求参数,格式满足name1=value1&name2=value2的形式。
* @return URL所代表远程资源的响应
*/
public static String sendGet(String url , String param)
{
String result = "";
String urlName = url + "?" + param;
try
{
URL realUrl = new URL(urlName);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent"
, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
// 建立实际的连接
conn.connect();
// 获取所有响应头字段
Map<String, List<String>> map = conn.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet())
{
System.out.println(key + "--->" + map.get(key));
}
try(
// 定义BufferedReader输入流来读取URL的响应
BufferedReader in = new BufferedReader(
new InputStreamReader(conn.getInputStream() , "utf-8")))
{
String line;
while ((line = in.readLine())!= null)
{
result += "\n" + line;
}
}
}
catch(Exception e)
{
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
return result;
}
/**
* 向指定URL发送POST方法的请求
* @param url 发送请求的URL
* @param param 请求参数,格式应该满足name1=value1&name2=value2的形式。
* @return URL所代表远程资源的响应
*/
public static String sendPost(String url , String param)
{
String result = "";
try
{
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
try(
// 获取URLConnection对象对应的输出流
PrintWriter out = new PrintWriter(conn.getOutputStream()))
{
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
}
try(
// 定义BufferedReader输入流来读取URL的响应
BufferedReader in = new BufferedReader(new InputStreamReader
(conn.getInputStream() , "utf-8")))
{
String line;
while ((line = in.readLine())!= null)
{
result += "\n" + line;
}
}
}
catch(Exception e)
{
System.out.println("发送POST请求出现异常!" + e);
e.printStackTrace();
}
return result;
}
// 提供主方法,测试发送GET请求和POST请求
public static void main(String args[])
{
// 发送GET请求
String s = GetPostTest.sendGet("http://localhost:8888/abc/a.jsp"
, null);
System.out.println(s);
// 发送POST请求
String s1 = GetPostTest.sendPost("http://localhost:8888/abc/login.jsp"
, "name=crazyit.org&pass=leegang");
System.out.println(s1);
}
}

基于TCP协议的网络编程

TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端形成网络虚拟链路。一旦建立了虚拟链路,两端的程序就可以通过虚拟链路通信。Java使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。

TCP协议基础

TCP协议负责信息包的按顺序传送,接收端收到后再将其正确还原。TCP通过重发以及确认机制保证准确传送,同时能够自适应网络的拥塞情况。

使用ServerSoket创建TCP服务器端

ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,则一直处于等待状态。

  • Socket accept(): 如果接收到一个客户端Socket的连接请求,该方法将返回一个与客户端Socket对应的Socket。

构造器:

  • ServerSocket(int port)
  • ServerSocket(int prot, int backlog)
  • ServerSocket(int port, int backlog, InetAddress localAddr)

使用Socket进行通信

客户端可以使用Socket构造器来连接到指定的服务器:

  • Socket(InetAdress/String remoteAddress, int port)
  • Socket(InetAdress/String remoteAddress, int port, InetAddress localAddr, int localPort)

  • InputStream getInputStream()

  • OutputStream getOutputStream()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Server
{
public static void main(String[] args)
throws IOException
{
// 创建一个ServerSocket,用于监听客户端Socket的连接请求
ServerSocket ss = new ServerSocket(30000);
// 采用循环不断接受来自客户端的请求
while (true)
{
// 每当接受到客户端Socket的请求,服务器端也对应产生一个Socket
Socket s = ss.accept();
// 将Socket对应的输出流包装成PrintStream
PrintStream ps = new PrintStream(s.getOutputStream());
// 进行普通IO操作
ps.println("您好,您收到了服务器的新年祝福!");
// 关闭输出流,关闭Socket
ps.close();
s.close();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Client
{
public static void main(String[] args)
throws IOException
{
Socket socket = new Socket("127.0.0.1" , 30000); //①
// 将Socket对应的输入流包装成BufferedReader
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
// 进行普通IO操作
String line = br.readLine();
System.out.println("来自服务器的数据:" + line);
// 关闭输入流、socket
br.close();
socket.close();
}
}

可以设定超时时长:setSoTimeout(10000)

1
2
3
4
try {
Scanner scan = new Scanner(s.getInputStream());
String line = scan.nextLine();
} ...

另一种建立连接的方式:

1
2
Socket s = new Socket();
s.connect(new InetAddress(host, port), 10000);

加入多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyServer
{
//定义保存所有Socket的ArrayList
public static ArrayList<Socket> socketList
= new ArrayList<>();
public static void main(String[] args)
throws IOException
{
ServerSocket ss = new ServerSocket(30000);
while(true)
{
// 此行代码会阻塞,将一直等待别人的连接
Socket s = ss.accept();
socketList.add(s);
// 每当客户端连接后启动一条ServerThread线程为该客户端服务
new Thread(new ServerThread(s)).start();
}
}
}
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
// 负责处理每个线程通信的线程类
public class ServerThread implements Runnable
{
// 定义当前线程所处理的Socket
Socket s = null;
// 该线程所处理的Socket所对应的输入流
BufferedReader br = null;
public ServerThread(Socket s)
throws IOException
{
this.s = s;
// 初始化该Socket对应的输入流
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
}
public void run()
{
try
{
String content = null;
// 采用循环不断从Socket中读取客户端发送过来的数据
while ((content = readFromClient()) != null)
{
// 遍历socketList中的每个Socket,
// 将读到的内容向每个Socket发送一次
for (Socket s : MyServer.socketList)
{
PrintStream ps = new PrintStream(s.getOutputStream());
ps.println(content);
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
// 定义读取客户端数据的方法
private String readFromClient()
{
try
{
return br.readLine();
}
// 如果捕捉到异常,表明该Socket对应的客户端已经关闭
catch (IOException e)
{
// 删除该Socket。
MyServer.socketList.remove(s); // ①
}
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyClient
{
public static void main(String[] args)throws Exception
{
Socket s = new Socket("127.0.0.1" , 30000);
// 客户端启动ClientThread线程不断读取来自服务器的数据
new Thread(new ClientThread(s)).start(); // ①
// 获取该Socket对应的输出流
PrintStream ps = new PrintStream(s.getOutputStream());
String line = null;
// 不断读取键盘输入
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
while ((line = br.readLine()) != null)
{
// 将用户的键盘输入内容写入Socket对应的输出流
ps.println(line);
}
}
}
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
public class ClientThread implements Runnable
{
// 该线程负责处理的Socket
private Socket s;
// 该线程所处理的Socket所对应的输入流
BufferedReader br = null;
public ClientThread(Socket s)
throws IOException
{
this.s = s;
br = new BufferedReader(
new InputStreamReader(s.getInputStream()));
}
public void run()
{
try
{
String content = null;
// 不断读取Socket输入流中的内容,并将这些内容打印输出
while ((content = br.readLine()) != null)
{
System.out.println(content);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

半关闭的Socket

Socket提供半关闭方法,用来只关闭输入流或输出流,用以表示输出数据已经完成。

  • shutdownInput()
  • shutdownOutput()
  • isInputShutdown()
  • isOutputShutdown()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Server
{
public static void main(String[] args)
throws Exception
{
ServerSocket ss = new ServerSocket(30000);
Socket socket = ss.accept();
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("服务器的第一行数据");
ps.println("服务器的第二行数据");
//关闭socket的输出流,表明输出数据已经结束
socket.shutdownOutput();
//下面语句将输出false,表明socket还未关闭。
System.out.println(socket.isClosed());
Scanner scan = new Scanner(socket.getInputStream());
while (scan.hasNextLine())
{
System.out.println(scan.nextLine());
}
scan.close();
socket.close();
ss.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Client
{
public static void main(String[] args)
throws Exception
{
Socket s = new Socket("localhost" , 30000);
Scanner scan = new Scanner(s.getInputStream());
while (scan.hasNextLine())
{
System.out.println(scan.nextLine());
}
PrintStream ps = new PrintStream(s.getOutputStream());
ps.println("客户端的第一行数据");
ps.println("客户端的第二行数据");
ps.close();
scan.close();
s.close();
}
}

使用NIO实现非阻塞Socket通信

使用Java 7的AIO实现非阻塞通信

基于UDP协议的网络编程

UDP协议是一种不可靠的网络协议,它在通信实例的两段各建立一个Socket,但这两个Socket之间并没有虚拟链路,只是发送接受数据报的对象。Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagamSocket发送、接收的数据报。

UDP协议基础

同前面TCP一样,UDP协议直接建立于IP协议之上。IP实际上是网络层协议,UDP和TCP为传输层协议。

UDP面向非连接,通信效率高,当可靠性不如TCP协议,传输大小限制64KB一下,不保证正确顺序。

使用DatagramSocket发送、接收数据

Java使用DatagramSocket对象作为基于UDP协议的Socket,DatagramSocket本身只是码头,不维护状态,不能产生IO流,唯一作用是接收和发送数据报。Java使用DatagramPacket来代表数据报。

  • DatagramSocket()
  • DatagramSocket(int port)
  • DatagramSocket(int port, InetAddress laddr)

  • receive(DatagramPacket p)

  • send(DatagramPacket p)

DatagramSocket并不知道把数据发送到哪里,而是有DatagramPacket自身决定数据报的目的地。

DatagramPacket的构造器:

  • DatagramPacket(byte[] buf, int length) 接收数据
  • DatagramPacket(byte[] buf, int offset, int length)
  • DatagramPacket(byte[] buf, int length, InetAddress addr, int port) 发送数据
  • DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
1
2
3
4
5
DatagramPacket packet = new DatagramPacket(buf, 256)
socket.receive(packet); //等待数据报的到来

DatagramPacket packet = new DatagramPacket(buf, length, address, port);
socket.send(packet); //发送数据报
  • InetAddress getAddress()
  • int getPort()
  • SocketAddress getSocketAddress()
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
public class UdpServer
{
public static final int PORT = 30000;
// 定义每个数据报的最大大小为4K
private static final int DATA_LEN = 4096;
// 定义接收网络数据的字节数组
byte[] inBuff = new byte[DATA_LEN];
// 以指定字节数组创建准备接受数据的DatagramPacket对象
private DatagramPacket inPacket =
new DatagramPacket(inBuff , inBuff.length);
// 定义一个用于发送的DatagramPacket对象
private DatagramPacket outPacket;
// 定义一个字符串数组,服务器发送该数组的的元素
String[] books = new String[]
{
"疯狂Java讲义",
"轻量级Java EE企业应用实战",
"疯狂Android讲义",
"疯狂Ajax讲义"
};
public void init()throws IOException
{
try(
// 创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket(PORT))
{
// 采用循环接受数据
for (int i = 0; i < 1000 ; i++ )
{
// 读取Socket中的数据,读到的数据放入inPacket封装的数组里。
socket.receive(inPacket);
// 判断inPacket.getData()和inBuff是否是同一个数组
System.out.println(inBuff == inPacket.getData());
// 将接收到的内容转成字符串后输出
System.out.println(new String(inBuff
, 0 , inPacket.getLength()));
// 从字符串数组中取出一个元素作为发送的数据
byte[] sendData = books[i % 4].getBytes();
// 以指定字节数组作为发送数据、以刚接受到的DatagramPacket的
// 源SocketAddress作为目标SocketAddress创建DatagramPacket。
outPacket = new DatagramPacket(sendData
, sendData.length , inPacket.getSocketAddress());
// 发送数据
socket.send(outPacket);
}
}
}
public static void main(String[] args)
throws IOException
{
new UdpServer().init();
}
}
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
public class UdpClient
{
// 定义发送数据报的目的地
public static final int DEST_PORT = 30000;
public static final String DEST_IP = "127.0.0.1";
// 定义每个数据报的最大大小为4K
private static final int DATA_LEN = 4096;
// 定义接收网络数据的字节数组
byte[] inBuff = new byte[DATA_LEN];
// 以指定字节数组创建准备接受数据的DatagramPacket对象
private DatagramPacket inPacket =
new DatagramPacket(inBuff , inBuff.length);
// 定义一个用于发送的DatagramPacket对象
private DatagramPacket outPacket = null;
public void init()throws IOException
{
try(
// 创建一个客户端DatagramSocket,使用随机端口
DatagramSocket socket = new DatagramSocket())
{
// 初始化发送用的DatagramSocket,它包含一个长度为0的字节数组
outPacket = new DatagramPacket(new byte[0] , 0
, InetAddress.getByName(DEST_IP) , DEST_PORT);
// 创建键盘输入流
Scanner scan = new Scanner(System.in);
// 不断读取键盘输入
while(scan.hasNextLine())
{
// 将键盘输入的一行字符串转换字节数组
byte[] buff = scan.nextLine().getBytes();
// 设置发送用的DatagramPacket里的字节数据
outPacket.setData(buff);
// 发送数据报
socket.send(outPacket);
// 读取Socket中的数据,读到的数据放在inPacket所封装的字节数组里。
socket.receive(inPacket);
System.out.println(new String(inBuff , 0
, inPacket.getLength()));
}
}
}
public static void main(String[] args)
throws IOException
{
new UdpClient().init();
}
}

使用MulticastSocket实现多点广播

DatagramSocket只允许数据报发送给制定的目标地址,而MulticastSocket可以将数据报以广播形式发送到多个客户端。

若要使用多点广播,则需要让一个数据报标有一组目标主机地址,当数据报发出后,整个组的所有主机都能收到该数据报。IP多点广播实现了将单一信息发送到多个接受者的广播,其主要思想是设置一批特殊的网络地址作为多点广播地址,每个多点广播地址可以看为一个组,当需要发送,接收广播信息时,加入该组就可。当不需要时,就离开该组。

  • public MulticastSocket()
  • public MulticastSocket(int protNum)
  • public MulticastSocket(SocketAddress binaddr)

  • joinGroup(InetAddress multicastAddr): 将MulticastSocket加入一个组

  • leaveGroup(InetAddress multicastAddr): 将MulticastSocket离开一个组

  • setTimeToLive(int ttl),设置数据报最多可以跨过多少个网络。

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
92
93
94
95
public class MulticastSocketTest implements Runnable
{
// 使用常量作为本程序的多点广播IP地址
private static final String BROADCAST_IP
= "230.0.0.1";
// 使用常量作为本程序的多点广播目的的端口
public static final int BROADCAST_PORT = 30000;
// 定义每个数据报的最大大小为4K
private static final int DATA_LEN = 4096;
//定义本程序的MulticastSocket实例
private MulticastSocket socket = null;
private InetAddress broadcastAddress = null;
private Scanner scan = null;
// 定义接收网络数据的字节数组
byte[] inBuff = new byte[DATA_LEN];
// 以指定字节数组创建准备接受数据的DatagramPacket对象
private DatagramPacket inPacket
= new DatagramPacket(inBuff , inBuff.length);
// 定义一个用于发送的DatagramPacket对象
private DatagramPacket outPacket = null;
public void init()throws IOException
{
try(
// 创建键盘输入流
Scanner scan = new Scanner(System.in))
{
// 创建用于发送、接收数据的MulticastSocket对象
// 由于该MulticastSocket对象需要接收数据,所以有指定端口
socket = new MulticastSocket(BROADCAST_PORT);
broadcastAddress = InetAddress.getByName(BROADCAST_IP);
// 将该socket加入指定的多点广播地址
socket.joinGroup(broadcastAddress);
// 设置本MulticastSocket发送的数据报会被回送到自身
socket.setLoopbackMode(false);
// 初始化发送用的DatagramSocket,它包含一个长度为0的字节数组
outPacket = new DatagramPacket(new byte[0]
, 0 , broadcastAddress , BROADCAST_PORT);
// 启动以本实例的run()方法作为线程体的线程
new Thread(this).start();
// 不断读取键盘输入
while(scan.hasNextLine())
{
// 将键盘输入的一行字符串转换字节数组
byte[] buff = scan.nextLine().getBytes();
// 设置发送用的DatagramPacket里的字节数据
outPacket.setData(buff);
// 发送数据报
socket.send(outPacket);
}
}
finally
{
socket.close();
}
}
public void run()
{
try
{
while(true)
{
// 读取Socket中的数据,读到的数据放在inPacket所封装的字节数组里。
socket.receive(inPacket);
// 打印输出从socket中读取的内容
System.out.println("聊天信息:" + new String(inBuff
, 0 , inPacket.getLength()));
}
}
// 捕捉异常
catch (IOException ex)
{
ex.printStackTrace();
try
{
if (socket != null)
{
// 让该Socket离开该多点IP广播地址
socket.leaveGroup(broadcastAddress);
// 关闭该Socket对象
socket.close();
}
System.exit(1);
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args)
throws IOException
{
new MulticastSocketTest().init();
}
}

使用代理服务器