Java中的TCP/UDP网络通信编程

网络应用中基本上都是TCP(Transmission Control Protocol传输控制协议)和UDP(User Datagram Protocol用户数据报协议),TCP是面向连接的通信协议,UDP是无连接的通信协议.

127.0.0.1是回路地址,用于测试,相当于localhost本机地址,没有网卡,不设DNS都可以访问.

端口地址在0~65535之间,其中0~1023之间的端口是用于一些知名的网络服务和应用,用户的普通网络应用程序应该使用1024以上的端口.

Socket连接套接字,Java分别为TCP和UDP提供了相应的类,TCP是java.net.ServerSocket(用于服务器端)和java.net.Socket(用于客户端);UDP是java.net.DatagramSocket.

1,Java编写UDP网络程序

1.1,DatagramSocket

DatagramSocket有如下构造方法:
1,DatagramSocket() :构造数据报套接字并将其绑定到本地主机上任何可用的端口。
2,DatagramSocket(int port):创建数据报套接字并将其绑定到本地主机上的指定端口。
3,DatagramSocket(int port, InetAddress laddr):创建数据报套接字,将其绑定到指定的本地地址。即指定网卡发送和接收数据.

如果在创建DatagramSocket对象时,没有指定网卡的IP 地址,在发送数据时,底层驱动程序会自动选择一块网卡去发送,在接收数据时,会接收所有的网卡收到的与端口一致的数据.

发送信息时,可以不指定端口号,接收信息时,要指定端口号,因为要接收指定的数据.

发送数据使用DatagramSocket.send(DatagramPacket p)方法,接收数据使用DatagramSocket.receive(DatagramPacket p)方法.

1.2,DatagramPacket

DatagramPacket类有如下构造方法:
1,DatagramPacket(byte[] buf, int length):构造 DatagramPacket,用来接收长度为length的数据包。
2,DatagramPacket(byte[] buf, int length, InetAddress address, int port):构造数据报包,用来将长度为length的包发送到指定主机上的指定端口号。

接收数据时使用第一次构造方法,发送数据时使用第二种构造方法.

1.3,InetAddress

Java中对IP地址进行包装的类,

DatagramPacket.getAddress()可以获取发送或接收方的IP地址.DatagramPacket.getPort()可以获取发送或接收方的端口.

1.4,UDP程序例子

发送程序:

 

[java] view plaincopyprint?
  1. import java.net.DatagramPacket;  
  2. import java.net.DatagramSocket;  
  3. import java.net.InetAddress;  
  4.   
  5. public class UdpSend {  
  6.   
  7. public static void main(String[] args) throws Exception {  
  8. DatagramSocket ds = new DatagramSocket();  
  9. String str = "hello , world!";  
  10. DatagramPacket dp = new DatagramPacket(str.getBytes(),str.length(),InetAddress.getByName("192.168.0.105"),3000);  
  11. ds.send(dp);  
  12. ds.close(); //关闭连接   
  13. }  
  14. }  

接收程序:

 

[java] view plaincopyprint?
  1. import java.net.DatagramPacket;  
  2. import java.net.DatagramSocket;  
  3.   
  4. public class UdpRecv {  
  5. public static void main(String[] args) throws Exception {  
  6. DatagramSocket ds = new DatagramSocket(3000);  
  7. byte[] buf = new byte[1024];  
  8. DatagramPacket dp = new DatagramPacket(buf,buf.length);  
  9. ds.receive(dp);  
  10. String str = new String(dp.getData(),0,dp.getLength());  
  11. System.out.println(str);  
  12. System.out.println("IP:" + dp.getAddress().getHostAddress() + ",PORT:" + dp.getPort());  
  13. ds.close();  
  14. }  
  15. }  

测试要先运行接收程序,再运行发送程序.如果接收程序没有接收到数据,则会一直阻塞,接收到数据后才会关闭程序.如果网络上没有数据发送过来,接收程序也没有阻塞,通常都是使用了一个已经被占用的端口.

2,Java编写TCP网络程序

2.1,ServerSocket

