計(jì)算機(jī)網(wǎng)絡(luò)基礎(chǔ)
利用通信線路和通信設(shè)備,將地理位置不同的、功能獨(dú)立的多臺(tái)計(jì)算機(jī)互連起來(lái),以功能完善的網(wǎng)絡(luò)軟件來(lái)實(shí)現(xiàn)資源共享和信息傳遞,就構(gòu)成了計(jì)算機(jī)網(wǎng)絡(luò)系統(tǒng)
IP地址和端口
通過(guò)IP地址,區(qū)分不同的計(jì)算機(jī)
每一臺(tái)電腦在一個(gè)網(wǎng)絡(luò)上擁有一個(gè)獨(dú)屬于自己的IP地址,用于區(qū)別其他電腦,可以通過(guò)對(duì)方的IP地址進(jìn)行訪問(wèn);所以當(dāng)我們的手機(jī)開(kāi)著流量的時(shí)候,是無(wú)法訪問(wèn)連著WIFI的電腦的
電腦上運(yùn)行的大量程序可能需要通過(guò)網(wǎng)絡(luò)來(lái)訪問(wèn)其他計(jì)算機(jī),這時(shí)通過(guò)端口號(hào)來(lái)進(jìn)行區(qū)分
因此一般看到的是地址都是IP:port
形式訪問(wèn)目標(biāo)主機(jī)的一個(gè)應(yīng)用程序,端口號(hào)的值為[0, 65535]
IP地址分為IPv4和IPv6,IPv4類似于192.168.0.11
,上面提到的例子都是使用的IPv4,它一共有四組數(shù)字,每組數(shù)字占8個(gè)bit位,IPv4地址0.0.0.0
表示為2進(jìn)制就是:00000000.00000000.00000000.00000000,共32個(gè)bit,最大為255.255.255.255
IPv6能夠保存128個(gè)bit位,因此它也可以表示更多的IP地址,一個(gè)IPv6地址看起來(lái)像這樣:1030::C9B4:FF12:48AA:1A2B
,目前也正在向IPv6的階段過(guò)渡
TCP和 UDP
- TCP:當(dāng)一臺(tái)計(jì)算機(jī)想要與另一臺(tái)計(jì)算機(jī)通訊時(shí),兩臺(tái)計(jì)算機(jī)之間的通信需要暢通且可靠(會(huì)進(jìn)行三次握手,斷開(kāi)也會(huì)進(jìn)行四次揮手),這樣才能保證正確收發(fā)數(shù)據(jù),因此TCP更適合一些可靠的數(shù)據(jù)傳輸場(chǎng)景
- UDP:它是一種無(wú)連接協(xié)議,數(shù)據(jù)不會(huì)建立可靠傳輸,傳輸過(guò)程中有可能會(huì)導(dǎo)致部分?jǐn)?shù)據(jù)丟失,但是它比TCP傳輸更加簡(jiǎn)單高效,適合視頻直播之類的
Socket技術(shù)
通過(guò)Socket技術(shù)(它是計(jì)算機(jī)之間進(jìn)行通信的一種約定或一種方式),可以實(shí)現(xiàn)兩臺(tái)計(jì)算機(jī)之間的通信,Socket也被翻譯為套接字
,是操作系統(tǒng)底層提供的一項(xiàng)通信技術(shù),它支持TCP和UDP。而Java就對(duì)socket底層支持進(jìn)行了一套完整的封裝,因此可以通過(guò)Java來(lái)實(shí)現(xiàn)Socket通信。
要實(shí)現(xiàn)Socket通信,需創(chuàng)建一個(gè)數(shù)據(jù)發(fā)送者和一個(gè)數(shù)據(jù)接收者,也就是客戶端和服務(wù)端,我們需要提前啟動(dòng)服務(wù)端,來(lái)等待客戶端的連接,而客戶端只需要隨時(shí)啟動(dòng)去連接服務(wù)端即可
//服務(wù)端
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(8080)){ //將服務(wù)端創(chuàng)建在端口8080上
System.out.println("正在等待客戶端連接...");
Socket socket = server.accept(); //當(dāng)沒(méi)有客戶端連接時(shí),線程會(huì)阻塞,直到有客戶端連接為止
System.out.println("客戶端已連接,IP地址為:"+socket.getInetAddress().getHostAddress());
}catch (IOException e){
e.printStackTrace();
}
}
用戶端
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 8080)){
System.out.println("已連接到服務(wù)端!");
}catch (IOException e){
System.out.println("服務(wù)端連接失?。?);
e.printStackTrace();
}
}
實(shí)際上他是一個(gè)TCP連接的過(guò)程,一旦連接建立,只要客戶端不主動(dòng)關(guān)閉,整個(gè)程序就不會(huì)停止運(yùn)行;服務(wù)端也不僅僅支持一個(gè)客戶端,在同一網(wǎng)絡(luò)的前提下,可以連接多個(gè)服務(wù)端
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(8080)) { //port,也就是端口號(hào)為8080
System.out.println("等待連接...");
while(true) {
Socket s = server.accept(); //無(wú)限循環(huán)等待客戶端的連接
System.out.println("IP地址為:" + socket.getInetAddress().getHostAddress());
}
}catch(IOException e) {
e.printStackTrace();
}
}
使用Socket進(jìn)行數(shù)據(jù)傳輸
通過(guò)Socket對(duì)象,我們就可以獲取到對(duì)應(yīng)的I/O流進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)傳輸:
服務(wù)端
//Server端
public class server {
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(8080)){ //try-with-resource語(yǔ)句,自動(dòng)關(guān)閉,不需要close
System.out.println("正在等待客戶端連接:");
Socket socket = server.accept();
socket.setSoTimeout(5000); // timeout
System.out.println("客戶端已連接,IP地址為:" + socket.getInetAddress().getHostAddress());
System.out.println("讀取客戶端數(shù)據(jù):");
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("內(nèi)容為:" + reader.readLine());
OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("收到\n");
writer.flush();
socket.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
用戶端
public class Main {
public static void main(String[] args) {
try(Socket s = new Socket("localhost", 8080);
Scanner sc = new Scanner(System.in)){
System.out.println("已連接到服務(wù)區(qū)");
OutputStreamWriter writer = new OutputStreamWriter(s.getOutputStream()); //讀取發(fā)送內(nèi)容
writer.write(sc.nextLine() + "\n"); //加換行符是讀取需要
writer.flush();
s.shutdownOutput();
System.out.println("數(shù)據(jù)已發(fā)送");
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
System.out.println("收到服務(wù)端響應(yīng):" + reader.readLine());
}catch(IOException e) {
System.out.println("連接失敗");
e.printStackTrace();
}finally {
System.out.println("斷開(kāi)連接");
}
}
}
先啟動(dòng)Server端,再啟動(dòng)用戶端;127.0.0.1是自己電腦
手動(dòng)關(guān)閉單向的流:
socket.shutdownOutput(); //輸出方向關(guān)閉
socket.shutdownInput(); //關(guān)閉輸入方向
可以通過(guò)調(diào)用setSoTimeout()
方法來(lái)設(shè)定IO超時(shí)時(shí)間:
socket.setSoTimeout(3000);
當(dāng)超過(guò)設(shè)定時(shí)間都依然沒(méi)有收到客戶端或是服務(wù)端的數(shù)據(jù)時(shí),會(huì)拋出異常:
java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.test.Main.main(Main.java:41)
也可以不自動(dòng)鏈接
try (Socket socket = new Socket();
Scanner sc = new Scanner(System.in)) {
socket.connect(new InetSocketAddress("localhost", 8080), 1000);
}
可能會(huì)出現(xiàn)連接雙方出現(xiàn)意外卻通知不到對(duì)方的情況,這個(gè)時(shí)候其中一方還持有聯(lián)系,就會(huì)占用資源,可以使用setKeepAlive()
的方法來(lái)防止:
socket.setKeepAlive(true);
當(dāng)客戶端連接之后,如果此函數(shù)設(shè)置值為true,當(dāng)對(duì)方?jīng)]有發(fā)送任何數(shù)據(jù)過(guò)來(lái)的時(shí)候,超過(guò)一個(gè)時(shí)間(要看系統(tǒng)內(nèi)核參數(shù)配置),這邊就會(huì)發(fā)送一個(gè)ack探測(cè)包,探測(cè)TCP/IP連接是否有效
此緩沖區(qū)大小為:8192,我們可以手動(dòng)調(diào)整其大小來(lái)優(yōu)化傳輸效率:
socket.setReceiveBufferSize(25565); //TCP接收緩沖區(qū)
socket.setSendBufferSize(25565); //TCP發(fā)送緩沖區(qū)
使用Socket傳輸文件
實(shí)現(xiàn)文件傳輸
用戶端
public class Main {
public static void main(String[] args) {
try(Socket s = new Socket("localhost", 8080)){
FileInputStream fis = new FileInputStream("test.txt"); //輸入文件
OutputStream stream = s.getOutputStream(); //輸出流
byte[] bytes = new byte[1024];
int len;
while((len = fis.read(bytes)) != -1) {
stream.write(bytes, 0, len);
}
fis.close();
stream.flush();
stream.close();
System.out.println("文件已傳輸");
}catch(IOException e) {
System.out.println("連接失敗");
e.printStackTrace();
}
}
}
服務(wù)端
public class server {
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(8080)){
Socket socket = server.accept();
InputStream stream = socket.getInputStream();
FileOutputStream file = new FileOutputStream("toserver.txt");
byte[] buffer = new byte[1024];
int len;
while((len = stream.read(buffer)) != -1){
file.write(buffer, 0, len);
}
file.flush();
file.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
使用瀏覽器訪問(wèn)Socket服務(wù)器
public class server {
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(8080)){
System.out.println("正在等待客戶端連接:");
Socket socket = server.accept();
System.out.println("IP地址為:" + socket.getInetAddress().getHostAddress());
InputStream in = socket.getInputStream();
while(true) {
int read = in.read();
if(read == -1) {
break;
}
System.out.print((char) read);
}
}catch(IOException e){
e.printStackTrace();
}
}
}
啟動(dòng)Server端,用瀏覽器輸入http://localhost:8080或是http://127.0.0.1:8080/訪問(wèn);會(huì)發(fā)現(xiàn)瀏覽器無(wú)法訪問(wèn),但是IDEA給了一堆反饋
我們的服務(wù)端能夠讀取HTTP請(qǐng)求。但是Http協(xié)議并不會(huì)保持長(zhǎng)連接,在得到我們響應(yīng)的數(shù)據(jù)后會(huì)立即關(guān)閉TCP連接。
既然使用的是Http連接,如果我們的服務(wù)器要支持響應(yīng)HTTP請(qǐng)求,那么就需要按照HTTP協(xié)議的規(guī)則,返回一個(gè)規(guī)范的響應(yīng)文本,首先是響應(yīng)頭,它至少要包含一個(gè)響應(yīng)碼:
HTTP/1.1 200 Accpeted
然后就是響應(yīng)內(nèi)容(注意一定要換行再寫),我們嘗試來(lái)編寫一下支持HTTP協(xié)議的響應(yīng)內(nèi)容:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-724910.html
public static void main(String[] args) {
try(ServerSocket server = new ServerSocket(8080)){
System.out.println("正在等待客戶端連接...");
Socket socket = server.accept();
System.out.println("客戶端已連接,IP地址為:"+socket.getInetAddress().getHostAddress());
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("接收到客戶端數(shù)據(jù):");
while (reader.ready()) System.out.println(reader.readLine());
OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("HTTP/1.1 200 Accepted\r\n"); //200是響應(yīng)碼,Http協(xié)議規(guī)定200為接受請(qǐng)求,400為錯(cuò)誤的請(qǐng)求,404為找不到此資源(不止這些,還有很多)
writer.write("\r\n"); //在請(qǐng)求頭寫完之后還要進(jìn)行一次換行,然后寫入響應(yīng)實(shí)體(會(huì)在瀏覽器上展示的內(nèi)容)
writer.write("You're on your own Kids!");
writer.flush();
}catch (Exception e){
e.printStackTrace();
}
}
我們可以打開(kāi)瀏覽器的開(kāi)發(fā)者模式,我們來(lái)觀察一下瀏覽器的實(shí)際請(qǐng)求過(guò)程。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-724910.html
到了這里,關(guān)于使用Socket技術(shù)進(jìn)行數(shù)據(jù)傳輸、傳輸文件;瀏覽器訪問(wèn)Socket服務(wù)器的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!