title: 『 Socket 』使用Socket實現(xiàn)TCP編程及聊天室的編寫
tags: socket
categories: socket
若圖片無法顯示堕战,請前往我的博客查看墓赴,相應(yīng)文章鏈接:http://codingxiaxw.cn/2016/11/29/57-socket-chatroom/#more
本篇文章分為兩個部分丹允,一個部分是總結(jié)使用Socket實現(xiàn)TCP的編程的知識,主要就是完成服務(wù)器端和客戶端兩個對象的代碼編寫携茂;另一個部分是通過Java寫一個聊天室來對我們的Socket編程進行鞏固你踩。
文章結(jié)構(gòu):首先是對TCP的簡單介紹,然后在分析Socket通信的模型后進行Java Socket實現(xiàn)TCP編程的代碼編寫讳苦,最后是利用Socket的知識編寫一個簡單的聊天室带膜。
1.TCP簡介
TCP(Transmission Control Protocol 傳輸控制協(xié)議)是一種面向連接的、可靠的鸳谜、基于字節(jié)流的傳輸層通信協(xié)議膝藕,由IETF的RFC 793定義。在簡化的計算機網(wǎng)絡(luò)OSI模型中咐扭,它完成第四層傳輸層所指定的功能芭挽,用戶數(shù)據(jù)報協(xié)議(UDP,本篇文章不介紹UDP)是同一層內(nèi)另一個重要的傳輸協(xié)議蝗肪。在因特網(wǎng)協(xié)議族(Internet protocol suite)中袜爪,TCP層是位于IP層之上,應(yīng)用層之下的中間層穗慕。不同主機的應(yīng)用層之間經(jīng)常需要可靠的、像管道一樣的連接妻导,但是IP層不提供這樣的流機制逛绵,而是提供不可靠的包交換。
首先了解一下網(wǎng)絡(luò)模型中的數(shù)據(jù)傳遞:應(yīng)用層向TCP層發(fā)送用于網(wǎng)間傳輸?shù)木缶隆⒂?位字節(jié)表示的數(shù)據(jù)流术浪,然后TCP把數(shù)據(jù)流分區(qū)成適當長度的報文段(通常受該計算機連接的網(wǎng)絡(luò)的數(shù)據(jù)鏈路層的最大傳輸單元(MTU)的限制)。之后TCP把結(jié)果包傳給IP層寿酌,由它來通過網(wǎng)絡(luò)將包傳送給接收端實體的TCP層胰苏。TCP為了保證不發(fā)生丟包,就給每個包一個序號醇疼,同時序號也保證了傳送到接收端實體的包的按序接收硕并。然后接收端實體對已成功收到的包發(fā)回一個相應(yīng)的確認(ACK)法焰;如果發(fā)送端實體在合理的往返時延(RTT)內(nèi)未收到確認,那么對應(yīng)的數(shù)據(jù)包就被假設(shè)為已丟失將會被進行重傳倔毙。TCP用一個校驗和函數(shù)來檢驗數(shù)據(jù)是否有錯誤埃仪;在發(fā)送和接收時都要計算校驗和。
2.Socket通信模型
所謂socket 通常也稱作”套接字“陕赃,用于描述IP地址和端口卵蛉,是一個通信鏈的句柄。應(yīng)用程序通常通過”套接字”向網(wǎng)絡(luò)發(fā)出請求或者應(yīng)答網(wǎng)絡(luò)請求么库。首先我們看看Socket基于TCP的通信模型圖:
[圖片上傳失敗...(image-1149ea-1526380908286)]
圖中通信模型的各個步驟如下:
- 在服務(wù)端建立一個ServerSocket,綁定相應(yīng)的端口傻丝,并且在指定的端口進行偵聽,等待客戶端的連接诉儒。
- 當客戶端創(chuàng)建連接Socket并且向服務(wù)端發(fā)送請求葡缰。
- 服務(wù)器收到請求,并且接受客戶端的請求信息允睹。一旦接收到客戶端的連接請求后运准,會創(chuàng)建一個連接socket,用來與客戶端的socket進行通信缭受。通過相應(yīng)的輸入/輸出流進行數(shù)據(jù)的交換胁澳,數(shù)據(jù)的發(fā)送接收以及數(shù)據(jù)的響應(yīng)等等。
- 當客戶端和服務(wù)端通信完畢后米者,需要分別關(guān)閉socket韭畸,結(jié)束通信。
也就是基于服務(wù)器和客戶端的開發(fā)蔓搞,所以在服務(wù)器端和客戶端我們分別需要完成的代碼就是:
對于服務(wù)器端需要完成的工作是:
- 創(chuàng)建ServerSocket對象胰丁,綁定監(jiān)聽器
- 通過accept()方法監(jiān)聽客戶端請求
- 連接建立以后通過讀取客戶端發(fā)送請求消息
- 通過輸出流向客戶端發(fā)送響應(yīng)信息
- 關(guān)閉資源
ServerSocket類中涉及到的常用方法:
- ServerSocket(int port)——創(chuàng)建并綁定到特定端口的服務(wù)器套接字
- accept()——偵聽并接受到此套接字的連接
- close()——關(guān)閉此套接字
- getInetAddress()——得到ServerSocket對象綁定的IP地址。如果ServerSocket對象未綁定IP地址喂分,返回0.0.0.0
- getLocalPort()——返回此套接字在其上偵聽的端口
客戶端需要完成的工作是:
- 創(chuàng)建Socket對象锦庸,指明需要連接的服務(wù)器地址和端口號(1023以后的端口,因為0~1023之間的端口號是我們系統(tǒng)需要使用的端口號)
- 連接建立后,通過輸出流向服務(wù)器端請求
- 通過輸入流獲取服務(wù)器響應(yīng)信息
- 關(guān)閉資源
Socket類中常用的方法:
- Socket(InetAddress address, int port)——創(chuàng)建一個套接字并將其連接到指定ip地址的指定端口號
- Socket(String host, int port)——創(chuàng)建一個套接字并將其連接到指定主機上的指定端口號
- close()——關(guān)閉此套接字
- getInetAddress()——返回套接字連接的地址
- getInputStream()——返回此套接字的輸入流
- getOutputStream——返回此套接字的輸出流
好了蒲祈,通過上述的描述甘萧,對Socket的編程就講述的很清楚了,接下來針對上述描述進行我們服務(wù)器和客戶端代碼的編碼工作梆掸。
3.基于Tcp的Socket開發(fā)代碼編寫
首先是服務(wù)器端Server.java的代碼編寫:
/**
* Created by codingBoy on 16/11/29.
* 基于TCP協(xié)議的Socket通信扬卷,實現(xiàn)客戶登錄
*/
public class Server
{
public static void main(String[] args)
{
//1.創(chuàng)建一個服務(wù)器Socket,即ServerSocket,指定綁定的端口酸钦,并堅挺
try {
ServerSocket serverSocket=new ServerSocket(8888);
//2怪得,調(diào)用accept()開始監(jiān)聽,等待客戶端的鏈接
System.out.println("****服務(wù)器即將啟動,等待客戶端的連接****");
Socket socket=serverSocket.accept();
//3.獲取輸入流并獲取客戶信息
InputStream in=socket.getInputStream();
InputStreamReader isr=new InputStreamReader(in,"utf-8");
BufferedReader br=new BufferedReader(isr);
String info;
StringBuilder sb=new StringBuilder();
while ((info=br.readLine())!=null)
{
sb.append(info);
}
System.out.println("我是服務(wù)器徒恋,客戶端發(fā)來的消息為:"+sb);
socket.shutdownInput();//關(guān)閉輸入流
//4.獲取輸出流蚕断,用于響應(yīng)客戶端的請求
OutputStream os=socket.getOutputStream();
PrintWriter pw=new PrintWriter(os);
pw.write("歡迎您");
pw.flush();//將緩沖輸出
//4.關(guān)閉相關(guān)資源
pw.close();
os.close();
br.close();
isr.close();
in.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
然后是客戶端Client.java的代碼編寫:
public class Client {
public static void main(String[] args)
{
//1.創(chuàng)建客戶端Socket,指定服務(wù)器端地址和端口號
try {
Socket socket=new Socket("localhost",8888);
//2.獲取輸出流,用來向服務(wù)器端發(fā)送登錄信息
OutputStream os=socket.getOutputStream();
PrintWriter pw=new PrintWriter(os);//將輸出流打包成打印流
pw.write("用戶名:codingxiaxw;密碼:123");
pw.flush();//刷新緩存
socket.shutdownOutput();//關(guān)閉輸出流
//3.獲取服務(wù)器傳過來的輸入流因谎,讀取服務(wù)器的響應(yīng)信息
InputStream in=socket.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(in,"utf-8"));
String info;
StringBuilder sb=new StringBuilder();
while ((info=br.readLine())!=null)
{
sb.append(info);
}
System.out.println("我是客戶端基括,服務(wù)器給我的信息為:"+sb);
//3.關(guān)閉資源
br.close();
in.close();
pw.close();
os.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
此時我們的客戶端和服務(wù)器端的代碼便完成了,首先運行服務(wù)器Server.java财岔,代碼在執(zhí)行到Socket socket=serverSocket.accept();
時會在此阻塞风皿,控制臺輸出:
****服務(wù)器即將啟動,等待客戶端的連接****
直到等到客戶端連接到該端口號的服務(wù)器后服務(wù)器的代碼才會向下執(zhí)行匠璧,此時運行客戶端Client.java桐款,客戶端的控制臺輸出:
我是客戶端,服務(wù)器給我的信息為:歡迎您
Process finished with exit code 0
然后此時切換到服務(wù)器的控制臺夷恍,發(fā)現(xiàn)輸出信息:
****服務(wù)器即將啟動魔眨,等待客戶端的連接****
我是服務(wù)器,客戶端發(fā)來的消息為:用戶名:codingxiaxw;密碼:123
Process finished with exit code 0
此時我們便使用Socket完成了一個服務(wù)器和一個客戶端之間的通信酿雪,那么問題來了遏暴,如何實現(xiàn)一個服務(wù)器與多個客戶端之間的通信呢?我們使用多線程服務(wù)器的方式指黎。創(chuàng)建一個ServerThread.java用于編寫服務(wù)器端多線程接收客戶端傳遞過來的信息的代碼編寫朋凉,代碼如下:
public class ServerThread extends Thread
{
private Socket socket;
public ServerThread(Socket socket)
{
this.socket=socket;
}
public void run()
{
//3.獲取輸入流并獲取客戶信息
InputStream in= null;
InputStreamReader isr=null;
BufferedReader br=null;
OutputStream os=null;
PrintWriter pw=null;
try {
in = socket.getInputStream();
isr=new InputStreamReader(in,"utf-8");
br=new BufferedReader(isr);
String info;
StringBuilder sb=new StringBuilder();
while ((info=br.readLine())!=null)
{
sb.append(info);
}
System.out.println("我是服務(wù)器,客戶端發(fā)來的消息為:"+sb);
socket.shutdownInput();//關(guān)閉輸入流
//4.獲取輸出流醋安,用于響應(yīng)客戶端的請求
os=socket.getOutputStream();
pw=new PrintWriter(os);
pw.write("歡迎您");
pw.flush();//將緩沖輸出
} catch (IOException e) {
e.printStackTrace();
}finally {
//4.關(guān)閉相關(guān)資源
try {
if (pw!=null) pw.close();
if (os!=null) os.close();
if (br!=null) br.close();
if (isr!=null) isr.close();
if (in!=null) in.close();
if (socket!=null) socket.close();
}catch (Exception e)
{
e.printStackTrace();
}
}
}
}
實際上我們就是將之前寫在Server.java中獲取客戶端傳遞過來的信息的那部分代碼拿出來寫在了該線程中杂彭,然后修改Server.java中的代碼為:
public class Server
{
public static void main(String[] args)
{
//1.創(chuàng)建一個服務(wù)器Socket,即ServerSocket,指定綁定的端口吓揪,并堅挺
try {
ServerSocket serverSocket=new ServerSocket(8888);
//2亲怠,調(diào)用accept()開始監(jiān)聽,等待客戶端的鏈接
System.out.println("****服務(wù)器即將啟動柠辞,等待客戶端的連接****");
//記錄客戶端的數(shù)量
int count=0;
while (true) {
//調(diào)用accept()方法開始監(jiān)聽团秽,等待客戶端的連接
Socket socket = serverSocket.accept();
//創(chuàng)建一個新的線程
ServerThread serverThread=new ServerThread(socket);
//啟動線程
serverThread.start(); //如果不要啟動線程的話這里直接調(diào)用run()也行.
count++;//統(tǒng)計客戶端的數(shù)量
System.out.println("客戶端的數(shù)量:"+count);
InetAddress address=socket.getInetAddress();
System.out.println("當前客戶端的IP:"+address.getHostAddress());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
運行Server.java,服務(wù)器控制臺輸出信息:
****服務(wù)器即將啟動叭首,等待客戶端的連接****
然后運行Client.java习勤,客戶端控制臺輸出信息:
我是客戶端,服務(wù)器給我的信息為:歡迎您
此時跳轉(zhuǎn)到服務(wù)器控制臺放棒,信息變?yōu)?
****服務(wù)器即將啟動姻报,等待客戶端的連接****
我是服務(wù)器己英,客戶端發(fā)來的消息為:用戶名:codingxiaxw;密碼:123
客戶端的數(shù)量:1
當前客戶端的IP:127.0.0.1
然后更改Client.java中傳遞給服務(wù)器的數(shù)據(jù)代碼為:pw.write("用戶名:codingxiaxw;密碼:456");
间螟,再運行Client.java,發(fā)現(xiàn)服務(wù)器的控制臺輸出信息變?yōu)?
****服務(wù)器即將啟動,等待客戶端的連接****
我是服務(wù)器厢破,客戶端發(fā)來的消息為:用戶名:codingxiaxw;密碼:13
客戶端的數(shù)量:1
當前客戶端的IP:127.0.0.1
我是服務(wù)器荣瑟,客戶端發(fā)來的消息為:用戶名:codingxiaxw;密碼:456
客戶端的數(shù)量:2
當前客戶端的IP:127.0.0.1
這樣我們便完成了多線程服務(wù)器的編碼工作,到此我們便成功使用Socket完成了基于TCP協(xié)議的編程摩泪。接下來趁熱打鐵笆焰,用Socket實現(xiàn)一個簡單的聊天室功能。
4.使用Socket實現(xiàn)一個簡單的聊天室
功能概述:客戶端用于發(fā)送信息(在控制臺中發(fā)送信息),將在控制臺輸出的信息轉(zhuǎn)換為輸出流輸出到服務(wù)器端见坑,服務(wù)器通過socket.getOutputStream()
方法接收客戶端傳來的信息嚷掠,并將發(fā)送該信息的客戶端地址及信息發(fā)送在控制臺中,這樣我們便簡單的實現(xiàn)了我們的聊天室荞驴。
這里給大家講講qq通信的原理:qq使用c/s模式進行通信不皆,qq中的用戶A發(fā)送信息給用戶B,過程是這樣的:當A打開和B的聊天窗口時即和B還有服務(wù)器建立了一個聊天室(同時服務(wù)器和客戶端開啟連接)熊楼,A發(fā)送信息霹娄,其實是發(fā)送到了qq聊天室服務(wù)器的接收容器中,然后qq服務(wù)器將該客戶端地址(即qq頭像)和信息內(nèi)容顯示在聊天室中(即聊天窗口)鲫骗,你每和一個好友進行聊天打開一個窗口就等于和她(另一個客戶端)還有我們的qq服務(wù)器組成了一個聊天室(當然聊天室的服務(wù)器肯定是多線程的)犬耻。qq上還有多人聊天的功能,實現(xiàn)道理也是這樣执泰,只是該聊天室中有多個客戶端給服務(wù)器發(fā)送消息罷了枕磁。(這是qq剛興起時的聊天功能設(shè)計,也就是我們本篇文章需要實現(xiàn)的簡單的聊天室功能坦胶,下面的內(nèi)容與設(shè)計聊天室無關(guān)透典,但是我覺得還是有必要跟大家介紹清楚如今的qq時怎樣工作的,了解便可)
qq服務(wù)器掛在騰訊的某臺主機上顿苇,相當于起了一個中轉(zhuǎn)站的成分峭咒,這種聊天功能的實現(xiàn)對于客戶端數(shù)量比較少時服務(wù)器端還能接受,但是在客戶端數(shù)量很多時服務(wù)器肯定要癱瘓纪岁。
所以為了減少服務(wù)器端的壓力凑队,需要實現(xiàn)客戶端和客戶端之間的直接通信,這樣客戶端上的qq既要實現(xiàn)服務(wù)器端的功能(用于接收信息)又要實現(xiàn)客戶端的功能(用于發(fā)送信息)幔翰。此時qq服務(wù)器就不再作為一個中轉(zhuǎn)站的功能了漩氨,它主要用于:用于客戶端程序登陸,驗證用戶名密碼,獲取其他在線好友信息等等。
分析了功能后接下來進行我們服務(wù)器端和客戶端代碼的編寫遗增,首先創(chuàng)建一個ChatRoom.java叫惊,運行后用于開啟服務(wù)器和客戶端的連接,代碼如下:
待更新做修。
待更新霍狰。
2018.3.19更
歡迎加入我的Java交流1群:659957958抡草。群里目前已有1800人,每天都非痴崤鳎活躍康震,但為了篩選掉那些不懷好意的朋友進來搞破壞,所以目前入群方式已改成了付費方式宾濒,你只需要支付9塊錢腿短,即可獲取到群文件中的所有干貨以及群里面各位前輩們的疑惑解答;為了鼓勵良好風氣的發(fā)展绘梦,讓每個新人提出的問題都得到解決橘忱,所以我將得到的入群收費收入都以紅包的形式發(fā)放到那些主動給新手們解決疑惑的朋友手中。在這里卸奉,我們除了談技術(shù)鹦付,還談生活、談理想择卦;在這里敲长,我們?yōu)槟愕膶W(xué)習(xí)方向指明方向,為你以后的求職道路提供指路明燈秉继;在這里祈噪,我們把所有好用的干貨都與你分享。還在等什么尚辑,快加入我們吧辑鲤!
2018.4.21更:如果群1無法加入,請加Java學(xué)習(xí)交流2群:305335626 杠茬。群2作為群1的附屬群月褥,除了日常的技術(shù)交流、資料分享瓢喉、學(xué)習(xí)方向指明外宁赤,還會在每年互聯(lián)網(wǎng)的秋春招時節(jié)在群內(nèi)發(fā)布大量的互聯(lián)網(wǎng)內(nèi)推方式,話不多說栓票,快上車吧决左!
5.聯(lián)系
If you have some questions after you see this article,you can tell your doubts in the comments area or you can find some info by clicking these links.