三步学会Java Socket编程

引用:http://blog.csdn.net/shanliangliuxing/article/details/6894092

508人阅读 评论(0) 收藏 举报

[html] view plain copy
  1.  这两天没事看《TCP/IP协议详解-卷1》,把那些TFTP,FTP,TELNET,RPC/NFS都看了一下。  
  2.   
  3. 终于又长见识了,一句话总结: 一个<span style="color:#ff0000;">TCP连接</span>是由4元组:  
  4. [remote IP,remote port,local IP,local port] 唯一决定的。  
  5.   
  6. 人人皆知的做法:  
  7.   
  8. 服务器:bind在固定端口PORT等待连接。  
  9. 客户端:connect服务器的固定端口PORT。  
  10.   
  11. 结果:客户端内核自动分配local port,服务端accept得到连接SOCKET,并且这些SOCKET的端口都是PORT。  
  12. 长久以来的疑惑:尼玛所有的客户端都往服务端的PORT上发包,凭什么就知道送给哪个SOCKET呢?  
  13.   
  14. 解惑:一个<span style="color:#ff0000;">TCP连接</span>是由4元组:  
  15. [remote IP,remote port,local IP,local port] 唯一决定的。  
  16.   
  17. 服务端IP/PORT都一样,但客户端使用的PORT各不相同,所以服务端各SOCKET对应唯一的TCP连接。  
  18.   
  19. 这个概念是在看FTP得时候学到的:  
  20.   
  21. FTP服务器提供端口20做控制命令,FTP客户端创建SOCKET BIND(0)在临时端口PORT做数据通道等待连接,并将IP/PORT发往服务器20端口,服务器创建一个SOCKET,bind在21端口向客户端PORT发起主动连接。  
  22.   
  23. 当大量客户端请求数据时,服务器创建无数个SOCKET,都bind在21端口,然后向不同的客户端发起主动连接,通过SO_REUSEADDR选项可以实现。    
  24.   
  25. 这是不是很郁闷,一台服务器上bind一堆20端口,然后向外connect不同的客户端(和TCP accept类似)。  
  26.   
  27. 这是因为虽然服务器的一堆SOCKET都在20端口,但remote ip/port是不同的,所以4元组不同就是不同的链接。  
  28.   
  29. 讲解完毕,如果对remote ip/port 和 local ip/port 还没深刻理解的盆友需要先去理解一下再来理解这个。  
  30.   
  31. (UDP无连接,如果你让N个UDP SOCKET bind在同一个PORT,那外边来的包注定不知道给哪个SOCKET,这就是有连接和无连接的区别。 有连接是4元组,无连接是2元组)  
[html] view plain copy
  1. (一个TCP套接字是由一个四元组(源IP地址,源端口号,目的IP地址,目的端口号)来标识的。与UDP不同的是,即使两个TCP报文段的目的IP地址和目的端口号相同,只要他们的源IP地址和源端口号不同的话,他们就将被定向到不同的套接字。一个UDP套接字是由一个包含目的ip地址和目的端口号的二元组来标识的。如果两个UDP报文有不同的源IP地址或者源端口号,只要他们具有相同的目的IP地址和目的端口号,就会被定向到同一个套接字,数据被交给同一进程。)  

首先看一个用socket实现的多线程实例,可以实现服务器端和客户端的交互: 

服务器端代码:

[java] view plain copy
  1. import java.net.ServerSocket;  
  2.   
  3. /** 
  4.  * 服务器端Socket 
  5.  *   
  6.  * @author Administrator 
  7.  *   
  8.  */  
  9. public class SocketServer {  
  10.   
  11.  /** 
  12.   * 服务器端Socket构造方法 
  13.   */  
  14.  public SocketServer() {  
  15.   try {  
  16.   
  17.   int clientcount = 0// 统计客户端总数  
  18.   
  19.   boolean listening = true// 是否对客户端进行监听  
  20.   
  21.   ServerSocket server = null// 服务器端Socket对象  
  22.   
  23.   try {  
  24.   // 创建一个ServerSocket在端口2121监听客户请求  
  25.   server = new ServerSocket(2121);  
  26.   
  27.   System.out.println("Server starts...");  
  28.   } catch (Exception e) {  
  29.   System.out.println("Can not listen to. " + e);  
  30.   }  
  31.   
  32.   while (listening) {  
  33.   // 客户端计数  
  34.   clientcount++;  
  35.   
  36.   // 监听到客户请求,根据得到的Socket对象和客户计数创建服务线程,并启动之  
  37.   new ServerThread(server.accept(), clientcount).start();  
  38.   }  
  39.   } catch (Exception e) {  
  40.   System.out.println("Error. " + e);  
  41.   }  
  42.  }  
  43.   
  44.  /** 
  45.   * 主方法 
  46.   *   
  47.   * @param args 
  48.   */  
  49.  public static void main(String[] args) {  
  50.   new SocketServer();  
  51.  }  
  52. }  


