Java TCP通信:Java ServerSocket类和Socket类

TCP 网络程序是指利用 Socket 编写的通信程序。利用 TCP 协议进行通信的两个应用程序是有主次之分的,一个是服务器程序,一个是客户端程序,两者的功能和编写方法不太一样。其中 ServerSocket 类表示 Socket 服务器端,Socket 类表示 Socket 客户端,两者之间的交互过程如下:

  1. 服务器端创建一个 ServerSocket(服务器端套接字),调用 accept() 方法等待客户端来连接。
  2. 客户端程序创建一个 Socket,请求与服务器建立连接。
  3. 服务器接收客户的连接请求,同时创建一个新的 Socket 与客户建立连接,服务器继续等待新的请求。

ServerSocket 类

ServerSocket 类是与 Socket 类相对应的用于表示通信双方中的服务器端,用于在服务器上开一个端口,被动地等待数据(使用 accept() 方法)并建立连接进行数据交互。

服务器套接字一次可以与一个套接字连接,如果多台客户端同时提出连接请求,服务器套接字会将请求连接的客户端存入队列中,然后从中取出一个套接字与服务器新建的套接字连接起来。若请求连接大于最大容纳数,则多出的连接请求被拒绝;默认的队列大小是 50。

下面简单介绍一下 ServerSocket 的构造方法和常用方法。

ServerSocket 的构造方法

ServerSocket 的构造方法如下所示。

  • ServerSocket():无参构造方法。
  • ServerSocket(int port):创建绑定到特定端口的服务器套接字。
  • ServerSocket(int port,int backlog):使用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口。
  • ServerSocket(int port,int backlog,InetAddress bindAddr):使用指定的端口、监听 backlog 和要绑定到本地的 IP 地址创建服务器。


在上述方法的参数中 port 指的是本地 TCP 端口,backlog 指的是监听 backlog,bindAddr 指的是要将服务器绑定到的 InetAddress。

创建 ServerSocket 时可能会拋出 IOException 异常,所以要进行异常捕捉。如下所示为使用 8111 端口的 ServerSocket 实例代码。

 
  1. try
  2. {
  3. ServerSocket serverSocket=new ServerSocket(8111);
  4. }
  5. catch(IOException e)
  6. {
  7. e.printStackTrace();
  8. }

ServerSocket 的常用方法

ServerSocket 的常用方法如下所示。

  • Server accept():监听并接收到此套接字的连接。
  • void bind(SocketAddress endpoint):将 ServerSocket 绑定到指定地址(IP 地址和端口号)。
  • void close():关闭此套接字。
  • InetAddress getInetAddress():返回此服务器套接字的本地地址。
  • int getLocalPort():返回此套接字监听的端口。
  • SocketAddress getLocalSoclcetAddress():返回此套接字绑定的端口的地址,如果尚未绑定则返回 null。
  • int getReceiveBufferSize():获取此 ServerSocket 的 SO_RCVBUF 选项的值,该值是从 ServerSocket 接收的套接字的建议缓冲区大小。


调用 accept() 方法会返回一个和客户端 Socket 对象相连接的 Socket 对象,服务器端的 Socket 对象使用 getOutputStream() 方法获得的输出流将指定客户端 Socket 对象使用 getInputStream() 方法获得那个输入流。同样,服务器端的 Socket 对象使用的 getInputStream() 方法获得的输入流将指向客户端 Socket 对象使用的 getOutputStream() 方法获得的那个输出流。也就是说,当服务器向输出流写入信息时,客户端通过相应的输入流就能读取,反之同样如此,整个过程如图 1 所示。

图1 服务器与客户端连接示意图

例 1

