Java Socket

文章大纲来自于网络:使用Socket进行数据传输_哔哩哔哩_bilibili

1.Server and client

Socket 就像是两个人之间的电话线。

  1. 当一个人想要与另一个人通话,他们会拿起电话(创建一个Socket)。
  2. 两人之间可以通过这条电话线(Socket)传递信息,无论是语言、文字还是其他形式的数据。
  3. 通话结束后,他们可以挂断电话(关闭Socket),使得两者之间的通信中断。

在这个比喻中,电话线(Socket)提供了一种通信的方式,使得两个人可以在不同的地方进行交流。同样地,Socket 允许计算机在网络上进行通信。

首先,我们创建一个类表示服务器,在这个服务器里,我们希望和客户端进行联系,那我们肯定需要在服务器里接受通过socket传输过来的客户端类的对象。

所以我们先在server类里创建一个socket来表示服务器本身,并给这个服务器分配8080端口:

ServerSocket server = new ServerSocket(8080)

然后调用ServerSocket类的accept方法,接受通过socket传输过来的内容,这个对象就是对客户端的抽象:

 Socket socket1=server.accept();

当没有客户端链接的时候,accept方法会一直阻塞线程。

 这个socket里包含一系列客户端的信息,我们可以打印出来(第一个get方法是取socket的地址,第二个get方法是将这个地址转化为string类型):

System.out.println("IP address:"+socket1.getInetAddress().getHostAddress());

现在我们已经完成了对客户端的操作,需要关闭服务器。如果不关闭服务器,服务器就会一直占用这个8080端口 。

我们可以这样写,手动关闭server

