@[toc]
网络编程(二)
一、TCP协议通信
1.上传图片练习
1)将图片上传到服务器端保存。
示例代码:
package com.heisejiuhuche.socket;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;
public class PicUploadClient {
public static void main(String[] args) throws Exception {
upload();
}
private static void upload() throws Exception {
/* 创建Socket对象,指定主机和端口 */
Socket s = new Socket("localhost", 10007);
/* 创建File对象,关联文件 */
File file = new File("C://Users//jeremy//Documents//javaTmp//me.jpg");
/* 创建输入输出流对象 */
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());
int b = 0;
/* 上传文件 */
while((b = bis.read()) != -1) {
bos.write(b);
bos.flush();
}
/* 发送结束符 */
s.shutdownOutput();
/* 获取反馈信息 */
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int msg = in.read(buf);
System.out.println(new String(buf, 0, msg));
bis.close();
s.close();
}
}
package com.heisejiuhuche.socket;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class PicUploadServer {
public static void main(String[] args) throws Exception {
recv();
}
private static void recv() throws Exception {
ServerSocket ss = new ServerSocket(10007);
Socket s = ss.accept();
/* 创建输入输出流对象 */
BufferedInputStream bis = new BufferedInputStream(s.getInputStream());
File file = new File("C://Users//jeremy//Documents//me.jpg");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
int b = 0;
/* 保存文件 */
while((b = bis.read()) != -1) {
bos.write(b);
bos.flush();
}
/*发送反馈信息 */
OutputStream out = s.getOutputStream();
out.write("上传成功".getBytes());
bos.close();
s.close();
ss.close();
}
}
程序运行结果:上传成功
2)TCP客户端并发上传图片
这个服务端有局限性。当一个客户端连接服务器之后,因为服务器是单线程,那么其他客户端如果想连接服务器,只能带前一个客户完成所有任务,才能连接。要同时处理多个客户端请求,应该明确客户端要在服务器端要执行的代码,然后将每个客户端封装到一个线程当中,同步执行。
示例代码:
package com.heisejiuhuche.socket;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;
public class UploadPicByThreadClient {
public static void main(String[] args) throws Exception {
/* 限制文件格式,大小等条件 */
if(args.length != 1) {
System.out.println("请指定一个jpg文件上传...");
return;
}
File file = new File(args[0]);
if(!(file.exists() && file.isFile())) {
System.out.println("文件不存在或者不是文件类型...");
return;
}
if(!(file.getName().endsWith(".jpg"))) {
System.out.println("格式不正确...");
return;
}
if(file.length() > 1024 * 1024 * 5) {
System.out.println("文件过大!");
return;
}
/* 创建Socket对象,指定主机和端口 */
Socket s = new Socket("localhost", 10007);
/* 创建File对象,关联文件 */
// File file = new File("C://Users//jeremy//Documents//javaTmp//me.jpg");
/* 创建输入输出流对象 */
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());
int b = 0;
/* 上传文件 */
while((b = bis.read()) != -1) {
bos.write(b);
bos.flush();
}
/* 发送结束符 */
s.shutdownOutput();
/* 获取反馈信息 */
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int msg = in.read(buf);
System.out.println(new String(buf, 0, msg));
bis.close();
s.close();
}
}
package com.heisejiuhuche.socket;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class UploadPicByThreadServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(10007);
while(true) {
Socket s = ss.accept();
new Thread(new PicServerThread(s)).start();
}
}
}
/* 将客户端上传的代码封装成线程 */
class PicServerThread implements Runnable {
private Socket s;
PicServerThread(Socket s) {
this.s = s;
}
public void run() {
String ip = s.getInetAddress().getHostAddress();
int count = 1;
try {
System.out.println(ip + " connected..");
/* 避免文件名重复 */
File file = new File("C://Users//jeremy//Documents//" + ip + "(" + count + ")" + ".jpg");
/* 循环判断,如果文件存在,让count自增 */
while(file.exists())
file = new File("C://Users//jeremy//Documents//" + ip + "(" + count++ + ")" + ".jpg");
/* 读取文件,写入服务端 */
BufferedInputStream bis = new BufferedInputStream(s.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
int b = 0;
while((b = bis.read()) != -1) {
bos.write(b);
bos.flush();
}
OutputStream out = s.getOutputStream();
out.write("上传成功".getBytes());
bos.close();
s.close();
} catch(IOException e) {
throw new RuntimeException(ip + " 上传失败...");
}
}
}
2.检测用户名练习
客户端通过键盘录入用户名,如果该用户存在,在服务器端显示xxx,已登录;在客户端显示xxx,欢迎光临。如果用户不存在,在服务端显示xxx,尝试登录;在客户端显示xxx,该用户不存在。
示例代码:
package com.heisejiuhuche.socket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class LoginClient {
public static void main(String[] args) throws Exception {
Socket s = new Socket("localhost", 10009);
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
for(int x = 0; x < 3; x++) {
String line = bufr.readLine();
/* 如果输入为空,直接结束循环 */
if(line == null)
break;
/* 发送名字到服务器端 */
pw.println(line);
/* 读取回馈信息 */
String feedback = bufIn.readLine();
System.out.println(feedback);
/* 如果回馈信息有欢迎字样,结束循环 */
if(feedback.contains("欢迎"))
break;
}
bufr.close();
s.close();
}
}
package com.heisejiuhuche.socket;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class LoginServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(10009);
while(true) {
Socket s = ss.accept();
new Thread(new UserThread(s)).start();
}
}
}
class UserThread implements Runnable {
private Socket s;
UserThread(Socket s) {
this.s = s;
}
public void run() {
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + " connected...");
try {
for(int x = 0; x < 3; x++) {
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String name = bufIn.readLine();
if(name == null)
break;
/* 这一行代码一定放在for循环里面,不然readLine的指针回不到文件开端,读不到名字 */
BufferedReader bufr = new BufferedReader(new FileReader("C://Users//jeremy//Documents//javaTmp//user.txt"));
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
String line = null;
Boolean flag = false;
/* 根据flag的结果判断要进行的操作 */
while((line = bufr.readLine()) != null) {
if(name.equals(line)) {
flag = true;
break;
}
}
if(flag) {
System.out.println(name + "已登录!");
pw.println(name + ", 欢迎登录!");
break;
} else {
System.out.println(ip + "正在尝试登录...");
pw.println(name + "不存在...");
}
bufr.close();
}
s.close();
} catch(IOException e) {
throw new RuntimeException(ip + "校验失败...");
}
}
}
3.自定义浏览器
1)HTTP请求消息头
请求头信息中包括了发送该请求的主机地址,端口,路径,可以接受的应用程序类型,所使用语言,封装压缩形式等信息。请求头信息示例如下:
GET / HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: 127.0.0.1:11000
DNT: 1
Connection: Keep-Alive
模拟一个浏览器,想服务器发送请求头信息,获取页面。
示例代码:
package com.heisejiuhuche.socket;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.Socket;
public class BrowserClient {
public static void main(String[] args) throws Exception {
/* 访问本机的Tomcat服务器,端口8080 */
Socket s = new Socket("localhost", 8080);
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
/* 发送HTTP请求头 */
out.println("GET / HTTP/1.1");
out.println("Accept: */*");
out.println("Accept-Language: en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3");
out.println("Accept-Encoding: gzip, deflate");
out.println("Connection: closed");
/* 用空行分割请求头和请求体 */
out.println();
out.println();
/* 获取服务器返回的信息 */
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf, 0, len));
s.close();
}
}
二、URL类
1.概述URL(Uniform Resource Locator)
类代表一个同一资源定位符。该类是指向互联网资源的指针。
2.常用方法
-String getFile():获取此URL的文件名
-String getHost():获取此URL的主机名
-String getPath():获取此URL的路径部分
-int getPort():获取此URL的端口号
-String getProtocol():获取此URL的协议名称
-String getQuery():获取此URL的查询部
3.常用方法演示
示例代码:
package com.heisejiuhuche.socket;
import java.net.MalformedURLException;
import java.net.URL;
public class URLDemo {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://192.168.0.100:8080/myweb/index.html/?name=hsjhc&age=25");
/* 演示常用方法 */
System.out.println("getProtocol: " + url.getProtocol());
System.out.println("getHost: " + url.getHost());
System.out.println("getPort: " + url.getPort());
System.out.println("getPath: " + url.getPath());
System.out.println("getFile: " + url.getFile());
System.out.println("getQuery: " + url.getQuery());
}
}
程序运行结果:
getProtocol: http
getHost: 192.168.0.100
getPort: 8080
getPath: /myweb/index.html/
getFile: /myweb/index.html/?name=hsjhc&age=25
getQuery: name=hsjhc&age=25
注意:
没有指定端口,返回-1;开发时要进行判断,如果port的值为-1,要给port赋值为80
三、URLConnection类
1.概述URLConnection
类对象是URL对象调用openConnect()
方法之后的返回值,就能得到这个URL
的连接对象,也就是目标主机对象;获取URLConnection
对象可以方便获取Socket输入流对象
。
2.URLConnection类演示
示例代码:
package com.heisejiuhuche.socket;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
public class URLConnectionDemo {
public static void main(String[] args) throws Exception {
/* 封装URL对象 */
URL url = new URL("http://192.168.0.100:8080/myweb/indetml/?name=hsjhc&age=25");
/* 调用url对象的openConnection方法,得到URLConnection对象 */
URLConnection conn = url.openConnection();
System.out.println(conn);
/* 因为URLConnection对象中封装了Socket对象
* 所以可以调用getInputStream()方法获取输入流来读取服务器端数据 */
InputStream in = conn.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf, 0, len));
}
}
注意:
url.openConnection().getInputStream()这行代码,可以简写成url.openStream();该方法是URLConnection类中获取InputStream的便捷方法
四、扩展知识
1.Socket空参数构造函数
通过调用connect()
方法连接主机;connect()
方法可以接收一个SocketAddress
对象作为参数;SocketAddress
类的子类InetSocketAddress
类封装IP地址
和端口
;那么以前在创建Socket对象
的时候分开写主机IP和端口,现在也可以调用connect
方法,传递一个InetSocketAddress
对象即可。
2.ServerSocket类接收的backlog参数backlog
表示队列的最大长度。它代表能同时连接到服务器的最大客户端个数。在服务器端开发时,要指定这个参数,避免服务器因连接数过多而死机。
五、域名解析
1.概述IP地址
不容易记忆,通常人们在访问网站的时候,在地址栏输入的都是类似于www.baidu.com
这样的域名。但是要访问到网络上对应这个域名的那个主机,需要将这个域名解析成IP地址。这个过程,需要DNS域名解析服务器
来完成。DNS服务器
中记录的是每个域名对应的IP地址映射表。
2.基本原理
PC在访问网站的时候,要走这4步流程。在浏览器输入域名之后,第1步,到本机C盘``(C:\windows\Systems\drivers\etc\)
下的hosts文件
,寻找这个域名是否在本地有已经配置的IP地址,如果有,直接访问该IP,否则,进入第2步;第2步,访问公网DNS服务器,找到DNS服务器上的域名——IP映射关系;第3步,DNS服务器将对应的IP地址返回给PC;第4步,PC端访问该IP地址,找到公网上的指定主机。
注意:
127.0.0.1与localhost之间的映射关系在本机上C盘下的hosts文件中。该文件可以用于屏蔽恶意网站。