TCP/IP 實現通信圖示:
服務器端: ServerSocket
第一步: 服務器端啓動一個ServerSocket, 綁定到特定的端口號上,並對該端口號進行監聽,等待用戶的請求
ServerSocket serverSocket = new ServerSocket(5566);
第二步: 調用accept() 方法,監聽端口號, 如果沒有用戶發送請求連接,這個accept方法是會一致阻塞的,用戶發送了請求之後就會被這個方法監聽到,並返回一個發送方的socket,(類型是Socket)
socket = serverSocket.accept()
第三步: 建立了連接之後,就通過I/O的方式進行通信,做數據的傳遞
InputStream In = socket.getInputStream(); //節點流
OutputStream out = socket.getOutputStream(); // 節點流
需要注意的一點是,服務器一直在監聽,當它捕獲到一個請求時,會爲此請求啓動一個線程,爲此分配一個綁定到不同端口地址的套接字,而之前的那個端口號繼續進行監聽
客戶端: Socket(套接字): 連接運行在網絡上的兩個程序間的雙向通信的端點
第一步: 向服務器端發送連接請求(需要知道服務器的IP和端口號)
Socket socket = new Socket(InetAddress.getLocalHost(), 5566);
第二步: 上一步建好連接之後,就通過I/O的方式進行通信
InputStream In = socket.getInputStream(); //節點流
OutputStream out = socket.getOutputStream(); // 節點流
一個程序是隻能佔用一個端口號的,下面是一個小例子,通過線程的方式實現服務器端和客戶端的通信,將讀寫操作分別建立線程
服務器端代碼
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
//
public class ServerSocketTest {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(5566);
Socket socket;
while (true){
socket = serverSocket.accept(); // 如果沒有客戶端的請求,這一句是會一直阻塞的
// 啓動讀線程
serverReadThread readThread = new serverReadThread(socket);
readThread.start();
// 啓動寫線程
User user = new User("liuxw",18,"server");
serverWriteThread writeThread = new serverWriteThread(socket,user);
writeThread.start();
}
}
}
class serverReadThread extends Thread {
private Socket socket;
private User user;
public serverReadThread(Socket socket) {
this.socket = socket;
}
public void run() {
try {
// try 語句不要寫在循環裏面,要儘量寫在循環外面,因爲異常對象的創建與處理消耗時間
while (true) {
ObjectInputStream in = new ObjectInputStream( socket
.getInputStream());
ArrayList<User> list = (ArrayList<User>) in.readObject();
System.out.println(list.get(0).getName());
System.out.println(list.get(1).getName());
// 如何關閉呢, 如果直接加上in.close() 這樣的話就會頻繁的進行創建銷燬,代價較大
// 解決方法,要麼連接一直開着,要麼定義一個特定字符來關閉流,要麼使用連接池來管理連接
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class serverWriteThread extends Thread {
private Socket socket;
private User user;
public serverWriteThread(Socket socket, User user) {
this.socket = socket;
this.user = user;
}
public void run() {
try {
// try 語句不要寫在循環裏面,要儘量寫在循環外面,因爲異常對象的創建與處理消耗時間
while (true) {
ObjectOutputStream objOut = new ObjectOutputStream(socket.getOutputStream());
// ObjectOutputStream 也是可以把一個集合寫進去的,讀的時候按照集合的方式來讀就行了
ArrayList<User> list = new ArrayList<User>();
User user2 = new User("cao",22,"sss");
list.add(user);
list.add(user2);
objOut.writeObject(list);
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
客戶端程序:
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class ClientSocketTest {
public static void main(String[] args) throws Exception {
Socket socket = new Socket(InetAddress.getLocalHost(), 5566);
ObjectOutputStream objOut = new ObjectOutputStream(socket.getOutputStream());
User user = new User("wangsw", 20, "client");
// 啓動讀線程, 其實這裏的操作和服務器端的操作是一致的,因此我們就不再重複的寫另外一個類
serverReadThread readThread = new serverReadThread(socket);
readThread.start();
// 啓動寫線程
serverWriteThread writeThread = new serverWriteThread(socket,user);
writeThread.start();
}
}
UDP 實現網絡通信:
UDP 是面向的無連接的通信方式,因此UDP基本上是不區分服務器端和客戶端的,但是我們在這裏可以進行區分一下,我們把首先啓動進行監聽UDP報文消息的叫做服務器端,把先進行發送數據的叫做客戶端。
爲什麼我會想着在這裏做一下區分呢?這是因爲我在寫下面的測試程序的時候,發現兩個用戶如果啓動的順序不對,那麼是收不到報文的,當時我就想爲什麼順序不對會接收不到呢?
這是因爲,監聽的一方先啓動,那麼就會一直對網絡進行監聽,當有人向他發送消息時他能捕獲到,但是當發送的一方先啓動,那麼報文傳輸到網絡中,一定時間內找不到要發送的對象,那麼就會發生報文丟失的現象。
當時爲了弄懂這個原因,我還看了幫助文檔,卻意外的發現了有一個connect方法,不是面向無連接的嗎?爲什麼有一個connect連接方法,這個我會在下一篇中詳細進行講解。
現在先讓我們來看看如何在java中實現UDP通信吧
java中使用DatagramSocket 和DatagramPacket 來實現UDP通信,Socket類是用來創建一個socket的,Packet是用來創建一個要發送的報文的。
socket.send(), socket.recieve()用來發送和接收數據包
需要注意的是,我們在發送數據包的時候,數據包裏面要設置被髮送方的IP和Port, 但是隱藏的我們自身的IP 和Port也會添加到這個數據包裏面, 這樣對方接收到我們發送的數據包,就能夠從數據包裏面獲取到我們的IP和Port 然後就可以向我們發送數據包。
流程如下:
服務器端(先進行監聽的一方)
第一步: 首選創建一個DatagramSocket,爲其分配一個端口號,然後就會一直監聽從這個端口號傳進來的UDP數據包
DatagramSocket socket = new DatagramSocket(7000);
第二步: 創建一個DatagramPacket 包用來存儲從該端口接收到的數據包,然後調用socket.receive() 方法,將收到的數據包放在創建的對象裏。
byte[] buff = new byte[100];
DatagramPacket packetRecieve = new DatagramPacket(buff, 0, buff.length);
socket.receive(packetRecieve);
第三步: 獲取數據包中的信息,並且根據數據包中發送方的IP和Port向客戶端發送數據包。
String str = "world";
DatagramPacket packetSend = new DatagramPacket(str.getBytes(),
str.length(), packetRecieve.getAddress(),
packetRecieve.getPort());
socket.send(packetSend);
第四步:關閉socket
socket.close();
客戶端的代碼和這個差不多,只不過順序顛倒一下,先發送後接收。
下面給出了一個實現了UDP通信的小例子
服務器端(先監聽的)
// UDP傳輸是沒有IO的,每次發送的信息的包裏都包含了要發送對象的IP 和端口號 ,而TCP、IP 一旦建立連接就通過流來讀取接收數據
public class UDPServerTest {
// 服務器端先接收消息,然後在發送消息
public static void main(String[] args) throws Exception {
// 自己開一個7000端口號,監聽網絡中的UDP數據包
DatagramSocket socket = new DatagramSocket(7000);
// 接收一個用戶的packet
// 首先定義一個packet,這個packet 用來存儲接收到的packet
byte[] buff = new byte[100];
DatagramPacket packetRecieve = new DatagramPacket(buff, 0, buff.length);
socket.receive(packetRecieve);
System.out.println(new String(buff, 0, packetRecieve.getLength())); // 輸出接收到的消息
// 然後再向用戶發送消息
// 首先將要發送的數據包封裝到一個packet裏面, 通過接收到的包獲取對方的Ip 和端口號
String str = "world";
DatagramPacket packetSend = new DatagramPacket(str.getBytes(),
str.length(), packetRecieve.getAddress(),
packetRecieve.getPort());
socket.send(packetSend);
socket.close();
}
}
客戶端(先發送的)
public class UDPClientTest {
// 客戶端先發送消息
public static void main(String[] args) throws Exception {
// 先new 一個 socket ,其中設置你自己的端口號
DatagramSocket socket = new DatagramSocket(7001);
// socket.connect(InetAddress.getLocalHost(), 7000);
// socket.disconnect();
// 將你要發送的消息,封裝到一個data
String str = "hello";
// 注意的是packet包中都是字節數組,所以數據要轉化爲字節數組
// 首先將消息封裝到一個packet中,packet中包含了指定的對方的ip,port,還有發送給對方的消息(隱藏的:自己的ip,port也會在這個packet中)
DatagramPacket packetSend = new DatagramPacket(str.getBytes(), str.length(), InetAddress.getLocalHost(), 7000);
// 發送
socket.send(packetSend);
byte[] buff = new byte[100];
DatagramPacket packetRecieve = new DatagramPacket(buff, 0, buff.length);
socket.receive(packetRecieve);
System.out.println(new String(buff, 0, packetRecieve.getLength())); // 輸出接收到的消息
socket.close();
}
}