服务器端实现的多线程类:

[java] view plain copy
  1. import java.io.BufferedReader;  
  2. import java.io.InputStreamReader;  
  3. import java.io.PrintWriter;  
  4. import java.net.Socket;  
  5.   
  6. public class ServerThread extends Thread {  
  7.   
  8.  private static int number = 0// 保存本进程的客户计数  
  9.   
  10.  Socket socket = null// 保存与本线程相关的Socket对象  
  11.   
  12.  public ServerThread(Socket socket, int clientnum) {  
  13.   
  14.   this.socket = socket;  
  15.   number = clientnum;  
  16.   System.out.println("当前在线的用户数: " + number);  
  17.  }  
  18.   
  19.  public void run() {  
  20.   try {  
  21.   
  22.   // 由Socket对象得到输入流,并构造相应的BufferedReader对象  
  23.   BufferedReader in = new BufferedReader(new InputStreamReader(socket  
  24.   .getInputStream()));  
  25.   
  26.   // 由Socket对象得到输出流,并构造PrintWriter对象  
  27.   PrintWriter out = new PrintWriter(socket.getOutputStream());  
  28.   
  29.   // 由系统标准输入设备构造BufferedReader对象  
  30.   BufferedReader sysin = new BufferedReader(new InputStreamReader(  
  31.   System.in));  
  32.   
  33.   // 在标准输出上打印从客户端读入的字符串  
  34.   System.out.println("[Client " + number + "]: " + in.readLine());  
  35.   
  36.   String line; // 保存一行内容  
  37.   
  38.   // 从标准输入读入一字符串  
  39.   line = sysin.readLine();  
  40.   
  41.   while (!line.equals("bye")) { // 如果该字符串为 "bye",则停止循环  
  42.   
  43.   // 向客户端输出该字符串  
  44.   out.println(line);  
  45.   
  46.   // 刷新输出流,使Client马上收到该字符串  
  47.   out.flush();  
  48.   
  49.   // 在系统标准输出上打印读入的字符串  
  50.   System.out.println("[Server]: " + line);  
  51.   
  52.   // 从Client读入一字符串,并打印到标准输出上  
  53.   System.out.println("[Client " + number + "]: " + in.readLine());  
  54.   
  55.   // 从系统标准输入读入一字符串  
  56.   line = sysin.readLine();  
  57.   }  
  58.   
  59.   out.close(); // 关闭Socket输出流  
  60.   in.close(); // 关闭Socket输入流  
  61.   socket.close(); // 关闭Socket  
  62.   } catch (Exception e) {  
  63.   System.out.println("Error. " + e);  
  64.   }  
  65.  }  
  66.   
  67. }  


客户端代码:

