飞道的博客

网络编程套接字之三【TCP】

295人阅读  评论(0)

目录

1. ServerSocket API(给服务器端使用的类)

2. Socket API(既给服务器使用,也给客户端使用)

3. 写TCP回显—服务器

4. 使用线程池后的TCP服务器代码(最终)

5. 写回显-客户端

6. TCP回显—客户端代码

7. 运行回显服务器和客户端


TCP流套接字编程

1. ServerSocket API(给服务器端使用的类)

 ServerSocket 是创建TCP服务端Socket的API。

 构造方法

方法签名 说明
ServerSocket(int port) 创建一个服务端流套接字Socket,并绑定到指定端口

方法

方法签名 说明
Socket accept() 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于改Socket建立与客户端的连接,否则阻塞等待(接受客户端的连接)
void close() 关闭此套接字

2. Socket API(既给服务器使用,也给客户端使用)

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

不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的

构造方法

方法签名 说明
Socket(String host, int port)

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

进程建立连接(尝试和指定的服务器建立连接)

方法

方法签名 说明
InetAddress getInetAddress() 返回套接字所连接的地址(返回套接字获取到对方的IP地址和端口)
InputStream getInputStream() 返回此套接字的输入流(通过Socket可以获取到两个流对象,分别用来读和写)
OutputStream getOutputStream() 返回此套接字的输出流

3. 写TCP回显—服务器

1.先写一个ServerSocket对象

    private ServerSocket listenSocket = null;

2.下面写Tcp服务器构造方法


   
  1. public TcpEchoServer (int port) throws IOException {
  2. listenSocket = new ServerSocket(port);
  3. }

3.写一个启动服务器的方法start(),在start中写上while循环来执行

   a)调用accept来接收客户端的连接

   b)再处理这个连接,这里写一个processConnection()方法来处理


   
  1. public void start () throws IOException {
  2. System.out.println( "服务器启动!");
  3. while( true) {
  4. //1. 先调用 accept 来接受客户端的连接
  5. Socket clientSocket = listenSocket.accept();
  6. //2. 再处理这个连接
  7. processConnection(clientSocket);
  8. }
  9. }

4.下面来写这个processConnection()方法,处理连接客户端连接

   方法中写try(这里写上InputStream(读)和OutPutStream(写)对象,写在try中帮助资源回收) {这里写上写具体处理逻辑步骤}

注意最后必须要写上finally来close关闭clientSocket


   
  1. private void processConnection (Socket clientSocket) throws IOException {
  2. System.out.printf( "[%s:%d] 客户端上线!\n",
  3. clientSocket.getInetAddress().toString(),
  4. clientSocket.getPort());
  5. //接下来处理客户端请求
  6. try( InputStream inputStream = clientSocket.getInputStream();
  7. OutputStream outputStream = clientSocket.getOutputStream()) {
  8. while( true) {
  9. }
  10. } finally {
  11. //这里要关闭socket,是因为
  12. //socket也是一个文件,一个进程能够同时打开的文件个数有上限(PCB文件描述符表,不是无限的
  13. clientSocket.close();
  14. }
  15. }

   a)读取请求并解析


   
  1. Scanner scanner = new Scanner(inputStream);
  2. if(!scanner.hasNext()) {
  3. //读完了,连接可以断开了
  4. System.out.printf( "[%s:%d] 客户端下线!\n",
  5. clientSocket.getInetAddress().toString(),
  6. clientSocket.getPort());
  7. break;
  8. }
  9. String request = scanner.next();

   b)根据请求计算响应(这里写一个process方法)

                String response = process(request);

   
  1. private String process (String request) {
  2. return request;
  3. }

 c)响应写回到客户端


   
  1. PrintWriter printWriter = new PrintWriter(outputStream);
  2. printWriter.println(response);
  3. //刷新缓冲区确保数据确实通过网卡发送出去了
  4. printWriter.flush();

   d)将发送的信息显示到服务器界面上


   
  1. System.out.printf( "[%s:%d] req: %s; resp: %s\n",
  2. clientSocket.getInetAddress().toString(),
  3. clientSocket.getPort(),
  4. request,response);