public class Server {
    public static void main(String args[]){
        try {
            ServerSocket server = new ServerSocket(8080);
            //accept() will block the thread until the clint send a request
            // accept() will return a socket represent the client
            System.out.println("connecting...");
            Socket socket1=server.accept();
            System.out.println("IP address:"+socket1.getInetAddress().getHostAddress());
            //If you do not close the server, the port will be occupied and you are not able to use the port
            server.close();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

也可以这样写进try catch语句,自动关闭:

public static void main(String args[]){
        try(ServerSocket server = new ServerSocket(8080)) {
            //accept() will block the thread until the clint send a request
            // accept() will return a socket represent the client
            System.out.println("connecting...");
            Socket socket1=server.accept();
            System.out.println("IP address:"+socket1.getInetAddress().getHostAddress());
            //If you do not close the server, the port will be occupied and you are not able to use the port
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

try后括号里的内容需要实现autocloseable 接口。

AutoCloseable 是 Java 编程语言中的一个接口(Interface)。这个接口主要用于支持 Java 7 引入的 "try-with-resources" 语句。大致作用是,如果我们在try中new了一个实现了AutoCloseable 接口的对象,java会帮我们自动关闭这个对象。

client

新建一个socket对象表示client本身:

Socket socket = new Socket("localhost",8080)

 localhost是主机的ip地址,8080是端口。在创建client对象socket的时候,已经和服务器绑定好了。

同时,这个socket也需要被自动关闭,而socket类也实现了autocloseable接口,所以我们可以直接放进try语句。

import java.io.IOException;
import java.net.Socket;

public class Client {
    public static void main(String[] args) {
        //if you run on your own computer, then you can use "localhost"
        //if you run in the local area network(LAN), use "ipconfig" to see your source
        try(Socket socket = new Socket("localhost",8080)){
            //here is the same way as Server
            //socket should close and I write it in try block
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

先运行server,再运行client,server在client运行之前,会一直监听端口。

这实际上是tcp的握手过程,只不过java全部封装了。

接受多个client

用while循环就行了

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String args[]){
        try(ServerSocket server = new ServerSocket(8080)) {
            //accept() will block the thread until the clint send a request
            // accept() will return a socket represent the client
            System.out.println("connecting...");
            while (true){
                Socket socket1=server.accept();
                System.out.println("IP address:"+socket1.getInetAddress().getHostAddress());
            }
            //If you do not close the server, the port will be occupied and you are not able to use the port
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

 这里的client和server是怎么联系的呢?

我们在client里创建了一个socket,给这个socket指定端口8080,然后我们在server里创建一个

ServerSocket,通过监听8080端口来连接这个socket。同时,把这个监听到的socket又通过accept接受为一个本地的socket,在例子里命名为socket1.这个本地化的socket1其实和client里的socket是连接的,但是名字可以不一样。
  1. server.accept():这是在服务器端Socket编程中的一种操作,其中server是一个ServerSocket对象。ServerSocket对象通常在服务器端用于监听特定的网络端口,以便接受客户端的连接请求。accept()方法是一个阻塞调用,它会等待客户端的连接请求,一旦有客户端连接请求到达,它会接受这个连接请求,并返回一个新的Socket对象,该Socket对象代表与客户端建立的连接。

  2. Socket socket1=server.accept();:这行代码执行了accept()方法,并将返回的Socket对象存储在变量socket1中。这个socket1对象表示与一个特定客户端建立的连接,可以使用它进行双向通信,即从客户端接收数据和向客户端发送数据。

2.客户端发送数据

client

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) {
        //if you run on your own computer, then you can use "localhost"
        //if you run in the local area network(LAN), use "ipconfig" to see your source
        try(Socket socket = new Socket("localhost",8080);
            Scanner scanner=new Scanner(System.in)){ //Scanner also need to close, so write scanner in try block
            System.out.println("Already connected to the server!");
            System.out.println("write something that I can send to server:");
            //this is a convert stream
            // it is more convenient than socket.getOutputStream()
            OutputStreamWriter writer=new OutputStreamWriter(socket.getOutputStream());
            writer.write(scanner.nextLine()+"\n");
            //flush and client will send it to server
            writer.flush();
            //here is the same way as Server, "socket" should close and I write it in try block
            System.out.println("data already sent");
        }catch(IOException e){
            System.out.println("fail to connect to server");
            e.printStackTrace();
        }
    }
}
I/O 1 

注意

OutputStreamWriter writer=new OutputStreamWriter(socket.getOutputStream());
writer.write(scanner.nextLine()+"\n");

OutputStreamWriter 是 Java 中用于将字符写入输出流的类,它是字符流与字节流之间的桥梁。具体而言,OutputStreamWriter 可以将字符按照指定的字符编码转换为字节,并写入到底层的输出流中。

以下是创建 OutputStreamWriter 对象的一般形式:

OutputStreamWriter writer = new OutputStreamWriter(OutputStream out, Charset charset);

其中:

  • out 是底层的输出流,例如 socket.getOutputStream() 返回的输出流。
  • charset 是指定的字符编码,例如 StandardCharsets.UTF_8

上面的两行代码创建了一个OutputStreamWriter对象,该对象连接到给定的套接字(socket)的输出流,以便将字符数据写入该套接字所以我可以向OutputStreamWriter写入数据,写的数据会被放到套接字(socket)的输出流。

想象一个水管,本来的一头是套接字(socket)的输出流,但是输入的水不能直接从这种头输入水管,所以我们要给水管换一个头,就是这个OutputStreamWriter,然后我们从这个新头往水管里加水(writer.write(scanner.nextLine()+"\n"))

I/O 2

ChatGPT

ChatGPT

字节流(Byte Streams)和字符流(Character Streams)是用于处理输入和输出的两种基本的I/O流,它们之间的主要区别在于处理的数据类型和应用场景。

1. 字节流(Byte Streams):
  • 数据处理单位: 字节流以字节为单位处理数据。InputStreamOutputStream 是字节流的主要类。

  • 用途: 适用于处理二进制数据,如图像、音频、视频等。它们不关心数据的具体内容,而是简单地处理字节序列。

  • 读写: 字节流通常用于读写字节,不考虑字符编码。它们提供了 read()write() 等方法来逐字节读写数据。

  • 底层: 字节流是连接到底层输入和输出设备(例如文件、网络连接)的抽象,可以处理任何类型的数据。

2. 字符流(Character Streams):
  • 数据处理单位: 字符流以字符为单位处理数据。ReaderWriter 是字符流的主要类。

  • 用途: 适用于处理文本数据,字符流会考虑字符的编码。它们提供了更高层次的抽象,可以直接读取和写入字符。

  • 读写: 字符流用于读写字符,考虑字符的编码和字符集。它们提供了 read()write() 方法,可以按字符而不是字节来读写数据。

  • 底层: 字符流通常建立在字节流之上,使用 InputStreamReaderOutputStreamWriter 进行字节到字符的转换。

总结:
  • 如果处理的是二进制数据或不考虑字符编码,例如处理图像、音频等,使用字节流更合适。
  • 如果处理的是文本数据,并且需要考虑字符编码,例如读写文本文件,使用字符流更合适。

在实际应用中,根据处理的数据类型和需求来选择合适的流。在处理文本数据时,字符流通常更方便,因为它们处理字符编码和换行符等细节,而在处理二进制数据时,字节流更为直接。

demo
使用字节流处理二进制文件:

注意这里用的时ascii码

import java.io.*;

public class ByteStreamDemo {
    public static void main(String[] args) {
        // 写入二进制文件
        try (OutputStream outputStream = new FileOutputStream("binary_data.bin")) {
            byte[] data = { 65, 66, 67, 68, 69 }; // ASCII codes for 'A', 'B', 'C', 'D', 'E'
            outputStream.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 读取二进制文件
        try (InputStream inputStream = new FileInputStream("binary_data.bin")) {
            int byteData;
            while ((byteData = inputStream.read()) != -1) {
                System.out.print((char) byteData + " ");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
使用字符流处理文本文件: 

这里直接使用字符本身

import java.io.*;

public class CharacterStreamDemo {
    public static void main(String[] args) {
        // 写入文本文件
        try (Writer writer = new FileWriter("text_data.txt")) {
            String textData = "Hello, World!\nThis is a text file.";
            writer.write(textData);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 读取文本文件
        try (Reader reader = new FileReader("text_data.txt")) {
            int charData;
            while ((charData = reader.read()) != -1) {
                System.out.print((char) charData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

server

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server {
    public static void main(String args[]){
        try(ServerSocket server = new ServerSocket(8080);) {
            //accept() will block the thread until the clint send a request
            // accept() will return a socket represent the client
            System.out.println("connecting...");

            while (true){
                Socket socket1=server.accept();
                System.out.println("IP address:"+socket1.getInetAddress().getHostAddress());
                System.out.println("reading data...");
                //BufferedReader easy to output the data
                BufferedReader reader=new BufferedReader(new InputStreamReader(socket1.getInputStream()));
                //socket need to close and did not write in try block
                //so need to close here
                System.out.println(reader.readLine());
                socket1.close();
            }
            //If you do not close the server, the port will be occupied and you are not able to use the port
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

同理

BufferedReader reader=new BufferedReader(new InputStreamReader(socket1.getInputStream()));
I/O 3

InputStreamReader 是 Java 中用于从输入流读取字符的类,它是字符流与字节流之间的桥梁。InputStreamReader 将字节从底层的输入流转换为字符,并根据指定的字符编码来解码字节。

 BufferedReader 是 Java 中用于从字符输入流读取文本的类,它可以提供缓冲功能,从而提高读取性能。缓冲功能是指在读取或写入数据时,将数据暂时存储在内存中的缓冲区(buffer)中,而不是每次读取或写入一个字节或字符就进行实际的IO操作。使用缓冲技术可以减少实际的IO次数,从而提高读写性能。同时BufferedReader提供了很多方法让我们更方便地打印数据。

这行代码创建了一个BufferedReader对象,该对象连接到给定的套接字(socket1)的输入流,以便以字符流的方式从套接字中读取数据

3.服务端发送数据到客户端 

server:

 // 这行代码创建了一个OutputStreamWriter对象,该对象连接到给定的套接字(socket)的输出流
                // 以便将字符数据写入该套接字,所以我可以向OutputStreamWriter写入数据
                // 写的数据会被放到套接字(socket)的输出流。
 OutputStreamWriter writer=new OutputStreamWriter(socket1.getOutputStream());
                writer.write("as a client, you have sent "+str);
                writer.flush();

client:

//这行代码创建了一个BufferedReader对象,该对象连接到给定的套接字(socket1)的输入流
            // 以便以字符流的方式从套接字中读取数据B
            //其实InputStreamReader就已经可以接收到数据了,但是不好打印
            //所以我们又嵌套了一层BufferedReader
            //因为输入的时候不需要打印,所以直接用OutputStreamWriter就行,少了一层嵌套
            BufferedReader reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
            System.out.println("data sent form server "+reader.readLine());

 4.其他

设置超时时间

为了防止客户端迟迟不给服务端发送数据导致的服务端阻塞,我们可以通过

socket1.setSoTimeout(3000);

设置一个时间,超过这个时间服务端就会抛出一个异常。

如果通过 socket1 的输入流进行读取时,在 3 秒内没有可读取的数据,将抛出 SocketTimeoutException 异常。

手动将客户端连接服务器

之前我们都是通过构造方法,在new的时候就直接将客户端socket和服务器连接,我们也可以先不连接,在后面用connect方法显式连接。

1000设置了超时时间,如果超出1秒钟还没有连接,就会抛出一个异常

//here we use construct method to connect to the server
            // but if we want to connect manually
            // we can use connect method
            socket.connect(new InetSocketAddress("localhost",8080),1000);

保持连接的活跃

有时候我们虽然在客户端和服务端之间建立的连接,但是服务器出现了严重的错误,已经崩溃。此时我们的客户端并不知道,依然保持连接,这样浪费了资源。所以我们可以用setKeepAlive方法,来定期向服务器发送数据,如果服务器没有相应,我们就知道服务器崩溃了。 后续是否会自动关闭连接取决于机器本身的协议。

socket.setKeepAlive(true);

client:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) {
        //if you run on your own computer, then you can use "localhost"
        //if you run in the local area network(LAN), use "ipconfig" to see your source
        try(Socket socket = new Socket("localhost",8080);
            Scanner scanner=new Scanner(System.in)){ //Scanner also need to close, so write scanner in try block

            // setKeepAlive(true) 是 Socket 类的一个方法,用于设置 TCP 连接的保持活动状态(keep-alive)。
            // 当启用 TCP 连接保持活动状态时,系统将定期发送小的探测数据包(keep-alive 消息)到远程主机,以确保连接仍然保持活跃。
            socket.setKeepAlive(true);
            //here we use construct method to connect to the server
            // but if we want to connect manually
            // we can use connect method(the following line is not necessary)
            socket.connect(new InetSocketAddress("localhost",8080),1000);

            System.out.println("Already connected to the server!");
            System.out.println("write something that I can send to server:");
            //this is a convert stream
            // it is more convenient than socket.getOutputStream()
            OutputStreamWriter writer=new OutputStreamWriter(socket.getOutputStream());
            writer.write(scanner.nextLine()+"\n");
            //flush and client will send it to server
            writer.flush();
            //here is the same way as Server, "socket" should close and I write it in try block
            System.out.println("data already sent");



            //这行代码创建了一个BufferedReader对象,该对象连接到给定的套接字(socket1)的输入流
            // 以便以字符流的方式从套接字中读取数据B
            //其实InputStreamReader就已经可以接收到数据了,但是不好打印
            //所以我们又嵌套了一层BufferedReader
            //因为输入的时候不需要打印,所以直接用OutputStreamWriter就行,少了一层嵌套
            BufferedReader reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
            System.out.println("data sent form server "+reader.readLine());
        }catch(IOException e){
            System.out.println("fail to connect to server");
            e.printStackTrace();
        }
    }
}

server:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server {
    public static void main(String args[]){
        try(ServerSocket server = new ServerSocket(8080);) {
            //accept() will block the thread until the clint send a request
            // accept() will return a socket represent the client
            System.out.println("connecting...");

            while (true){
                //这里接受的并不是唯一的客户端,任何多个客户端都可以通过8080端口连接服务器
                Socket socket1=server.accept();
                //如果通过 socket1 的"输入流"进行读取时,在 3 秒内没有可读取的数据,将抛出 SocketTimeoutException 异常。
                socket1.setSoTimeout(3000);
                System.out.println("IP address:"+socket1.getInetAddress().getHostAddress());
                System.out.println("reading data...");
                //这行代码创建了一个BufferedReader对象,该对象连接到给定的套接字(socket1)的输入流
                // 以便以字符流的方式从套接字中读取数据B
                BufferedReader reader=new BufferedReader(new InputStreamReader(socket1.getInputStream()));
                //socket need to close and did not write in try block
                //so need to close here
                String str=reader.readLine();
                System.out.println(str);


                // 这行代码创建了一个OutputStreamWriter对象,该对象连接到给定的套接字(socket)的输出流
                // 以便将字符数据写入该套接字,所以我可以向OutputStreamWriter写入数据
                // 写的数据会被放到套接字(socket)的输出流。
                OutputStreamWriter writer=new OutputStreamWriter(socket1.getOutputStream());
                writer.write("as a client, you have sent "+str);
                writer.flush();
                socket1.close();
            }
            //If you do not close the server, the port will be occupied and you are not able to use the port
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值