目录
🚩TCP流套接字编程
俩个关键的类
- ServerSocket (给服务器使用的类,使用这个类来绑定端口号)
- Socket(既会给服务器用,又会给客户端用)
这俩个类都是用来表示socket文件的。(抽象了网卡这样的硬件设备)
🍭ServerSocket API
ServerSocket 是创建TCP服务端Socket的API。 ServerSocket 构造方法:方法签名 | 方法说明 |
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
方法签名 | 方法说明 |
Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待 |
void close() | 关闭此套接字 |
🍭Socket API
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。 不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。 Socket 构造方法:方法签名 | 方法说明 |
Socket(String host,int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程连接。 |
Socket方法:
方法签名 | 方法说明 |
InetAddress.getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
TCP是有连接的,连接就意味着通信双方会记录保存对端的信息,UDP来说,每次发送数据都得手动send方法中指定目标地址(UDP自身没有存储这个消息)TCP来说,则不需要,前提是需要先把连接给建立上。那么,如果建立连接呢?不需要代码干预,是系统内核自动负责完成的,对应app来说,客户端这边,主要是要发起”建立连接"动作,服务器这边,主要要把建立好的连接从内核中拿到app里。
我们看到大型商场里,都有很有海底捞,每天海底捞的人是非常多的,你要想去吃海底捞,就要排号。我们给海底捞店当作应用程序。而内核就是一个队列等待着去吃海底捞,每当有客人吃完,走了,空出了一桌,服务员就会叫号。
如果有客户端和服务器建立连接,这个时候服务器的应用程序是不需要任何操作(也没有任何感知的),内核直接完成了连接建立的流程(三次握手),完成流程之后,就会在内核的队列中(这个队列是每个serverSocket都有一个这样的队列)排队。
应用程序要想和客户端进行通信,就需要通过一个accept方法把内核队列里已经建立好的连接对象,拿到app中。
TCP中用ServerSocket (给服务器使用的类,使用这个类来绑定端口号),服务器启动之后用到accept方法,把内核中建立好的连接到应用程序中去(建立连接的过程是内核自动完成的,应用程序是捡漏的),如果没有与客户端连接,那么就阻塞等待。
private ServerSocket serverSocket=null; public TCPEchoServer(int port) throws IOException { serverSocket =new ServerSocket(port); } public void start() throws IOException { System.out.println("服务器正式启动"); while (true){ //通过accpet方法,把内核中已经建立好的连接拿到app中 //建立连接的细节流程都是内核自动完成的,app只需要“捡现成”的 Socket clientSocket=serverSocket.accept(); } }
在销售A卖楼盘的时候,销售在路上看到一个人想找一个人问问需不需要买房,然后那个男生就带去楼盘区,然后销售找了一个专业的置业顾问B,来介绍楼盘。而这个销售就去路上继续找人。
A:在外面招揽客人
B:给客人提供详细的服务
然后我们调用方法来处理当前的连接。首先我们先打印一个日志,表示已经与客户端建立连接了。
- getPort() 和 getInetAddress() 得到对端的IP和端口 (客户端)
- getLocalAddress() 和getLocalPort() 得到本地的IP和端口(服务器)
//打印这个日志,来表示客户端连上了 System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
- 首先我们接收请求,用Scanner包装输入流,并读取请求。
- 然后我们根据请求,计算响应
- 最后把响应写回客户端 ,用PrintWriter类进行包装输出流,并flush().
Scanner scanner=new Scanner(inputStream); //1.读取请求并解析,此处就以next来作为读取请求的方式,.next的规则是,读到“空白符”就返回 String request=scanner.next(); //2.根据请求,计算响应 String response=process(request); //3.把响应写回客户端 //可以把String转成字节数组,写入到OutputStream, //也可以使用PrintWriter把OutputStream包裹一下,来写入字符串 PrintWriter printWriter=new PrintWriter(outputStream); //此处的println不是打印到控制台了,而是写入到outputStream对应的流对象,也就是写入到clientSocket //自然这个数据也就通过网络发送出去了, //此处使用println带有\n 也是为了后续 客户端这边 可以使用 scanner.next来读取数据 printWriter.write(response);
空白符 是一类特殊的字符,换行,回车符,空格,制表符,翻页符,垂直制表符
后续客户端发起的请求,会以空白符作为结束标记(此处就约定使用\n)
🍭TCP服务器
package TCP; 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("服务器正式启动"); while (true){ //通过accpet方法,把内核中已经建立好的连接拿到app中 //建立连接的细节流程都是内核自动完成的,app只需要“捡现成”的 Socket clientSocket=serverSocket.accept(); Thread t=new Thread(()->{ processConnection(clientSocket); }); t.start(); } } //通过这个方法,来处理当前的连接 public void processConnection(Socket clientSocket) { //打印这个日志,来表示客户端连上了 System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort()); try(InputStream inputStream=clientSocket.getInputStream(); OutputStream outputStream=clientSocket.getOutputStream()) { //使用try()方式,避免后续用完了流对象,忘记关闭 //由于客户端发来的数据,可能是"多条数据“,针对多条数据,就循环处理 while (true){ Scanner scanner=new Scanner(inputStream); if(!scanner.hasNext()){ //连接断开了,此时循环就应该结束 System.out.printf("[%s:%d] 客户端下线!\n",clientSocket.getInetAddress(),clientSocket.getPort()); break; } //1.读取请求并解析,此处就以next来作为读取请求的方式,.next的规则是,读到“空白符”就返回 String request=scanner.next(); //2.根据请求,计算响应 String response=process(request); //3.把响应写回客户端 //可以把String转成字节数组,写入到OutputStream, //也可以使用PrintWriter把OutputStream包裹一下,来写入字符串 PrintWriter printWriter=new PrintWriter(outputStream); //此处的println不是打印到控制台了,而是写入到outputStream对应的流对象,也就是写入到clientSocket //自然这个数据也就通过网络发送出去了, //此处使用println带有\n 也是为了后续 客户端这边 可以使用 scanner.next来读取数据 printWriter.println(response); //此处还要记得有个操作,刷新缓冲区,如果没有刷新操作,可能数据仍然是在内存中,没有被写入网卡 printWriter.flush(); //打印了这次请求交互过程的内容 System.out.printf("[%s:%d] req=%s resp=%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response); } } catch (IOException e) { throw new RuntimeException(e); }finally { //进行clientSocket的关闭 try { clientSocket.close(); } catch (IOException e) { throw new RuntimeException(e); } } } public String process(String request){ return request; } public static void main(String[] args) throws IOException { TCPEchoServer tcpEchoServer=new TCPEchoServer(9090); tcpEchoServer.start(); } }
如果我们出现了多个客户端连接同一个服务器该怎么办呢?这就考虑到多线程的知识了,如果有客户端就开启线程。
🍭TCP客户端
private Socket socket=null; public TCPClient(String serverIP, int serverPort) throws IOException { //需要在创建Socket的同时,和服务器”建立连接“ 此时就得告诉Socket服务器在哪里 //具体建立连接的细节,不需要咱们代码手动干预,是内核自动负责的 //当我们new这个对象的时候,操作系统内核,就开始使用 三次握手 具体细节 完成建立连接的过程 socket =new Socket(serverIP,serverPort); }
TCP是连接的,我们需要对端的IP和端口号。所以我们要构造服务器的IP和端口号。
客户端是发出请求,并接收服务器返回的响应。
- 我们先输入,并判断是否有输入,如果有输入就返回字符串。
- 发出请求,就是用PrintWriter类来封装输出流,并flush()。
- 然后接收服务器返回的响应,用Scanner来封装输入流。然后调用next()方法返回String字符串
- 然后打印处理。
package TCP; 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 TCPClient { private Socket socket=null; public TCPClient(String serverIP, int serverPort) throws IOException { //需要在创建Socket的同时,和服务器”建立连接“ 此时就得告诉Socket服务器在哪里 //具体建立连接的细节,不需要咱们代码手动干预,是内核自动负责的 //当我们new这个对象的时候,操作系统内核,就开始使用 三次握手 具体细节 完成建立连接的过程 socket =new Socket(serverIP,serverPort); } public void start(){ System.out.println("客户端启动"); //tcp客户端行为和udp客户端差不多 //从服务器读取响应 //把响应显示到界面上 Scanner scanner=new Scanner(System.in); try(InputStream inputStream=socket.getInputStream(); OutputStream outputStream=socket.getOutputStream()) { Scanner scanner1=new Scanner(inputStream); while (true){ //1.从控制台输入内容 System.out.println("->"); String request=scanner.next(); //2.把字符串作为请求,发送给服务器 PrintWriter printWriter=new PrintWriter(outputStream); printWriter.println(request); printWriter.flush(); //3.读取服务器返回的响应 String response=scanner1.next(); //4.界面显式内容 System.out.println(response); } } catch (IOException e) { throw new RuntimeException(e); } } public static void main(String[] args) throws IOException { TCPClient tcpClient=new TCPClient("172.20.10.2",9090); tcpClient.start(); } }
你跑的快,耳边全是风声。