网络编程
Socket
我们开发的网络应用程序位于应用层,TCP和UDP属于传输层协议,在应用层如何使用传输层的服务呢?在应用层和传输层之间,则使用套接字Socket来进行分离
套接字就像是传输层为应用层开的一个小口,应用程序通过这个小口向远程发送数据,或者接收远程发来的数据;而这个小口以内,也就是数据进入这个口之后,或者数据从这个口出来之前,是不知道也不需要知道的,也不会关心它如何传输,这属于网络其他层次工作
Socket实际是传输层供给应用层的编程接口。Socket就是应用层与传输层之间的桥梁。使用Socket变成可以开发客户机和服务器的应用程序,可以在本地网络上通信,也可以通过Internet在全球范围内通信
Java网络编程中的常用类
Java为了跨平台,在网络应用通信时是不允许直接调用操作系统接口的,而是由java.net包来提供网络功能。
InetAddress的使用
作用:封装计算机的IP地址和DNS(没有端口信息)
特点:
这个类没有构造方法,如果想要得到对象,只能通过静态方法:getLocalHost() 、 getByName() 、 getALLByName() 、 getAddress() 、 getHostName()
获取本机信息
获取本机信息需要使用getLocalHost()方法创建InetAddress对象,这个对象包含了本机的IP地址,计算机名等信息
java
public class InetTest {
public static void main(String[] args) {
try {
InetAddress localHost = InetAddress.getLocalHost();
String hostAddress = localHost.getHostAddress();
String hostName = localHost.getHostName();
System.out.println(hostAddress);
System.out.println(hostName);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
}
根据域名获取计算机的信息
根据域名获取计算机信息时需要使用getByName("域名")方法创建InetAddress对象
java
public class InetTest2 {
public static void main(String[] args) {
try {
InetAddress inetAddress = InetAddress.getByName("www.baidu.com");
System.out.println(inetAddress.getHostAddress());
System.out.println(inetAddress.getHostName());
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
}
根据IP地址获取计算机的信息
根据IP地址获取计算机的信息时需要使用getByName("IP")方法创建InetAddress对象
java
public class InetTest3 {
public static void main(String[] args) {
try {
InetAddress inetAddress = InetAddress.getByName("110.242.68.4");
System.out.println(inetAddress.getHostName());
System.out.println(inetAddress.getHostAddress());
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
}
InetSocketAddress的使用
作用:包含IP和端口信息,常用于Socket通信。此类实现IP套接字地址(IP地址+端口号),不依赖任何协议
InetSocketAddress相比较InetAddress多了一个端口号,端口的作用:一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现
java
public class InetSocketTest {
public static void main(String[] args) {
InetSocketAddress inetSocketAddress = new InetSocketAddress("www.baidu.com",80);
System.out.println(inetSocketAddress.getAddress().getHostAddress());
System.out.println(inetSocketAddress.getHostName());
}
}
URL的使用
IP地址标识了Internet上唯一的计算机,而URL则标识了这些计算机上的资源。URL代表一个资源定位符,它是指向互联网"资源"的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或者搜索引擎的查询
为了方便程序员编程,JDK中提供了URL类,该类的全名是java.net.URL,有了这样一个类,就可以使用它的各种方法来对URL对象进行分割、合并等处理
java
public class UrlTest {
public static void main(String[] args) {
try {
URL url = new URL("http://www.edu2act.cn/task/list/finished/");
System.out.println("获取当前协议的默认端口:" + url.getDefaultPort());
System.out.println("访问资源:" + url.getFile());
System.out.println("主机名" + url.getHost());
System.out.println("访问资源的路径:" + url.getPath());
System.out.println("协议" + url.getProtocol());
System.out.println("参数部分" + url.getQuery());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
}
通过URL实现最简单的网络爬虫
java
public class UrlTest2{
public static void main(String[] args)throws Exception {
URL url = new URL("http://dbms.wangding.co/design/ch04-database-design-1/");
try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
StringBuilder sb = new StringBuilder();
String temp;
while ((temp = br.readLine()) != null) {
sb.append(temp);
}
System.out.println(sb);
} catch (Exception e) {
e.printStackTrace();
}
}
}
TCP通信的实现和项目案例
TCP通信实现原理
前边我们提到TCP协议是面向的连接的,在通信时客户端与服务器端必须建立连接。在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序,简称客户端,而在第一次通讯中等待连接的程序被称作服务器端(Server)程序,简称服务器。一旦通讯建立,则客户端和服务器端完全一样,没有本质的区别。
请求-相应 模式
- Socket类:发送TCP信息
- ServerSocket类:创建服务器
套接字Socket是一种进程间的数据交换机制。这些进程既可以在同一机器上,也可以在通过网络连接的不同机器上。换句话说,套接字起到通信端点的作用。单个套接字是一个端点,而一对套接字则构成一个双向通信信道,使非关联进程可以在本地或通过网络进行数据交换。一旦建立套接字连接,数据即可在相同或不同的系统中双向或单向发送,直到其中一个端点关闭连接。套接字与主机地址和端口地址相关联。主机地址就是客户端或服务器程序所在的主机的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通信入门案例
创建服务端
java
public class BasicSocketServer {
public static void main(String[] args) throws IOException {
System.out.println("服务器已启动,等待监听....");
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8888);
Socket socket = serverSocket.accept();
//连接成功后会得到与客户端对应的Socket对象,并解除线程阻塞
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
System.out.println(br.readLine());
OutputStream out = socket.getOutputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
serverSocket.close();
}
}
}
创建客户端
java
public class BasicSocketClient {
public static void main(String[] args) throws IOException {
Socket socket = null;
PrintWriter pw = null;
try {
socket = new Socket("127.0.0.1",8888);
OutputStream out = socket.getOutputStream();
pw = new PrintWriter(out);
pw.write("服务端,你好!");
pw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
pw.close();
socket.close();
}
}
}
TCP单向通信
单向通信是指通信双方中,一方固定为发送端,一方固定为接收端
创建服务端
java
public class OneWaySocketServer {
public static void main(String[] args) {
System.out.println("服务器启动,开始监听");
try(ServerSocket serverSocket = new ServerSocket(8888);) {
Socket socket = serverSocket.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter pw = new PrintWriter(socket.getOutputStream());
System.out.println("连接成功");
while (true){
String str = br.readLine();
System.out.println("客户端说" + str);
if ("exit".equals(str)){
break;
}
pw.println(str);
pw.flush();
}
} catch (IOException e) {
System.out.println("服务器启动失败");
throw new RuntimeException(e);
}
}
}
创建客户端
java
public class OneWaySocketClient {
public static void main(String[] args) {
try(Socket socket = new Socket("127.0.0.1",8888)) {
Scanner scanner = new Scanner(System.in);
PrintWriter pw = new PrintWriter(socket.getOutputStream());
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (true){
String s = scanner.nextLine();
pw.println(s);
pw.flush();
if ("exit".equals(s)){
break;
}
String serverInput = br.readLine();
System.out.println("服务器返回的" + serverInput);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
TCP双向通信
双向通信是指通信双方中,任何一方都可为发送端,任何一方都可为接收端
服务端
java
public class TwoWaySocketServer {
public static void main(String[] args) {
System.out.println("服务器启动,监听8888端口");
try(ServerSocket serverSocket = new ServerSocket(8888);) {
Socket socket = serverSocket.accept();
//创建键盘输入对象
Scanner scanner = new Scanner(System.in);
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter pw = new PrintWriter(socket.getOutputStream());
while (true){
String str = br.readLine();
System.out.println("客户端说:" + str);
String keyInput = scanner.nextLine();
pw.println(keyInput);
pw.flush();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
客户端
java
public class TwoWaySocketClient {
public static void main(String[] args) {
try(Socket socket = new Socket("127.0.0.1",8888)) {
Scanner scanner = new Scanner(System.in);
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter pw = new PrintWriter(socket.getOutputStream());
while (true){
String keyInput = scanner.nextLine();
pw.println(keyInput);
pw.flush();
String str = br.readLine();
System.out.println("服务端说:" + str);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
创建点对点的聊天应用
创建服务端
主线程
java
public class ChatSocketServer {
public static void main(String[] args) {
try(ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("服务端启动,等待连接");
Socket socket = serverSocket.accept();
new Thread(new SendThread(socket)).start();
new Thread(new ReceiveThread(socket)).start();
}catch (Exception e){
e.printStackTrace();
}
}
}
接收消息线程
java
public class ReceiveThread implements Runnable{
private Socket socket;
public ReceiveThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
this.receiveMsg();
}
private void receiveMsg(){
try(BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));){
while (true){
String msg = br.readLine();
System.out.println("客户端说:" + msg);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
发送消息线程
java
public class SendThread implements Runnable{
private Socket socket;
public SendThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
this.sendMsg();
}
private void sendMsg(){
try(Scanner scanner = new Scanner(System.in);
PrintWriter pw = new PrintWriter(socket.getOutputStream());
){
while (true){
String msg = scanner.nextLine();
pw.println(msg);
pw.flush();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
创建客户端
主线程
java
public class ChatSocketClient {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1",8888);
System.out.println("连接成功");
new Thread(new ClientSendThread(socket)).start();
new Thread(new ClientReceiveThread(socket)).start();
}catch (Exception e){
e.printStackTrace();
}
}
}
接收消息线程被
java
public class ClientReceiveThread implements Runnable{
private Socket socket;
public ClientReceiveThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
receiveMsg();
}
private void receiveMsg() {
try(BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
while (true){
String msg = br.readLine();
System.out.println("服务端说:" + msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
发送消息线程
java
public class ClientSendThread implements Runnable{
private Socket socket;
public ClientSendThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
this.sendMsg();
}
private void sendMsg() {
try(Scanner scanner = new Scanner(System.in);
PrintWriter pw = new PrintWriter(socket.getOutputStream())){
while (true){
String msg = scanner.nextLine();
pw.println(msg);
pw.flush();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
优化点对点的聊天应用
java
public class GoodTCP {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
Socket socket = null;
try {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入:server,<port> 获取 <ip>,<port>");
String str = scanner.nextLine();
String[] arr = str.split(",");
if("server".equals(arr[0])){
System.out.println("TCP Server Listen at" + arr[1] + "......");
serverSocket = new ServerSocket(Integer.parseInt(arr[1]));
socket = serverSocket.accept();
new Receive(socket);
}else {
socket = new Socket(arr[0],Integer.parseInt(arr[1]));
System.out.println("连接成功");
}
new Thread(new Send(socket, scanner)).start();
new Thread(new Receive(socket)).start();
}catch (Exception e){
e.printStackTrace();
}finally {
if(serverSocket != null){
serverSocket.close();
}
}
}
}
java
public class Send implements Runnable{
private Socket socket;
private Scanner scanner;
public Send(Socket socket, Scanner scanner) {
this.socket = socket;
this.scanner = scanner;
}
@Override
public void run() {
this.sendMsg();
}
private void sendMsg(){
try(PrintWriter pw = new PrintWriter(socket.getOutputStream());
){
while (true){
String msg = scanner.nextLine();
pw.println(msg);
pw.flush();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
java
public class Receive implements Runnable{
private Socket socket;
public Receive(Socket socket){
this.socket = socket;
}
@Override
public void run() {
sendMsg();
}
private void sendMsg(){
try(BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));){
while (true){
String msg = br.readLine();
System.out.println("客户端说:" + msg);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
一对多应用
服务端应该将serverSocket.accept()放入while(true)循环中
一对多聊天服务器设计
难点在于解决线程同步
当没有消息发送时,发送线程处于等待状态,当接收线程接收到消息后,唤醒所有等待的发送线程
java
public class ChatRoomServer {
public static String buf;
public static void main(String[] args) {
System.out.println("Chat Server Version 1.0");
System.out.println("Listen at 8888......");
try(ServerSocket serverSocket = new ServerSocket(8888)){
while (true){
Socket socket = serverSocket.accept();
System.out.println("连接到" + socket.getInetAddress());
new Thread(new ChatReceiveThread(socket)).start();
new Thread(new ChatSendThread(socket)).start();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
java
public class ChatReceiveThread implements Runnable {
private Socket socket;
public ChatReceiveThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
receiveMsg();
}
private void receiveMsg() {
try(BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));){
while (true){
String msg = br.readLine();
synchronized ("abc"){
ChatRoomServer.buf = "[" + this.socket.getInetAddress() + "]" + msg;
"abc".notifyAll();
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
java
public class ChatSendThread implements Runnable{
Socket socket;
public ChatSendThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
sendMsg();
}
private void sendMsg() {
try(PrintWriter pw = new PrintWriter(socket.getOutputStream());){
while (true){
synchronized ("abc"){
//先让发送消息的线程处于等待状态
"abc".wait();
//将公共数据区的数据发送给客户端
pw.println(ChatRoomServer.buf);
pw.flush();
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
UDP通信实现原理
UDP协议与之前讲到的TCP协议不同,是面向无连接的,双方不需要建立连接便可通信。UDP通信所发送的数据需要进行封包操作(使用DatagramPacket类),然后才能接收或发送(使用DatagramSocket类)。
DatagramPacket:数据容器(封包)的作用
此类表示数据报包。 数据报包用来实现封包的功能。
常用方法
方法名 | 使用说明 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造数据报包,用来接收长度为 length 的数据包 |
DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号 |
getAddress() | 获取发送或接收方计算机的IP地址,此数据报将要发往该机器或者是从该机器接收到的 |
getData() | 获取发送或接收的数据 |
setData(byte[] buf) | 设置发送的数据 |
DatagramSocket:用于发送或接收数据报包
当服务器要向客户端发送数据时,需要在服务器端产生一个DatagramSocket对象,在客户端产生一个DatagramSocket对象。服务器端的DatagramSocket将DatagramPacket发送到网络上,然后被客户端的DatagramSocket接收。
DatagramSocket有两种常用的构造函数。一种是无需任何参数的,常用于客户端;另一种需要指定端口,常用于服务器端。如下所示:
- DatagramSocket() :构造数据报套接字并将其绑定到本地主机上任何可用的端口。
- DatagramSocket(int port) :创建数据报套接字并将其绑定到本地主机上的指定端口。
常用方法
方法名 | 使用说明 |
---|---|
send(DatagramPacket p) | 从此套接字发送数据报包 |
receive(DatagramPacket p) | 从此套接字接收数据报包 |
close() | 关闭此数据报套接字 |
UDP通信编程基本步骤:
1、创建客户端的DatagramSocket,创建时,定义客户端的监听端口。
2、创建服务器端的DatagramSocket,创建时,定义服务器端的监听端口。
3、在服务器端定义DatagramPacket对象,封装待发送的数据包。
4、客户端将数据报包发送出去。
5、服务器端接收数据报包。
UDP通信入门案例
服务端
java
public class UDPServer {
public static void main(String[] args) {
//创建服务端接收数据的DatagramSocket对象
try(DatagramSocket datagramSocket = new DatagramSocket(9999)){
//创建数据缓冲区
byte[] b = new byte[1024];
//创建数据报包对象
DatagramPacket datagramPacket = new DatagramPacket(b,b.length);
//等待接收客户端所发送的数据
datagramSocket.receive(datagramPacket);
//取出数据
String str = new String(datagramPacket.getData(),0,datagramPacket.getLength());
System.out.println(str);
}catch (Exception e){
e.printStackTrace();
}
}
}
客户端
java
public class UDPClient {
public static void main(String[] args) {
try(DatagramSocket datagramSocket = new DatagramSocket(8888);) {
byte[] bytes = "Triticale".getBytes();
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,new InetSocketAddress("127.0.0.1",9999));
datagramSocket.send(datagramPacket);
}catch (Exception e){
e.printStackTrace();
}
}
}
基本数据类型通信
服务端
java
try(DataInputStream dis = new DataInputStream(new ByteArrayInputStream(datagramPacket.getData()));){
System.out.println(dis.readLong());
}
客户端
java
long n = 2000l;
try(ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);){
dos.writeLong(n);
byte[] arr = bos.toByteArray();
DatagramPacket datagramPacket1 = new DatagramPacket(arr,arr.length,new InetSocketAddress("127.0.0.1",9999));
}
传递自定义数据类型
创建服务端
java
public class ObjectTypeUDPServer {
public static void main(String[] args) {
try(DatagramSocket datagramSocket = new DatagramSocket(9999)){
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length);
datagramSocket.receive(datagramPacket);
try(ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(datagramPacket.getData()))){
Person person = (Person) ois.readObject();
System.out.println(person.toString());
}
}catch (Exception e){
e.printStackTrace();
}
}
}
创建客户端
java
public class ObjectTypeUDPClient {
public static void main(String[] args) {
try(DatagramSocket datagramSocket = new DatagramSocket(8888);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)){
Person person = new Person();
person.setName("张三");
person.setAge(18);
oos.writeObject(person);
byte[] byteArray = bos.toByteArray();
DatagramPacket datagramPacket = new DatagramPacket(byteArray,byteArray.length,new InetSocketAddress("127.0.0.1",9999));
datagramSocket.send(datagramPacket);
}catch (Exception e){
e.printStackTrace();
}
}
}