5.最后再写上mian方法来执行服务器


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

  
  1. import java.io.IOException;
  2. import java.io.InputStream;
  3. import java.io.OutputStream;
  4. import java.io.PrintWriter;
  5. import java.net.ServerSocket;
  6. import java.net.Socket;
  7. import java.util.Scanner;
  8. import java.util.concurrent.Semaphore;
  9. /**
  10. * Created with IntelliJ IDEA.
  11. * Description:
  12. * User: 28463
  13. * Date: 2022—10—14
  14. * Time: 17:05
  15. */
  16. public class TcpEchoServer {
  17. //代码中会设计到多个 socket 对象,使用不同的名字来区分
  18. private ServerSocket listenSocket = null;
  19. public TcpEchoServer (int port) throws IOException {
  20. listenSocket = new ServerSocket(port);
  21. }
  22. public void start () throws IOException {
  23. System.out.println( "服务器启动!");
  24. while( true) {
  25. //1. 先调用 accept 来接受客户端的连接
  26. Socket clientSocket = listenSocket.accept();
  27. //2. 再处理这个连接
  28. processConnection(clientSocket);
  29. }
  30. }
  31. private void processConnection (Socket clientSocket) throws IOException {
  32. System.out.printf( "[%s:%d] 客户端上线!\n",
  33. clientSocket.getInetAddress().toString(),
  34. clientSocket.getPort());
  35. //接下来处理客户端请求
  36. try( InputStream inputStream = clientSocket.getInputStream();
  37. OutputStream outputStream = clientSocket.getOutputStream()) {
  38. while( true) {
  39. //1.读取请求并解析
  40. Scanner scanner = new Scanner(inputStream);
  41. if(!scanner.hasNext()) {
  42. //读完了,连接可以断开了
  43. System.out.printf( "[%s:%d] 客户端下线!\n",
  44. clientSocket.getInetAddress().toString(),
  45. clientSocket.getPort());
  46. break;
  47. }
  48. String request = scanner.next();
  49. //2.根据请求计算响应
  50. String response = process(request);
  51. //3.响应写回到客户端
  52. PrintWriter printWriter = new PrintWriter(outputStream);
  53. printWriter.println(response);
  54. //刷新缓冲区确保数据确实通过网卡发送出去了
  55. printWriter.flush();
  56. System.out.printf( "[%s:%d] req: %s; resp: %s\n",
  57. clientSocket.getInetAddress().toString(),
  58. clientSocket.getPort(),
  59. request,response);
  60. }
  61. } catch (IOException e) {
  62. e.printStackTrace();
  63. } finally {
  64. clientSocket.close();
  65. }
  66. }
  67. private String process (String request) {
  68. return request;
  69. }
  70. public static void main (String[] args) throws IOException {
  71. TcpEchoServer tcpEchoServer = new TcpEchoServer( 9090);
  72. tcpEchoServer.start();
  73. }
  74. }

 下面思考为啥代码最后finally要执行clientSocket的close,而前面的listenSocket以及UDP程序中的socekt为啥就没close?

但是上面的代码有个问题,只能处理一个客户端的请求,(本质上第二个客户端的消息是发出去了,但服务器此时还在执行第一个客户端的请求,只要从第一个客户端这里出来,服务器就会立刻执行第二个客户端的消息)

我们希望的是既能够快速重复的调用到accept(也就是连接多个客户端),又能够循环的处理客户端的请求。所以就需要使用到多线程了

那么为什么前面UDP就不需要考虑这个问题,而TCP需要考虑 

UDP是无连接,客户端直接发消息就行(不必专注于处理某一个客户端)

TCP建立连接之后,要处理客户端的多次请求,才导致无法快速的调用到accept(长连接)(主要原因)

