网络编程(3)-----------Javaweb
咱們既然要寫一個TCP版本的客戶端服務器,我們要建立連接TCP三次握手(建立連接):
1)客戶端:
Socket對象
我們直接通過創建Socket對象,把服務器的IP地址和端口號傳入進去,這樣就模擬了三次握手
2)服務器:
ServerSocket(房屋中介)
Socket(小姐姐)
我們在服務器直接調用ServerSocket的accept方法建立連接,沒有方法建立連接,那么就會阻塞
? ? ? ? ?
2)下面我們來寫一下 TCP網絡編程(下面是我們需要注意的一些點)
服務器流程:
1)第一個類:ServerSocket,創建的是實例是listensocket,需要給listenSocket關聯上一個端口號,他的構造方法是ServerSocket(int port);
2)他的其中的accept這樣的方法和TCP有連接有著很大的關系,accept就相當于接電話這個操作,監聽指定端口,相當于在外場拉客(買房子在場外拉客,來和我們的業務人員溝通一下買房唄),把一個內核建立好的鏈接交給Socket代碼來處理
listenSocket是ServerSocket創建的實例,它主要的作用是:
1)調用accept方法,是用來與另一臺主機進行連接,確定是有連接的,如果主機一直無法建立連接,就會出現阻塞
2)把主機傳輸過來的數據交給我們的climentSocket進行處理,場外拉客的人直接把客戶交給業務人員
也就是說客戶端嘗試建立連接,首先是服務器的操作系統這一層和客戶端進行一些相關的流程,先把這個連接建立好,用戶代碼調用accept才真正把這個鏈接放到用戶代碼中
3)第二個類 Socket,在服務器收到客戶端建立連接后,返回的服務器端Socket,它主要是在內場給客戶端提供具體的服務,把一臺主機傳輸過來的數據進行解析,并進行返回
4)再使用climentsocket的getInputStream和getoutputStream對象得到一個字節流對象,這時就可以進行讀取和寫入了;(此時的讀操作可以調用inputstream.read()里面傳入一個字節數組,然后再轉化成String,但是比較麻煩),我們使用inputStream.read()的時候,就相當于是讀取客戶端發送過來的數據
5)我們可以直接通過climentSocket.getInputStream()來進行獲取到流對象,但是具體讀的時候Scanner 來進行具體的讀,new Scanner(InputStreaam),這事就巧妙地讀到了客戶端的請求,在調用scan.next();寫的時候,直接利用Printstream new Printstream()構造方法里面直接寫Outputstream,當調用println方法的時候,就默認寫回到客戶端了,當我們使用outputStream.write()方法的時候,就相當于向我們的客戶端返回了數據
一:比如說我以后想去買房,剛出門迎面就走來了一個西裝革履的小哥哥,這個小哥哥就帶著我來到了樓盤售樓部,這里面是人山人海,這個小哥哥進去之后就打了聲招呼,喊出了一個年輕的小姐姐
二:這個小哥哥就說:這個小姐姐是咱們這個樓盤里面的置業顧問,由它來給你介紹這個該樓盤的銷售情況,然后這個小哥哥就走了,他會繼續回到這個馬路上面,回到馬路牙子上,繼續去拉其他的人
三:然后我就和這個小姐姐不斷地進行交流,由此可見小哥哥就是listenSocke(只有一個人),小姐姐(多個)就是climentSocket,買房的人就是一個客戶端(數據),對于咱們的每一個想要買房的人,都需要自動分配一個climentSocket(小姐姐)
所以這里面就分成了兩步,一個是專門負責數據連接,一個是專門負責數據通信
客戶端過程:全程只需要使用Socket對象
1)創建一個socket對象,創建的時候同時指定服務器的IP和端口號,然后把它們傳入到socket中,這個過程就會讓客戶端和服務器建立連接,這就是三次握手,這個過程就是內核完成的;
2)這時客戶端就可以通過Socket對象中的getInputStream和getOutputstream,來得到一個字節流對象,這時就可以與服務器進行通信了;
3)在讀的時候,要注意此時只寫一個Socket文件,直接把服務器的端口號和IP地址,傳入進去進行構造,就自動地和客戶端進行了連接;此時還要有InputStreamSocket和OutstreamSocket;此時要把屏幕中讀到的字符串想辦法和Socket關聯起來,按我的理解來說,發送數據直接依靠println,讀取數據直接依靠scan.next即可 先啟動服務器,再啟動客戶端
——————————————————————————————————————
1)對于accept的返回值對應Socket這個文件,是有一個close方法的,如果打開一個文件后,是要記得去關閉文件的,如果不關閉,就可能會造成文件資源泄漏的情況;一個socket文件寫完之后本應是要關閉的,但是咱們前面寫的UDP程序是不可以關閉的,當時的socket是有生命周期的,都是要跟隨整個程序的,當客戶端不在工作時,進程都沒了,PCB也沒了,文件描述符就更沒有了
2)咱們的TCP服務器有一個listenSocket,但是會有多個clientsocket,可以說每一個客戶端,每一個請求都對應一個climentSocket;如果這個客戶端斷開鏈接了,對應的clientSocket也就需要銷毀了
下面是客戶端的代碼:
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner;class Request{int serverport=0;String serverIp=null;Socket socket=null;public Request(String serverIp,int serverport)throws IOException {this.serverIp=serverIp;this.serverport=serverport;this.socket=new Socket(serverIp,serverport); //讓Socket創建的同時,就與服務器建立了鏈接,相當于撥電話的操作,這個操作對標于服務器中的climentSocket.accept操作,我們客戶端的IP地址就是本機的IP地址,咱們的端口號是由系統自動進行分配的 //我們在這里面傳入的IP和端口號不是自己進行綁定,而是表示和這個IP端口建立連接}public void start()throws IOException { //1從鍵盤也就是控制臺上讀取請求Scanner scan = new Scanner(System.in); try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {while (true) {System.out.println("請輸入你的請求內容");System.out.println("->");String request = scan.next();if (request.equals("goodbye")) {System.out.println("即將退出客戶端");break;}//2把這個從客戶端讀取的內容,構造請求發給服務器PrintStream printStream=new PrintStream(outputStream);printStream.println(request); //在這里我們懷疑println不是把數據發送到服務器中了,而是放到緩沖區里面了,我們刷新一下緩沖區,強制進行發送printStream.flush();//如果不刷新,服務器無法及時收到數據//3我們從服務器那邊讀取響應,并進行解析Scanner scanreturn=new Scanner(inputStream);String response=scanreturn.next();//System.out.println(1); //4我們把結果顯示在控制臺上面String string=String.format("請求是 %s,回應是 %s",request,response);System.out.println(string);}}catch(IOException e){e.printStackTrace();}}public static void main(String[] args) throws IOException {Request request=new Request("127.0.0.1",9090);request.start();} }下面是服務端的代碼:
我們一般是先啟動服務器,再啟動客戶端;
1)當啟動服務器的時候,此時的服務器就會阻塞到accept這里,此時還沒客戶端建立連接;
2)客戶端在這里再啟動,就會調用Socket的構造方法,在構造方法中就會和服務器建立連接;
3)當客戶端和服務器建立連接后,accept方法就會返回;
4)procession方法中,就會進入循環,嘗試讀取數據,就會阻塞在next方法中,當客戶端真正發請求為止;
5)此時的客戶端也進入到start中的next方法中,也會阻塞在next方法中,等待用戶在鍵盤上輸入一個字符串;
6)當下的狀態是客戶端阻塞在用戶往建盤中輸入數據,服務器阻塞在等待客戶端的請求;接下來,用戶輸入數據的時候,此時客戶端的阻塞就結束了,然后回發送一個數據給服務器(Outputstream),同時服務器就會從等待讀取客戶端請求的狀態中恢復過來,執行后面的procession邏輯;
7)當客戶端發送完請求之后,又會在第二個next方法里面進行阻塞,等待服務器的響應的數據;
8)服務器響應并把數據發送回去后,客戶端才會在第二個next的等待中返回;
咱們的服務器要想拿到客戶端的端口號就要通過climentSocket(Socket創建的實例)
IP地址:climentSocket.getInetAddress().toString();
端口號:climentSocket.getPort();
9)我們要使用printWriter中的println方法而不是write();
package API;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 TCPServer {ServerSocket listenSocket=null;public TCPServer(int serverPort) throws IOException {listenSocket=new ServerSocket(serverPort);//在一開始服務器進行啟動的時候,我們就需要指定一個端口,后續客戶端要根據這個端口來進行訪問,服務器的IP地址默認是主機IP//后續客戶端進行訪問服務器的時候,目的IP就是服務器的IP,不需要我們服務器的開發者來進行綁定了,只要我們確定服務器在一臺電腦上//IP地址就是確定了}public void start() throws IOException {System.out.println("TCP服務器開始進行啟動");while(true){//1.由于咱們的TCP是有連接的,我們不能一上來就進行讀數據,而是我們要先進行接電話操作(房屋中介)//2.咱們的accept操作,本質上就是相當于是接電話,接電話的前提是,必須是有人給你打了電話,如果說此時沒有客戶端和你建立連接//3.這個accept方法就會阻塞,直到有人向他建立了連接//4.因為我們的客戶端的請求的主機可能有多份,所以我們的每一個客戶端主機都會有一個climentSocket來進行處理//UDP的服務器進入主循環之后,就嘗試用receive讀取請求了,這是無連接的 //但是我們的TCP是有連接的,首先需要做的事,先建立起連接,相當于客戶端的代碼是貨品,就要通過一條公路來把這些貨發送過去 //當服務器運行的時候,是否有客戶端來建立連接,不確定,如果客戶端沒有建立連接,accept就會進行阻塞等待 // 如果有連接,accept方法就會返回一個Socket對象,也就是說進一步的客戶端和服務器的交互就交給Socket clientSocket= listenSocket.accept();procession(clientSocket);}}private void procession(Socket clientSocket) throws IOException {System.out.printf("我們這一次客戶端請求的IP地址是%s 端口號是%d",clientSocket.getInetAddress().toString(),clientSocket.getPort());//對于每一次請求,我們都要創建一個procession方法來進行處理,我們接下來就來處理請求和響應//這里面的針對TCP Socket的文件讀寫是和文件讀寫是一模一樣的//我們在里面主要是對socket文件來進行讀和寫//TCP和UDP是全雙工,我們既可以讀Socket文件,也可以寫Socket文件try(InputStream inputStream=clientSocket.getInputStream()){try(OutputStream outputStream= clientSocket.getOutputStream()){ //我們來進行循環處理請求,來進行處理響應,我們的一臺主機是要給服務器發送多次請求的Scanner scanner=new Scanner(inputStream);while(true){if(!scanner.hasNext()){System.out.printf("客戶端斷開連接%s %d",clientSocket.getInetAddress(),clientSocket.getPort());break;}}//我們在這里面使用Scanner是更方便的,如果說我們不使用Scanner就需要進行使用原生的inputStream中的read方法就可以了//只不過我們需要創建一個字節數組,然后使用stringbulider來進行拼接// 1.讀取請求并進行解析String request= scanner.next(); //2.根據請求計算并執行邏輯,我們創建process方法執行String response=process(request); //3.幫我們寫的邏輯返回給客戶端,為了方便起見,我們直接使用PrintWriter來進行對OutputStream來進行包裹一下PrintWriter printWriter=new PrintWriter(outputStream);printWriter.println(response);printWriter.flush();//我們在這里賣你要進行刷新緩沖區,如果沒有這個刷新,那么我們的客戶端時不能第一時間獲取到這個響應結果 //4打印信息 System.out.printf("[客戶端的端口號是%d 客戶端的IP地址是%s],請求數據是%s,響應數據是%s",clientSocket.getPort(),clientSocket.getInetAddress(),request,response);}} catch (IOException e) {e.printStackTrace();}finally {clientSocket.close();listenSocket.close();}}private String process(String request) {return request+"我愛你";}public static void main(String[] args) throws IOException {TCPServer server=new TCPServer(9099);server.start();} }1)在我們寫TCP服務器的時候,我們都針對了這里面的climentSocket(Socket創建的實例)關閉了一下,但是我們對于listenSocket(ServerSocket創建的實例)卻沒有進行關閉
2)同時在UDP的代碼里面我們也沒有針對DatagramSocket對象和DatagramPacket來進行關閉
catch (IOException e) {e.printStackTrace();}finally {clientSocket.close();//listenSocket.close();在這里面是不能進行關閉的}這是為什么呢?
1)我們關閉的目的是為了釋放資源,釋放資源的一個前提是這個資源已經不再進行使用了,對于咱們的UDP程序和ServerSocket來說,這些Socket都是是重要貫穿始終的,只要程序啟動運行我們就要用到;
2)這些實例什么時候不用?啥時候咱們的服務器進行關閉,啥時候不用;
3)咱們的這些資源最遲最遲也就會隨著進程一起進行釋放了,進程才是操作系統分配資源的自小單位
4)但是咱們的climentSocket的生命周期是很短的,針對咱們的每一個客戶端程序,都要進行創建一個climentSocket對象,當我們的對應的客戶端斷開連接之后,咱們的服務器的對應的climentSocket對象也就永遠不會再進行試用了,我們就需要關閉文件釋放資源(咱們的climentSocket對象有很多)
——————————————————————————————————————————
利用UDP來創建一個字典服務器:
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; import java.util.HashMap;public class Server {int port;DatagramSocket socket=null;HashMap<String,String> map=new HashMap<>();public Server(int port)throws SocketException {this.port = port;this.socket =new DatagramSocket(port);map.put("及時雨","宋江");map.put("國民女神","高圓圓");map.put("大聰明","李嘉欣");map.put("王者大神","李佳偉");}public void start()throws IOException{System.out.println("服務器即將啟動");while(true){DatagramPacket requestpacket=new DatagramPacket(new byte[4096],4096);socket.receive(requestpacket);System.out.println(1);String request=new String(requestpacket.getData(),0,requestpacket.getLength());String response=processon(request);DatagramPacket responsepacket=new DatagramPacket(response.getBytes(),response.getBytes().length,requestpacket.getSocketAddress());socket.send(responsepacket);String str=String.format("[%s:%d] request=%s;reponse=%s",requestpacket.getAddress(),requestpacket.getPort(),request,response);System.out.println(str);}}public String processon(String request){return map.getOrDefault(request,"你查詢的詞不存在");// return request;}public static void main(String[] args)throws SocketException,IOException{Server server=new Server(9090);server.start();}} import java.io.IOException; import java.net.*; import java.util.Scanner;public class user{DatagramSocket socket=null;int serverport;String serverIP;public user(int serverport,String serverIP)throws SocketException {this.serverIP=serverIP;this.serverport=serverport;this.socket=new DatagramSocket();}public void start()throws UnknownHostException, IOException{System.out.println("客戶端即將啟動");while(true){System.out.println("請輸入你的請求");Scanner scanner = new Scanner(System.in);System.out.println("->");String request=scanner.nextLine();if(request.equals("exit")){System.out.println("goodbye");return;}DatagramPacket requestpacket=new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIP),serverport);socket.send(requestpacket);DatagramPacket responsepacket=new DatagramPacket(new byte[4096],4096);socket.receive(responsepacket);String response=new String(responsepacket.getData(),0,responsepacket.getLength());String str=String.format("request=%s,response=%s",request,response);System.out.println(str);}}public static void main(String[] args)throws SocketException,IOException{user user1=new user(9090,"127.0.0.1");user1.start();}}下面我們來測試一個程序TCP服務器
1)一個服務器對應多個客戶端,此時就需要多次啟動這個客戶端實例;
現象是當我們啟動第一個客戶端之后,服務器進行提示上線,當我們啟動第二個客戶端的時候,服務器此時就沒有任何響應了,況且發送請求的時候沒有進行任何響應;但是當我們退出客戶端的時候,此時神奇的事情出現了,服務器提示了客戶端二上線,況且客戶端二也得到了服務器的響應,但是此時客戶端三沒有任何反應,當我們把客戶端2的主機退出,那么客戶端3給服務器發送的數據就有效了
2)所以當前的服務器,在同一時刻,只可以給一個客戶端提供服務,只有前一個客戶端下了,下一個客戶端才可以上來;
3)當咱們的第一個客戶端嘗試與服務器建立連接的時候,服務器會與客戶端建立連接,這個時候客戶端發送數據,服務器就會做出響應,客戶端多次發送數據,咱們的服務器就會循環處理請求;
3.1)調用listenSocket的accept方法,與客戶端建立連接
3.2)執行process方法,來循環進行處理客戶端給服務器發送過來的請求
4)但是此時我們的第二個,第三個,第四個客戶端想要給服務器發送數據,不可能成功建立連接
5)原因是在服務器中的hasNext那里在等待第一個客戶端發送數據,他并沒有退出第一個客戶端對應的這個procession這個方法,也就是說直接死在第一個客戶端的procession這個方法的while循環里面(一直進行工作),所以整個服務器的程序就卡死在hasNext這個代碼塊里面了,主函數的外層的while循環無法進行下一輪,也就無法調用第二次accept方法,(服務器無法再次調用accept方法與下一個客戶端進行三次握手,建立連接);
最終造成的結果是,客戶端什么時候退出,整個循環就啥時候結束;
4)所以問題的關鍵在于,如果第一個客戶端沒有退出,此時服務器的邏輯就一直在procession里面打轉,也就沒有機會再次調用accept方法,也就無法再次去處理第二個連接;第一個客戶端退出以后,結束里面的循環,結束上一個procession服務器才可以執行到第二個accept,才可以建立連接;
5)我們這個問題就類似于,好像你接了一個電話,和對方你一言,我一語的進行通話,別人再繼續給我們進行打電話,我們就沒有辦法進行接通了
6)咱們解決上述問題,就需要第一次執行的procession方法,不能影響到咱們的下一次循環掃描accept的執行;
public class Server {HashMap<String,String> map=new HashMap<>();int serverport=0;ServerSocket listenSocket=null;public Server(int serverport) throws IOException {this.serverport=serverport;this.listenSocket=new ServerSocket(serverport);map.put("及時雨","宋江");map.put("國民女神","高圓圓");map.put("老子","李佳偉");}public void start() throws IOException {System.out.println("服務器即將啟動");while(true){Socket climentSocket=listenSocket.accept(); //我們的改進方案是每一次accept方法成功,那么我們就創建一個新的線程,有一個新的線程來執行這次process方法,這樣就實現了代碼之間的解耦合Thread thread=new Thread(){public void run(){String str=String.format("客戶端的IP地址是 %s 客戶端對應的端口號是 %d",climentSocket.getInetAddress().toString(),climentSocket.getPort());System.out.println(str);try {procession(climentSocket);} catch (IOException e) {e.printStackTrace();}}};thread.start();}}public void procession(Socket climentSocket) throws IOException {try(InputStream inputStream=climentSocket.getInputStream();OutputStream outputStream=climentSocket.getOutputStream()){Scanner scanner=new Scanner(inputStream);PrintStream printStream=new PrintStream(outputStream);while(true){if(!scanner.hasNext()){System.out.println("這個客戶端對應的服務器已經完成了工作");return;}String request=scanner.next();String response=hhhh(request);printStream.println(response);String str=String.format("請求是 %s 響應是 %s",request,response);System.out.println(str);}}}public String hhhh(String request){return map.getOrDefault(request,"沒有這個參數");} 這是改進后的服務器代碼引入多線程之后,保證主線程始終在調用accept,每次都有一個新的連接來創建新線程來處理請求響應,線程都是一個獨立的執行流,每一個線程都會執行自己的同一段邏輯,并發執行
1)咱們剛才的UDP版本的程序就沒有用到多線程?因為咱們的UDP編程不需要處理連接,咱們的UDP只需要一個循環,就可以處理所有客戶端的請求
2)DatagramPacket requestpacket=new DatagramPacket(new Byte[4096],4096);
requestSocket.receive(requestPacket);這個start方法進入循環,不管誰,不管哪一個客戶端發送過來了請求,服務器都會進行處理返回,一個循環把所有客戶端都給伺候好了
3)但是咱們的TCP機既要處理連接,又要處理一個連接中的若干次請求,就需要兩個循環,里層循環就會影響外層循環的進度了
4)主線程循環調用accept方法,當我們有客戶端嘗試進行連接的時候,我們直接讓主線程創建一個新線程,由新線程負責并發處理若干個客戶端的請求,在新線程里面,我們通過while循環來進行處理請求,這個時候,多線程就是并發執行的關系了,就是各自執行各自的,彼此之間不會相互干擾
2)但是在實際開發中,客戶端的數目可能有很多,這個時候可以嗎?
雖然線程比進程更輕量,但是如果有很多的客戶端連接又退出,這就會導致咱們當前服務器頻繁的創建銷毀線程,如何改進這個問題?
這時我們就需要用到線程池了
3)當我們的客戶端new Socket()成功的時候,其實本質上從操作系統內核層面,已經建立好了連接(TCP三次握手),但是咱們的應用程序沒有建立這個鏈接
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Scanner; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;public class Server {HashMap<String,String> map=new HashMap<>();int serverport=0;ServerSocket listenSocket=null;public Server(int serverport) throws IOException {this.serverport=serverport;this.listenSocket=new ServerSocket(serverport);map.put("及時雨","宋江");map.put("國民女神","高圓圓");map.put("老子","李佳偉");}public void start() throws IOException {System.out.println("服務器即將啟動");while(true){Socket climentSocket=listenSocket.accept();String str=String.format("客戶端的IP地址是 %s 客戶端對應的端口號是 %d",climentSocket.getInetAddress().toString(),climentSocket.getPort());System.out.println(str);ExecutorService executorService= Executors.newCachedThreadPool();executorService.submit(new Runnable() {@Overridepublic void run() {try {procession(climentSocket);} catch (IOException e) {e.printStackTrace();}}});}}public void procession(Socket climentSocket) throws IOException {try(InputStream inputStream=climentSocket.getInputStream();OutputStream outputStream=climentSocket.getOutputStream()){Scanner scanner=new Scanner(inputStream);PrintStream printStream=new PrintStream(outputStream);while(true){if(!scanner.hasNext()){System.out.println("這個客戶端對應的服務器已經完成了工作");return;}String request=scanner.next();String response=hhhh(request);printStream.println(response);String str=String.format("請求是 %s 響應是 %s",request,response);System.out.println(str);}}}public String hhhh(String request){return map.getOrDefault(request,"沒有這個參數");}public static void main(String[] args) throws IOException {Server server=new Server(8080);server.start();} }假設極端情況下,一個服務器面臨著很多很多客戶端,這些客戶端,連接上了并沒有退出,這個時候服務器這邊,就會存在很多很多線程,會有上萬個線程?這個情況下,會有一些其他的問題嗎?這是科學的解決方法嗎?
1)實際上這種現象是不安全的,不科學,每一個線程都會占據一定的系統資源,如果線程太多太多了,這時候許多系統資源(內存資源+CPU資源)就會十分緊張,達到一定程度,機器可能就會宕機(因為你創建線程就要有PCB,還要為這個線程分配棧和程序計數器的空間)
上萬個線程會搶這一兩個CPU,操作系統的內核就會十分激烈,線程之間的調度開銷是十分激烈的,比如說線程1正在執行,執行一會被操作系統的內核調度出去了,下一次啥時候上CPU執行,就不知道了,因為排的隊,實在是太多太多了,線程之間非常卷,服務器對于客戶端的響應能力,返回數據的時間,就會大大降低了;
2)例如在雙十一/春運這種場景,如果一個系統,同時收到太高的并發,就可能會出現問題,例如每一個并發都需要消耗系統資源,并發多了,系統資源消耗就多了,系統剩下的資源就少了,響應能力就變慢了,再進一步,把系統都給消耗沒了,系統也就無法正常工作了(我們的服務器不可能響應無數個客戶端)
1)使用攜程來進行代替線程,完成并發,很多協程的實現,是一個M:N的關系(一大堆的攜程是通過一個線程來進行完成的),協程比線程還要輕量
2)使用IO多路復用的機制,完成并發;
2.1)這會從根本上來解決服務器高并發的這樣一個問題,在內核里面來支持這樣的功能
2.2)假設現在有1W個客戶端,在這個服務器里面就會用一定的數據結構把1W個客戶端對應的Socket保存好,不需要一個線程對應一個客戶端,一共就有幾個線程,IO多路賦用機制,就可以做到,哪個Socket上面有數據了,就通知到這個應用程序,讓這個線程從這個socket里面來讀數據;雖然是1w個客戶端,但是在同一時刻,也就只有不到1K個客戶端來給服務器發送請求;靠系統來通知應用程序,誰可以讀,就去讀誰,通過一個線程就可以處理多個Socket,在C++方向,典型實現就是epoll(mac/linux),kqueue(windows),我們在Java里面通過NIO這樣的一系列的庫,封裝了epoll等IO多路復用機制
3)使用多個服務器(分布式),就算我們使用協程,就算使用IO多路復用,咱們的系統同一時刻處理的線程數仍然是有限的,只是節省了一個客戶端對應的資源,但是隨著客戶端的增加,還是會消耗更多的資源,我們就使用更多的硬件資源,此時每一臺主機承受的壓力就小了
以上三種都是一個基本處理高并發場景的,所使用的方法
咱們的一個TCP服務器,是否可以讓一個UDP客戶端連接上呢?
1)TCP和UDP,他們無論是API代碼,還是協議底層的工作過程,都是差異巨大的,不是單純的把流轉化成數據包就可以的
2)一次通信,我們使用5元組,協議類型不匹配,通信時無法進行完成的
總結
以上是生活随笔為你收集整理的网络编程(3)-----------Javaweb的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【uniapp 动态设置 起始页 默认展
- 下一篇: 读东野圭吾《白夜行》有感