了解上面的基础知识后,下面使用 ServerSocket 类在本机上创建一个使用端口 8888 的服务器端套接字,实例代码如下所示。

 
  1. public static void main(String[] args)
  2. {
  3. try
  4. {
  5. //在8888端口创建一个服务器端套接字
  6. ServerSocket serverSocket=new ServerSocket(8888);
  7. System.out.println("服务器端Socket创建成功");
  8. while(true)
  9. {
  10. System.out.println("等待客户端的连接请求");
  11. //等待客户端的连接请求
  12. Socket socket=serverSocket.accept();
  13. System.out.println("成功建立与客户端的连接");
  14. }
  15. }
  16. catch(IOException e)
  17. {
  18. e.printStackTrace();
  19. }
  20. }


如上述代码所示,在成功建立 8888 端口的服务器端套接字之后,如果没有客户端的连接请求,则 accept() 方法为空,所以不会输出“成功建立与客户端的连接”,运行结果如下所示。

服务器端S.ocket创違成功
等待客户端的连接请求

Socket 类

Socket 类表示通信双方中的客户端,用于呼叫远端机器上的一个端口,主动向服务器端发送数据(当连接建立后也能接收数据)。下面简单介绍一下 Socket 类的构造方法和常用方法。

Socket 的构造方法

Socket 的构造方法如下所示。

  • Socket():无参构造方法。
  • Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口。
  • Soclcet(InetAddress address,int port,InetAddress localAddr,int localPort):创建一个套接字并将其连接到指定远程地址上的指定远程端口。
  • Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指定端口。
  • Socket(String host,int port,InetAddress localAddr,int localPort):创建一个套接字并将其连接到指定远程地址上的指定远程端口。Socket 会通过调用 bind() 函数来绑定提供的本地地址及端口。


在上述方法的参数中,address 指的是远程地址,port 指的是远程端口,localAddr 指的是要将套接字绑定到的本地地址,localPort 指的是要将套接字绑定到的本地端口。

Socket 的常用方法

Socket 的常用方法如下所示。

  • void bind(SocketAddress bindpoint):将套接字绑定到本地地址。
  • void close():关闭此套接字。
  • void connect(SocketAddress endpoint):将此套接字连接到服务器。
  • InetAddress getInetAddress():返回套接字的连接地址。
  • InetAddress getLocalAddress():获取套接字绑定的本地地址。
  • InputStream getInputStream():返回此套接字的输入流。
  • OutputStream getOutputStream():返回此套接字的输出流。
  • SocketAddress getLocalSocketAddress():返回此套接字绑定的端点地址,如果尚未绑定则返回 null。
  • SocketAddress getRemoteSocketAddress():返回此套接字的连接的端点地址,如果尚未连接则返回 null。
  • int getLoacalPort():返回此套接字绑定的本地端口。
  • intgetPort():返回此套接字连接的远程端口。

例 2

编写 TCP 程序,包括一个客户端和一个服务器端。要求服务器端等待接收客户端发送的内容,然后将接收到的内容输出到控制台并做出反馈。

(1) 创建一个类作为客户端,首先在 main() 方法中定义一个 Socket 对象、一个 OutputStream 对象和一个 InputStream 对象并完成初始化。接着定义服务器端的 IP 地址和端口号,代码如下所示。

 
  1. public static void main(String[] args)
  2. {
  3. Socket socket=null;
  4. OutputStream out=null;
  5. InputStream in=null;
  6. String serverIP="127.0.0.1"; //服务器端 IP 地址
  7. int port=5000; //服务器端端口号
  8. }


(2) 建立与服务器端的连接并将数据发送到服务器端,代码如下所示。

 
  1. socket=new Socket(serverIP,port); //建立连接
  2. out=socket.getOutputStream(); //发送数据
  3. out.write("我是客户端数据 ".getBytes());


(3) 从输入流中读出服务器的反馈信息并输出到控制台,代码如下所示。

 
  1. byte[] b=new byte[1024];
  2. in=socket.getInputStream();
  3. int len=in.read(b);
  4. System.out.println(" 服务器端的反馈为:"+new String(b,0,len));


(4) 关闭输入/输出流以及 Socket 对象,代码如下所示。

 
  1. in.close();
  2. out.close();
  3. socket.close();


