------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流!
一、概述
首先通過下面的圖例簡單演示不同主機的程序之間的數據傳輸過程:
在整個信息發送過程中,要找到對方的IP地址,將數據逐層封包,通過主機至網絡層發送到對方主機上,然後向上逐層拆包,找到對方的指定應用程序,將數據發到該指定應用程序上。這樣信息就在不同主機間發送成功了。整個過程就涉及到了我們要學習的網絡通信的重要知識點:網絡模型、網絡通信要素(IP地址、端口、傳輸協議)。
網絡模型:有OSI參考模型和TCP/IP參考模型。二者的結構如下圖:
我們接下來學習的網絡編程都處於傳輸層和網際層,每一個層都有對應的通信協議。應用層對應的是FTP和HTTP協議,傳輸層是TCP和UDP,網際層的協議爲IP。從上面的QQ發送信息的過程中可以看到,用戶在應用層傳入數據,經過逐層封包,最後到達主機至網絡層發送給另一臺主機,然後再逐級向上拆包。
網絡通訊三要素:IP地址、端口、傳輸協議。
(1)IP地址:它是網絡中的設備標識,可以用主機名來表示,兩者存在映射關係。本機的迴環地址是:127.0.0.1 (主機名:localhost)。Java中把IP地址封裝成了一個InetAddress類,存放在java.net包中。
通過這個類的方法,可以實現對IP地址、主機名的獲得等功能,常用功能如下:
1.獲得該類對象: InetAddress i=InetAddress.getByName(String host);獲取指定主機的InetAddress對象,注意最好用IP地址獲取,因爲主機名需要解析。
InetAddress i=InetAddress.getByName(String host);獲取指定主機的所有InetAddress對象,返回一個數組
InetAddress i=InetAddress.getLocalHost();獲取本機的InetAddress對象。
2.獲取IP地址和主機名
i.getHostAddress();返回IP地址字符串(文本形式)。
i.getHostName();返回IP地址對應的主機名。
注意,通過上面的方法,傳入一個指定的主機名,就可以獲得它的IP地址對象,獲取其具體的一個或多個地址。但如果IP地址和對應的主機名的映射關係在網絡上不存在,就會解析失敗。
下面通過一段代碼進行演示:
public static void main(String[] args) throws UnknownHostException {
//獲取sohu的IP地址對象
InetAddress[] i=InetAddress.getAllByName("www.sohu.com");
//獲取具體的IP地址
for(InetAddress ia:i)
System.out.println(ia.getHostAddress());
//獲取本機的IP對象,並獲取具體IP地址和主機名
InetAddress i1=InetAddress.getLocalHost();
String name=i1.getHostName();
String ip=i1.getHostAddress();
System.out.println(name+":"+ip);
}
(2)端口號:用於標示進程的邏輯地址,不同進程的標識。有效端口的設置範圍爲0~65535,注意系統會保留0~1024端口。所以設置時要避開。
(3)傳輸協議: 就是通訊規則,主要是TCP和UDP協議。下面對這兩種協議的特點和應用分別進行介紹:
UDP協議:面向無連接,不建立連接,將數據及源和目的封裝在數據包中來發送,每個數據包的大小限制住在64K以內,因無連接,速度快,但可能出現數據丟失,是不可靠協議。應用於聊天程序、桌面共享、視頻會議等。
TCP協議:面向連接,在建立連接後,形成傳輸數據的通道,可以進行大數據量的傳輸。因通過三次握手建立連接,速度稍慢,但能保證數據的安全性,是可靠的協議。應用於下載文件、文件傳輸等程序中。
三次握手:第一次本機發送連接請求,第二次對方確認連接,第三次本方再次確認連接成功。
網絡通信的步驟:
(1)找到IP地址。
(2)數據要發送到對方的指定應用程序上,這時就要用數字標識該應用程序上,就是用到端口。如果沒有定義端口,數據就無法發送到該應用程序上。
(3)定義通信規則,國際組織定義了通用協議 TCP/IP。
二、TCP和UDP傳輸的建立與應用
1.Socket:端點,就相當於港口,是網絡服務提供的一種機制,通信兩端都要有Socket對象,才能建立通信服務。網絡通信就是Socket之間的通信,數組在兩個Socket之間通過IO傳輸。
2.UDP傳輸
DatagramSocket類,表示用來發送和接收數據報包的套接字,即Socket;DatagramPacket類,表示數據報包。下面就通過建立這個兩個類的對象,調用它們的特有方法,實現數據的UDP傳輸。具體步驟如下:
(1)發送端:
1.通過創建DatagramSocket對象,建立Socket服務。可以指定本程序端口,也可不指定根據系統自動分配。
2.確定數據,將要發送的數據封裝到數據報包DatagramPacket對象中,數據報包對象中還要指定接收數據的主機和端口。
3.通過Socket服務中的send(DatagramPacket dp),將數據報包發送出去。
4.關閉資源。
如同下面這段代碼:
class UDPSend {
public static void main(String[] args) throws IOException {
//創建DatagramSocket對象,建立Socket服務,並指定本程序端口。
DatagramSocket ds=new DatagramSocket(10000);
//將提供的數據封裝成數據報包,並在數據報包中指定要發送到的主機和端口。
byte[]by="大河向東流,天上的星星參北斗".getBytes();
DatagramPacket dp=new DatagramPacket(by,by.length,InetAddress.getLocalHost(),10001);
//通過send方法,將數據報包發送出去
ds.send(dp);
//關閉資源
ds.close();
}
}
(2)接收端:1.創建DatagramSocket對象,建立Socket服務,定義時要監聽一個端口,就是給應用程序定義數字標識,用來接收發送
給該應用程序的數據。
2. 定義數據報包,用來存儲接收到的數據,
3.通過Socket服務中的receive方法,將接收到的數據存入數據報包中。
4.利用DatagramPacket中的特有功能提取數據信息。
5.關閉資源。
具體如下:
class UDPRec {
public static void main(String[] args) throws IOException {
//1,創建udp socket,定義一個監聽端口。
DatagramSocket ds = new DatagramSocket(10001);
//2,定義數據包。用於存儲數據。
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//3,通過receive方法將收到數據存入數據包中。
ds.receive(dp);
//4,通過數據包的方法獲取其中的數據。
String ip=dp.getAddress().getHostAddress();
String data=new String(dp.getData(),0,dp.getLength());
int port=dp.getPort();
System.out.println(ip+"::"+data+"::"+port);
//5,關閉資源
ds.close();
}
}
要注意的是,發送端和接收端是兩個獨立的程序,定義發送端時,要在數據報包中明確接收數據的主機和端口;定義接收端時要定義一個監聽的端口。下面利用UDP傳輸協議的獲取鍵盤錄入,發送數據的程序。如下:
//發送端
class UDPSend {
public static void main(String[] args) throws IOException {
//創建DatagramSocket對象,建立Socket服務,並指定本程序端口。
DatagramSocket ds=new DatagramSocket(10000);
//建立流對象,與鍵盤錄入相關聯,加入緩衝技術
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String line=null;
while((line=br.readLine())!=null){
if("over".equals(line))
break;
byte[]by=line.getBytes();
//將獲取的數據封裝成數據報包,並在數據報包中指定要發送到的主機和端口。
DatagramPacket dp=new DatagramPacket(by,by.length,InetAddress.getLocalHost(),10001);
//通過send方法,將數據報包發送出去
ds.send(dp);
}
//關閉資源
ds.close();
}
}
//接收端
class UDPRec {
public static void main(String[] args) throws IOException {
//1,創建udp socket,定義一個監聽端口。
DatagramSocket ds = new DatagramSocket(10001);
//2,定義數據包。用於存儲數據。
while(true){
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//3,通過receive方法將收到數據存入數據包中。
ds.receive(dp);
//4,通過數據包的方法獲取其中的數據。
String ip=dp.getAddress().getHostAddress();
String data=new String(dp.getData(),0,dp.getLength());
int port=dp.getPort();
System.out.println(ip+"::"+data+"::"+port);
}
}
}
定義一個聊天程序,實現信息發送和收到的同時運行,這時就需要用到多線程來實現。如下:
class UDPTest {
public static void main(String[] args) throws IOException {
//建立DatagramSocket對象
DatagramSocket ds1=new DatagramSocket();
DatagramSocket ds2=new DatagramSocket(10002);
//啓動程序
new Thread(new SendDemo(ds1)).start();
new Thread(new RecDemo(ds2)).start();
}
}
//定義一個類,表示發送,實現Runnable接口
class SendDemo implements Runnable{
//定義DatagramSocket的引用
private DatagramSocket ds;
SendDemo(DatagramSocket ds){
this.ds=ds;
}
//複寫run方法,定義要運行的代碼
public void run(){
BufferedReader br=null;
try {
br=new BufferedReader(new InputStreamReader(System.in));
String line=null;
//獲取鍵盤錄入,封裝成數據報包,併發送出去
while((line=br.readLine())!=null){
if("88".equals(line))
break;
byte[]by=line.getBytes();
DatagramPacket dp=new DatagramPacket(by,by.length,InetAddress.getLocalHost(),10002);
ds.send(dp);
}
//關閉資源
ds.close();
} catch (Exception e) {
throw new RuntimeException("獲取鍵盤錄入失敗");
}
}
}
//定義一個類表示接收,實現Runnable接口
class RecDemo implements Runnable{
//定義DatagramSocket的引用
private DatagramSocket ds;
RecDemo(DatagramSocket ds){
this.ds=ds;
}
//複寫run方法,定義要運行的代碼
public void run(){
while(true){
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//通過receive方法將收到數據存入數據包中。
try {
ds.receive(dp);
} catch (IOException e) {
throw new RuntimeException("數據接收失敗");
}
//通過數據包的方法獲取其中的數據。
String ip=dp.getAddress().getHostAddress();
String data=new String(dp.getData(),0,dp.getLength());
if("88".equals(data))
break;
int port=dp.getPort();
System.out.println(ip+":"+data);
}
}
}
3.TCP傳輸:
使用TCP傳輸,要建立客戶端和服務器端,分別用Socket和ServerSocket的對象表示。建立連接後,通過Socket中的IO流進行數據傳輸。客戶端與服務器端是兩個獨立的應用程序。Socket對象建立時就可以連接指定的主機,因爲TCP是面向連接的,所以在建立Socket服務時,就要有服務器端存在,連接成功,形成通路後,就可以在該通道進行數據傳輸了。注意,空參數Socket對象可以通過connect方法連接服務器端。設置基本的TCP傳輸步驟如下:
(1)服務器端:
1.創建SeverSocket對象,建立Socket服務,監聽一個端口。
2.通過accept方法,獲取鏈接過來的客戶端對象。這個方法是阻塞式的,沒有連接就會等。
3.客戶端如果過來數據,那麼服務端就使用對應的客戶端對象,並獲取到該客戶端對象的讀取流來讀取發過來的數據。
4.關閉服務器(可選操作)。
如下:
class TCPServer {
public static void main(String[] args) throws IOException {
//創建ServerSocket對象,建立Socket服務,指定監聽端口
ServerSocket ss=new ServerSocket(10004);
while(true){
//獲取連接的客戶端對象。
Socket s=ss.accept();
//通過獲取客戶端對象的讀取流來讀取客戶端發送來的數據。
InputStream is=s.getInputStream();
byte[]by=new byte[1024];
int len=is.read(by);
System.out.println(new String(by,0,len));
}
}
}
(2)客戶端:
1.建立Socket服務,指定要連接的主機和端口。
2.獲取Socket服務中的輸出流,將數據寫入到流中,通過網絡發送個服務器端。
3.如果服務器端有反饋信息,獲取Socket流中的輸入流,獲取服務器端反饋的數據信息。
4.關閉資源。
如下:
class TCPClient {
public static void main(String[] args) throws Exception {
//建立Socket服務,指定要連接的主機和端口
Socket s=new Socket(InetAddress.getLocalHost(),10004);
//獲取輸出流,寫入數據。
OutputStream os=s.getOutputStream();
os.write("大河向東流,天上的星星參北斗".getBytes());
//關閉資源
s.close();
}
}
當客戶端進行數據傳輸給服務器端時,數據傳輸成功後,服務器端一般會有信息反饋給客戶端。程序如下:
//定義服務端
public class TCPServer {
public static void main(String[] args) throws Exception {
//創建ServerSocket對象,建立Socket服務,指定監聽端口
ServerSocket ss=new ServerSocket(10004);
while(true){
//獲取連接的客戶端對象。
Socket s=ss.accept();
//通過獲取客戶端對象的讀取流來讀取客戶端發送來的數據。
InputStream is=s.getInputStream();
byte[]by=new byte[1024];
int len=is.read(by);
System.out.println(new String(by,0,len));
//通過獲取客戶端對象的輸出流給客戶端發送反饋信息
OutputStream out = s.getOutputStream();
out.write("數據已接收".getBytes());
s.close();
}
}
}
//定義客戶端
class TCPClient {
public static void main(String[] args) throws Exception {
//建立Socket服務,指定要連接的主機和端口
Socket s=new Socket(InetAddress.getLocalHost(),10004);
//獲取輸出流,寫入數據。
OutputStream os=s.getOutputStream();
os.write("大河向東流,天上的星星參北斗".getBytes());
//獲取輸入流,讀取服務器端的反饋信息
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf,0,len));
}
}
下面是一個TCP傳輸的練習:
/*
需求:建立一個文本轉換服務器。
客戶端給服務端發送文本,服務單會將文本轉成大寫在返回給客戶端。
而且客戶度可以不斷的進行文本轉換。當客戶端輸入over時,轉換結束。
分析:
客戶端:
既然是操作設備上的數據,那麼就可以使用io技術,並按照io的操作規律來思考。
源:鍵盤錄入。
目的:網絡設備,網絡輸出流。
而且操作的是文本數據。可以選擇字符流。
步驟
1,建立服務。
2,獲取鍵盤錄入。
3,將數據發給服務端。
4,服務端返回大寫數據。
5,關閉資源。
都是文本數據,可以使用字符流進行操作,同時提高效率,加入緩衝。
*/
import java.io.*;
import java.net.*;
class TransClient
{
public static void main(String[] args) throws Exception
{
Socket s = new Socket(InetAddress.getLocalHost(),10005);
//定義讀取鍵盤數據的流對象。
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));
//定義目的,將數據寫入到socket輸出流。發給服務端。
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//定義一個socket讀取流,讀取服務端返回的大寫信息。
BufferedReader bufIn =
new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
out.println(line);
String str =bufIn.readLine();
System.out.println(str);
}
//關閉資源
bufr.close();
s.close();
}
}
/*
服務端:
源:socket讀取流。
目的:socket輸出流。
都是文本,裝飾。
*/
class TransServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10005);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"....connected");
//讀取socket讀取流中的數據。
BufferedReader bufIn =
new BufferedReader(new InputStreamReader(s.getInputStream()));
//目的。socket輸出流。將大寫數據寫入到socket輸出流,併發送給客戶端。
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
String line = null;
while((line=bufIn.readLine())!=null)
{
System.out.println(line);
out.println(line.toUpperCase());
}
s.close();
ss.close();
}
}
在這個程序中加入緩衝技術,提高了程序的運行效率。要注意的是read方法是阻塞式方法,當沒有讀到數據或沒有讀到結束標記時兩邊read方法都會等待。
通過TCP傳輸,可以實現不同主機之間的文件拷貝,就是把客戶端的文件上傳到服務端上,一般上傳成功後,服務器端會提示上傳成功,如下面的這個程序:
/**
客戶端向服務器上傳一個文件。上傳完畢後,服務器向客戶端反饋上傳成功。
*/
//定義客戶端
class TextClient
{
public static void main(String[] args) throws Exception
{
Socket s = new Socket(InetAddress.getLocalHost(),10006);
//建立讀取流,和要上傳的文件相關連
BufferedReader bufr =
new BufferedReader(new FileReader("F:\\123.txt"));
//獲取輸出流。
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//實現上傳
String line = null;
while((line=bufr.readLine())!=null)
{
out.println(line);
}
//關閉客戶端的輸出流。相當於給流中加入一個結束標記-1.
s.shutdownOutput();
//獲取讀取流,爲了提高效率,加入轉換流和緩衝技術
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
//獲取服務器端反饋信息
String str = bufIn.readLine();
System.out.println(str);
//關閉資源
bufr.close();
s.close();
}
}
//定義服務端
class TextServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10006);
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"....connected");
//獲取連接對象的輸入流,加入轉換流和緩衝技術,提高效率
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
//定義輸出流,指定目的文件
PrintWriter out = new PrintWriter(new FileWriter("F:\\234.txt"),true);
//接收並輸出數據到指定文件
String line = null;
while((line=bufIn.readLine())!=null)
out.println(line);
//給客戶端發送反饋信息
PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
pw.println("上傳成功");
//關閉資源
out.close();
s.close();
ss.close();
}
}
在這個程序中,要注意的是在客戶端的上傳數據上傳完畢後,要通過shutdownOutput()方法告訴服務器端,數據已全部上傳完畢。否則服務器端一直繼續等待客戶端上傳數據。shutdownOutput()方法,相當於給輸出流加了一個結束標記。
這個程序是單線程的,當一個客戶端連接上服務器,被服務器獲取到後,其他的客戶端就必須等待,而在實際生活中每個服務器都有大量的客戶端進行連接,這時爲了實現客戶端的併發使用服務器,就需要使用多線程技術,將每一個客戶端對象都封裝在一個線程裏,實現客戶端的併發使用。下面的程序就是服務器端實現了多線程,客戶端可以併發使用,如下:
//將每一個客戶端對象封裝到線程裏
class PicThread implements Runnable
{
private Socket s;
PicThread(Socket s)
{
this.s = s;
}
public void run()
{
int count = 1;
String ip = s.getInetAddress().getHostAddress();
try
{
System.out.println(ip+"....connected");
InputStream in = s.getInputStream();
File dir = new File("d:\\pic");
File file = new File(dir,ip+"("+(count)+")"+".jpg");
while(file.exists())
file = new File(dir,ip+"("+(count++)+")"+".jpg");
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len = 0;
while((len=in.read(buf))!=-1)
{
fos.write(buf,0,len);
}
OutputStream out = s.getOutputStream();
out.write("上傳成功".getBytes());
fos.close();
s.close();
}
catch (Exception e)
{
throw new RuntimeException(ip+"上傳失敗");
}
}
}
class PicServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10007);
//多個客戶端可以同時訪問
while(true)
{
Socket s = ss.accept();
new Thread(new PicThread(s)).start();
}
}
}
下面是一個登陸服務器端的程序:
/*
客戶端通過鍵盤錄入用戶名。
服務端對這個用戶名進行校驗。
如果該用戶存在,在服務端顯示xxx,已登陸。
並在客戶端顯示 xxx,歡迎光臨。
如果該用戶存在,在服務端顯示xxx,嘗試登陸。
並在客戶端顯示 xxx,該用戶不存在。
最多就登錄三次。
*/
import java.io.*;
import java.net.*;
//定義客戶端
class LoginClient
{
public static void main(String[] args) throws Exception
{
Socket s = new Socket(InetAddress.getLocalHost(),10008);
//建立流對象,獲取鍵盤錄入,加入緩衝
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));
//獲取Socket輸出流
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//獲取Socket輸入流,加入緩衝
BufferedReader bufIn =
new BufferedReader(new InputStreamReader(s.getInputStream()));
//限定登陸次數
for(int x=0; x<3; x++)
{
String line = bufr.readLine();
//不再輸入,循環結束
if(line==null)
break;
out.println(line);
//獲取服務端反饋
String info = bufIn.readLine();
System.out.println("info:"+info);
//登陸成功,結束
if(info.contains("歡迎"))
break;
}
//關閉資源
bufr.close();
s.close();
}
}
//定義客戶端線程
class UserThread implements Runnable
{
private Socket s;
UserThread(Socket s)
{
this.s = s;
}
public void run()
{
String ip = s.getInetAddress().getHostAddress();
try
{ //限制登陸次數
for(int x=0; x<3; x++)
{
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
//讀取用戶傳入的登陸名
String name = bufIn.readLine();
//用戶不再輸入,循環結束
if(name==null)
break;
//建立流對象,與登錄名列表的文件相關聯
BufferedReader bufr = new BufferedReader(new FileReader("F:\\list.txt"));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
String line = null;
//定義判斷標識
boolean flag = false;
while((line=bufr.readLine())!=null)
{ //查詢用戶名是否存在
if(line.equals(name))
{
flag = true;
break;
}
}
//登陸成功
if(flag)
{
System.out.println(name+",已登錄");
out.println(name+",歡迎光臨");
break;
}
//登陸失敗
else
{
System.out.println(name+",嘗試登錄");
out.println(name+",用戶名不存在");
}
}
s.close();
}
catch (Exception e)
{
throw new RuntimeException(ip+"校驗失敗");
}
}
}
class LoginServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss = new ServerSocket(10008);
//客戶端可併發訪問
while(true)
{
Socket s = ss.accept();
new Thread(new UserThread(s)).start();
}
}
}
三、瀏覽器和Tomcat服務器
瀏覽器就是最常見的客戶端,它能夠對html代碼進行解析。下面自定義一個服務器,通過瀏覽器對其進行訪問。程序如下:
public static void main(String[] args) throws Exception {
//建立ServerSocket對象,創建Socket服務
ServerSocket ss=new ServerSocket(10009);
//獲取連接客戶端對象
Socket s=ss.accept();
//獲取Socket服務的輸出流。
PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
//輸出數據
pw.println("<font color='black' size='8'>歡迎光臨服務器</font>");
//關閉資源
s.close();
ss.close();
}
在瀏覽器輸入IP地址和端口號,就能看到服務端的輸出數據。效果如下:
還可以通過瀏覽器訪問Tomcat服務器,Tomcat是常用的一種服務器,它內部封裝了ServerSocket對象,可以讀取自定義的資源。可以把自定義的資源存儲在Tomcat目錄中的myweb目錄裏。下面我們就通過定義程序,模擬瀏覽器,訪問Tomcat服務器,獲取數據。這時就需要通過向Tomcat發送http請求消息頭,來獲取資源。如下:
public static void main(String[] args)throws Exception
{
//建立Socket對象,創建Socket服務
Socket s = new Socket("192.168.1.113",8080);
//獲取輸出流
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//發送請求消息頭
out.println("GET /myweb/2.html HTTP/1.1");//請求獲取指定資源路徑下的資源
out.println("Accept: */*");//可以接收所有數據類型
out.println("Accept-Language: zh-cn");//接收語言
out.println("Host: 192.168.1.113:8080");//明確IP地址和端口
out.println("Connection: Keep-Alive");//連接狀態
//請求消息頭必須和消息體有空行。
out.println();
out.println();
//獲取輸入流
BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line=bufr.readLine())!=null)
{
System.out.println(line);
}
//關閉資源
s.close();
}
}
要注意的請求消息頭和請求消息之間一定要用空行隔開。接下來我們再結合圖形化界面,創建一個簡易的瀏覽器,因爲要獲取輸入的指定IP地址、主機名、端口等,這時就要用到URL對象,URL代表一個統一資源定位符,內部帶有協議的封裝了Socket對象,通過其特有方法獲取I要連接的服務器端,通過openConnection方法與服務器端建立連接。下面就通過URL對象,實現瀏覽器的創建和功能實現。如下:
class IEDemo
{
//定義組件引用
private Frame f;
private TextField tf;
private Button but;
private TextArea ta;
private Dialog d;
private Label lab;
private Button okBut;
IEDemo()
{
init();
}
public void init()
{
//窗體的創建、基本設置
f = new Frame("my window");
f.setBounds(300,100,600,500);
f.setLayout(new FlowLayout());
tf = new TextField(60);
but = new Button("轉到");
ta = new TextArea(25,70);
d = new Dialog(f,"提示信息-self",true);
d.setBounds(400,200,240,150);
d.setLayout(new FlowLayout());
lab = new Label();
okBut = new Button("確定");
d.add(lab);
d.add(okBut);
f.add(tf);
f.add(but);
f.add(ta);
//加載事件
myEvent();
f.setVisible(true);
}
private void myEvent()
{
okBut.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
d.setVisible(false);
}
});
d.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
d.setVisible(false);
}
});
//文本框添加鍵盤監聽
tf.addKeyListener(new KeyAdapter()
{
public void keyPressed(KeyEvent e)
{
try
{
if(e.getKeyCode()==KeyEvent.VK_ENTER)
showDir();
}
catch (Exception ex)
{
throw new RuntimeException("操作失敗");
}
}
});
//按鈕添加活動監聽
but.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
showDir();
}
catch (Exception ex)
{
throw new RuntimeException("操作失敗");
}
}
});
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
//定義連接客戶端的方法
private void showDir()throws Exception
{
ta.setText("");
String urlPath = tf.getText();
//建立URL對象
URL url = new URL(urlPath);
//實現連接
URLConnection conn = url.openConnection();
//獲取輸入流
InputStream in = conn.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
//將數據寫入文本區
ta.setText(new String(buf,0,len));
}
public static void main(String[] args)
{
new IEDemo();
}
}
注意一般輸入網址,是不帶端口號的,此時通過URL的getPort()獲取。若通過網址返回的port爲-1,則分配一個默認的80端口小知識點:
1.當Socket對象中構造函數爲空時,可以通過connect方法連接服務器。
2.ServerSocket(int port ,int backlog ),backlog表示隊列的最大長度,即最多有幾個客戶端。通過這個構造函數,對客戶端數量進行限制。
3.我們上網時輸入的都是網址,而不是ip地址,是怎麼連接到指定主機的呢?這時就需要域名解析(DNS),將主機名變成IP地址。我們訪問一個網址的過程是這樣的:我們在進行訪問時,會現在本機的hosts文件中尋找對應映射,有的話就直接返回。沒有,就到DNS中去找對應的映射,找打後返回對應的IP地址,再通過IP地址找到對應的服務器。
-------------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! -------