[java] view plain copy
  1. import java.io.BufferedReader;  
  2. import java.io.InputStreamReader;  
  3. import java.io.PrintWriter;  
  4. import java.net.Socket;  
  5.   
  6. /** 
  7.  * 客户端Socket 
  8.  *   
  9.  * @author Administrator 
  10.  *   
  11.  */  
  12. public class SocketClient {  
  13.   
  14.  /** 
  15.   * 客户端Socket构造方法 
  16.   */  
  17.  public SocketClient() {  
  18.   try {  
  19.   
  20.   // 向本机的2121端口发出客户请求  
  21.   Socket socket = new Socket("localhost"2121);  
  22.   
  23.   System.out.println("Established a connection...");  
  24.   
  25.   // 由系统标准输入设备构造BufferedReader对象  
  26.   BufferedReader sysin = new BufferedReader(new InputStreamReader(  
  27.   System.in));  
  28.   
  29.   // 由Socket对象得到输出流,并构造PrintWriter对象  
  30.   PrintWriter out = new PrintWriter(socket.getOutputStream());  
  31.   
  32.   // 由Socket对象得到输入流,并构造相应的BufferedReader对象  
  33.   BufferedReader in = new BufferedReader(new InputStreamReader(socket  
  34.   .getInputStream()));  
  35.   
  36.   String line; // 保存一行内容  
  37.   
  38.   // 从系统标准输入读入一字符串  
  39.   line = sysin.readLine();  
  40.   
  41.   while (!line.equals("bye")) { // 若从标准输入读入的字符串为 "bye"则停止循环  
  42.   
  43.   // 将从系统标准输入读入的字符串输出到Server  
  44.   out.println(line);  
  45.   
  46.   // 刷新输出流,使Server马上收到该字符串  
  47.   out.flush();  
  48.   
  49.   // 在系统标准输出上打印读入的字符串  
  50.   System.out.println("[Client]: " + line);  
  51.   
  52.   // 从Server读入一字符串,并打印到标准输出上  
  53.   System.out.println("[Server]: " + in.readLine());  
  54.   
  55.   // 从系统标准输入读入一字符串  
  56.   line = sysin.readLine();  
  57.   
  58.   }  
  59.   
  60.   out.close(); // 关闭Socket输出流  
  61.   in.close(); // 关闭Socket输入流  
  62.   socket.close(); // 关闭Socket  
  63.   } catch (Exception e) {  
  64.   System.out.println("Error. " + e);  
  65.   }  
  66.  }  
  67.   
  68.  /** 
  69.   * 主方法 
  70.   *   
  71.   * @param args 
  72.   */  
  73.  public static void main(String[] args) {  
  74.   new SocketClient();  
  75.  }  
  76.   
  77. }  


运行结果:

三步学会Java Socket编程_第1张图片

 

 

 

第一步 充分理解Socket

   1.什么是socket

   所谓socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过"套接字"向网络发出请求或者应答网络请求。

   以J2SDK-1.3为例,Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。

   重要的Socket API:

   java.net.Socket继承于java.lang.Object,有八个构造器,其方法并不多,下面介绍使用最频繁的三个方法,其它方法大家可以见JDK-1.3文档。

   . Accept方法用于产生"阻塞",直到接受到一个连接,并且返回一个客户端的Socket对象实例。"阻塞"是一个术语,它使程序运行暂时"停留"在这个地方,直到一个会话产生,然后程序继续;通常"阻塞"是由循环产生的。

   . getInputStream方法获得网络连接输入,同时返回一个IutputStream对象实例,。

   . getOutputStream方法连接的另一端将得到输入,同时返回一个OutputStream对象实例。

   注意:其中getInputStream和getOutputStream方法均会产生一个IOException,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。

   2.如何开发一个Server-Client模型的程序

   开发原理:

   服务器,使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。

   客户端,使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。

   {建立服务器}

[java] view plain copy
  1. import java.net.*;   
  2. import java.io.*;   
  3.   
  4. public class Server   
  5. {   
  6. private ServerSocket ss;   
  7. private Socket socket;   
  8. private BufferedReader in;   
  9. private PrintWriter out;   
  10.   
  11. public Server()   
  12. {   
  13. try   
  14. {   
  15. ss = new ServerSocket(10000);   
  16.   
  17. while (true)   
  18. {   
  19. socket = ss.accept();   
  20. in = new BufferedReader(new InputStreamReader(socket.getInputStream()));   
  21. out = new PrintWriter(socket.getOutputStream(),true);   
  22.   
  23. String line = in.readLine();   
  24. out.println("you input is :" + line);   
[java] view plain copy
  1. System.out.println("you input is : " + line);  
  2. out.close();   
  3. in.close();   
  4. socket.close();   
  5. }   
  6. ss.close();   
  7. }   
  8. catch (IOException e)   
  9. {}   
  10. }   
  11.   
  12. public static void main(String[] args)   
  13. {   
  14. new Server();   
  15. }   
  16. }   


这个程序建立了一个服务器,它一直监听10000端口,等待用户连接。在建立连接后给客户端返回一段信息,然后结束会话。这个程序一次只能接受一个客户连接。

{建立客户端}

[java] view plain copy
  1. import java.io.*;   
  2. import java.net.*;   
  3.   
  4. public class Client   
  5. {   
  6. Socket socket;   
  7. BufferedReader in;   
  8. PrintWriter out;   
  9.   
  10. public Client()   
  11. {   
  12. try   
  13. {   
  14. socket = new Socket("xxx.xxx.xxx.xxx"10000);   
  15. in = new BufferedReader(new InputStreamReader(socket.getInputStream()));   
  16. out = new PrintWriter(socket.getOutputStream(),true);   
  17. BufferedReader line = new BufferedReader(new InputStreamReader(System.in));   
  18.   
  19. out.println(line.readLine());   
  20. line.close();   
  21. out.close();   
  22. in.close();   
  23. socket.close();   
  24. }   
  25. catch (IOException e)   
  26. {}   
  27. }   
  28.   
  29. public static void main(String[] args)   
  30. {   
  31. new Client();   
  32. }   
  33. }   