(5) 创建一个类作为服务器端,编写 main() 方法,创建 ServerSocket、Socket、InputStream、OutputStream 以及端口号并初始化,代码如下所示。

 
  1. ServerSocket ServerSocket=null;
  2. Socket socket=null;
  3. InputStream in=null;
  4. OutputStream out=null;
  5. int port=5000;


(6) 开启服务器并接收客户端发送的数据,代码如下所示。

 
  1. ServerSocket=new ServerSocket(port); //创建服务器套接字
  2. System.out.println("服务器开启,等待连接。。。");
  3. socket=ServerSocket.accept(); //获得连接
  4. //接收客户端发送的内容
  5. in=socket.getInputStream();
  6. byte[] b=new byte[1024];
  7. int len=in.read(b);
  8. System.out.println("客户端发送的内容为:"+new String(b,0,len));


(7) 使用输出流对象将信息反馈给客户端,代码如下所示。

 
  1. out=socket.getOutputStream();
  2. out.write("我是服务器端".getBytes());


(8) 关闭输入/输出流、Socket 对象以及 ServerSocket 对象,代码如下所示。

 
  1. in.close();
  2. out.close();
  3. ServerSocket.close();
  4. socket.close();


(9) 运行服务器端程序代码,运行结果如下所示。

服务器开启,等待连接。。。


(10) 为了使程序的结果更加清晰,在步骤 (2) 的代码最后加入一句代码“Thread.sleep(1000);”。接着运行客户端程序代码,刚开始会出现如下所示的运行结果。

服务器开启,等待连接。。。
客户端发送的内容为:我是客户端数据


紧接着又会出现如下所示的运行结果。

客户端的反馈为:我是服务器端

客户端与服务器端的简单通信

在了解 TCP 通信中 ServerSocket 类和 Socket 类的简单应用之后,本节将编写一个案例实现客户端向服务器发送信息,服务器读取客户端发送的信息,并将读取的数据写入到数据流中。

首先来看一下客户端的代码,如下所示:

 
  1. public class SocketDemo
  2. {
  3. public static void main(String[] args)
  4. {
  5. Socket socket=null;
  6. PrintWriter out=null;
  7. BufferedReader in=null;
  8. String serverIP="127.0.0.1"; //服务器端ip地址
  9. int port=5000; //服务器端端口号
  10. try
  11. {
  12. socket=new Socket(serverIP,port);
  13. in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
  14. out=new PrintWriter(socket.getOutputStream(),true);
  15. while(true)
  16. {
  17. int number=(int)(Math.random()*10)+1;
  18. System.out.println("客户端正在发送的内容为:"+number);
  19. out.println(number);
  20. Thread.sleep(2000);
  21. }
  22. }
  23. catch(IOException | InterruptedException e)
  24. {
  25. // TODO 自动生成的 catch 块
  26. e.printStackTrace();
  27. }
  28. }
  29. }


如上述代码所示,客户端代码主要是使用 Socket 连接 IP 为 127.0.0.1(本机)的 5000 端口。在建立连接之后将随机生成的数字使用 PrintWriter 类输出到套接字。休眠 2 秒后,再次发送随机数,如此循环。

再来看一个服务器端的代码,如下所示:

 
  1. package ch16;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.InputStreamReader;
  6. import java.io.OutputStream;
  7. import java.net.ServerSocket;
  8. import java.net.Socket;
  9. public class SocketDemoServer1
  10. {
  11. public static void main(String[] args)
  12. {
  13. ServerSocket serverSocket=null;
  14. Socket clientSocket=null;
  15. BufferedReader in=null;
  16. int port=5000;
  17. String str=null;
  18. try
  19. {
  20. serverSocket=new ServerSocket(port); //创建服务器套接字
  21. System.out.println("服务器开启,等待连接。。。");
  22. clientSocket=serverSocket.accept();// 获得链接
  23. //接收客户端发送的内容
  24. in=new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
  25. while(true)
  26. {
  27. str=in.readLine();
  28. System.out.println("客户端发送的内容为:"+str);
  29. Thread.sleep(2000);
  30. }
  31. }
  32. catch(IOException | InterruptedException e)
  33. {
  34. // TODO 自动生成的 catch 块
  35. e.printStackTrace();
  36. }
  37. }
  38. }


