TCP流套接字编程

server/2024/9/24 23:26:19/

TCP流套接字编程

ServerSocket API

        ServerSocket 是专门给服务器使用的 Socket 对象。

构造方法

方法签名

方法说明

ServerSocket (int port)

创建一个服务端流套接字Socket,并绑定到指定端口

普通方法

方法签名

方法说明

Socket accept()

开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket 对象,并基于该Socket建立与客户端的连接,否则阻塞等待。

void close()

关闭此套接字

Socket API

        Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。既会给客户端使用,也会给服务器使用。

构造方法

方法签名

方法说明

Socket(String host, int port)

创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

普通方法

方法签名

方法说明

InetAddress getInetAddress()

返回套接字所连接的地址

InputStream getInputStream()

返回此套接字的输入流

OutputStream getOutputStream()

返回此套接字的输出流

         TCP 不需要额外使用一个类来表示“TCP 数据报”,因为 TCP 是以字节的方式流式传输的。 

TCP中的长短连接

TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:

        短连接:客户端每次给服务器发消息,是先建立连接,发送请求,读取响应,关闭连接;下次再发送,则重新建立连接。

        长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。

两者区别如下:

        建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。

        主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。

        两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等。

示例一:回显服务器(长连接)

TCP服务端

import java.io.*;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;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) {// 此处使用 CachedThreadPool, 使用 FixedThreadPool 不太合适 (线程数不太应该是有固定的....)ExecutorService threadPool = Executors.newCachedThreadPool();Socket clientSocket = serverSocket.accept(); // 使用这个 clientSocket 和具体的客户端进行交流.threadPool.submit(() -> {processConnection(clientSocket);});}}// 使用这个方法来处理一个连接.// 这一个连接对应到一个客户端. 但是这里可能会涉及到多次交互.private void processConnection(Socket clientSocket) {System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());// 基于上述 socket 对象和客户端进行通信try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {// 由于要处理多个请求和响应, 也是使用循环来进行.while (true) {// 1. 读取请求Scanner scanner = new Scanner(inputStream);if (!scanner.hasNext()) {// 没有下个数据, 说明读完了. (客户端关闭了连接)System.out.printf("[%s:%d] 客户端下线! \n", clientSocket.getInetAddress().toString(), clientSocket.getPort());break;}// 注意!! 此处使用 next 是一直读取到换行符/空格/其他空白符结束, 但是最终返回结果里不包含上述 空白符 .String request = scanner.next();// 2. 根据请求构造响应String response = process(request);// 3. 返回响应结果.//    OutputStream 没有 write String 这样的功能. 可以把 String 里的字节数组拿出来, 进行写入;//    outputStream.write(response.getBytes());//    也可以用字符流来转换一下.PrintWriter printWriter = new PrintWriter(outputStream);// 此处使用 println 来写入. 让结果中带有一个 \n 换行. 方便对端来接收解析.printWriter.println(response);// flush 用来刷新缓冲区, 保证当前写入的数据, 雀食是发送出去了.printWriter.flush();System.out.printf("[%s:%d] req: %s; resp: %s \n", clientSocket.getInetAddress().toString(), clientSocket.getPort(),request, response);}} catch (IOException e) {e.printStackTrace();} finally {// 更合适的做法, 是把 close 放到 finally 里面, 保证一定能够执行到!!try {clientSocket.close();} catch (IOException e) {e.printStackTrace();}}}public String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(6666);server.start();}
}

        因此在使用完毕之后,就要进行“释放”,UDP 的 socket 没释放是因为 socket 生命周期更长(跟随整个程序),数量也不多;TCP 这里的生命周期更短(这个请求完了就没了),也就是数量也会更多。 

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 TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIp, int serverPort) throws IOException {// Socket 构造方法, 能够识别 点分十进制格式的 IP 地址. 比 DatagramPacket 更方便.// new 这个对象的同时, 就会进行 TCP 连接操作.socket = new Socket(serverIp, serverPort);}public void start() {System.out.println("客户端启动!");Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {while (true) {// 1. 先从键盘上读取用户输入的内容System.out.print("> ");String request = scanner.next();if (request.equals("exit")) {System.out.println("goodbye");break;}// 2. 把读到的内容构造成请求, 发送给服务器.PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(request);// 此处加上 flush 刷新保证数据确实发送出去了.printWriter.flush();// 3. 读取服务器的响应Scanner respScanner = new Scanner(inputStream);String response = respScanner.next();// 4. 把响应内容显示到界面上System.out.println(response);}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1", 6666);client.start();}
}

                             相当于打电话的两端,对象是不一样的

示例二:回显服务器(短连接)

TCP服务端

