什么是Socket
網(wǎng)絡(luò)上的兩個程序通過一個雙向的通訊連接實現(xiàn)數(shù)據(jù)的交換吃嘿,這個雙向鏈路的一端稱為一個Socket祠乃。Socket通常用來實現(xiàn)客戶方和服務(wù)方的連接。Socket是TCP/IP協(xié)議的一個十分流行的編程界面兑燥,一個Socket由一個IP地址和一個端口號唯一確定
但是亮瓷,Socket所支持的協(xié)議種類不僅TCP/IP一種,因此兩者之間是沒有必然聯(lián)系的降瞳。在Java環(huán)境下嘱支,Socket編程主要是指基于TCP/IP協(xié)議的網(wǎng)絡(luò)編程
PS:雖然湊字數(shù)這種技能早就點滿了,但關(guān)于更多Socket及TCP/IP相關(guān)概念挣饥,還請各位看官自行/先行了解除师,這里不再多做贅述
本次Demo預(yù)覽
工具準備
- Eclipse(若你沒有Eclipse也沒事兒,后邊告訴你用命令行編譯運行H臃恪)
- AndroidStudio(若你本身就是用Eclipse開發(fā)安卓程序汛聚,那Eclipse就夠了)
服務(wù)端
OK,話不多說短荐,開干
首先在Eclipse新建一個Java項目倚舀,就叫SocketDemo吧
接下來咱們要監(jiān)聽是否有客戶端發(fā)送連接請求叹哭,如果有,則連接并處理
SocketDemo.java:
public class SocketDemo {
/**
* 端口號 注意:0~1023為系統(tǒng)所保留端口號痕貌,選擇端口號時應(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ā)送連接請求,
//則返回一個socket供處理與客戶端的連接舵稠,否則一直阻塞監(jiān)聽
Socket socket = serverSocket.accept();
System.out.println("與客戶端連接成功...");
//這個MySocket是啥呢超升?是一個對socket的封裝,方便操作
MySocket mySocket = new MySocket(socket);
//由于MySocket繼承于Thread柱查,所以需要start()一下
//致于為啥要繼承于Thread來封裝socket廓俭,請看下方 MySocket類
mySocket.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注釋中的兩個問題,很好理解唉工,不多說研乒,直接看看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)讀取消息,所以每條消息最后必須加換行符 \n,否則讀取不到
mWriter.write(msg + "\n");
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會方便一點
那么至此烈菌,服務(wù)端的程序已經(jīng)寫完了
什么?你問怎么這么簡單花履?芽世!原因有兩個:
- 這只是一個基礎(chǔ)的Socket服務(wù)端程序,不用考慮那么多其他情況诡壁,自然幾行代碼就搞定了
- 沒錯济瓢,Socket就是這么簡單!
接下來你會發(fā)現(xiàn)妹卿,客戶端特么更簡單旺矾!
客戶端(Android)
第一步新建一個安卓項目,也叫SocketDemo吧夺克,畢竟箕宙,湊字數(shù)這個技能我比較熟練
簡單一點,布局中就一個按鈕(id=btn_send)铺纽,用來發(fā)送消息柬帕,初窺嘛,簡單就是王道,布局代碼就不上了
接下來看看MainActivity的代碼:
不行雕崩,在看MainActivity之前還有一些話要交代清楚:
- 如果你將安卓程序跑在電腦的虛擬機上魁索,則你訪問的IP地址為:10.0.2.2(虛擬機只能通過這個IP訪問電腦)
- 如果你將安卓程序跑在真機上,那么你需要在CMD中輸入ipconfig獲取到IPv4地址盼铁,并且確保手機和電腦在同一個網(wǎng)絡(luò)下(連接了同一個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;
//這個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ù)端是按行讀取消息,所以每條消息最后必須加換行符 \n
mWriter.write(msg + "\n");
mWriter.flush();
} catch (IOException e) {
Toast.makeText(this,"發(fā)送失敺羟蕖:服務(wù)端已關(guān)閉服務(wù)当辐!",Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
}
這就寫完了客戶端?鲤看?對缘揪,這就寫完了...那你別問為啥Socket咋就這么點內(nèi)容,Socket本來也不是啥難點~
并且义桂,這只是一個非常非痴殷荩基礎(chǔ)的Demo
OK,到這里就可以來跑一下程序試一試了
跑起來
跑服務(wù)端程序
- 如果你有Eclipse慷吊,那么直接在Eclipse內(nèi)跑起來就行了袖裕!
-
如果很不巧,你沒有Eclipse
- 新建本文章服務(wù)端部分的
SocketDemo.java
和MySocket.java
兩個文件溉瓶,并且放在同一個文件夾下急鳄,上面代碼沒有寫出import包,不能直接copy進文件內(nèi)用堰酿,文末我會放出所有源代碼疾宏,到文末copy一下放在兩個文件內(nèi)就行了(當然你得確保你有JDK環(huán)境!雖然作為安卓狗胞锰,這是必要的灾锯,但還是提醒一下>ふァ) -
打開CMD嗅榕,切換進入上述兩個文件所在的目錄
- 執(zhí)行
javac *.java java SocketDemo
就將程序跑起來了(ctrl+c退出程序)
- 新建本文章服務(wù)端部分的
- 注意事項:
- 在Eclipse內(nèi)運行的程序,切記:如果修改內(nèi)容后要重新啟動程序吵聪,請先將正在運行的程序關(guān)閉脂男,否則將一直占用端口端考!無法再以此端口再次啟用一次程序!
- 如果用CMD運行的程序,提示編碼錯誤辰晕,請將所有中文替換成英文,或者將兩個.java文件內(nèi)容轉(zhuǎn)換成GBK編碼(建議換成英文!英文好的哥們兒,上佃乘!)
跑客戶端程序
直接跑安卓程序就行了!
在Eclipse跑服務(wù)端的圖已經(jīng)在文首放出驹尼,這里放一個CMD下跑服務(wù)端的圖片:
注:不知為什么發(fā)送消息的時候趣避,命令行及LogCat不會即時顯示出內(nèi)容,在我ctrl+c退出程序之后才會一次全出來新翎,若有知道的朋友程帕,還望指教!萬分感謝地啰!
改進一個不足
想一下愁拭,服務(wù)端程序只響應(yīng)一個客戶端,如果又有客戶端發(fā)出連接請求亏吝,那豈不是無法響應(yīng)了岭埠!
再想一下覺得不對,也就是我自己測試蔚鸥,哪來的第二個客戶端發(fā)出連接請求
再再想一下枫攀,如果你改了一下安卓端的代碼,又一次點了運行株茶,那誰來響應(yīng)你来涨?!這樣的話启盛,因為修改安卓端代碼蹦掐,又得去把服務(wù)端的程序停了,再啟動一下僵闯,多麻煩卧抗!
好吧,既然分析了確實有這個麻煩鳖粟,那就把它解決掉:
public class SocketDemo {
public static int PORT = 2745;
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(PORT);
System.out.println("服務(wù)啟動...");
//寫一個死循環(huán)社裆,如果有一個客戶端連接成功,那么繼續(xù)讓serverSocket.accept()阻塞住
//等待下一個客戶端請求向图,這樣不論有多少個客戶端請求過來泳秀,都可以響應(yīng)到,
//結(jié)束調(diào)試的時候再關(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不解釋了
至此整個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 + "\n");
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");
}
}
- 客戶端(安卓端)的我就不放了吕嘀!