127.0.0.1是回路地址,用於測試,相當於localhost本機地址,沒有網卡,不設DNS都可以訪問.
端口地址在0~65535之間,其中0~1023之間的端口是用於一些知名的網絡服務和應用,用戶的普通網絡應用程序應該使用1024以上的端口.
網絡應用中基本上都是TCP(Transmission Control Protocol傳輸控制協議)和UDP(User Datagram Protocol用戶數據報協議),TCP是面向連接的通信協議,UDP是無連接的通信協議.
Socket連接套接字,Java分別為TCP和UDP提供了相應的類,TCP是java.net.ServerSocket(用於服務器端)和java.net.Socket(用於客戶端);UDP是Java.Net.DatagramSocket.
1,Java編寫UDP網絡程序
1.1,DatagramSocket
DatagramSocket有如下構造方法:
1,DatagramSocket() :構造數據報套接字並將其綁定到本地主機上任何可用的端口。
2,DatagramSocket(int port):創建數據報套接字並將其綁定到本地主機上的指定端口。
3,DatagramSocket(int port, InetAddress laddr):創建數據報套接字,將其綁定到指定的本地地址。即指定網卡發送和接收數據.
如果在創建DatagramSocket對象時,沒有指定網卡的IP 地址,在發送數據時,底層驅動程序會自動選擇一塊網卡去發送,在接收數據時,會接收所有的網卡收到的與端口一致的數據.
發送信息時,可以不指定端口號,接收信息時,要指定端口號,因為要接收指定的數據.
發送數據使用DatagramSocket.send(DatagramPacket p)方法,接收數據使用DatagramSocket.receive(DatagramPacket p)方法.
1.2,DatagramPacket
DatagramPacket類有如下構造方法:
1,DatagramPacket(byte[] buf, int length):構造 DatagramPacket,用來接收長度為length的數據包。
2,DatagramPacket(byte[] buf, int length, InetAddress address, int port):構造數據報包,用來將長度為length的包發送到指定主機上的指定端口號。
接收數據時使用第一次構造方法,發送數據時使用第二種構造方法.
1.3,InetAddress
Java中對IP地址進行包裝的類,
DatagramPacket.getAddress()可以獲取發送或接收方的IP地址.DatagramPacket.getPort()可以獲取發送或接收方的端口.
1.4,UDP程序例子
發送程序:
import Java.Net.DatagramPacket;
import Java.Net.DatagramSocket;
import Java.Net.InetAddress;
public class UdpSend {
public static void main(String[] args) throws Exception {
DatagramSocket ds = new DatagramSocket();
String str = "hello , world!";
DatagramPacket dp = new DatagramPacket(str.getBytes(),str.length(),InetAddress.getByName("192.168.0.105"),3000);
ds.send(dp);
ds.close(); //關閉連接
}
}
接收程序:
import Java.Net.DatagramPacket;
import Java.Net.DatagramSocket;
public class UdpRecv {
public static void main(String[] args) throws Exception {
DatagramSocket ds = new DatagramSocket(3000);
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
ds.receive(dp);
String str = new String(dp.getData(),0,dp.getLength());
System.out.println(str);
System.out.println("IP:" + dp.getAddress().getHostAddress() + ",PORT:" + dp.getPort());
ds.close();
}
}
測試要先運行接收程序,再運行發送程序.如果接收程序沒有接收到數據,則會一直阻塞,接收到數據後才會關閉程序.如果網絡上沒有數據發送過來,接收程序也沒有阻塞,通常都是使用了一個已經被占用的端口.
2,Java編寫TCP網絡程序
2.1,ServerSocket
編寫TCP網絡服務程序,首先要用到Java.Net.ServerSocket類用以創建服務器Socket.它的常用構造方法有:
1,ServerSocket(int port):創建綁定到特定端口的服務器套接字。
2,ServerSocket(int port, int backlog):利用指定的backlog(服務器忙時保持連接請求的等待客戶數量),創建服務器套接字並將其綁定到指定的本地端口號。
3,ServerSocket(int port, int backlog, InetAddress bindAddr):使用指定的端口、偵聽 backlog 和要綁定到的本地 IP 地址創建服務器。
2.2,Socket
客戶端要與服務器建立連接,必須先創建一個Socket對象,它的常用構造方法有:
1,Socket(String host, int port):創建一個流套接字並將其連接到指定主機上的指定端口號。
2,Socket(InetAddress address, int port):創建一個流套接字並將其連接到指定 IP 地址的指定端口號。
3,Socket(InetAddress address, int port, InetAddress localAddr, int localPort):創建一個套接字並將其連接到指定遠程端口上的指定遠程地址。
4,Socket(String host, int port, InetAddress localAddr, int localPort):創建一個套接字並將其連接到指定遠程主機上的指定遠程端口。
對於通常情況的應用,使用第1個構造方法來創建客戶端的Socket對象,並與服務器建立連接,是非常簡單和方便的.
服務器端程序調用ServerSocket.accept方法等待客戶端的連接請求,一旦accept接收了客戶端連接請求,該方法返回一個與該客戶端建立了專線連接的Socket對象,不用程序去創建這個Socket對象.建立了連接的兩個Socket是以IO流的方式進行數據交換的,Java提供了Socket.getInputStream返回Socket的輸入流對象,Socket.getOutputStream返回Socket的輸出流對象.
2.3,TCP程序例子的服務器程序:
import Java.io.InputStream;
import Java.io.OutputStream;
import Java.Net.ServerSocket;
import Java.Net.Socket;
public class TcpServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(8000);
Socket s = ss.accept();
InputStream ips = s.getInputStream();
OutputStream ops = s.getOutputStream();
ops.write("hello,World!".getBytes());
byte[] buf = new byte[1024];
int len = ips.read(buf);
System.out.println(new String(buf,0,len));
ips.close();
ops.close();
s.close();
ss.close();
}
}
在這個程序裡,創建了一個在8000端口上等待連接的ServerSocket對象,當接收到一個客戶的連接請求後,程序從與這個客戶建立了連接的Socket對象中獲得輸入輸出流對象,通過輸出流首先向客戶端發送一串字符,然後通過輸入流讀取客戶端發送過來的信息,並將這些信息打印,然後關閉所有資源.
要先運行服務器程序,然後才能運行客戶端程序,當TCP服務器程序運行到Socket.accpet()方法等待客戶連接時,accept方法將阻塞,一直到有客戶連接請求到來,該方法才會返回,如果又沒有請求到來,又沒有發生阻塞,通常都是使用了一個已經被占用的端口.
我們可以使用Windows提供的telnet工具在命令行窗口中測試一下服務器程序:命令如下:telnet localhost 8000
可以看到,telnet只要有輸入就發送,因此我們如果想要在服務器端一次讀多個字符的話,還需要進一步處理,看如下代碼:
import Java.io.BufferedReader;
import Java.io.InputStream;
import Java.io.InputStreamReader;
import Java.io.OutputStream;
import Java.Net.ServerSocket;
import Java.Net.Socket;
public class TcpServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(8000);
Socket s = ss.accept();
InputStream ips = s.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(ips)); //對InputStream進行包裝,增加了緩存
OutputStream ops = s.getOutputStream();
ops.write("hello,World!".getBytes());
System.out.println(br.readLine());
br.close(); //關閉包裝類,會自動關閉裡面的基類
ops.close();
s.close();
ss.close();
}
}
再次使用telnet工具可以看到,這次可以發送不止一個字符了,按回車鍵後發送數據到服務器端.
2.4,TCP程序例子改進後的服務器程序:
大多數情況下,服務器端都要服務多個客戶端,但一次accept方法調用只接收一個連接,因此,要把accept方法放在一個循環語句中,這樣就可以接收多個連接.每個連接的數據交換代碼也放在一個循環中,這樣才能保證兩者可以不停地交換數據.
每個連接的數據交換代碼必須放在獨立的線程中運行,否則,這在段代碼運行期間,就沒法執行其他的程序代碼,accept方法也得不到調用,新的連接無法進入.
下面是一個例子,客戶端向服務器發送一個字符串,服務器將這個字符串中的所有字符反向排列後回送給客戶端.客戶端輸入"quit",退出程序.
import Java.io.BufferedReader;
import Java.io.DataOutputStream;
import Java.io.InputStream;
import Java.io.InputStreamReader;
import Java.io.OutputStream;
import Java.Net.ServerSocket;
import Java.Net.Socket;
public class TcpServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(8000);
while(true){
Socket s = ss.accept();
new Thread(new Servicer(s)).start();
}
}
}
class Servicer implements Runnable{
Socket s;
public Servicer(Socket s){
this.s = s;
}
public void run(){
try{
InputStream ips = s.getInputStream();
OutputStream ops = s.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(ips));
DataOutputStream DOS = new DataOutputStream(ops);
while(true){
String strWord = br.readLine();
if(strWord.equalsIgnoreCase("quit")){
break;
}
String strEcho = (new StringBuffer(strWord).reverse().toString());
DOS.writeBytes(strWord + "------->" + strEcho + System.getProperty("line.separator"));
}
br.close();
DOS.close();
s.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
2.5,TCP程序例子客戶端程序:
import Java.io.BufferedReader;
import Java.io.DataOutputStream;
import Java.io.InputStream;
import Java.io.InputStreamReader;
import Java.io.OutputStream;
import Java.Net.InetAddress;
import Java.Net.Socket;
public class TcpClIEnt {
public static void main(String[] args) throws Exception{
if(args.length < 2){
System.out.println("Usage:Java TcpClIEnt ServerIP ServerPort");
return ;
}
Socket s = new Socket(InetAddress.getByName(args[0]),Integer.parseInt(args[1]));
InputStream ips = s.getInputStream();
OutputStream ops = s.getOutputStream();
BufferedReader brKey = new BufferedReader(new InputStreamReader(System.in));
DataOutputStream DOS = new DataOutputStream(ops);
BufferedReader brNet = new BufferedReader(new InputStreamReader(ips));
while(true){
String strWord = brKey.readLine();
DOS.writeBytes(strWord + System.getProperty("line.separator"));
if("quit".equalsIgnoreCase(strWord)){
break;
}else{
System.out.println(brNet.readLine());
}
}
DOS.close();
brNet.close();
brKey.close();
s.close();
}
}
先運行服務器程序,再在命令行使用Java TcpClIEnt 192.168.0.3 8000,這樣就啟動了客戶端程序.我們可以啟動多個客戶端程序.
我們可以利用netstat工具來查看已經被使用的端口