编写TCP网络服务程序,首先要用到java.net.ServerSocket类用以创建服务器Socket.它的常用构造方法有:
1,ServerSocket(int port):创建绑定到特定端口的服务器套接字。
2,ServerSocket(int port, int backlog):利用指定的backlog(服务器忙时保持连接请求的等待客户数量),创建服务器套接字并将其绑定到指定的本地端口号。
3,ServerSocket(int port, int backlog, InetAddress bindAddr):使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。

2.2,Socket

客户端要与服务器建立连接,必须先创建一个Socket对象,它的常用构造方法有:
1,Socket(String host, int port):创建一个流套接字并将其连接到指定主机上的指定端口号。
2,Socket(InetAddress address, int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
3,Socket(InetAddress address, int port, InetAddress localAddr, int localPort):创建一个套接字并将其连接到指定远程端口上的指定远程地址。
4,Socket(String host, int port, InetAddress localAddr, int localPort):创建一个套接字并将其连接到指定远程主机上的指定远程端口。

对于通常情况的应用,使用第1个构造方法来创建客户端的Socket对象,并与服务器建立连接,是非常简单和方便的.

服务器端程序调用ServerSocket.accept方法等待客户端的连接请求,一旦accept接收了客户端连接请求,该方法返回一个与该客户端建立了专线连接的Socket对象,不用程序去创建这个Socket对象.建立了连接的两个Socket是以IO流的方式进行数据交换的,Java提供了Socket.getInputStream返回Socket的输入流对象,Socket.getOutputStream返回Socket的输出流对象.

2.3,TCP程序例子的服务器程序:

[java] view plaincopyprint?
  1. import java.io.InputStream;  
  2. import java.io.OutputStream;  
  3. import java.net.ServerSocket;  
  4. import java.net.Socket;  
  5.   
  6. public class TcpServer {  
  7. public static void main(String[] args) throws Exception {  
  8. ServerSocket ss = new ServerSocket(8000);  
  9. Socket s = ss.accept();  
  10. InputStream ips = s.getInputStream();  
  11. OutputStream ops = s.getOutputStream();  
  12. ops.write("hello,World!".getBytes());  
  13. byte[] buf = new byte[1024];  
  14. int len = ips.read(buf);  
  15. System.out.println(new String(buf,0,len));  
  16. ips.close();  
  17. ops.close();  
  18. s.close();  
  19. ss.close();  
  20. }  
  21. }  

在这个程序里,创建了一个在8000端口上等待连接的ServerSocket对象,当接收到一个客户的连接请求后,程序从与这个客户建立了连接的Socket对象中获得输入输出流对象,通过输出流首先向客户端发送一串字符,然后通过输入流读取客户端发送过来的信息,并将这些信息打印,然后关闭所有资源.要先运行服务器程序,然后才能运行客户端程序,当TCP服务器程序运行到Socket.accpet()方法等待客户连接时,accept方法将阻塞,一直到有客户连接请求到来,该方法才会返回,如果又没有请求到来,又没有发生阻塞,通常都是使用了一个已经被占用的端口.
我们可以使用windows提供的telnet工具在命令行窗口中测试一下服务器程序:命令如下:telnet localhost 8000
可以看到,telnet只要有输入就发送,因此我们如果想要在服务器端一次读多个字符的话,还需要进一步处理,看如下代码:

[java] view plaincopyprint?
  1. import java.io.BufferedReader;  
  2. import java.io.InputStream;  
  3. import java.io.InputStreamReader;  
  4. import java.io.OutputStream;  
  5. import java.net.ServerSocket;  
  6. import java.net.Socket;  
  7.   
  8. public class TcpServer {  
  9. public static void main(String[] args) throws Exception {  
  10. ServerSocket ss = new ServerSocket(8000);  
  11. Socket s = ss.accept();  
  12. InputStream ips = s.getInputStream();  
  13. BufferedReader br = new BufferedReader(new InputStreamReader(ips)); //对InputStream进行包装,增加了缓存   
  14. OutputStream ops = s.getOutputStream();  
  15. ops.write("hello,World!".getBytes());  
  16. System.out.println(br.readLine());  
  17. br.close(); //www.cdtarena.com关闭包装类,会自动关闭里面的基类   
  18. ops.close();  
  19. s.close();  
  20. ss.close();  
  21. }  
  22. }  

再次使用telnet工具可以看到,这次可以发送不止一个字符了,按回车键后发送数据到服务器端.

2.4,TCP程序例子改进后的服务器程序:

大多数情况下,服务器端都要服务多个客户端,但一次accept方法调用只接收一个连接,因此,要把accept方法放在一个循环语句中,这样就可以接收多个连接.每个连接的数据交换代码也放在一个循环中,这样才能保证两者可以不停地交换数据.

每个连接的数据交换代码必须放在独立的线程中运行,否则,这在段代码运行期间,就没法执行其他的程序代码,accept方法也得不到调用,新的连接无法进入.

下面是一个例子,客户端向服务器发送一个字符串,服务器将这个字符串中的所有字符反向排列后回送给客户端.客户端输入"quit",退出程序.

[java] view plaincopyprint?
  1. import java.io.BufferedReader;  
  2. import java.io.DataOutputStream;  
  3. import java.io.InputStream;  
  4. import java.io.InputStreamReader;  
  5. import java.io.OutputStream;  
  6. import java.net.ServerSocket;  
  7. import java.net.Socket;  
  8.   
  9. public class TcpServer {  
  10. public static void main(String[] args) throws Exception {  
  11. ServerSocket ss = new ServerSocket(8000);  
  12. while(true){  
  13. Socket s = ss.accept();  
  14. new Thread(new Servicer(s)).start();  
  15. }  
  16. }  
  17. }  
  18.   
  19. class Servicer implements Runnable{  
  20. Socket s;  
  21. public Servicer(Socket s){  
  22. this.s = s;  
  23. }  
  24. public void run(){  
  25. try{  
  26. InputStream ips = s.getInputStream();  
  27. OutputStream ops = s.getOutputStream();  
  28.   
  29. BufferedReader br = new BufferedReader(new InputStreamReader(ips));  
  30. DataOutputStream dos = new DataOutputStream(ops);  
  31. while(true){  
  32. String strWord = br.readLine();  
  33. if(strWord.equalsIgnoreCase("quit")){  
  34. break;  
  35. }  
  36. String strEcho = (new StringBuffer(strWord).reverse().toString());  
  37. dos.writeBytes(strWord + "------->" + strEcho + System.getProperty("line.separator"));  
  38. }  
  39. br.close();  
  40. dos.close();  
  41. s.close();  
  42. }catch(Exception e){  
  43. e.printStackTrace();  
  44. }  
  45. }  
  46. }  

2.5,TCP程序例子客户端程序:

[java] view plaincopyprint?
  1. import java.io.BufferedReader;  
  2. import java.io.DataOutputStream;  
  3. import java.io.InputStream;  
  4. import java.io.InputStreamReader;  
  5. import java.io.OutputStream;  
  6. import java.net.InetAddress;  
  7. import java.net.Socket;  
  8.   
  9. public class TcpClient {  
  10. public static void main(String[] args) throws Exception{  
  11. if(args.length < 2){  
  12. System.out.println("Usage:java TcpClient ServerIP ServerPort");  
  13. return ;  
  14. }  
  15. Socket s = new Socket(InetAddress.getByName(args[0]),Integer.parseInt(args[1]));  
  16. InputStream ips = s.getInputStream();  
  17. OutputStream ops = s.getOutputStream();  
  18. BufferedReader brKey = new BufferedReader(new InputStreamReader(System.in));  
  19. DataOutputStream dos = new DataOutputStream(ops);  
  20. BufferedReader brNet = new BufferedReader(new InputStreamReader(ips));  
  21.   
  22. while(true){  
  23. String strWord = brKey.readLine();  
  24. dos.writeBytes(strWord + System.getProperty("line.separator"));  
  25. if("quit".equalsIgnoreCase(strWord)){  
  26. break;  
  27. }else{  
  28. System.out.println(brNet.readLine());  
  29. }  
  30. }  
  31. dos.close();  
  32. brNet.close();  
  33. brKey.close();  
  34. s.close();  
  35. }  
  36. }  

先运行服务器程序,再在命令行使用java TcpClient 192.168.0.3 8000,这样就启动了客户端程序.我们可以启动多个客户端程序.

我们可以利用netstat工具来查看已经被使用的端口

posted @ 2013-04-10 14:21  代码单点  阅读(1676)  评论(0编辑  收藏  举报