在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序,简称客户端,而在第一次通讯中等待连接的程序被称作服务器端(Server)程序,简称服务器。一旦通讯建立,则客户端和服务器端完全一样,没有本质的区别。
“请求-响应”模式:
1. Socket类:发送TCP消息。
2. ServerSocket类:创建服务器。
套接字是一种进程间的数据交换机制。这些进程既可以在同一机器上,也可以在通过网络连接的不同机器上。换句话说,套接字起到通信端点的作用。单个套接字是一个端点,而一对套接字则构成一个双向通信信道,使非关联进程可以在本地或通过网络进行数据交换。一旦建立套接字连接,数据即可在相同或不同的系统中双向或单向发送,直到其中一个端点关闭连接。套接字与主机地址和端口地址相关联。主机地址就是客户端或服务器程序所在的主机的IP地址。端口地址是指客户端或服务器程序使用的主机的通信端口。
在客户端和服务器中,分别创建独立的Socket,并通过Socket的属性,将两个Socket进行连接,这样,客户端和服务器通过套接字所建立的连接使用输入输出流进行通信。
TCP/IP套接字是最可靠的双向流协议,使用TCP/IP可以发送任意数量的数据。
实际上,套接字只是计算机上已编号的端口。如果发送方和接收方计算机确定好端口,他们就可以通信了。
客户端与服务器端的通信关系图:
TCP/IP通信连接的简单过程:
位于A计算机上的TCP/IP软件向B计算机发送包含端口号的消息,B计算机的TCP/IP软件接收该消息,并进行检查,查看是否有它知道的程序正在该端口上接收消息。如果有,他就将该消息交给这个程序。
要使程序有效地运行,就必须有一个客户端和一个服务器。
通过Socket的编程顺序:
1. 创建服务器ServerSocket,在创建时,定义ServerSocket的监听端口(在这个端口接收客户端发来的消息)。
2. ServerSocket调用accept()方法,使之处于阻塞状态。
3. 创建客户端Socket,并设置服务器的IP及端口。
4. 客户端发出连接请求,建立连接。
5. 分别取得服务器和客户端Socket的InputStream和OutputStream。
6. 利用Socket和ServerSocket进行数据传输。
7. 关闭流及Socket。
TCP:单向通信Socket之服务器端
import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; /** * 最简单的服务器端代码 * @author Administrator */ public class BasicSocketServer { public static void main(String[] args) { Socket socket = null; BufferedWriter bw = null; try { // 建立服务器端套接字:指定监听的接口 ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务端建立监听"); // 监听,等待客户端请求,并愿意接收连接 socket = serverSocket.accept(); // 获取socket的输出流,并使用缓冲流进行包装 bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); // 向客户端发送反馈信息 bw.write("hhhh"); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭流及socket连接 if (bw != null) { try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
TCP:单向通信Socket之客户端
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.InetAddress; import java.net.Socket; /** * 最简单的Socket客户端 * @author Administrator */ public class BasicSocketClient { public static void main(String[] args) { Socket socket = null; BufferedReader br = null; try { /* * 创建Scoket对象:指定要连接的服务器的IP和端口而不是自己机器的 * 端口。发送端口是随机的。 */ socket = new Socket(InetAddress.getLocalHost(), 8888); //获取scoket的输入流,并使用缓冲流进行包装 br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //接收服务器端发送的信息 System.out.println(br.readLine()); } catch (Exception e) { e.printStackTrace(); } finally { // 关闭流及socket连接 if (br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
TCP:双向通信Socket之服务器端
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args){ Socket socket = null; BufferedReader in = null; BufferedWriter out = null; BufferedReader br = null; try { //创建服务器端套接字:指定监听端口 ServerSocket server = new ServerSocket(8888); //监听客户端的连接 socket = server.accept(); //获取socket的输入输出流接收和发送信息 in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); br = new BufferedReader(new InputStreamReader(System.in)); while (true) { //接收客户端发送的信息 String str = in.readLine(); System.out.println("客户端说:" + str); String str2 = ""; //如果客户端发送的是“end”则终止连接 if (str.equals("end")){ break; } //否则,发送反馈信息 str2 = br.readLine(); // 读到\n为止,因此一定要输入换行符! out.write(str2 + "\n"); out.flush(); } } catch (IOException e) { e.printStackTrace(); } finally { //关闭资源 if(in != null){ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if(out != null){ try { out.close(); } catch (IOException e) { e.printStackTrace(); } } if(br != null){ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if(socket != null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
【示例12-10】TCP:双向通信Socket之客户端
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) { Socket socket = null; BufferedReader in = null; BufferedWriter out = null; BufferedReader wt = null; try { //创建Socket对象,指定服务器端的IP与端口 socket = new Socket(InetAddress.getLocalHost(), 8888); //获取scoket的输入输出流接收和发送信息 in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); wt = new BufferedReader(new InputStreamReader(System.in)); while (true) { //发送信息 String str = wt.readLine(); out.write(str + "\n"); out.flush(); //如果输入的信息为“end”则终止连接 if (str.equals("end")) { break; } //否则,接收并输出服务器端信息 System.out.println("服务器端说:" + in.readLine()); } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭资源 if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if (wt != null) { try { wt.close(); } catch (IOException e) { e.printStackTrace(); } } if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
执行结果如图所示:
服务器端:
客户端:
注:
运行时,要先启动服务器端,再启动客户端,才能得到正常的运行效果。
但是,上面这个程序,必须按照安排好的顺序,服务器和客户端一问一答!不够灵活!!可以使用多线程实现更加灵活的双向通讯!!
服务器端:一个线程专门发送消息,一个线程专门接收消息。
客户端:一个线程专门发送消息,一个线程专门接收消息。
TCP:聊天室之服务器端
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; public class ChatServer { public static void main(String[] args) { ServerSocket server = null; Socket socket = null; BufferedReader in = null; try { server = new ServerSocket(8888); socket = server.accept(); //创建向客户端发送消息的线程,并启动 new ServerThread(socket).start(); // main线程负责读取客户端发来的信息 in = new BufferedReader(new InputStreamReader(socket.getInputStream())); while (true) { String str = in.readLine(); System.out.println("客户端说:" + str); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (in != null) { in.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (socket != null) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } } } } /** * 专门向客户端发送消息的线程 * * @author Administrator * */ class ServerThread extends Thread { Socket ss; BufferedWriter out; BufferedReader br; public ServerThread(Socket ss) { this.ss = ss; try { out = new BufferedWriter(new OutputStreamWriter(ss.getOutputStream())); br = new BufferedReader(new InputStreamReader(System.in)); } catch (IOException e) { e.printStackTrace(); } } public void run() { try { while (true) { String str2 = br.readLine(); out.write(str2 + "\n"); out.flush(); } } catch (IOException e) { e.printStackTrace(); } finally { try { if(out != null){ out.close(); } } catch (IOException e) { e.printStackTrace(); } try { if(br != null){ br.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; public class ChatClient { public static void main(String[] args) { Socket socket = null; BufferedReader in = null; try { socket = new Socket(InetAddress.getByName("127.0.1.1"), 8888); // 创建向服务器端发送信息的线程,并启动 new ClientThread(socket).start(); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // main线程负责接收服务器发来的信息 while (true) { System.out.println("服务器说:" + in.readLine()); } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (socket != null) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (in != null) { in.close(); } } catch (IOException e) { e.printStackTrace(); } } } } /** * 用于向服务器发送消息 * * @author Administrator * */ class ClientThread extends Thread { Socket s; BufferedWriter out; BufferedReader wt; public ClientThread(Socket s) { this.s = s; try { out = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); wt = new BufferedReader(new InputStreamReader(System.in)); } catch (IOException e) { e.printStackTrace(); } } public void run() { try { while (true) { String str = wt.readLine(); out.write(str + "\n"); out.flush(); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (wt != null) { wt.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (out != null) { out.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
执行结果如图所示:
服务器端:
客户端:
▪ DatagramSocket:用于发送或接收数据报包
当服务器要向客户端发送数据时,需要在服务器端产生一个DatagramSocket对象,在客户端产生一个DatagramSocket对象。服务器端的DatagramSocket将DatagramPacket发送到网络上,然后被客户端的DatagramSocket接收。
DatagramSocket有两种常用的构造函数。一种是无需任何参数的,常用于客户端;另一种需要指定端口,常用于服务器端。如下所示:
DatagramSocket() :构造数据报套接字并将其绑定到本地主机上任何可用的端口。
DatagramSocket(int port) :创建数据报套接字并将其绑定到本地主机上的指定端口。
常用方法:
send(DatagramPacket p) :从此套接字发送数据报包。
receive(DatagramPacket p) :从此套接字接收数据报包。
close() :关闭此数据报套接字。
▪ DatagramPacket:数据容器(封包)的作用
此类表示数据报包。 数据报包用来实现封包的功能。
常用方法:
DatagramPacket(byte[] buf, int length) :构造数据报包,用来接收长度为 length 的数据包。
DatagramPacket(byte[] buf, int length, InetAddress address, int port) :构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
getAddress() :获取发送或接收方计算机的IP地址,此数据报将要发往该机器或者是从该机器接收到的。
getData() :获取发送或接收的数据。
setData(byte[] buf) :设置发送的数据。
UDP通信编程基本步骤:
1. 创建客户端的DatagramSocket,创建时,定义客户端的监听端口。
2. 创建服务器端的DatagramSocket,创建时,定义服务器端的监听端口。
3. 在服务器端定义DatagramPacket对象,封装待发送的数据包。
4. 客户端将数据报包发送出去。
5. 服务器端接收数据报包。
【示例12-13】UDP:单向通信之客户端
import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; public class Client { public static void main(String[] args) throws Exception { byte[] b = "张三".getBytes(); //必须告诉数据报包要发到哪台计算机的哪个端口,发送的数据以及数据的长度 DatagramPacket dp = new DatagramPacket(b,b.length,new InetSocketAddress("localhost",8999)); //创建数据报套接字:指定发送信息的端口 DatagramSocket ds = new DatagramSocket(9000); //发送数据报包 ds.send(dp); //关闭资源 ds.close(); } }
【示例12-14】UDP:单向通信之服务器端
import java.net.DatagramPacket; import java.net.DatagramSocket; public class Server { public static void main(String[] args) throws Exception { //创建数据报套接字:指定接收信息的端口 DatagramSocket ds = new DatagramSocket(8999); byte[] b = new byte[1024]; //创建数据报包,指定要接收的数据的缓存位置和长度 DatagramPacket dp = new DatagramPacket(b, b.length); //接收客户端发送的数据报 ds.receive(dp); // 阻塞式方法 //dp.getLength()返回实际收到的数据的字节数 String string = new String(dp.getData(), 0, dp.getLength()); System.out.println(string); //关闭资源 ds.close(); } }
执行结果如图所示:
通过字节数组流ByteArrayInputStream、ByteArrayOutputStream与数据流DataInputStream、DataOutputStream联合使用可以传递基本数据类型。
UDP:基本数据类型的传递之客户端
import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; public class Client { public static void main(String[] args) throws Exception { long n = 2000L; ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); dos.writeLong(n); //获取字节数组流中的字节数组(我们要发送的数据) byte[] b = bos.toByteArray(); //必须告诉数据报包要发到哪台计算机的哪个端口,发送的数据以及数据的长度 DatagramPacket dp = new DatagramPacket(b,b.length,new InetSocketAddress("localhost",8999)); //创建数据报套接字:指定发送信息的端口 DatagramSocket ds = new DatagramSocket(9000); //发送数据报包 ds.send(dp); //关闭资源 dos.close(); bos.close(); ds.close(); } }
UDP:基本数据类型的传递之服务器端
import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; public class Server { public static void main(String[] args) throws Exception { //创建数据报套接字:指定接收信息的端口 DatagramSocket ds = new DatagramSocket(8999); byte[] b = new byte[1024]; //创建数据报包,指定要接收的数据的缓存位置和长度 DatagramPacket dp = new DatagramPacket(b, b.length); //接收客户端发送的数据报 ds.receive(dp); // 阻塞式方法 //dp.getData():获取客户端发送的数据,返回值是一个字节数组 ByteArrayInputStream bis = new ByteArrayInputStream(dp.getData()); DataInputStream dis = new DataInputStream(bis); System.out.println(dis.readLong()); //关闭资源 dis.close(); bis.close(); ds.close(); } }
执行结果如图所示:
通过字节数组流ByteArrayInputStream、ByteArrayOutputStream与数据流ObjectInputStream、ObjectOutputStream联合使用可以传递对象。
UDP:对象的传递之Person类
import java.io.Serializable; public class Person implements Serializable{ private static final long serialVersionUID = 1L; int age; String name; public Person(int age, String name) { super(); this.age = age; this.name = name; } @Override public String toString() { return "Person [age=" + age + ", name=" + name + "]"; } }
UDP:对象的传递之客户端
import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; public class Client { public static void main(String[] args) throws Exception { //创建要发送的对象 Person person = new Person(20, "张三"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(person); //获取字节数组流中的字节数组(我们要发送的数据) byte[] b = bos.toByteArray(); //必须告诉数据报包要发到哪台计算机的哪个端口,发送的数据以及数据的长度 DatagramPacket dp = new DatagramPacket(b,b.length,new InetSocketAddress("localhost",8999)); //创建数据报套接字:指定发送信息的端口 DatagramSocket ds = new DatagramSocket(9000); //发送数据报包 ds.send(dp); //关闭资源 oos.close(); bos.close(); ds.close(); } }
UDP:对象的传递之服务器端
import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; public class Server { public static void main(String[] args) throws Exception { //创建数据报套接字:指定接收信息的端口 DatagramSocket ds = new DatagramSocket(8999); byte[] b = new byte[1024]; //创建数据报包,指定要接收的数据的缓存位置和长度 DatagramPacket dp = new DatagramPacket(b, b.length); //接收客户端发送的数据报 ds.receive(dp); // 阻塞式方法 //dp.getData():获取客户端发送的数据,返回值是一个字节数组 ByteArrayInputStream bis = new ByteArrayInputStream(dp.getData()); ObjectInputStream ois = new ObjectInputStream(bis); System.out.println(ois.readObject()); //关闭资源 ois.close(); bis.close(); ds.close(); } }
执行结果如图所示: