今天简单来介绍一下Java的另一种TCP协议下的网络编程。作为开始后先介绍一下什么是Socket(可能讲的有些晚了,应该在上一篇UDP中就进行讲解)。
Socket也就是平时所说的“套接字”,用于描述IP地址和端口。应用程序可以通过使用Socket来向网络发出请求或者应答网络的请求。
TCP协议是一种面向连接的协议,也就是说需要在通信的两端形成一条通信的线路。它与打电话的情形相似。如果想要接通一个电话的话,首先要在通信双方建立一条通信的线路(可以物理可以虚拟),当这条线路连通后便可以进行说话了。TCP协议就是需要先形成这样的一条通信线路在进行数据的传输(会使用“三次握手”原则建立这条通路)。这种方式的好处是不会丢失数据(具体的检测原则可以自行查阅相关的资料),但是接通线路的延时较长。
下面介绍一下应用的几个重要的java.net包中的用于TCP传输的几个类。
Socket:实现了客户端的套接字(两台机器间通讯的端点)。常用的构造方法有:Socket(),如果用空参的构造方法需要使用Socket类中的connect(SocketAddress endpoint)来连接指定的服务端;Socket(InetAddress address, int port),这个构造方法需要传入指定的地址和端口号,用这个构造器(或者其他重载的功能相似的构造器)表示客户端一建立就需要连接服务端。这个类中既有输入流又有输出流。常用的方法有void shutdownOutput(), 关闭客户端的输出流,相当于给流加入一个结束标记“-1”.
ServerSocket:实现服务端的套接字,用来监听和接受客户端的套接字。常用的构造方法有:ServerSocket(int port),绑定到指定端口的服务端套接字。常用的方法有Socket accept(),监听并接收客户端套接字的连接。
说一下例子完成的功能,服务器端接收客服端发来的请求(字符串),在服务器端进行输出,并将该字符串转换成大写返回给客户端,并在客户端进行输出下面说一下编写程序的流程:
首先要了解Java中需要使用的两个和Socket有关的类是Socket和ServerSocket。ServerSocket用于服务器端用于监听端口,Socket用于建立网络连接。在连接成功的时候,应用程序的服务器端和客户端都会产生一个Socket实例,而应用程序只需要操作Socket实例来完成所需要的会话。
回到例子的需求,要在服务器端建立一个ServerSocket的对象用于监听端口,端口可以随意指定,但是不能使用已被占用的端口(如80端口等),如果监听到客户端的请求则和客户端连接,和客户端进行会话。完成会话后关闭连接。对于客户端那边,用Socket对服务器端的某一个端口发送连接请求,当服务器端接受请求后便进行会话,会话完成后关闭Socket即可。
这个博客稍后给出的例子只完成了服务器端处理一个客户端,处理多个客户端的情况会在介绍完多线程编程后在给出。
客户器端的代码如下:
package lzl.demo.socket.client;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class Client
{
public static int PORT = 8088;
public static void main(String[] args)
{
Socket socket = null;
BufferedReader cbr = null;
BufferedReader sbr = null;
BufferedWriter sbw = null;
String str = null;
try
{
socket = new Socket("localhost",PORT);
System.out.println("Socket = " + socket);
cbr = new BufferedReader(new InputStreamReader(System.in));
sbr = new BufferedReader(new InputStreamReader(socket.getInputStream()));
sbw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
System.out.println("Please input : ");
while(null != (str = cbr.readLine()))
{
if("quit".equalsIgnoreCase(str))
break;
//to write the message to the socket's output stream and add a new line
sbw.write(str);
sbw.newLine();
sbw.flush();
System.out.println("Server send back message: " + sbr.readLine());
}
}catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
System.out.println("Close ...");
//socket.shutdownOutput();
cbr.close();
sbr.close();
sbw.close();
socket.close();
}catch(Exception e2)
{
e2.printStackTrace();
}
}
}
}
服务器端的代码如下:
package lzl.demo.socket.server;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class Server
{
public static final int PORT = 8088;
public static void main(String[] args)
{
ServerSocket ss = null;
Socket socket = null;
BufferedReader br = null;
PrintWriter pw = null;
String str = null;
try
{
ss = new ServerSocket(PORT);
System.out.println("ServerSocket Start:" + ss);
socket = ss.accept();
System.out.println(socket + " connection accept");
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//instead of BufferedReader, because it can flush automatic
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
while(null != (str = br.readLine()))
{
System.out.println("the client say : " + str);
pw.println(str.toUpperCase());
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
System.out.println("Close ...");
try
{
br.close();
pw.close();
socket.close();
ss.close();
}catch(Exception e2)
{
e2.printStackTrace();
}
}
}
}
看完代码之后,再来一起分析一下。和net打交道肯定会涉及到io操作,所以首先需要选择流操作的类。分析方法可以参照Java学习—I/O概述篇。服务端和客户端分别运用两种对于写的流的操作对象,一个是BufferedWriter,一个是PrintWriter。PrintWriter相对于BufferedWriter来说是可以允许自动刷新的,即自动从当前的缓冲区中刷新到写入的对象中。回到客户端的代码,BufferedReader的readLine方法返回的是不包含在控制台输入结束后的那个回车的,但是判断循环的条件需要这个换行符,所以需要加上。而服务端调用pw.println时也是把这个换行符加上了。当客户端输入“quit”后,循环跳出,会关闭socket,当关闭socket后相当于在socket流中加入了结束标记“-1”,所以服务端也会跳出循环并关闭。
进一步思考一下,如果要实现从客服端想服务器端上传一个文件,当文件中的内容已经全部传送完毕时,这是服务器端的循环读是跳不出来的(因为没有文件结束的标志),所以这时候可以在客户端中加入socket.shutdownOutput()方法来往流中加入一个文件的技术符,即“-1”。当然,也可以加入另一个标记辅助判断是否文件结束,如用时间戳。
这篇关于应用TCP协议进行网络编程的博客内容就讲这多,关于网络编程这一块也就想介绍这么多,不过在后续的学习中可能还会涉及到这一块的程序,如实现多线程的时候可能还会实现这一块的东西,毕竟服务器端处理请求时需要能够同时处理多个。
参考资料:
1. JDK1.7 API:http://docs.oracle.com/javase/7/docs/api/
2. Java编程思想(第四版)
3. Java核心技术 卷1/卷2
4. 传智播客毕向东Java基础视频教程
说明:
如有错误还请各位指正,欢迎大家一起讨论给出指导。
上述程序完整代码的下载链接:
https://github.com/zeliliu/BlogPrograms/tree/master/Java%E5%AD%A6%E4%B9%A0/tcp_touppercase_demo
最后更新时间:2013-04-19