这个客户端连接到地址为xxx.xxx.xxx.xxx的服务器,端口为10000,并从键盘输入一行信息,发送到服务器,然后接受服务器的返回信息,最后结束会话。

第二步 多个客户同时连接

   在实际的网络环境里,同一时间只对一个用户服务是不可行的。一个优秀的网络服务程序除了能处理用户的输入信息,还必须能够同时响应多个客户端的连接请求。在java中,实现以上功能特点是非常容易的。

   设计原理:

   主程序监听一端口,等待客户接入;同时构造一个线程类,准备接管会话。当一个Socket会话产生后,将这个会话交给线程处理,然后主程序继续监听。运用Thread类或Runnable接口来实现是不错的办法。

   {实现消息共享}

[java] view plain copy
  1. import java.io.*;   
  2. import java.net.*;   
  3.   
  4. public class Server extends ServerSocket   
  5. {   
  6. private static final int SERVER_PORT = 10000;   
  7.   
  8. public Server() throws IOException   
  9. {   
  10. super(SERVER_PORT);   
  11.   
  12. try   
  13. {   
  14. while (true)   
  15. {   
  16. Socket socket = accept();   
  17. new CreateServerThread(socket);   
  18. }   
  19. }   
  20. catch (IOException e)   
  21. {}   
  22. finally   
  23. {   
  24. close();   
  25. }   
  26. }   
  27. //--- CreateServerThread   
  28. class CreateServerThread extends Thread   
  29. {   
  30. private Socket client;   
  31. private BufferedReader in;   
  32. private PrintWriter out;   
  33.   
  34. public CreateServerThread(Socket s) throws IOException   
  35. {   
  36. client = s;   
  37.   
  38. in = new BufferedReader(new InputStreamReader(client.getInputStream(), "GB2312"));   
  39. out = new PrintWriter(client.getOutputStream(), true);   
  40. out.println("--- Welcome ---");   
  41. start();   
  42. }   
  43.   
  44. public void run()   
  45. {   
  46. try   
  47. {   
  48. String line = in.readLine();   
  49.   
  50. while (!line.equals("bye"))   
  51. {   
  52. String msg = createMessage(line);   
  53. out.println(msg);   
  54. line = in.readLine();   
  55. }   
  56. out.println("--- See you, bye! ---");   
  57. client.close();   
  58. }   
  59. catch (IOException e)   
  60. {}   
  61. }   
  62.   
  63. private String createMessage(String line)   
  64. {   
  65. xxxxxxxxx;   
  66. }   
  67. }   
  68.   
  69. public static void main(String[] args) throws IOException   
  70. {   
  71. new Server();   
  72. }   
  73. }   


这个程序监听10000端口,并将接入交给CreateServerThread线程运行。CreateServerThread线程接受输入,并将输入回应客户,直到客户输入"bye",线程结束。我们可以在createMessage方法中,对输入进行处理,并产生结果,然后把结果返回给客户。 第三步 实现信息共享:在Socket上的实时交流

   网络的伟大之一也是信息共享,Server可以主动向所有Client广播消息,同时Client也可以向其它Client发布消息。下面看看如何开发一个可以实时传递消息的程序。

设计原理:

   服务器端接受客户端的连接请求,同时启动一个线程处理这个连接,线程不停的读取客户端输入,然后把输入加入队列中,等候处理。在线程启动的同时将线程加入队列中,以便在需要的时候定位和取出。

   {源码}