如果TCP每个连接只处理一个客户端的请求,也能够保证快速调用到accept(短连接)

 下面使用多线程,给每个客户端连上来的都分配一个新的线程负责处理请求

 但是直接这样使用多线程,如果循环多次,对应就会创建很多线程,等线程执行完,又会消毁很多的线程,所以更好的方法就是使用线程池

4. 使用线程池后的TCP服务器代码(最终)


  
  1. import java.io.IOException;
  2. import java.io.InputStream;
  3. import java.io.OutputStream;
  4. import java.io.PrintWriter;
  5. import java.net.ServerSocket;
  6. import java.net.Socket;
  7. import java.util.Scanner;
  8. import java.util.concurrent.Executor;
  9. import java.util.concurrent.ExecutorService;
  10. import java.util.concurrent.Executors;
  11. import java.util.concurrent.Semaphore;
  12. /**
  13. * Created with IntelliJ IDEA.
  14. * Description:
  15. * User: 28463
  16. * Date: 2022—10—14
  17. * Time: 17:05
  18. */
  19. public class TcpEchoServer {
  20. //代码中会设计到多个 socket 对象,使用不同的名字来区分
  21. private ServerSocket listenSocket = null;
  22. public TcpEchoServer (int port) throws IOException {
  23. listenSocket = new ServerSocket(port);
  24. }
  25. public void start () throws IOException {
  26. System.out.println( "服务器启动!");
  27. ExecutorService service = Executors.newCachedThreadPool();
  28. while( true) {
  29. //1. 先调用 accept 来接受客户端的连接
  30. Socket clientSocket = listenSocket.accept();
  31. //2. 再处理这个连接,这里应该要使用多线程,每个客户端连上来都分配一个新的线程负责处理
  32. service.submit( new Runnable() {
  33. @Override
  34. public void run () {
  35. try {
  36. processConnection(clientSocket);
  37. } catch (IOException e) {
  38. e.printStackTrace();
  39. }
  40. }
  41. });
  42. }
  43. }
  44. private void processConnection (Socket clientSocket) throws IOException {
  45. System.out.printf( "[%s:%d] 客户端上线!\n",
  46. clientSocket.getInetAddress().toString(),
  47. clientSocket.getPort());
  48. //接下来处理客户端请求
  49. try( InputStream inputStream = clientSocket.getInputStream();
  50. OutputStream outputStream = clientSocket.getOutputStream()) {
  51. while( true) {
  52. //1.读取请求并解析
  53. Scanner scanner = new Scanner(inputStream);
  54. if(!scanner.hasNext()) {
  55. //读完了,连接可以断开了
  56. System.out.printf( "[%s:%d] 客户端下线!\n",
  57. clientSocket.getInetAddress().toString(),
  58. clientSocket.getPort());
  59. break;
  60. }
  61. String request = scanner.next();
  62. //2.根据请求计算响应
  63. String response = process(request);
  64. //3.响应写回到客户端
  65. PrintWriter printWriter = new PrintWriter(outputStream);
  66. printWriter.println(response);
  67. //刷新缓冲区确保数据确实通过网卡发送出去了
  68. printWriter.flush();
  69. System.out.printf( "[%s:%d] req: %s; resp: %s\n",
  70. clientSocket.getInetAddress().toString(),
  71. clientSocket.getPort(),
  72. request,response);
  73. }
  74. } catch (IOException e) {
  75. e.printStackTrace();
  76. } finally {
  77. //这里要关闭socket,是因为
  78. //socket也是一个文件,一个进程能够同时打开的文件个数有上限(PCB文件描述符表,不是无限的
  79. clientSocket.close();
  80. }
  81. }
  82. private String process (String request) {
  83. return request;
  84. }
  85. public static void main (String[] args) throws IOException {
  86. TcpEchoServer tcpEchoServer = new TcpEchoServer( 9090);
  87. tcpEchoServer.start();
  88. }
  89. }

