目錄
1.網(wǎng)絡(luò)編程的基本概念
1.1為什么需要網(wǎng)絡(luò)編程?
1.2服務(wù)端與用戶端
1.3網(wǎng)絡(luò)編程五元組?
1.4套接字的概念
2.UDP套接字編程
2.1UDP套接字的特點(diǎn)
?2.2UDP套接字API
2.2.1DatagramSocket類
2.2.2DatagramPacket類?
2.2.3基于UDP的回顯程序
2.2.4基于UDP的單詞查詢?
3.TCP套接字編程
3.1TCP套接字的特點(diǎn)
3.2TCP中的長(zhǎng)短連接
3.3TCP套接字的API
3.3.2Socket類
3.3.3基于TCP的回顯程序
3.3.4對(duì)TCP回顯程序的優(yōu)化
1.網(wǎng)絡(luò)編程的基本概念
網(wǎng)絡(luò)編程,指網(wǎng)絡(luò)上的主機(jī),通過不同的進(jìn)程,以編程的方式實(shí)現(xiàn)網(wǎng)絡(luò)通信(或稱為網(wǎng)絡(luò)數(shù)據(jù)傳輸)。對(duì)于我們程序員來說,我們比較關(guān)注應(yīng)用層到傳輸層這一操作,我們代碼的書寫是發(fā)生在應(yīng)用層的,然后通過傳輸層提供的API(UDP和TCP)進(jìn)行包裝,再由應(yīng)用層發(fā)送給傳輸層。
1.1為什么需要網(wǎng)絡(luò)編程?
?這個(gè)最為直接的說話莫過于人民需要他了,哪里有需求,哪里就提供響應(yīng)嘛。
相比于本地的資源,網(wǎng)絡(luò)上有這更為豐富的資源,而網(wǎng)絡(luò)上的資源又需要通過網(wǎng)絡(luò)編程來上傳,因此就相互督促相互促進(jìn)了對(duì)方的發(fā)展。
1.2服務(wù)端與用戶端
服務(wù)端:在常見的網(wǎng)絡(luò)數(shù)據(jù)傳輸場(chǎng)景下,把提供服務(wù)的一方進(jìn)程,稱為服務(wù)端,可以提供對(duì)外服務(wù)。
客戶端:獲取服務(wù)的一方進(jìn)程,稱為客戶端。
簡(jiǎn)單的說,客戶端會(huì)接收到客戶的請(qǐng)求,再將客戶的請(qǐng)求發(fā)送給服務(wù)端;服務(wù)端將請(qǐng)求按照業(yè)務(wù)邏輯完成響應(yīng),再將響應(yīng)返回給客戶端,客戶端再將響應(yīng)按照操作顯示或者運(yùn)作。
1.3網(wǎng)絡(luò)編程五元組?
- 源IP:就是發(fā)送方IP.
- 源端口:發(fā)送方端口號(hào), 服務(wù)器需要手動(dòng)指定, 客戶端讓系統(tǒng)隨機(jī)分配即可.
- 目的IP: 接收方IP, 包含在拿到的數(shù)據(jù)報(bào)中, 服務(wù)器響應(yīng)時(shí)的目的IP就在客戶端發(fā)來的數(shù)據(jù)報(bào)中, 客戶端發(fā)送請(qǐng)求時(shí)的目的IP就是服務(wù)器的IP.
- 目的端口: 接收方端口號(hào)包含在數(shù)據(jù)報(bào)中, 服務(wù)器響應(yīng)時(shí)的目的端口就在客戶端發(fā)來的數(shù)據(jù)報(bào)中, 客戶端發(fā)送請(qǐng)求時(shí)的目的端口就是服務(wù)器的端口號(hào).
- 協(xié)議類型:如UDP/TCP.
1.4套接字的概念
Socket(套接字)可以看成是兩個(gè)網(wǎng)絡(luò)應(yīng)用程序進(jìn)行通信時(shí),各自通信連接中的端點(diǎn),這是一個(gè)邏輯上的概念。Socket是由IP地址和端口結(jié)合的,提供向應(yīng)用層進(jìn)程傳送數(shù)據(jù)包的機(jī)制?
套接字的表示方法:Socket=Ip接口:端口號(hào)
2.UDP套接字編程
2.1UDP套接字的特點(diǎn)
特點(diǎn)如下:
- 無連接:不關(guān)心和誰對(duì)話,也不會(huì)刻意留意對(duì)方。
- 不可靠的:不關(guān)心他收沒收到。
- 面向數(shù)據(jù)報(bào):以一個(gè)UDP數(shù)據(jù)報(bào)為單位。
- 全雙工通信:一條路徑,但是路徑兩方都可以對(duì)話。(雙向?qū)υ挘?/li>
?2.2UDP套接字API
UDP套接字的API中主要包括兩個(gè)類?:1.DatagramSocket 2.DatagramPacket。
下面就開始介紹這兩個(gè)類
2.2.1DatagramSocket類
?DatagramSocket類就是實(shí)例一個(gè)UDP版本的套接字也是就數(shù)據(jù)包套接字。Socket對(duì)象對(duì)應(yīng)到系統(tǒng)中的一個(gè)特殊文件(socket文件),socket文件不是數(shù)據(jù)存儲(chǔ)區(qū)域的一部分,而是對(duì)應(yīng)到網(wǎng)卡這個(gè)硬件設(shè)備,為什么對(duì)應(yīng)到網(wǎng)卡呢?因?yàn)榫W(wǎng)卡是一個(gè)硬件,對(duì)于代碼而已,不好直接操作,因此將它抽象成了一個(gè)文件進(jìn)行間接操作。
DatagramSocket類構(gòu)造方法:
方法簽名 | 方法說明 |
---|---|
DatagramSocket() | 創(chuàng)建一個(gè)UDP數(shù)據(jù)報(bào)套接字的Socket,綁定到本機(jī)任意一個(gè)隨機(jī)端口 (一般用于客戶端) |
DatagramSocket(int port) |
創(chuàng)建一個(gè)UDP數(shù)據(jù)報(bào)套接字的Socket,綁定到本機(jī)指定的端口(一般用 于服務(wù)端) |
我們一般使用第二個(gè)方法,我們知道端口號(hào)是在一個(gè)計(jì)算機(jī)中尋找一個(gè)程序的“門牌號(hào)”。實(shí)際操作時(shí),如果端口號(hào)是系統(tǒng)隨機(jī)綁定的話,我們就不知道我們服務(wù)端服務(wù)于哪一個(gè)客戶端了,就沒有辦法找到客戶端的家了。
DatagramSocket類方法:
方法簽名 | 方法說明 |
---|---|
void receive(DatagramPacket p) |
從此套接字接收數(shù)據(jù)報(bào)(如果沒有接收到數(shù)據(jù)報(bào),該方法會(huì)阻 塞等待) |
void send(DatagramPacket p) |
從此套接字發(fā)送數(shù)據(jù)報(bào)包(不會(huì)阻塞等待,直接發(fā)送) |
void close() | 關(guān)閉此數(shù)據(jù)報(bào)套接字 |
第一個(gè)receive方法它的參數(shù)需要準(zhǔn)備一個(gè)空的DatagramPacket對(duì)象(對(duì)象需要給予存儲(chǔ)空間),它把DatagramPacket實(shí)例對(duì)象再裝入到準(zhǔn)備好的空的DatagramPacket對(duì)象中去 ,為什么這樣做呢?就像我們平常接收東西時(shí),我們需要一個(gè)載體來承接這個(gè)物件,舉個(gè)例子吧,當(dāng)我們盛飯時(shí),我們不能夠直接用手去捧著飯,而是需要一個(gè)空飯盒去接打的飯。
第二個(gè)send方法他是DatagramPacket實(shí)例對(duì)象載入到接收緩沖區(qū)。
第三個(gè)close方法,因?yàn)镈atagramSocket類屬于文件資源,當(dāng)我們不用的時(shí)候,我們需要將它給手動(dòng)關(guān)閉了。
2.2.2DatagramPacket類?
DatagramPacket是UDP Socket發(fā)送和接收的數(shù)據(jù)報(bào)。?
DatagramPacket類構(gòu)造方法
方法簽名 | 方法說明 |
---|---|
DatagramPacket(byte[] buf, int length) |
構(gòu)造一個(gè)DatagramPacket以用來接收數(shù)據(jù)報(bào),接收的數(shù)據(jù)保存在 字節(jié)數(shù)組(第一個(gè)參數(shù)buf)中,接收指定長(zhǎng)度(第二個(gè)參數(shù) length) |
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) |
構(gòu)造一個(gè)DatagramPacket以用來發(fā)送數(shù)據(jù)報(bào),發(fā)送的數(shù)據(jù)為字節(jié) 數(shù)組(第一個(gè)參數(shù)buf)中,從0到指定長(zhǎng)度(第二個(gè)參數(shù) length)。address指定目的主機(jī)的IP和端口號(hào) |
這兩個(gè)構(gòu)造方法都會(huì)使用到,第一個(gè)構(gòu)造方法:他就是我們上方說的空的DatagramPacket對(duì)象也就是我們舉例說的“空飯盒”,第二個(gè)構(gòu)造方法是在我們將接收到的請(qǐng)求進(jìn)行封裝時(shí)使用的,假設(shè)我們接收到的請(qǐng)求是字符串,我們就要將它轉(zhuǎn)化成byte數(shù)組,再算出長(zhǎng)度,并且需要指定它要發(fā)送到那里去也就是SockerAddress類的一個(gè)子類InetSocketAddress類它里面包含目的ip和目的端口號(hào)。
DatagramPacket類方法:
方法簽名 | 方法說明 |
---|---|
InetAddress getAddress() |
從接收的數(shù)據(jù)報(bào)中,獲取發(fā)送端主機(jī)IP地址;或從發(fā)送的數(shù)據(jù)報(bào)中,獲取 接收端主機(jī)IP地址 |
int getPort() | 從接收的數(shù)據(jù)報(bào)中,獲取發(fā)送端主機(jī)的端口號(hào);或從發(fā)送的數(shù)據(jù)報(bào)中,獲 取接收端主機(jī)端口號(hào) |
byte[] getData() | 獲取數(shù)據(jù)報(bào)中的數(shù)據(jù) |
2.2.3基于UDP的回顯程序
UDP服務(wù)端:
服務(wù)器設(shè)計(jì)邏輯:?
- ?創(chuàng)建DatagramSocket對(duì)象,指定服務(wù)器端口號(hào)。
- 服務(wù)器啟動(dòng)了,我們需要一個(gè)DatagramPacket實(shí)例運(yùn)來當(dāng)作載體,當(dāng)沒有請(qǐng)求時(shí),在這里就會(huì)發(fā)生阻塞,有請(qǐng)求時(shí),就寫入到DatagramPacket實(shí)例好的載體中。
- 接收到數(shù)據(jù)后,我們將數(shù)據(jù)拆分成我們需要的格式,因?yàn)槲覀兪且粋€(gè)回顯,故我們只需將它改成字符串就行了,再調(diào)用process方法,在這里完成業(yè)務(wù)需求,我們這里只需返回字符串就像了。
- 將處理好的請(qǐng)求,再封裝一下,封裝成DatagramPacket實(shí)例對(duì)象,將這個(gè)響應(yīng)發(fā)生使用send方法發(fā)送給客戶端即可。
package UdpNetWork;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
//服務(wù)器端
public class UdpEchoServer {
private DatagramSocket socket=null;
public UdpEchoServer(int port) throws SocketException {
this.socket=new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服務(wù)器啟動(dòng)");
while (true) {
//requestPacket是一個(gè)載體,receive方法會(huì)將發(fā)送過來的數(shù)據(jù)放入到這個(gè)載體中
DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
//將發(fā)來的數(shù)據(jù)拆解
String request=new String(requestPacket.getData(),0, requestPacket.getLength());
//做出響應(yīng)
String response=process(request);
//將做出的響應(yīng)包裝起來,發(fā)給客戶端
DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());
socket.send(responsePacket);
//打印日志
System.out.printf("[%s:%d] req:%s,resp:%s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),
request,response);
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer udpEchoServer=new UdpEchoServer(9090);
udpEchoServer.start();
}
}
?UDP客戶端:
?客戶端設(shè)計(jì)邏輯:
- ?客戶端需要知道服務(wù)端的位置,因此我們需要服務(wù)端的ip地址和端口號(hào),故成員變量包含Ipserver、serverPort以及一個(gè)DatagramSocket對(duì)象(可以理解為整合ip和端口號(hào))。
- 開始運(yùn)行客戶端,通過Scaner接收客戶的請(qǐng)求。
- 拿到請(qǐng)求后,我們需要將它封裝成DatagramPacket對(duì)象,發(fā)送給服務(wù)端。
- 發(fā)送完就等待服務(wù)端返回響應(yīng)。
- 等到拿到響應(yīng)之后,我們就可以對(duì)響應(yīng)做出操作了,因?yàn)槲覀冞@里是回顯,我們只需將返回過來的字符串打印出來即可。
package UdpNetWork;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
//客戶端
public class UdpEchoClient {
private DatagramSocket socket=null;
private int serverPort;
private String IpServer;
public UdpEchoClient(int port, String ipServer) throws SocketException {
socket=new DatagramSocket();
this.serverPort=port;
this.IpServer=ipServer;
}
public void start() throws IOException {
Scanner sc=new Scanner(System.in);
while (true) {
System.out.print("輸入字符->");
String request=sc.next();
//將輸入的指令打包
DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(IpServer),serverPort);
//發(fā)送給服務(wù)器端
socket.send(requestPacket);
DatagramPacket responsePacket=new DatagramPacket(new byte[4096], 4096);
//接收服務(wù)器端的響應(yīng)結(jié)果
socket.receive(responsePacket);
//打印服務(wù)端的響應(yīng)
String response=new String(responsePacket.getData(),0,responsePacket.getLength());
//打印日志
System.out.printf("req:%s,resp:%s\n",request,response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient=new UdpEchoClient(9090,"127.0.0.1");
udpEchoClient.start();
}
}
2.2.4基于UDP的單詞查詢?
對(duì)于這個(gè)服務(wù)端,我們不需要再寫一邊start方法了,我們可以直接讓它繼承udpEchoServer類,并且,我們?cè)谏戏椒?wù)端,我們將對(duì)請(qǐng)求處理的部分拿出來當(dāng)作了一個(gè)單獨(dú)的函數(shù),我們?cè)趯⑸戏降暮瘮?shù)進(jìn)行重寫就行了,還有就是,我們需要一個(gè)存儲(chǔ)單詞,并且它還方便查詢的東西,我們就想到一個(gè)數(shù)據(jù)結(jié)構(gòu)就是Map,剛好可以適合這個(gè)系統(tǒng)。
package UdpNetWork;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
public class UdpDictServer extends UdpEchoServer{
//字典表
private Map<String, String> dict = new HashMap<>();
public UdpDictServer(int port) throws SocketException {
super(port);
dict.put("dog", "小狗");
dict.put("cat", "小貓");
dict.put("fuck", "臥槽");
// ........... 可以無限的添加很多很多數(shù)據(jù). 有道詞典和咱們相比, 就是人家的這個(gè)表更大!!
}
@Override
public String process(String request) {
return dict.getOrDefault(request, "該單詞沒有查到!");
}
public static void main(String[] args) throws IOException {
UdpDictServer udpDictServer = new UdpDictServer(9090);
udpDictServer.start();
}
}
3.TCP套接字編程
3.1TCP套接字的特點(diǎn)
特點(diǎn)如下:
- 有鏈接:雙方通信,都需要可以留意對(duì)方信息。
- 可靠傳輸:盡可能的將數(shù)據(jù)傳輸給對(duì)方,但也不能保證100%。
- 面向字節(jié)流:以一個(gè)字節(jié)傳輸為基本單位。
- 全雙工:雙方都可以向?qū)Ψ桨l(fā)送信息。
3.2TCP中的長(zhǎng)短連接
TCP發(fā)送數(shù)據(jù)時(shí),需要先建立連接,什么時(shí)候關(guān)閉連接就決定是短連接還是長(zhǎng)連接:
短連接:每次接收到數(shù)據(jù)并返回響應(yīng)后,都關(guān)閉連接,即是短連接。也就是說,短連接只能一次收發(fā)數(shù)據(jù)。
長(zhǎng)連接:不關(guān)閉連接,一直保持連接狀態(tài),雙方不停的收發(fā)數(shù)據(jù),即是長(zhǎng)連接。也就是說,長(zhǎng)連接可以多次收發(fā)數(shù)據(jù)。
長(zhǎng)連接與短鏈接的區(qū)別:
- 建立連接、關(guān)閉連接的耗時(shí):短連接每次請(qǐng)求、響應(yīng)都需要建立連接,關(guān)閉連接;而長(zhǎng)連接只需要第一次建立連接,之后的請(qǐng)求、響應(yīng)都可以直接傳輸。相對(duì)來說建立連接,關(guān)閉連接也是要耗時(shí)的,長(zhǎng)連接效率更高。
- 主動(dòng)發(fā)送請(qǐng)求不同:短連接一般是客戶端主動(dòng)向服務(wù)端發(fā)送請(qǐng)求;而長(zhǎng)連接可以是客戶端主動(dòng)發(fā)送請(qǐng)求,也可以是服務(wù)端主動(dòng)發(fā)。
- 兩者的使用場(chǎng)景有不同:短連接適用于客戶端請(qǐng)求頻率不高的場(chǎng)景,如瀏覽網(wǎng)頁等。長(zhǎng)連接適用于客戶端與服務(wù)端通信頻繁的場(chǎng)景,如聊天室,實(shí)時(shí)游戲等。
3.3TCP套接字的API
TCP套接字的API中主要包含兩個(gè)類:1.ServerSocket 2.Socket
下面介紹這兩個(gè)類
3.3.1ServerSocket類
這個(gè)類是在看名字就很好理解,它主要用于服務(wù)端,它的作用是創(chuàng)建一個(gè)服務(wù)端套接字。
?ServerSocket類構(gòu)造方法:
方法簽名 | 方法說明 |
---|---|
ServerSocket(int port) | 創(chuàng)建一個(gè)服務(wù)端流套接字Socket,并綁定到指定端口 |
在實(shí)例對(duì)象時(shí),我們就將端口號(hào)傳進(jìn)來,完成實(shí)例。
?ServerSocket類方法:
方法簽 名 |
方法說明 |
---|---|
Socket accept() |
開始監(jiān)聽指定端口(創(chuàng)建時(shí)綁定的端口),有客戶端連接后,返回一個(gè)服務(wù)端Socket 對(duì)象,并基于該Socket建立與客戶端的連接,否則阻塞等待 |
void close() |
關(guān)閉此套接字 |
?第一個(gè)accept方法它是接收客戶端的信息,如果客戶端沒有請(qǐng)求,那它就會(huì)阻塞等待,如果有請(qǐng)求,就會(huì)建立連接,它的返回值是一個(gè)套接字。
3.3.2Socket類
?這個(gè)類就是創(chuàng)建一個(gè)套接字實(shí)例對(duì)象,他的成員變量有兩個(gè):IP與端口號(hào)。
Socket類構(gòu)造方法:
方法簽名 | 方法說明 |
Socket(String host, int port) |
創(chuàng)建一個(gè)客戶端流套接字Socket,并與對(duì)應(yīng)IP的主機(jī)上,對(duì)應(yīng)端口的 進(jìn)程建立連接 |
Socket類方法:
方法簽名 | 方法說明 |
InetAddress getInetAddress() | 返回套接字所連接的地址 |
InputStream getInputStream() | 返回此套接字的輸入流 |
OutputStream getOutputStream() | 返回此套接字的輸出流 |
?3.3.3基于TCP的回顯程序
服務(wù)端?
?服務(wù)器設(shè)計(jì)邏輯:
- 創(chuàng)建一個(gè)ServerSocket類對(duì)象,指定服務(wù)器端口號(hào)。
- 開始運(yùn)行服務(wù)端;通過第一步實(shí)例好的對(duì)象調(diào)用accept方法,如果有無請(qǐng)求,就阻塞,如果有請(qǐng)求,就發(fā)到下面進(jìn)行處理。
- 進(jìn)入處理請(qǐng)求方法,將請(qǐng)求通過讀接收緩沖區(qū)寫入,再將寫入的請(qǐng)求發(fā)給處理程序進(jìn)行處理。
- 處理完畢后,將響應(yīng)發(fā)給客服端。
package TcpNetWork;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoServer {
private ServerSocket serverSocket=null;
public TcpEchoServer(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服務(wù)器啟動(dòng)");
while (true) {
//接收到客服端請(qǐng)求
Socket clientSocket=serverSocket.accept();
//將請(qǐng)求發(fā)給解決請(qǐng)求的方法
processConnection(clientSocket);
}
}
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d] 客戶端上線了\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
try (InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream=clientSocket.getOutputStream()){
// 沒有這個(gè) scanner 和 printWriter, 完全可以!! 但是代價(jià)就是得一個(gè)字節(jié)一個(gè)字節(jié)扣, 找到哪個(gè)是請(qǐng)求的結(jié)束標(biāo)記 \n
// 不是不能做, 而是代碼比較麻煩.
// 為了簡(jiǎn)單, 把字節(jié)流包裝好了更方便的字符流~~
Scanner sc=new Scanner(inputStream);
PrintWriter printWriter=new PrintWriter(outputStream);
while (true) {
//1.接到客服端請(qǐng)求
if (!sc.hasNext()) {
System.out.printf("[%s:%d] 客戶端下線了\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
break;
}
String request=sc.next();
//2.對(duì)請(qǐng)求做出響應(yīng)
String response=process(request);
//3.把響應(yīng)返回給客戶端
printWriter.println(response);
printWriter.flush();
System.out.printf("[%s:%d] request:%s response:%s ",clientSocket.getInetAddress().toString(),
clientSocket.getPort(),request,response);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
clientSocket.close();
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer=new TcpEchoServer(9999);
tcpEchoServer.start();
}
}
客戶端
客戶端設(shè)計(jì)邏輯:
- 創(chuàng)建一個(gè)Socket實(shí)例,指定服務(wù)器的ip和端口號(hào)。
- 開啟客戶端,輸入請(qǐng)求,并刷新接收緩存區(qū)。
- 等到服務(wù)端給出響應(yīng),接收響應(yīng),對(duì)響應(yīng)做出動(dòng)作。
package TcpNetWork;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
private Socket socket=null;
public TcpEchoClient(int port, String serverIp) throws IOException {
socket=new Socket(serverIp,port);
}
public void start() {
Scanner sc=new Scanner(System.in);
try(InputStream inputStream=socket.getInputStream();
OutputStream outputStream=socket.getOutputStream()) {
Scanner scannerFromSocket=new Scanner(inputStream);
PrintWriter printWriter=new PrintWriter(outputStream);
while (true) {
//1.從鍵盤上讀取請(qǐng)求
System.out.print("輸入指令->");
String request=sc.next();
//2.將請(qǐng)求發(fā)送給服務(wù)器端,并刷新接收緩存區(qū)
printWriter.println(request);
printWriter.flush();
//3.接收服務(wù)器端的信息
String response=scannerFromSocket.next();
//4.把信息打印到控制臺(tái)
System.out.printf("request:%s response:%s\n",request,response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient=new TcpEchoClient(9999,"127.0.0.1");
tcpEchoClient.start();
}
}
3.3.4對(duì)TCP回顯程序的優(yōu)化
在上方的程序中,我們只能對(duì)一個(gè)客戶端進(jìn)行提供服務(wù),這是因?yàn)槲覀冊(cè)诮邮盏牟糠纸o寫成了死循環(huán),如果出現(xiàn)第二個(gè)客戶端時(shí),第二個(gè)客戶端只能進(jìn)行等待。我們?nèi)绾涡薷倪@個(gè)程序呢?這是我們就不得不提到多線程了,如果寫成多線程的話,就可以處理多個(gè)程序了。
程序代碼如下(服務(wù)端):?
package TcpNetWork;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoServer {
private ServerSocket serverSocket=null;
public TcpEchoServer(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服務(wù)器啟動(dòng)");
while (true) {
//接收到客服端請(qǐng)求
Socket clientSocket=serverSocket.accept();
//將請(qǐng)求發(fā)給解決請(qǐng)求的方法
Thread t=new Thread(()->{
try {
processConnection(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d] 客戶端上線了\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
try (InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream=clientSocket.getOutputStream()){
// 沒有這個(gè) scanner 和 printWriter, 完全可以!! 但是代價(jià)就是得一個(gè)字節(jié)一個(gè)字節(jié)扣, 找到哪個(gè)是請(qǐng)求的結(jié)束標(biāo)記 \n
// 不是不能做, 而是代碼比較麻煩.
// 為了簡(jiǎn)單, 把字節(jié)流包裝好了更方便的字符流~~
Scanner sc=new Scanner(inputStream);
PrintWriter printWriter=new PrintWriter(outputStream);
while (true) {
//1.接到客服端請(qǐng)求
if (!sc.hasNext()) {
System.out.printf("[%s:%d] 客戶端下線了\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
break;
}
String request=sc.next();
//2.對(duì)請(qǐng)求做出響應(yīng)
String response=process(request);
//3.把響應(yīng)返回給客戶端
printWriter.println(response);
printWriter.flush();
System.out.printf("[%s:%d] request:%s response:%s ",clientSocket.getInetAddress().toString(),
clientSocket.getPort(),request,response);
System.out.println();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
clientSocket.close();
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer=new TcpEchoServer(9999);
tcpEchoServer.start();
}
}
如果我們想要去驗(yàn)證這個(gè)程序是否可以對(duì)多個(gè)客戶端提供服務(wù),我們還需要對(duì)其修改一點(diǎn)配置,不然我們不能將同一個(gè)類運(yùn)行兩次。
?1.右鍵鼠標(biāo),選擇修改運(yùn)行配置?
?2.點(diǎn)開之后,點(diǎn)擊修改選項(xiàng)?
?3.將允許多個(gè)實(shí)例打勾,這一我們就可以同時(shí)運(yùn)行多個(gè)實(shí)例了。
寫在最后:
??????以上就是本文全部?jī)?nèi)容,如果對(duì)你有所幫助,希望能留下你的點(diǎn)贊+關(guān)注,我會(huì)更加努力的更新內(nèi)容!非常感謝??????
若本篇文章有錯(cuò)誤的地方,歡迎大佬們指正!文章來源:http://www.zghlxwxcb.cn/news/detail-419416.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-419416.html
到了這里,關(guān)于【JavaEE】網(wǎng)絡(luò)編程之TCP套接字、UDP套接字的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!