如上述代码所示,服务器端与客户端代码类似,首先使用 ServerSocket 在 IP为127.0.0.1(本机)的 5000 端口建立套接字监听。在 accept() 方法接收到客户端的 Socket 实例之后调用 BufferedReader 类的 readLine() 方法,从套接字中读取一行作为数据,再将它输出到控制后休眠 2 秒。

要运行本案例,必须先执行服务器端程序,然后执行客户端程序。客户端每隔 2 秒向服务器发送一个数字,如下所示。

客户端正在发送的内容为:10
客户端正在发送的内容为:5
客户端正在发送的内容为:10
客户端正在发送的内容为:4
客户端正在发送的内容为:3


服务器端会将客户端发送的数据输出到控制台,如下所示。

 
  1. 服务器幵启,等待连接。。。
  2. 客户端发送的内容为:7
  3. 客户端发送的内容为:2
  4. 客户端发送的内容为:10
  5. 客户端发送的内容为:5
  6. 客户端发送的内容为:10

......

传输对象数据

经过前面的学习,掌握了如何在服务器开始一个端口监听套接字,以及如何在客户端连接服务器,发送简单的数字。本次案例将实现如何在客户端发送一个对象到服务器端,服务器如何解析对象中的数据。

例 3

第一步是创建用于保存数据的类。这里使用的 User 类是一个普通的类,包含 name 和 password 两个成员。由于需要序列化这个对象以便在网络上传输,所以需要实现 java. io.Serializable 接 P。

User 类的代码如下:

 
  1. package ch16;
  2. public class User implements java.io.Serializable
  3. {
  4. private String name;
  5. private String password;
  6. public User(String name,String password)
  7. {
  8. this.name=name;
  9. this.password=password;
  10. }
  11. public String getName()
  12. {
  13. return name;
  14. }
  15. public void setName(String name)
  16. {
  17. this.name=name;
  18. }
  19. public String getPassword()
  20. {
  21. return password;
  22. }
  23. public void setPassword(String password)
  24. {
  25. this.password=password;
  26. }
  27. }


接下来编写服务器端的代码。服务器的作用是接收客户端发送过来的数据,将数据转换成 User 对象并输出成员信息,然后对 User 对象进行修改再输出给客户端。

服务器端 MyServer 类的实现代码如下:

 
  1. package ch16;
  2. import java.io.BufferedInputStream;
  3. import java.io.IOException;
  4. import java.io.ObjectInputStream;
  5. import java.io.ObjectOutputStream;
  6. import java.net.ServerSocket;
  7. import java.net.Socket;
  8. public class MyServer
  9. {
  10. public static void main(String[] args) throws IOException
  11. {
  12. // 监听10000端口
  13. ServerSocket server=new ServerSocket(10000);
  14. while(true)
  15. {
  16. //接收客户端的连接
  17. Socket socket=server.accept();
  18. //调用客户端的数据处理方法
  19. invoke(socket);
  20. }
  21. }
  22. private static void invoke(final Socket socket) throws IOException
  23. {
  24. //开启一个新线程
  25. new Thread(new Runnable()
  26. {
  27. public void run()
  28. {
  29. //创建输入流对象
  30. ObjectInputStream is=null;
  31. //创建输出流对象
  32. ObjectOutputStream os=null;
  33. try
  34. {
  35. is=new ObjectInputStream(socket.getInputStream());
  36. os=new ObjectOutputStream(socket.getOutputStream());
  37. //读取一个对象
  38. Object obj = is.readObject();
  39. //将对象转换为User类型
  40. User user=(User) obj;
  41. //在服务器端输出name成员和password成员信息
  42. System.out.println("user: "+user.getName()+"/"+user.getPassword());
  43. //修改当前对象的name成员数据
  44. user.setName(user.getName()+"_new");
  45. //修改当前对象的password对象数据
  46. user.setPassword(user.getPassword()+"_new");
  47. //将修改后的对象输出给客户端
  48. os.writeObject(user);
  49. os.flush();
  50. }
  51. catch(IOException|ClassNotFoundException ex)
  52. {
  53. ex.printStackTrace();
  54. }
  55. finally
  56. {
  57. try
  58. {
  59. //关闭输入流
  60. is.close();
  61. //关闭输出流
  62. os.close();
  63. //关闭客户端
  64. socket.close();
  65. }
  66. catch(Exception ex){}
  67. }
  68. }
  69. }).start();
  70. }
  71. }