5. 写回显-客户端

 1. 先写一个Socket对象,客户端用Socket来建立连接

private Socket socket = null;

2.下面写Tcp客户端构造(和Udp区别较大,有连接和无连接的区别)


   
  1. public TcpEchoClient (String serverIP, int serverPort) throws IOException {
  2. //和服务器建立连接,就需要知道服务器在哪
  3. socket = new Socket(serverIP,serverPort);
  4. }

3.写客户端执行方法start(),给start里面放try,try中执行的就是while,来让客户端循环输入,还要在try后面括号中写上InputStream(读)和OutputStream(写)的对象(写在括号中中try会自动帮助,关掉资源)


   
  1. public void start () throws IOException {
  2. Scanner scan = new Scanner(System.in);
  3. try( InputStream inputStream = socket.getInputStream();
  4. OutputStream outputStream = socket.getOutputStream()) {
  5. while( true) {
  6. }
  7. }
  8. }

while中写  a)从控制台读取数据,构造一个请求


   
  1. System.out.println( "-> ");
  2. String request = scan.next();

                 b)发送请求给服务器(PrintWriter)


   
  1. PrintWriter printWriter = new PrintWriter(outputStream);
  2. printWriter.println(request);
  3. //这个 flush 不要忘记,否则可能导致请求没有真发出去
  4. printWriter.flush();

                 c)从服务器读取响应


   
  1. Scanner respScanner = new Scanner(inputStream);
  2. String response = respScanner.next();

                 d)把响应显示到界面上

                System.out.println(response);

4.最后再写上mian方法来执行客户端


   
  1. public static void main (String[] args) throws IOException {
  2. TcpEchoClient tcpEchoClient = new TcpEchoClient( "127.0.0.1", 9090);
  3. tcpEchoClient.start();

 Udp和Tcp构造的区别,有连接和无连接 

6. TCP回显—客户端代码


  
  1. import java.io.IOException;
  2. import java.io.InputStream;
  3. import java.io.OutputStream;
  4. import java.io.PrintWriter;
  5. import java.net.Socket;
  6. import java.util.Scanner;
  7. /**
  8. * Created with IntelliJ IDEA.
  9. * Description:
  10. * User: 28463
  11. * Date: 2022—10—14
  12. * Time: 17:06
  13. */
  14. public class TcpEchoClient {
  15. //客户端需要使用这个 Socket 对象来建立连接
  16. private Socket socket = null;
  17. public TcpEchoClient (String serverIP, int serverPort) throws IOException {
  18. //和服务器建立连接,就需要知道服务器在哪
  19. socket = new Socket(serverIP,serverPort);
  20. }
  21. public void start () throws IOException {
  22. Scanner scan = new Scanner(System.in);
  23. try( InputStream inputStream = socket.getInputStream();
  24. OutputStream outputStream = socket.getOutputStream()) {
  25. while( true) {
  26. //1.从控制台读取数据,构造成一个请求
  27. System.out.println( "-> ");
  28. String request = scan.next();
  29. //2.发送请求给服务器
  30. PrintWriter printWriter = new PrintWriter(outputStream);
  31. printWriter.println(request);
  32. //这个 flush 不要忘记,否则可能导致请求没有真发出去
  33. printWriter.flush();
  34. //3.从服务器读取响应
  35. Scanner respScanner = new Scanner(inputStream);
  36. String response = respScanner.next();
  37. //4.把响应显示到界面上
  38. System.out.println(response);
  39. }
  40. }
  41. }
  42. public static void main (String[] args) throws IOException {
  43. TcpEchoClient tcpEchoClient = new TcpEchoClient( "127.0.0.1", 9090);
  44. tcpEchoClient.start();
  45. }
  46. }

7. 运行回显服务器和客户端

 

 


转载:https://blog.csdn.net/m0_58761900/article/details/127358914
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场