一、ServerSocket类和Socket类
ServerSocket类主要用在服务端程序的开发上,用于接受客户端的来凝结请求。
在服务器端每次运行时都要使用accept()方法等待客户端连接,此方法执行之后服务器端将进入阻塞状态,直到客户端连接之后程序才可以向下继续执行。此方法的返回值类型是Socket,每一个Socket都表示一个客户端对象。
ServerSocket常用的方法
//创建ServerSocket实例,并指定监听端口(构造方法)
public ServerSocket(int port) throws IOException {
this(port, 50, null);
}
//等待客户端连接,此方法连接之前一直阻塞
public Socket accept() throws IOException {
if (isClosed())
throw new SocketException("Socket is closed");
if (!isBound())
throw new SocketException("Socket is not bound yet");
Socket s = new Socket((SocketImpl) null);
implAccept(s);
return s;
}
//返回服务器的IP地址
public InetAddress getInetAddress() {
if (!isBound())
return null;
try {
InetAddress in = getImpl().getInetAddress();
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkConnect(in.getHostAddress(), -1);
return in;
} catch (SecurityException e) {
return InetAddress.getLoopbackAddress();
} catch (SocketException e) {
// nothing
// If we're bound, the impl has been created
// so we shouldn't get here
}
return null;
}
//返回ServerSocket的关闭状态
public boolean isClosed() {
synchronized(closeLock) {
return closed;
}
}
//关闭ServerSocket
public void close() throws IOException {
synchronized(closeLock) {
if (isClosed())
return;
if (created)
impl.close();
closed = true;
}
}
在客户端,程序可以通过Socket类的getInputStream()方法取得服务器的输出信息,在服务器端可以通过getOutputStream()方法取得客户端的输出信息。
网络程序中要使用输入及输出流的形式完成信息的传递,多以在开发时需要导入java.io包。
//接口实现
public
class Socket implements java.io.Closeable
Socket类的常用方法:
//构造Socket对象,捅死指定要连接服务器的主机名称及连接端口
public Socket(String host, int port)
throws UnknownHostException, IOException
{
this(host != null ? new InetSocketAddress(host, port) :
new InetSocketAddress(InetAddress.getByName(null), port),
(SocketAddress) null, true);
}
//返回此套接字的输入流
public InputStream getInputStream() throws IOException{...}
//返回此套接字的输出流
public OutputStream getOutputStream() throws IOException{...}
//关闭此Socket
public void close() throws IOException
//判断此套接字是否被关闭
public boolean isClosed()
二、第一个TCP程序
下面通过ServerSocket类及Socket类完成一个服务器的程序开发,此服务器向客户端输出"Hello world"的字符串信息
服务器端:
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 网络编程----TCP程序---服务器端
*
* @author Mona
*
*/
public class HelloServer {
public static void main(String[] args) throws Exception {
ServerSocket server = null;
Socket client = null;
PrintStream out = null;
server = new ServerSocket(8888);
System.out.println("服务器运行,等待客户端连接...");
client = server.accept();// 一个Socket对象表示一个客户端
String str = "Hello world";
out = new PrintStream(client.getOutputStream());// 实例化打印流对象,以向客户端输出
out.println(str);
out.close();// 关闭打印流
client.close();// 关闭客户端连接
server.close();// 关闭服务器连接
}
}
运行结果:
从程序的运行结果中可以发现,服务器程序一执行到accept()方法后,程序进入到阻塞状态,此阻塞状态会在客户端连接之后改变。
客户端:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* 网络编程----TCP程序--客户端
*
* @author Mona
*
*/
public class HelloClient {
public static void main(String[] args) throws Exception{
Socket client = null; //声明Socket对象
client = new Socket("localhost", 8888); //指定连接的主机和端口
BufferedReader buf = null; //声明BufferedReader对象,接收信息
buf = new BufferedReader(
new InputStreamReader(client.getInputStream())); //取得客户端的输入流
String str = buf.readLine(); //读取信息
System.out.println("服务器输出内容为:" + str);
client.close(); //关闭Socket
buf.close(); //关闭输入流
}
}
运行结果:
此时客户端从服务器端将信息读取进来,读取完毕后,因为服务器端此时只能处理一次连接请求,所以也将关闭。
三、案例:Echo 程序
Echo程序是一个网络编程通信交互的一个经典案例,称为回应程序,即客户瑞输人哪些内容,服务器端会在这些内容前加上“ECHO:”并将信息发回给客户端,下面就完成这样的一个程序。
之前的程序代码,服务器端每次执行完毕后服务器都会退出,这是因为服务器端只能接收一个客户端的连接,主要是由于accept()方法只能使用一次。本程序中将通过循环的方式使用acept这样,每一个客户端执行完毕后,服务器端都可以重新等待用户连接。
服务器端:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* ECHO-----服务器端
*
* @author Mona
*
*/
public class EchoServer {
public static void main(String[] args) throws Exception{
ServerSocket server = null;
Socket client = null;
PrintStream out = null;
BufferedReader buf = null;
server = new ServerSocket(8888);
boolean f = true; //定义一个标记位
while(f) { //无限制接受客户端连接
System.out.println("服务器运行,等待客户端连接...");
client = server.accept(); //接收客户端连接
buf = new BufferedReader(new InputStreamReader(client.getInputStream()));
out = new PrintStream(client.getOutputStream()); //实例化客户端的输出流
boolean flag = true; //标志位,表示一个客户端是否操作完毕
while(flag) {
String str = buf.readLine(); //不断接收信息
if(str == null || "".equals(str)) { //客户端操作结束
flag = false;
}else if("bye".equals(str)){
flag = false;
}else {
out.println("ECHO:" + str);
}
}
out.close();
client.close();
}
server.close();
}
}
客户端:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
/**
*
* ECHO----客户端
*
* @author Mona
*
*/
public class EchoClient {
public static void main(String[] args) throws Exception{
Socket client = new Socket("localhost", 8888); //声明Socket对象,指定连接的主机和端口
BufferedReader buf = null;
PrintStream out = null;
BufferedReader input = null; //声明BufferedReader对象
input = new BufferedReader(
new InputStreamReader(System.in)); //从键盘接收数据
out = new PrintStream(client.getOutputStream()); //实例化客户端的输出流
buf = new BufferedReader(new InputStreamReader(client.getInputStream()));
boolean flag = true; //定义一个标记位
while(flag) {
System.out.println("输入信息:");
String str = input.readLine();
out.println(str);
if("bye".equals(str)){
flag = false;
}else {
String echo = buf.readLine(); //接收ECHO信息
System.out.println(echo);
}
}
client.close();
buf.close();
}
}
运行结果:
从程序的运行结果中可以发现,所有的输入信息最终都会通过回显的方式发回给客户端,并且前面加上了“ECHO"的信息。另外在本程序中,当一个客户端结束之后,服务器端并不会退出,会等待下一个用户连接,继续执行。但在本程序中同时存在一个严重的问题,即现在的服务器端每次只能有一个用户连接,属于单线程的处理机制。此时无法连接其他客户端。
四、在服务器上应用多线程
对于服务器端来说,如果要加入多线程机制,则应该在每个用户连接之后启动一个新的线程。所以下面先建立一个EchoThread类,此类专门用于处理多线程操作,此时的多线程使用Runnable接口实现。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
/**
*
* 在网络服务器上应用多线程
*
* @author Mona
*
*/
public class EchoThread implements Runnable{
private Socket client = null; //接收客户端
public EchoThread(Socket client) { //通过构造方法设置Socket
this.client = client;
}
public void run(){
PrintStream out = null;
BufferedReader buf = null; //用于接收客户端发送来的信息
try {
buf = new BufferedReader(new InputStreamReader(client.getInputStream()));
out = new PrintStream(client.getOutputStream()); //实例化客户端的输出流
boolean flag = true; //标志位,表示一个客户端是否操作完毕
while(flag) {
String str = buf.readLine(); //不断接收信息
if(str == null || "".equals(str)) { //客户端操作结束
flag = false;
}else if("bye".equals(str)){
flag = false;
}else {
out.println("ECHO:" + str);
}
}
out.close();
client.close();
} catch (Exception e) {
}
}
}
从本线程类中可以发现,上述程序的 主要功能是接收每一个客户端的Socket, 并通过循环的方式接收客户端的输入信息及向客户端输出信息。下面编写EchoThreadServer 类,并使用以上的EchoThread类。
import java.net.ServerSocket;
import java.net.Socket;
/**
* 多线程---EchoThreadServer
*
* @author Mona
*
*/
public class EchoThreadServer {
public static void main(String[] args) throws Exception{
ServerSocket server = null;
Socket client = null;
server = new ServerSocket(8888);
boolean f = true; //定义一个标记位
while(f) {
System.out.println("服务器运行,等待客户端连接...");
client = server.accept(); //接收客户端连接
new Thread(new EchoThread(client)).start();
}
server.close();
}
}
在服务器端,每一个连接服务器端Socket都会以一个线程的方式预计女性,这样无论有多少个客户端连接都可以同时完成操作。
五、UDP程序设计
TCP的所有操作都必须建立可靠的连接,这样肯定会浪费大量的系统性能。为了减少这种开销, 在网络中又提供了 另外一种传输协议UDP,是不可靠的连接, 这种协议在各种聊天工具中被广泛地应用。
使用UDP发送的信息,对方不一定 会接收到。所有的信息使用数据报的形式发送出去,所以这就要求客户端要始终等待服务器发送的消息才能进行接收,在Java 中使用DatagramSocket类和DatagramPacket类完成UDP程序的开发。
(提示:关于UDP开发中服务器和客户端的解释。使用UDP开发的网络程序类似于平常使用的手机,手机实际上相当于一个客户端,如果手机要想正常地接收到信息,则手机肯定要先开机才行。)
在UDP开发中使用DatagramPacket类包装一条要发送的信息,之后使用DatagramSocket类用于完成信息的发送操作。
要想实现UDP程序,则首先应该从客户端编写,在客户端指定要接收数据的端口和取得数据。
UDP客户端--UDPClient:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* UDP客户端--UDPClient
*
* @author Mona
*
*/
public class UDPClient {
public static void main(String[] args) throws Exception{
DatagramSocket ds = null;
byte[] buf = new byte[1024]; //定义接收数据的字节数组
DatagramPacket dp = null;
ds = new DatagramSocket(9000);
dp = new DatagramPacket(buf, 1024); //指定接收数据的长度为1024
System.out.println("等待接收数据...");
ds.receive(dp); //接收数据
String str = new String(dp.getData(),0,dp.getLength())
+ " from" + dp.getAddress().getHostAddress()
+ " : " + dp.getPort(); //接收数据
System.out.println(str);
ds.close();
}
}
运行结果:
编写UDP发送的服务器端程序----UDPServer
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 编写UDP发送的服务器端程序----UDPServer
*
* @author Mona
*
*/
public class UDPServer {
public static void main(String[] args) throws Exception{
DatagramSocket ds = null;
DatagramPacket dp = null;
ds = new DatagramSocket(3000);
String str = "Hello World"; //准备好要发送的数据
//实例化DatagramPacket对象,指定数据内容、数据长度、要发送的目标地址、发送端口
dp = new DatagramPacket(str.getBytes(), str.length(),InetAddress.getByName("localhost"),9000);//此处向客户端发送9000端口
System.out.println("发送信息...");
ds.send(dp); //发送数据报
ds.close();
}
}
运行结果:
下面为先执行客户端,接着执行服务器端的运行结果:
客户端: