TCP流套接字编程

111 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第29天,点击查看活动详情

大家好,我是bug郭,一名双非科班的在校大学生。对C/JAVA、数据结构、Spring系列框架、Linux及MySql、算法等领域感兴趣,喜欢将所学知识写成博客记录下来。 希望该文章对你有所帮助!如果有错误请大佬们指正!共同学习交流

作者简介:

TCP流套接字编程

我们知道TCP是有连接的! 所以我们需要先对客户端建立连接! 我们来学习一下TCP协议对应的Socket API!

我们先来学习两个类: 通过这两个类我们就可以对TCP协议网络编程!

这两个类也在java.net包下! 在这里插入图片描述

  • ServerSocket 这个类实现了服务器套接字。

服务器套接字等待通过网络进入的请求。 它根据该请求执行一些操作,然后可能将结果返回给请求者。 服务器套接字的实际工作由SocketImpl类的实例执行。 应用程序可以更改创建套接字实现的套接字工厂,以配置自己创建适合本地防火墙的套接字! 构造方法: 在这里插入图片描述

  • ServerSocket() 创建未绑定的服务器套接字。
  • ServerSocket(int port) 创建绑定到指定端口的服务器套接字。
  • ServerSocket(int port, int backlog) 创建服务器套接字并将其绑定到指定的本地端口号,并指定了积压。
  • ServerSocket(int port, int backlog, InetAddress bindAddr) 创建一个具有指定端口的服务器,侦听backlog和本地IP地址绑定。

我们主要用到的是第二个构造方法!我们的服务器一般都需要绑定端口号,便于客户端访问! 这个类一般用于服务器程序!

需要用到的方法 在这里插入图片描述

  • Socket accept() 倾听要连接到此套接字并接受它!

通过这个方法,我们就可以与客户端建立连接! 返回的Socket对象,我们后面的操作就对socket对象进行就好了! ServerSocket对象就完成了他的任务和使命了!

就好比之前的电话接线员,连接后就和他没有关系了!剩下的就交个两个打电话的人了!这里的也是剩下的就交给socket对象就好了!

用通俗的话讲就是,这里的ServerSocket就做了一件事情,通过网络请求,与客户端建立连接,执行完后就将结果返回给请求者!

  • Socket

该类实现客户端套接字(也称为“套接字”)。 套接字是两台机器之间通讯的端点。 套接字的实际工作由SocketImpl类的实例执行。 应用程序通过更改创建套接字实现的套接字工厂,可以配置自己创建适合本地防火墙的套接字。 这里的Socket对象就相当于接电话的两个人,下面的通讯操作,主要通过这个类来实现!

构造方法 在这里插入图片描述

  • Socket() 创建一个未连接的套接字,并使用系统默认类型的SocketImpl。
  • Socket(InetAddress address, int port) 创建流套接字并将其连接到指定IP地址的指定端口号。

我们主要学习这两个构造方法! 第一个无参构造,一般用于服务器!当ServerSocket对象调用accept方法后就可以用这个对象接收!因为这个socket对象是来自客户端,所有已经有了端口号!

而第二个构造方法一般用于创建服务器socket! 注意: 这里的addressport并不是像UDP一样设置直接的端口号,而是连接到这个IP和端口号的服务器!

需要用到的方法

  • void close() 关闭此套接字。
  • InetAddress getInetAddress() 返回套接字所连接的地址。
  • InetAddress getLocalAddress() 获取套接字所绑定的本地地址。

这里一个是连接的服务器地址,一个是自己的地址!

  • int getLocalPort() 返回此套接字绑定到的本地端口号。
  • InputStream getInputStream() 返回此套接字的输入流。
  • OutputStream getOutputStream() 返回此套接字的输出流。

我们上面的这两个方法就实现了TCP面向字节流!

