本次實驗采用Java語言,編寫了一個簡單的聊天室程序,可以實現多人之間的聊天。以下將對該程序進行詳盡分析,並對比分析該編程語言提供的網絡接口API與Linux Socket API之間的關系。
1、 網絡通信相關要素
1) 協議
通信的協議還是比較復雜的, java.net 包中包含的類和接口,它們提供低層次的通信細節。我們可以直接使用這 些類和接口,來專注於網絡程序開發,而不用考慮通信的細節。 java.net 包中提供了兩種常見的網絡協議的支持: TCP:傳輸控制協議 (Transmission Control Protocol)。TCP協議是面向連接的通信協議,即傳輸數據之前, 在發送端和接收端建立邏輯連接,然后再傳輸數據,它提供了兩台計算機之間可靠無差錯的數據傳輸。 三次握手:TCP協議中,在發送數據的准備階段,客戶端與服務器之間的三次交互,以保證連接的可 靠。 第一次握手,客戶端向服務器端發出連接請求,等待服務器確認。 第二次握手,服務器端向客戶端回送一個響應,通知客戶端收到了連接請求。 第三次握手,客戶端再次向服務器端發送確認信息,確認連接。整個交互過程如下圖所示。
完成三次握手,連接建立后,客戶端和服務器就可以開始進行數據傳輸了。由於這種面向連接的特性,TCP協議可 以保證傳輸數據的安全,所以應用十分廣泛,例如下載文件、瀏覽網頁等。
UDP:用戶數據報協議(User Datagram Protocol)。UDP協議是一個面向無連接的協議。傳輸數據時,不需 要建立連接,不管對方端服務是否啟動,直接將數據、數據源和目的地都封裝在數據包中,直接發送。每個 數據包的大小限制在64k以內。它是不可靠協議,因為無連接,所以傳輸速度快,但是容易丟失數據。日常應 用中,例如視頻會議、QQ聊天等。
2) IP地址
IP地址:指互聯網協議地址(Internet Protocol Address),俗稱IP。IP地址用來給一個網絡中的計算機設 備做唯一的編號。
IP地址分類 IPv4:是一個32位的二進制數,通常被分為4個字節,表示成 a.b.c.d 的形式,例如 192.168.65.100 。其 中a、b、c、d都是0~255之間的十進制整數,那么最多可以表示42億個。IPv6:由於互聯網的蓬勃發展,IP地址的需求量愈來愈大,但是網絡地址資源有限,使得IP的分配越發緊張。 有資料顯示,全球IPv4地址在2011年2月分配完畢。 為了擴大地址空間,擬通過IPv6重新定義地址空間,采用128位地址長度,每16個字節一組,分成8組十六進 制數,表示成 ABCD:EF01:2345:6789:ABCD:EF01:2345:6789 ,號稱可以為全世界的每一粒沙子編上一個網 址,這樣就解決了網絡地址資源數量不夠的問題。
3) 端口號
網絡的通信,本質上是兩個進程(應用程序)的通信。每台計算機都有很多的進程,那么在網絡通信時,如何區分 這些進程呢? 如果說IP地址可以唯一標識網絡中的設備,那么端口號就可以唯一標識設備中的進程(應用程序)了。 端口號:用兩個字節表示的整數,它的取值范圍是0~65535。其中,0~1023之間的端口號用於一些知名的網 絡服務和應用,普通的應用程序需要使用1024以上的端口號。如果端口號被另外一個服務或應用所占用,會 導致當前程序啟動失敗。 利用 協議 + IP地址 + 端口號 三元組合,就可以標識網絡中的進程了,那么進程間的通信就可以利用這個標識與其 它進程進行交互。
2、 服務端程序主要代碼
/** * 創建服務器 啟動服務監聽1001端口 */
public AppServer() { sFrame = new ServerFrame(); try { serverSocket = new ServerSocket(1001); InetAddress address = InetAddress.getLocalHost(); sFrame.txtServerName.setText(address.getHostName()); sFrame.txtIP.setText(address.getHostAddress()); sFrame.txtPort.setText("1001"); } catch (IOException e) { fail(e, "不能啟動服務!"); } sFrame.txtStatus.setText("已啟動..."); this.start(); // 啟動線程
}
3、 客戶端程序主要代碼
public void run() { int intMessageCounter = 0; int intUserTotal = 0; boolean isFirstLogin = true; // 判斷是否剛登陸 boolean isFound; // 判斷是否找到用戶 Vector user_exit = new Vector(); try { for (;;) { Socket toServer; toServer = new Socket(strServerIp, 1001); // 將信息發往服務器 messobj = new Message(); ObjectOutputStream streamtoserver = new ObjectOutputStream( toServer.getOutputStream()); streamtoserver.writeObject((Message) messobj); // 收來自服務器的信息 ObjectInputStream streamfromserver = new ObjectInputStream( toServer.getInputStream()); messobj = (Message) streamfromserver.readObject(); // //////刷新聊天信息列表////////// if (isFirstLogin) // 如果剛登陸 { intMessageCounter = messobj.chat.size(); // 屏蔽該用戶登陸前的聊天內容 isFirstLogin = false; } if (!serverMessage.equals(messobj.serverMessage)) { serverMessage = messobj.serverMessage; taUserMessage.append("[系統消息]:" + serverMessage+"\n"); } for (int i = intMessageCounter; i < messobj.chat.size(); i++) { Chat temp = (Chat) messobj.chat.elementAt(i); String temp_message; if (temp.chatUser.equals(strLoginName)) { if (temp.chatToUser.equals(strLoginName)) { temp_message = "系統提示您:請不要自言自語!" + "\n"; } else { if (!temp.whisper) { temp_message = temp.chatUser + ":"+ temp.chatMessage+ "\n"; } } } else { if (temp.chatToUser.equals(strLoginName)) { if (!temp.whisper) { temp_message = temp.chatUser + ":"+ temp.chatMessage+ "\n"; } } else { if (!temp.chatUser.equals(temp.chatToUser)) // 對方沒有自言自語 { if (!temp.whisper) // 不是悄悄話 { temp_message = temp.chatUser + ":"+ temp.chatMessage+ "\n"; } else { temp_message = ""; } } else { temp_message = ""; } } } taUserMessage.append(temp_message); intMessageCounter++; }
4、 實驗結果截圖
實現基於tcp的網絡通信,服務端程序監聽本地ip地址127.0.0.1,監聽端口號為1001。客戶端程序向本機該端口號發送消息。服務器接收到客戶端消息,將該消息轉發給每一個客戶端,從而實現多人通信。
5、java socket接口與Linux socket api之間的關系
在Java中,提供了兩個類用於實現TCP通信程序: 1. 客戶端: java.net.Socket 類表示。創建 Socket 對象,向服務端發出連接請求,服務端響應請求,兩者建 立連接開始通信。 2. 服務端: java.net.ServerSocket 類表示。創建 ServerSocket 對象,相當於開啟一個服務,並等待客戶端 的連接。以下是java中socket編程中一些常用方法,即接口public InputStream getInputStream() : 返回此套接字的輸入流。 如果此Scoket具有相關聯的通道,則生成的InputStream 的所有操作也關聯該通道。 關閉生成的InputStream也將關閉相關的Socket。 public OutputStream getOutputStream() : 返回此套接字的輸出流。 如果此Scoket具有相關聯的通道,則生成的OutputStream 的所有操作也關聯該通道。 關閉生成的OutputStream也將關閉相關的Socket。 public void close() :關閉此套接字。 一旦一個socket被關閉,它不可再使用。 關閉此socket也將關閉相關的InputStream和OutputStream 。 public void shutdownOutput() : 禁用此套接字的輸出流。 任何先前寫出的數據將被發送,隨后終止輸出流。
那么java 這些接口與Linux socket api之間有哪些關系呢?
1)Socket toServer = new Socket(strServerIp, 1001)即socket接口對Linux API的調用,具體如下:
1 創建socket結構體
2 將對應socket和本地協議地址對應(執行bind函數),將當前網絡命名空間名和端口存到bhash(),可以理解為,綁定到系統能夠找到的地方。
3 listen()監聽,即檢查偵聽端口是否存在bhash中,初始化csk_accept_queue,將tcp_sock指針存放到listening_hash表。
2)accpet()方法在Linux 的底層詳細如下:
linux中由TCP服務器調用,從已完成的連接隊列的隊頭返回下一個已完成連接;成功則會返回由內核創建的新的描述符,在並發服務器中,accept()返回后,服務器會調用fork函數,創建一個子進程,由該子進程來和客戶端進行通訊,此時套接口對應文件描述符的引用計數會增加。