如上述代码所示,在服务器端分别使用 ObjectInputStream 和 ObjectOutputStream 来接收和发送 socket 中的 InputStream 和OutputStream,然后转换成 User 对象。

客户端需要连接服务器,接收服务器输出的数据并解析,同时需要创建 User 对象并发给服务器。客户端 MyClient 类的实现代码如下:

 
  1. package ch16;
  2. import java.io.IOException;
  3. import java.io.ObjectInputStream;
  4. import java.io.ObjectOutputStream;
  5. import java.net.Socket;
  6. public class MyClient
  7. {
  8. public static void main(String[] args) throws Exception
  9. {
  10. //循环100次
  11. for(int i=0;i<100;i++)
  12. {
  13. //创建客户端Socket
  14. Socket socket=null;
  15. //创建输入流
  16. ObjectOutputStream os=null;
  17. //创建输出流
  18. ObjectInputStream is=null
  19. try
  20. {
  21. //连接服务器
  22. socket=new Socket("localhost",10000);
  23. //接收输出流中的数据
  24. os=new ObjectOutputStream(socket.getOutputStream());
  25. //创建一个User对象
  26. User user=new User("user_"+i,"password_"+i);
  27. //将User对象写入输出流
  28. os.writeObject(user);
  29. os.flush();
  30. //接收输入流中的数据
  31. is=new ObjectInputStream(socket.getInputStream());
  32. //读取输入流中的数据
  33. Object obj=is.readObject();
  34. //如果数据不空则转换成User对象,然后输出成员信息
  35. if(obj!=null)
  36. {
  37. user=(User) obj;
  38. System.out.println("user: "+user.getName()+"/"+user.getPassword());
  39. }
  40. }
  41. catch(IOException ex)
  42. {
  43. ex.printStackTrace();
  44. }
  45. finally
  46. {
  47. try
  48. {
  49. //关闭输入流
  50. is.close();
  51. //关闭输出流
  52. os.close();
  53. //关闭客户端
  54. socket.close();
  55. }
  56. catch(Exception ex) {}
  57. }
  58. }
  59. }
  60. }


仔细观察上述代码可以发现,客户端与服务器端的代码类似,同样使用 ObjectOutputStream 和 ObjectInputStream 来处理数据。

先运行服务器端程序 MyServer,再运行客户端程序 MyClient。此时将在客户端看到下所示的输出。

user:user_86_nevj/password_86_new
user:user_87_new/password_87_new
user:user_88_new/password_88_new
user:user_89_new/password_89_new
user:user_90_new/password_90_new
user:user_91_new/password_91_new
user:user_92_new/password_92_new
user:user_93_new/password_93_new
user:user_94_new/password_94_new
user:user_95_new/password_95_new
user:user_96_new/password_96_new
user:user_97_new/password_97_new
user:user_98_new/password_98_new
user:user_99_new/password_99_new


服务器端的输出如下所示。

user:user_86/password_86
user:user_87/password_87
user:user_88/password_88
user:user_89/password_89
user:user_90/password_90
user:user_91/password_91
user:user_92/password_92
user:user_93/password_93
user:user_94/password_94
user:user_95/password_95
user:user_96/password_96
user:user_97/password_97
user:user_98/password_98
user:user_99/password_99
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值