TCP流套接字编程

发布于:2025-07-03 ⋅ 阅读:(16) ⋅ 点赞:(0)

1.两个关键的类

2.ServerSocket API

2.1构造方法

2.2 accept

2.3 close

为什么UDP的客户端服务器中没有这个close方法,因为这个socket的生命周期是与进程一样的,只要服务器运行这,socket就不能释放,当进程结束的时候,持有的资源都会全部释放,包括占有的内存和文件描述符表

3.Socket API

客户端和服务器都要用,TCP传输的是字节流,就是传输字节

3.1 构造方法

根据上述的构造方法,创立一个socket对象就能够和服务器建立连接,相当于拨号操作

3.2 其他方法

这个方法可以获取到socket内部的流对象,通过InputStream,OutputStream进行read和write操作对网卡进行操作

获取到对应的端口号和IP地址

4.代码

服务器

package network;

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){
            //通过accept方法,把内核中已经建立好的连接拿到应用程序中
            //建立连接的细节流程都是内核自动完成的,应用程序只需要捡现成的
            Socket clientSocket = serverSocket.accept();
            //就像买房外面有人去拉客人来,而里面有人专门和客户谈
            //serverSocket专门去接受连接,而clientSocket专门去和后续的客户端进行通信
            processConnection(clientSocket);
        }
    }

    //通过这个方法,来处理当前的连接
    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();
                //空白符:换行,空格,制表符,翻页符,垂直制表符....
                //后续客户端发起的请求,会以空白符作为结束标记(此处约定了\n)
                //一般会手动约定出,从哪里到哪里是一个完整的数据包
                //每次循环一次,就处理一个数据包即可
                //上述这里就是约定了使用\n作为数据包的结束标记,就正好可以搭配scanner.next来完成请求的过程


                //2.根据请求计算响应
                String respons = process(request);

                //3.把响应写回到客户端
                //可以把String转成字节数组,写入到OutputStream
                //也可以使用printwriter 把 OutputStream包裹一下,来写入字符串
                PrintWriter printWriter = new PrintWriter(outputStream);
                //此处的println不是打印到控制台了,而是写入到outputStream对应的流对象中,也就是写入到clientSocketlim
                //自然这个数据也就通过网络发送出去了(发给当前这个连接的另外一端)
                //使用println带有\n 也是为了后续 客户端这边 可以使用 scanner.next 来读取数据
                printWriter.println(respons);
                //此处还要记得有个操作,刷新缓冲区,如果没有这个刷新操作,那么可能当前这个数据还在内存中没写进网卡里面
                printWriter.flush();
                //4.打印一下这次请求交互过程的内容
                System.out.printf("[%s,%d] req=%s, resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(),request,respons);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                //在这个地方,进行clientSocket的关闭
                //processConnection就是在处理一个连接,这个方法执行完毕,这个连接也就处理完了
                clientSocket.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }

    }

    public String process(String request){
        //此处也是写的回显服务器,响应和请求是一样的
        return request;
    }
}

客户端

package network;

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的同时,和服务器建立连接,此时就得告诉Socket服务器在哪
        //具体建立连接的细节不需要我们代码手动干预,是内核自动负责的
        //当我们new这个对象的时候,操作系统内核就开始三次握手具体细节,完成建立的过程了
        socket = new Socket(serverIp, serverPort);
    }

    public void start(){
        //tcp的客户端行为和udp客户端差不多
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()){
            PrintWriter writer = new PrintWriter(outputStream);
            Scanner scannerNetwork = new Scanner(inputStream);
            while (true){
                //1.从控制台读取用户输入的内容
                System.out.println("->");
                String request = scanner.next();
                //2.把字符串作为请求,发送给服务器
                //这里使用println,是为了让请求后面带上换行
                //也就是和服务器读取请求,scanner.next呼应
                writer.println(request);
                writer.flush();
                //3.读取服务器返回到的响应
                String response = scannerNetwork.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", 9090);
        client.start();
    }
}

4.2 解析

等待客户端发送请求,服务器与客户端建立连接之后,返回一个socket对象,如果没有客户端发送请求就会阻塞 

TCP处理连接中接收请求的当遇到空白符的时候才会读取完毕,使用这个scanner直接收到的就是字符串,如果使用inputstream的read()方法还需要将字节转化为字符串,客户端在发送请求的时候,必须在每个请求的末尾加上空白符,这个要求是程序员之间通信细节的约定,TCP是按照字节的方式来进行传输的,实际上我们希望若干个字节能构成一个应用层的数据报,区分一个应用层的数据报就是通过空白符来进行分割

空白符是一类统称,空格,换行回车,制表符,翻页符...

响应也加一个空白符,这样就能区分出一个完整的响应,一个请求对应一个响应,因为这是TCP传输的普遍问题,发请求和返回响应都需要考虑分隔符

阻塞等待请求的到达,1)请求到了有明确的分隔符返回true,2)TCP连接断开了返回false

当单独启动服务器没有启动客户端时,会在accept处产生阻塞,

当启动客户端之后服务器便会解除阻塞接收客户端发来的请求

当结束客户端时,会在服务器程序控制台打印客户端下线,当强制结束客户端进程时,或者调用socket.close()方法时,操作系统内核就会感知到,从而TCP断开连接流程(触发四次挥手)

5.问题

.1 连接

什么意思?

就像去吃海底捞,有时候人太多了需要排号

这个时候内核中的人就可以进应用程序里吃饭了

内核里排队的人就是内核里的连接,应用程序里有位置了,就会从内核中拿一个连接进去

2.clientsocket用完要close

前面写的Datagramsocket,serversocket都没写close,但是没关系

因为datagramsocket和serversocket,都是在程序中只有那么一个对象,生命周期都是贯穿整个程序的

clientsocket则是在循环中,每次有一个新的客户来建立连接,都会创建出新的clientsocket


网站公告

今日签到

点亮在社区的每一天
去签到