通过操作上面的两个对象就可以实现通信,输入和输出了!

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 {
    //1.ServerSocket 只负责建立连接!
    ServerSocket serverSocket = null;
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while(true){
            //因为TCP是有连接的,我们要先建立连接(接电话)
            //ServerSocket只做了一件事情,调用accept方法建立连接
            //我们就得到了socketClient客户!!!
            //后面就只需要针对socketClient进行操作即可!
            //如果没有客户端连接,accept就会阻塞!
            Socket socketClient = serverSocket.accept();
            processConnection(socketClient);
        }
    }

    public void processConnection(Socket socketClient) {
        //TCP面向字节流,所以直接通过文件操作,便可以将数据读写!
        try(InputStream inputStream = socketClient.getInputStream()) {
            //调用getInputStrem方法获取到请求,用输入字节流接收!
            try(OutputStream outputStream = socketClient.getOutputStream()){
                //调用getOutStrem方法,用于输出应答!
                //通过scanner读取请求更便利!
                Scanner scanner = new Scanner(inputStream);
                //循环处理每一个请求,返回对应响应!
                while(true){
                    if(!scanner.hasNext()){
                        System.out.printf("[客户端IP:%s,port:%d] 退出连接\n",socketClient.getInetAddress(),socketClient.getPort());
                        break;
                    }
                    //获取到请求
                    String request = scanner.next();
                    //根据请求给出应答
                    String response = process(request);
                    //把这个响应给客户端!
                    //通过printwiter包裹OutputSteam!
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    //将请求字符串写入到输出流中!
                    printWriter.println(response);
                    printWriter.flush();//刷新缓冲器,让客户端立即获取到响应!
                    System.out.printf("[客户端IP:%s,port:%d] req:%s,res:%s\n",socketClient.getInetAddress(),socketClient.getPort(),request,response);
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                //关闭资源!
                socketClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public String process(String request) {
        //回显
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
        tcpEchoServer.start();
    }
}

//回显服务客户端
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 {
        //客户端
        //这里的IP和端口是服务器的,并且这里传入IP和port表示与这个服务器建立连接!
        socket = new Socket(serverIp,serverPort);
    }
    public void start(){
        System.out.println("和服务器连接成功!");
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream()){
            try(OutputStream outputStream = socket.getOutputStream()){
                while(true){
                    //1.从控制台读取请求
                    System.out.print("->");
                    String request = scanner.next();
                    //2.构造请求并发送给服务器
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(request);
                    printWriter.flush();
                    //3.读取响应
                    Scanner scannerResponse = new Scanner(inputStream);
                    String response = scannerResponse.next();
                    //把结果打印到控制台!
                    System.out.printf("req:%s,res:%s\n",request,response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                //关闭资源
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws IOException {
      TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);
      tcpEchoClient.start();
    }
}

启动服务器: 在这里插入图片描述 启动客户端: 在这里插入图片描述 在这里插入图片描述 可以看到,这里我们也实现了回显服务!

当我们多开几个客户端试试! 在这里插入图片描述 在这里插入图片描述 可以看到客户端并没有收到应答,服务器也没和客户端建立连接!

因为,我们的ServerSocket对象,调用了accept后就一直在循环中,如果当前客户端不和服务器断开连接的话,就不会和其他客户端建立连接了! 在这里插入图片描述 我们如何才能实现多客户端呢?

我们回顾之前的对线程操作!我们可以有多个线程同时对多个客户端建立连接!!! 那我们进行升级一下变成多进程版本的服务器!

//多线程版本服务器
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 TcpThreadEchoServer{
    //1.ServerSocket 只负责建立连接!
    ServerSocket serverSocket = null;
    public TcpThreadEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while(true){
            //因为TCP是有连接的,我们要先建立连接(接电话)
            //ServerSocket只做了一件事情,调用accept方法建立连接
            //我们就得到了socketClient客户!!!
            //后面就只需要针对socketClient进行操作即可!
            //如果没有客户端连接,accept就会阻塞!
            Thread thread = new Thread(()->{
                Socket socketClient = null;
                try {
                    socketClient = serverSocket.accept();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                processConnection(socketClient);
            });
           thread.start();
        }
    }
    public void processConnection(Socket socketClient) {
        //TCP面向字节流,所以直接通过文件操作,便可以将数据读写!
        try(InputStream inputStream = socketClient.getInputStream()) {
            //调用getInputStrem方法获取到请求,用输入字节流接收!
            try(OutputStream outputStream = socketClient.getOutputStream()){
                //调用getOutStrem方法,用于输出应答!
                //通过scanner读取请求更便利!
                Scanner scanner = new Scanner(inputStream);
                //循环处理每一个请求,返回对应响应!
                while(true){
                    if(!scanner.hasNext()){
                        System.out.printf("[客户端IP:%s,port:%d] 退出连接\n",socketClient.getInetAddress(),socketClient.getPort());
                        break;
                    }
                    //获取到请求
                    String request = scanner.next();
                    //根据请求给出应答
                    String response = process(request);
                    //把这个响应给客户端!
                    //通过printwiter包裹OutputSteam!
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    //将请求字符串写入到输出流中!
                    printWriter.println(response);
                    printWriter.flush();//刷新缓冲器,让客户端立即获取到响应!
                    System.out.printf("[客户端IP:%s,port:%d] req:%s,res:%s\n",socketClient.getInetAddress(),socketClient.getPort(),request,response);
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                //关闭资源!
                socketClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public String process(String request) {
        //回显
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpThreadEchoServer tcpThreadEchoServer = new TcpThreadEchoServer(9090);
        tcpThreadEchoServer.start();
    }
}

在这里插入图片描述 我们只更改了一点代码就实现了多个客户端!

我们来学习了线程池,我们在更改成线程池版本!

//线程池版本服务器
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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpThreadPoolEchoServer {
    //1.ServerSocket 只负责建立连接!
    ServerSocket serverSocket = null;
    public TcpThreadPoolEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动!");
        ExecutorService pool = Executors.newCachedThreadPool();
        while(true){
            //因为TCP是有连接的,我们要先建立连接(接电话)
            //ServerSocket只做了一件事情,调用accept方法建立连接
            //我们就得到了socketClient客户!!!
            //后面就只需要针对socketClient进行操作即可!
            //如果没有客户端连接,accept就会阻塞!

            //线程池版本!
            Socket socketClient = serverSocket.accept();
            pool.submit(new Runnable() {
                @Override
                public void run() {

                }
            });
            processConnection(socketClient);
        }
    }

    public void processConnection(Socket socketClient) {
        //TCP面向字节流,所以直接通过文件操作,便可以将数据读写!
        try(InputStream inputStream = socketClient.getInputStream()) {
            //调用getInputStrem方法获取到请求,用输入字节流接收!
            try(OutputStream outputStream = socketClient.getOutputStream()){
                //调用getOutStrem方法,用于输出应答!
                //通过scanner读取请求更便利!
                Scanner scanner = new Scanner(inputStream);
                //循环处理每一个请求,返回对应响应!
                while(true){
                    if(!scanner.hasNext()){
                        System.out.printf("[客户端IP:%s,port:%d] 退出连接\n",socketClient.getInetAddress(),socketClient.getPort());
                        break;
                    }
                    //获取到请求
                    String request = scanner.next();
                    //根据请求给出应答
                    String response = process(request);
                    //把这个响应给客户端!
                    //通过printwiter包裹OutputSteam!
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    //将请求字符串写入到输出流中!
                    printWriter.println(response);
                    printWriter.flush();//刷新缓冲器,让客户端立即获取到响应!
                    System.out.printf("[客户端IP:%s,port:%d] req:%s,res:%s\n",socketClient.getInetAddress(),socketClient.getPort(),request,response);
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                //关闭资源!
                socketClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public String process(String request) {
        //回显
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpThreadPoolEchoServer tcpThreadPoolEchoServer = new TcpThreadPoolEchoServer(9090);
        tcpThreadPoolEchoServer.start();
    }
}

我们对TCPUDP socket API网络编程的学习就到此为止,还有其他的内容,我们下次学习!