private void processConnection(Socket clientSocket) {System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());// 基于上述 socket 对象和客户端进行通信try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {// 1. 读取请求Scanner scanner = new Scanner(inputStream);if (!scanner.hasNext()) {// 没有下个数据, 说明读完了. (客户端关闭了连接)System.out.printf("[%s:%d] 客户端下线! \n", clientSocket.getInetAddress().toString(), clientSocket.getPort());}// 注意!! 此处使用 next 是一直读取到换行符/空格/其他空白符结束, 但是最终返回结果里不包含上述 空白符 .String request = scanner.next();// 2. 根据请求构造响应String response = process(request);// 3. 返回响应结果.//    OutputStream 没有 write String 这样的功能. 可以把 String 里的字节数组拿出来, 进行写入;//    outputStream.write(response.getBytes());//    也可以用字符流来转换一下.PrintWriter printWriter = new PrintWriter(outputStream);// 此处使用 println 来写入. 让结果中带有一个 \n 换行. 方便对端来接收解析.printWriter.println(response);// flush 用来刷新缓冲区, 保证当前写入的数据, 雀食是发送出去了.printWriter.flush();System.out.printf("[%s:%d] req: %s; resp: %s \n", clientSocket.getInetAddress().toString(), clientSocket.getPort(),request, response);} catch (IOException e) {e.printStackTrace();} finally {// 更合适的做法, 是把 close 放到 finally 里面, 保证一定能够执行到!!try {clientSocket.close();} catch (IOException e) {e.printStackTrace();}}}

        去掉 whlie(true) 即可 

TCP客户端

    public void start() {System.out.println("客户端启动!");Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {// 1. 先从键盘上读取用户输入的内容System.out.print("> ");String request = scanner.next();if (request.equals("exit")) {System.out.println("goodbye");}// 2. 把读到的内容构造成请求, 发送给服务器.PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(request);// 此处加上 flush 刷新保证数据确实发送出去了.printWriter.flush();// 3. 读取服务器的响应Scanner respScanner = new Scanner(inputStream);String response = respScanner.next();// 4. 把响应内容显示到界面上System.out.println(response);} catch (IOException e) {e.printStackTrace();}}

        也是去掉 whlie(true) 即可 

C10M 问题

        对于上述的情况,如果此时客户端非常多,也就意味着这个机器上就会有很多个线程,这是个很大的负担。虽然可以增加机器数和其他一些方案(如阻塞队列)来解决,但是似乎性价比不是很高。

        这时,操作系统就有个办法能一个线程处理多个客户端连接(就像煮开水的时候可以去拿个快递),这个办法叫做“IO 多路复用 / IO 多路连接”。首先会给这个线程安排个集合,这个集合里面就放了一堆连接,这个线程就负责监听这个集合,当哪个连接有数据来了,线程就会处理哪个连接;虽然连接有很多,但这些连接的请求并非严格意义上的同时,还是有先后的。在 Java 里 NIO 这个类就封装了上述多路复用的 API。


http://www.ppmy.cn/server/26996.html

相关文章

使用docker创建rocketMQ主从结构,使用

1、 创建目录 mkdir -p /docker/rocketmq/logs/nameserver-a mkdir -p /docker/rocketmq/logs/nameserver-b mkdir -p /docker/rocketmq/logs/broker-a mkdir -p /docker/rocketmq/logs/broker-b mkdir -p /docker/rocketmq/store/broker-a mkdir -p /docker/rocketmq/store/b…

经典机器学习法---感知模型机

优质博文:IT-BLOG-CN 1、模型形式 感知机模型主要用于解决二分类问题,即响应变量Y是个二分类变量(如性别)。其基本思想是拟找出一个超平面S,将样本空间中的训练集分为两个部分,使得位于超平面S合一侧的点具…

ESP32 蓝牙:使用 BTstack 库

ESP32S3+双模蓝牙智能音箱项目总目录_esp32蓝牙音箱-CSDN博客 目录 1.下载BTstack的源码 2.下载esp32的源码 3.基于BTstack源码生成esp32组件

ip ssl证书无限端口网站

IP SSL证书是由CA认证机构颁发的一种特殊数字证书。大部分SSL数字证书都需要用户使用域名进行申请,想要对公网IP地址加密实现https访问就需要申请IP SSL证书。IP SSL证书采用了强大的加密算法,可以有效地防止数据在传输过程中被窃取或篡改,具…

二,网络安全常用术语

黑客(hacker)——对计算机技术非常擅长的人,窃取数据,破坏计算机系统;全球最知名的一个黑客组织匿名(Anonymous)。 脚本小子——刚刚入门安全行业,学习了一些技术,只会用…

一站式AI创作平台:融合GPT会话、GPTs应用、Midjourney视觉艺术与Suno AI音乐合成模块

一、系统简介 星河易创AI系统基于ChatGPT的核心技术打造,集成了自然语言问答和艺术创作功能。该系统兼容Midjourney绘画技术,并支持官方GPT模型。它提供了多样化的应用,包括GPTs的多场景应用、实时GPT语音对话能力、GPT-4模型的先进特性&…

数据库加密数据的如何模糊查询?

方案一 通过数据库的加解密算法函数,在模糊查询的时候使用decode(key) like %partial%。这样做的优点是实现成本低,缺点也很明显只能使用数据库支持的一些常规加解密算法。 方案二 对密文数据进行分词组合,将分词组合的结果集分别进行加密&a…

C++学习随笔(12)—— list

本章我们来了解一下list 目录 1. list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 list iterator的使用 1.2.3 list capacity 1.2.4 list element access 1.2.5 list modifiers 1.2.6 list的迭代器失效 1. list的介绍及使用 1.1 list的介绍…