[java] view plain copy
  1. import java.io.*;   
  2. import java.net.*;   
  3. import java.util.*;   
  4. import java.lang.*;   
  5.   
  6. public class Server extends ServerSocket   
  7. {   
  8. private static ArrayList User_List = new ArrayList();   
  9. private static ArrayList Threader = new ArrayList();   
  10. private static LinkedList Message_Array = new LinkedList();   
  11. private static int Thread_Counter = 0;   
  12. private static boolean isClear = true;   
  13. protected static final int SERVER_PORT = 10000;   
  14. protected FileOutputStream LOG_FILE = new FileOutputStream("d:/connect.log"true);   
  15.   
  16. public Server() throws FileNotFoundException, IOException   
  17. {   
  18. super(SERVER_PORT);   
  19. new Broadcast();   
  20.   
  21. //append connection log   
  22. Calendar now = Calendar.getInstance();   
  23. String str = "[" + now.getTime().toString() + "] Accepted a connection\015\012";   
  24. byte[] tmp = str.getBytes();   
  25. LOG_FILE.write(tmp);   
  26.   
  27. try   
  28. {   
  29. while (true)   
  30. {   
  31. Socket socket = accept();   
  32. new CreateServerThread(socket);   
  33. }   
  34. }   
  35. finally   
  36. {   
  37. close();   
  38. }   
  39. }   
  40.   
  41. public static void main(String[] args) throws IOException   
  42. {   
  43. new Server();   
  44. }   
  45.   
  46. //--- Broadcast   
  47. class Broadcast extends Thread   
  48. {   
  49. public Broadcast()   
  50. {   
  51. start();   
  52. }   
  53.   
  54. public void run()   
  55. {   
  56. while (true)   
  57. {   
  58. if (!isClear)   
  59. {   
  60. String tmp = (String)Message_Array.getFirst();   
  61.   
  62. for (int i = 0; i < Threader.size(); i++)   
  63. {   
  64. CreateServerThread client = (CreateServerThread)Threader.get(i);   
  65. client.sendMessage(tmp);   
  66. }   
  67.   
  68. Message_Array.removeFirst();   
  69. isClear = Message_Array.size() > 0 ? false : true;   
  70. }   
  71. }   
  72. }   
  73. }   
  74.   
  75. //--- CreateServerThread   
  76. class CreateServerThread extends Thread   
  77. {   
  78. private Socket client;   
  79. private BufferedReader in;   
  80. private PrintWriter out;   
  81. private String Username;   
  82.   
  83. public CreateServerThread(Socket s) throws IOException   
  84. {   
  85. client = s;   
  86. in = new BufferedReader(new InputStreamReader(client.getInputStream()));   
  87. out = new PrintWriter(client.getOutputStream(), true);   
  88. out.println("--- Welcome to this chatroom ---");   
  89. out.println("Input your nickname:");   
  90. start();   
  91. }   
  92.   
  93. public void sendMessage(String msg)   
  94. {   
  95. out.println(msg);   
  96. }   
  97.   
  98. public void run()   
  99. {   
  100. try   
  101. {   
  102. int flag = 0;   
  103. Thread_Counter++;   
  104. String line = in.readLine();   
  105.   
  106. while (!line.equals("bye"))   
  107. {   
  108. if (line.equals("l"))   
  109. {   
  110. out.println(listOnlineUsers());   
  111. line = in.readLine();   
  112. continue;   
  113. }   
  114.   
  115. if (flag++ == 0)   
  116. {   
  117. Username = line;   
  118. User_List.add(Username);   
  119. out.println(listOnlineUsers());   
  120. Threader.add(this);   
  121. pushMessage("[< " + Username + " come on in >]");   
  122. }   
  123. else   
  124. {   
  125. pushMessage("<" + Username + ">" + line);   
  126. }   
  127.   
  128. line = in.readLine();   
  129. }   
  130.   
  131. out.println("--- See you, bye! ---");   
  132. client.close();   
  133. }   
  134. catch (IOException e)   
  135. {}   
  136. finally   
  137. {   
  138. try   
  139. {   
  140. client.close();   
  141. }   
  142. catch (IOException e)   
  143. {}   
  144.   
  145. Thread_Counter--;   
  146. Threader.remove(this);   
  147. User_List.remove(Username);   
  148. pushMessage("[< " + Username + " left>]");   
  149. }   
  150. }   
  151.   
  152.   
  153. private String listOnlineUsers()   
  154. {   
  155. String s ="-+- Online list -+-\015\012";   
  156.   
  157. for (int i = 0; i < User_List.size(); i++)   
  158. {   
  159. s += "[" + User_List.get(i) + "]\015\012";   
  160. }   
  161.   
  162. s += "-+---------------------+-";   
  163. return s;   
  164. }   
  165.   
  166. private void pushMessage(String msg)   
  167. {   
  168. Message_Array.addLast(msg);   
  169. isClear = false;   
  170. }   
  171. }   
  172. }   

三步学会Java Socket编程_第2张图片

这就是程序运行后,多用户登陆并且输入信息后的屏幕。实现了信息的实时广播。用户输入"l"就可以列出在线人员表。


你可能感兴趣的:(三步学会Java Socket编程)