摘要:什么是網(wǎng)絡(luò)上的兩個(gè)程序通過一個(gè)雙向的通訊連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)雙向鏈路的一端稱為一個(gè)。通常用來實(shí)現(xiàn)客戶方和服務(wù)方的連接。
什么是Socket
網(wǎng)絡(luò)上的兩個(gè)程序通過一個(gè)雙向的通訊連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)雙向鏈路的一端稱為一個(gè)Socket。Socket通常用來實(shí)現(xiàn)客戶方和服務(wù)方的連接。Socket是TCP/IP協(xié)議的一個(gè)十分流行的編程界面,一個(gè)Socket由一個(gè)IP地址和一個(gè)端口號唯一確定
但是,Socket所支持的協(xié)議種類不僅TCP/IP一種,因此兩者之間是沒有必然聯(lián)系的。在Java環(huán)境下,Socket編程主要是指基于TCP/IP協(xié)議的網(wǎng)絡(luò)編程
PS:雖然湊字?jǐn)?shù)這種技能早就點(diǎn)滿了,但關(guān)于更多Socket及TCP/IP相關(guān)概念,還請各位看官自行/先行了解,這里不再多做贅述
本次Demo預(yù)覽 工具準(zhǔn)備Eclipse(若你沒有Eclipse也沒事兒,后邊告訴你用命令行編譯運(yùn)行?。?/p>
AndroidStudio(若你本身就是用Eclipse開發(fā)安卓程序,那Eclipse就夠了)
服務(wù)端OK,話不多說,開干
首先在Eclipse新建一個(gè)Java項(xiàng)目,就叫SocketDemo吧
接下來咱們要監(jiān)聽是否有客戶端發(fā)送連接請求,如果有,則連接并處理
SocketDemo.java:
public class SocketDemo { /** * 端口號 注意:0~1023為系統(tǒng)所保留端口號,選擇端口號時(shí)應(yīng)大于1023,具體隨便你取 */ public static int PORT = 2345; public static void main(String[] args) { try { //serverSocket用于監(jiān)聽是否有客戶端發(fā)送連接請求 ServerSocket serverSocket = new ServerSocket(PORT); System.out.println("服務(wù)啟動..."); //serverSocket.accept():如果有客戶端發(fā)送連接請求, //則返回一個(gè)socket供處理與客戶端的連接,否則一直阻塞監(jiān)聽 Socket socket = serverSocket.accept(); System.out.println("與客戶端連接成功..."); //這個(gè)MySocket是啥呢?是一個(gè)對socket的封裝,方便操作 MySocket mySocket = new MySocket(socket); //由于MySocket繼承于Thread,所以需要start()一下 //致于為啥要繼承于Thread來封裝socket,請看下方 MySocket類 mySocket.start(); } catch (IOException e) { e.printStackTrace(); } } }
注釋中的兩個(gè)問題,很好理解,不多說,直接看看MySocket是怎么寫的吧:
MySocket.java
public class MySocket extends Thread { Socket mSocket; BufferedWriter mWriter; BufferedReader mReader; public MySocket(Socket socket) { this.mSocket = socket; try { mWriter = new BufferedWriter(new OutputStreamWriter( socket.getOutputStream(), "utf-8")); mReader = new BufferedReader(new InputStreamReader( socket.getInputStream(), "utf-8")); } catch (IOException e) { e.printStackTrace(); } } /** * 向客戶端發(fā)送消息 * msg 發(fā)送消息內(nèi)容 **/ public void send(String msg) { try { // 客戶端按行(readLine)讀取消息,所以每條消息最后必須加換行符 ,否則讀取不到 mWriter.write(msg + " "); mWriter.flush(); } catch (IOException e) { e.printStackTrace(); } } /** * * 不斷讀取來自客戶端的消息,一旦收到消息,則自動回復(fù) **/ @Override public void run() { super.run(); try { String line; //服務(wù)端按行讀取消息 //不斷按行讀取,獲得來自客戶端的消息 while ((line = mReader.readLine()) != null) { System.out.println("客戶端消息:" + line); //收到客戶端消息后,自動回復(fù) send("已經(jīng)收到你發(fā)送的"" + line + """); } mReader.close(); } catch (IOException e) { e.printStackTrace(); } System.out.println("end"); } }
看完MySocket之后豁然開朗,原來將讀取客戶端消息的操作是阻塞的,要放在子線程來做,所以繼承于Thread會方便一點(diǎn)
那么至此,服務(wù)端的程序已經(jīng)寫完了
什么?你問怎么這么簡單?!原因有兩個(gè):
這只是一個(gè)基礎(chǔ)的Socket服務(wù)端程序,不用考慮那么多其他情況,自然幾行代碼就搞定了
沒錯(cuò),Socket就是這么簡單!
接下來你會發(fā)現(xiàn),客戶端特么更簡單!
客戶端(Android)第一步新建一個(gè)安卓項(xiàng)目,也叫SocketDemo吧,畢竟,湊字?jǐn)?shù)這個(gè)技能我比較熟練
簡單一點(diǎn),布局中就一個(gè)按鈕(id=btn_send),用來發(fā)送消息,初窺嘛,簡單就是王道,布局代碼就不上了
接下來看看MainActivity的代碼:
不行,在看MainActivity之前還有一些話要交代清楚:
如果你將安卓程序跑在電腦的虛擬機(jī)上,則你訪問的IP地址為:10.0.2.2(虛擬機(jī)只能通過這個(gè)IP訪問電腦)
如果你將安卓程序跑在真機(jī)上,那么你需要在CMD中輸入ipconfig獲取到IPv4地址,并且確保手機(jī)和電腦在同一個(gè)網(wǎng)絡(luò)下(連接了同一個(gè)WIFI)
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); connectServer(); findViewById(R.id.btn_send).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { sendMsg("2333"); } }); } private Socket mSocket; private BufferedWriter mWriter; private BufferedReader mReader; //這個(gè)IP上面解釋過了噢,要理解一下 private static String IP = "10.0.2.2"; //切記端口號一定要和服務(wù)端保持一致! private static int PORT = 2345; private void connectServer() { new Thread(new Runnable() { @Override public void run() { try { mSocket = new Socket(IP, PORT); mWriter = new BufferedWriter(new OutputStreamWriter( mSocket.getOutputStream(), "utf-8")); mReader = new BufferedReader(new InputStreamReader( mSocket.getInputStream(), "utf-8")); Log.i(TAG, "連接服務(wù)端成功"); } catch (IOException e) { Log.i(TAG, "連接服務(wù)端失敗"); e.printStackTrace(); return; } try { String line; while ((line = mReader.readLine()) != null) { Log.i(TAG, "服務(wù)端消息: " + line); } } catch (IOException e) { e.printStackTrace(); Log.i(TAG, "服務(wù)端:已停止服務(wù)"); } } }).start(); } private void sendMsg(String msg) { // 如果mSocket為null有可能兩種情況: // 1.還在嘗試連接服務(wù)端 // 2.連接失敗 if (mSocket == null){ Toast.makeText(this,"連接未完成或連接失敗,無法發(fā)送消息!",Toast.LENGTH_SHORT).show(); return; } try { //服務(wù)端是按行讀取消息,所以每條消息最后必須加換行符 mWriter.write(msg + " "); mWriter.flush(); } catch (IOException e) { Toast.makeText(this,"發(fā)送失敗:服務(wù)端已關(guān)閉服務(wù)!",Toast.LENGTH_SHORT).show(); e.printStackTrace(); } } }
這就寫完了客戶端??對,這就寫完了...那你別問為啥Socket咋就這么點(diǎn)內(nèi)容,Socket本來也不是啥難點(diǎn)~
并且,這只是一個(gè)非常非常基礎(chǔ)的Demo
OK,到這里就可以來跑一下程序試一試了
跑起來 跑服務(wù)端程序如果你有Eclipse,那么直接在Eclipse內(nèi)跑起來就行了!
如果很不巧,你沒有Eclipse
新建本文章服務(wù)端部分的SocketDemo.java和MySocket.java兩個(gè)文件,并且放在同一個(gè)文件夾下,上面代碼沒有寫出import包,不能直接copy進(jìn)文件內(nèi)用,文末我會放出所有源代碼,到文末copy一下放在兩個(gè)文件內(nèi)就行了(當(dāng)然你得確保你有JDK環(huán)境!雖然作為安卓狗,這是必要的,但還是提醒一下?。?/p>
打開CMD,切換進(jìn)入上述兩個(gè)文件所在的目錄
執(zhí)行
javac *.java java SocketDemo
就將程序跑起來了(ctrl+c退出程序)
注意事項(xiàng):
在Eclipse內(nèi)運(yùn)行的程序,切記:如果修改內(nèi)容后要重新啟動程序,請先將正在運(yùn)行的程序關(guān)閉,否則將一直占用端口!無法再以此端口再次啟用一次程序!
如果用CMD運(yùn)行的程序,提示編碼錯(cuò)誤,請將所有中文替換成英文,或者將兩個(gè).java文件內(nèi)容轉(zhuǎn)換成GBK編碼(建議換成英文!英文好的哥們兒,上!)
跑客戶端程序直接跑安卓程序就行了!
在Eclipse跑服務(wù)端的圖已經(jīng)在文首放出,這里放一個(gè)CMD下跑服務(wù)端的圖片:
改進(jìn)一個(gè)不足注:不知為什么發(fā)送消息的時(shí)候,命令行及LogCat不會即時(shí)顯示出內(nèi)容,在我ctrl+c退出程序之后才會一次全出來,若有知道的朋友,還望指教!萬分感謝!
想一下,服務(wù)端程序只響應(yīng)一個(gè)客戶端,如果又有客戶端發(fā)出連接請求,那豈不是無法響應(yīng)了!
再想一下覺得不對,也就是我自己測試,哪來的第二個(gè)客戶端發(fā)出連接請求
再再想一下,如果你改了一下安卓端的代碼,又一次點(diǎn)了運(yùn)行,那誰來響應(yīng)你?!這樣的話,因?yàn)樾薷陌沧慷舜a,又得去把服務(wù)端的程序停了,再啟動一下,多麻煩!
好吧,既然分析了確實(shí)有這個(gè)麻煩,那就把它解決掉:
public class SocketDemo { public static int PORT = 2745; public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(PORT); System.out.println("服務(wù)啟動..."); //寫一個(gè)死循環(huán),如果有一個(gè)客戶端連接成功,那么繼續(xù)讓serverSocket.accept()阻塞住 //等待下一個(gè)客戶端請求,這樣不論有多少個(gè)客戶端請求過來,都可以響應(yīng)到, //結(jié)束調(diào)試的時(shí)候再關(guān)閉服務(wù)端程序 while (true) { Socket socket = serverSocket.accept(); System.out.println("客戶端連接成功..."); MySocket mySocket = new MySocket(socket); mySocket.start(); } } catch (IOException e) { e.printStackTrace(); } } }
so easy~不解釋了~
至此整個(gè)SocketDemo就完成了,對Socket的第一步已經(jīng)邁出了,那么趕緊理解好,然后再深入Socket吧!
源碼SocketDemo.java:
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class SocketDemo { public static int PORT = 2745; public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(PORT); System.out.println("服務(wù)啟動..."); while (true) { Socket socket = serverSocket.accept(); System.out.println("客戶端連接成功..."); MySocket mySocket = new MySocket(socket); mySocket.start(); } } catch (IOException e) { e.printStackTrace(); } } }
MySocket.java:
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; public class MySocket extends Thread { Socket mSocket; BufferedWriter mWriter; BufferedReader mReader; public MySocket(Socket socket) { this.mSocket = socket; try { mWriter = new BufferedWriter(new OutputStreamWriter( socket.getOutputStream(), "utf-8")); mReader = new BufferedReader(new InputStreamReader( socket.getInputStream(), "utf-8")); } catch (IOException e) { e.printStackTrace(); } } public void send(String msg) { try { mWriter.write(msg + " "); mWriter.flush(); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { super.run(); try { String line; while ((line = mReader.readLine()) != null) { System.out.println("客戶端消息:" + line); send("收到:"" + line + """); } mReader.close(); } catch (IOException e) { e.printStackTrace(); } System.out.println("end"); } }
客戶端(安卓端)的我就不放了!
結(jié)語更多內(nèi)容歡迎訪問我的主頁或我的博客
如果我的文章確實(shí)有幫助到你,請不要忘了點(diǎn)一下文末的"?"讓他變成"?"
作為新手難免很多地方理解不到位,文中若有錯(cuò)誤請直(bu)接(yao)指(ma)出(wo)
寫作不易!
致于題目叫"與自己聊次天",我想解釋一
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/69892.html
摘要:什么是網(wǎng)絡(luò)上的兩個(gè)程序通過一個(gè)雙向的通訊連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)雙向鏈路的一端稱為一個(gè)。通常用來實(shí)現(xiàn)客戶方和服務(wù)方的連接。 什么是Socket 網(wǎng)絡(luò)上的兩個(gè)程序通過一個(gè)雙向的通訊連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)雙向鏈路的一端稱為一個(gè)Socket。Socket通常用來實(shí)現(xiàn)客戶方和服務(wù)方的連接。Socket是TCP/IP協(xié)議的一個(gè)十分流行的編程界面,一個(gè)Socket由一個(gè)IP地址和一個(gè)端口號唯一確...
摘要:在大型的工程中,自己定義的變量函數(shù),類名與其他人定義的相沖突等問題。使用標(biāo)準(zhǔn)輸出控制臺和標(biāo)準(zhǔn)輸入鍵盤時(shí),必須包含頭文件以及標(biāo)準(zhǔn)命名空間。缺省參數(shù)概念缺省參數(shù)是聲明或定義函數(shù)時(shí)為函數(shù)的參數(shù)指定一個(gè)默認(rèn)值。 目錄 前言 1.命名空間 1.1命名空間定義 1.2 命名空間使用 2. C++的輸入和...
摘要:容器運(yùn)行時(shí),會打印一條信息消息并退出。因此,更好地做法是將需要使用的用戶加入用戶組。涉及到了兩條指令,和。執(zhí)行命令指令是用來執(zhí)行命令行命令的。 Docker 是個(gè)劃時(shí)代的開源項(xiàng)目,它徹底釋放了計(jì)算虛擬化的威力,極大提高了應(yīng)用的運(yùn)行效率,降低了云計(jì)算資源供應(yīng)的成本!使用 Docker,可以讓應(yīng)用的部署、測試和分發(fā)都變得前所未有的高效和輕松! 無論是應(yīng)用開發(fā)者、運(yùn)維人員、還是其他信息技術(shù)從...
摘要:容器運(yùn)行時(shí),會打印一條信息消息并退出。因此,更好地做法是將需要使用的用戶加入用戶組。涉及到了兩條指令,和。執(zhí)行命令指令是用來執(zhí)行命令行命令的。 Docker 是個(gè)劃時(shí)代的開源項(xiàng)目,它徹底釋放了計(jì)算虛擬化的威力,極大提高了應(yīng)用的運(yùn)行效率,降低了云計(jì)算資源供應(yīng)的成本!使用 Docker,可以讓應(yīng)用的部署、測試和分發(fā)都變得前所未有的高效和輕松! 無論是應(yīng)用開發(fā)者、運(yùn)維人員、還是其他信息技術(shù)從...
摘要:被移除的方法和屬性被改變?yōu)閺?fù)數(shù)形式的方法名以下這些方法名被更改為了復(fù)數(shù)形式。在中,使用單數(shù)的方法名將會得到一個(gè)警告。在中,被它的駝峰命名版本所取代。在中,中會刪去端口號,而在中,端口號會被保留。 前言 Express 5.0 仍處于alpha版中,但是我們還是想先來初窺一下新的express版本中將會有哪些改變,以及如何將你的應(yīng)用從Express 4 遷移至 Express 5。 Ex...
閱讀 2689·2021-10-12 10:12
閱讀 2335·2021-09-02 15:41
閱讀 2561·2019-08-30 15:55
閱讀 1399·2019-08-30 13:05
閱讀 2430·2019-08-29 11:21
閱讀 3535·2019-08-28 17:53
閱讀 3022·2019-08-26 13:39
閱讀 801·2019-08-26 11:50