目录
一. 网络基础知识
1. 计算机网络
计算机网络是两台或更多的计算机组成的网络,同一网络内的任意两台计算机可以直接通信,所有计算机必须遵循同一种网络协议。
2. 网络编程
对于计算机网络硬件以及底层的基础知识,我们在下学期的《计算机网络》一书中会详细学习。仅对于网络编程来说,大部分的程序设计语言都设计了专门的API实现这些功能,程序员只需要学会组织调用即可。什么是网络编程,简单来说网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据。程序员所作的事情就是把数据发送到指定的位置,或者接收到指定的数据来进行处理。
3. IP、域名与端口
(1)IP地址
为了能够方便的识别网络上的每个设备,网络中的每个设备都会有一个唯一的数字标识,这个就是IP地址。由于有这种唯一的地址,才保证了用户在连网的计算机上操作时,能够高效而且方便地从千千万万台计算机中选出自己所需的对象来。
(2)域名
由于IP地址是一组二进制,不好记忆于是就产生了域名,如www.baidu.com。一个IP地址可以对应多个域名,一个域名只能对应一个IP地址。域名的概念可以类比手机中的通讯簿,由于手机号码不方便记忆,所以添加一个姓名标识号码,在实际拨打电话时可以选择该姓名,然后拨打即可。
但是由于在网络中传输的数据,全部是以IP地址作为地址标识,所以在实际传输数据以前需要将域名转换为IP地址,实现这种功能的服务器称之为DNS服务器,也就是通俗的说法叫做域名解析。例如当用户在浏览器输入域名时,浏览器首先请求DNS服务器,将域名转换为IP地址,然后将转换后的IP地址反馈给浏览器,然后再进行实际的数据传输。
(3)端口
IP地址和域名使得我们可以在复杂的网络中找到请求的计算机对象,但是我们最终需要执行的是该计算机中的某个程序,同时也为了让一个计算机可以同时运行多个网络程序,我们就引出了端口。
同一个计算机中每个程序对应唯一的端口,这样一个计算机上就可以通过端口区分发送给每个端口的数据了,换句话说,也就是一个计算机上可以并发运行多个网络程序,而不会在互相之间产生干扰。在硬件上规定,端口的号码必须位于0-65535之间,0~1023的端口号为系统所保留(为了避免冲突在选择端口时尽量选择1024之上)每个端口唯一的对应一个网络程序,一个网络程序可以使用多个端口。这样一个网络程序运行在一台计算上时,不管是客户端还是服务器,都是至少占用一个端口进行网络通讯。在接收数据时,首先发送给对应的计算机,然后计算机根据端口把数据转发给对应的程序。
(4)localhost、127.0.0.1和本机IP之间的区别
- localhost等于127.0.0.1,不过localhost是域名,127.0.0.1是IP地址。
- localhost和127.0.0.1不需要联网,都是本机访问。
- 本机IP需要联网,本机IP是本机或外部访问, 本机 IP 就是本机对外放开访问的IP地址,这个网址就是与物理网卡绑定的IP地址。比如你叫小明,127.0.0.1是“自己”,而本机IP是“小明”。
4. 网络通讯过程
网络通讯基于“请求-响应”模型。这种一问一答的形式就是网络中的“请求-响应”模型。也就是通讯的一端发送数据,另外一端反馈数据,网络通讯都基于该模型。
在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序,简称客户端,而在第一次通讯中等待连接的程序被称作服务器端(Server)程序,简称服务器。一旦通讯建立,则客户端和服务器端完全一样,没有本质的区别。这种网络编程的结构被称作客户端/服务器结构,也叫做Client/Server结构,简称C/S结构,需要建立特定的服务器来服务特定的客户端。而有时候使用通用的客户端,例如浏览器,使用浏览器作为客户端的结构被称作浏览器/服务器结构,也叫做Browser/Server结构,简称为B/S结构。
5. 网络传输方式
(1)TCP协议:是一种面向连接的保证可靠传输的协议,它保障了两个应用程序之间的可靠通信,通讯时必需要建立连接,包括三次握手和四次挥手。它确保接收方完全正确地获取发送方所发送的全部数据。(类似于打电话通讯)
(2)UDP协议:是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。(类似于发短信通讯)
二. Java网络编程技术
1. 编程基础
java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。java.net 包中提供了两种常见的网络协议的支持:
-
TCP:TCP 是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。
-
UDP:UDP 是用户数据报协议的缩写,一个无连接的协议。提供了应用程序之间要发送的数据的数据包。
2. 关键组件
2.1 Socket
socket又被称为套接字,实际上socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API)。通过Socket,我们才能使用计算机底层的TCP/IP协议栈,实现通信传输。客户端要获取一个 Socket 对象通过实例化建立通信 ,而服务器获得一个 Socket 对象则通过accept() 方法的返回值。
(1)构造方法
序号 | 方法描述 |
1 | public Socket(String host, int port) throws UnknownHostException, IOException. 创建一个流套接字并将其连接到指定主机上的指定端口号。 |
2 | public Socket(InetAddress host, int port) throws IOException 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 |
(2)主要方法
1 | public InputStream getInputStream() throws IOException 返回此套接字的输入流(读流,从套接字中读取另一端的反馈信息)。 |
2 | public OutputStream getOutputStream() throws IOException 返回此套接字的输出流(写流,给另一端传输信息)。 |
3 | public void close() throws IOException 关闭此套接字。 |
注意:一旦服务器端与客户端建立连接以后,双方都由Socket来进行通讯,本质上没有什么区别。本质上,说到java的网络编程倒不如说是Java的I/O编程,因为整个过程中关于服务端和客户端的socket创建也就那么两三行代码,其余的都是操作字节流,字符流等。
2.2 ServerSocket
ServerSocket是服务器端监听器,服务器通过ServerSocket来监听某个端口的请求。
(1)构造方法
1 | public ServerSocket(int port) throws IOException 创建绑定到特定端口的服务器套接字。 |
2 | public ServerSocket(int port, int backlog) throws IOException 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。 |
(2)主要方法
1 | public Socket accept() throws IOException 侦听并接受到此套接字的连接。该方法为阻塞方法,直到接到请求才执行下一步 |
2 | public void setSoTimeout(int timeout) 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。 |
3 | public void bind(SocketAddress host, int backlog) 将 ServerSocket 绑定到特定地址(IP 地址和端口号)。 |
2.3 InetAddress
这个类表示互联网协议(IP)地址,将IP地址和域名相关的操作方法包含在该类的内部。其主要方法如下:
序号 | 方法描述 |
1 | static InetAddress getByAddress(byte[] addr) 在给定原始 IP 地址的情况下,返回 InetAddress 对象。 |
2 | static InetAddress getByAddress(String host, byte[] addr) 根据提供的主机名和 IP 地址创建 InetAddress。 |
3 | static InetAddress getByName(String host) 在给定主机名的情况下确定主机的 IP 地址。 |
4 | String getHostAddress() 返回 IP 地址字符串(以文本表现形式)。 |
5 | String getHostName() 获取此 IP 地址的主机名。 |
6 | static InetAddress getLocalHost() 返回本地主机。 |
7 | String toString() 将此 IP 地址转换为 String。 |
在后续的使用中,经常包含需要使用InetAddress对象代表IP地址的构造方法,当然,该类的使用不是必须的,也可以使用字符串来代表IP地址进行实现。
3. TCP网络编程步骤
3.1 客户端开发
(1)建立连接
Socket socket = new Socket("127.0.0.1",6666);//向ip地址127.0.0.1的计算机的6666端口发起连接请求
(2)获取流对象
//由Socket对象得到输出流,并构造相应的BufferedReader对象
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//由Socket对象得到输入流,并构造相应的BufferedReader对象
BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
//获取屏幕输入流
sin = new BufferedReader(new InputStreamReader(System.in));
(3)信息传输
while(true) {
System.out.print("请输入字符串:");
String line = sin.readLine();//屏幕输入字符串
//String line = scanner.nextLine();
bw.write(line + "\n");//注意+"\n"
bw.flush();//刷新
//pw.println(line);
//pw.flush();
if(line.equals("goodbye")) break;
//接受反馈信息
System.out.println("结果:" + br.readLine());
}
(4)关闭连接
try {
if(socket!=null)socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(pw!=null)pw.close();
if(bw!=null)
try {
bw.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
if(br!=null)br.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
3.2 服务端开发
(1)监听端口
ServerSocket server = null;
server = new ServerSocket(6666);
(2)建立连接
Socket socket = server.accept();//阻塞方法
(3)获得流对象
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//pw = new PrintWriter(socket.getOutputStream());
(4)传输信息
while(true) {
//System.out.println("*");
String line = br.readLine();
//System.out.println("**");
System.out.println(line);
if(line.equals("goodbye"))break;
bw.write(line.toUpperCase() + "\n");
bw.flush();
//pw.println(line.toUpperCase());
//pw.flush();
}
(5)关闭连接
try {
if(server!=null)server.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(bw!=null)
try {
bw.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
if(br!=null)br.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
3.3 完整代码
目的是将客户端字符串转为大写字符串后返回客户端显示。注意:
(1)在使用readLine()方法读取数据时:
读取数据:一行被视为由换行符(’\ n’),回车符(’\ r’)中的任何一个或随后的换行符终止。
返回结果:包含行的内容的字符串,不包含任何行终止字符,如果到达流末尾,则为null。
所以在往socket中写入信息时为了让readline能够顺利结束读取不阻塞在那,我们在使用write()时手动加上换行控制。
(2)在使用PrintWriter时,其println()方法可以自动打印换行,不用手动加入。
(1)Client端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
import javax.swing.text.AbstractDocument.BranchElement;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
public class SingleClient {
public static void main(String []args) {
Socket socket = null;
BufferedReader br = null;
BufferedWriter bw = null;
BufferedReader sin=null;
PrintWriter pw = null;
try {
socket = new Socket("127.0.0.1",6666);
System.out.println("连接成功...");
Scanner scanner = new Scanner(System.in);
sin = new BufferedReader(new InputStreamReader(System.in));
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
pw = new PrintWriter(socket.getOutputStream());
while(true) {
System.out.print("请输入字符串:");
String line = sin.readLine();
//String line = scanner.nextLine();
//bw.write(line + "\n");
//bw.flush();
pw.println(line);
pw.flush();
if(line.equals("goodbye")) break;
System.out.println("结果:" + br.readLine());
}
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
try {
if(socket!=null)socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(pw!=null)pw.close();
if(bw!=null)
try {
bw.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
if(br!=null)br.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
(2)Server端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import javax.naming.InitialContext;
public class SingleServer {
public static void main(String []args) {
ServerSocket server = null;
BufferedReader br = null;
BufferedWriter bw = null;
PrintWriter pw = null;
try {
server = new ServerSocket(6666);
System.out.println("服务器正在监听...");
Socket socket = server.accept();
System.out.println("接到请求...");
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
pw = new PrintWriter(socket.getOutputStream());
while(true) {
String line = br.readLine();
System.out.println(line);
if(line.equals("goodbye"))break;
bw.write(line.toUpperCase() + "\n");
bw.flush();
//pw.println(line.toUpperCase());
//pw.flush();
}
} catch (IOException e) {
System.out.println("端口被占用...");
}catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(server!=null)server.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(bw!=null)
try {
bw.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
if(br!=null)br.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
(3)运行结果
三. 多线程网络编程
我们知道一台服务器是不能每次只处理一个用户请求的。我们必须可以让服务器同时可以处理多个请求,这就涉及到了多线程。多线程改装代码如下:
1. MyThread
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class MyThread implements Runnable {
private Socket socket;
private int user;
public MyThread(Socket socket, int id) {
// TODO Auto-generated constructor stub
this.socket = socket;
user = id;
}
@Override
public void run() {
System.out.println("Server Thread " + user + " is running!");
BufferedReader br = null;
PrintWriter pw = null;
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
pw = new PrintWriter(socket.getOutputStream());
while (true) {
String line = br.readLine();
System.out.println("Thread " + user + " :" + line);
if (line.equals("goodbye"))
break;
pw.println(line.toUpperCase());
pw.flush();
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(br!=null)br.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
pw.close();
}
}
}
2. SingleServer
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.naming.InitialContext;
public class SingleServer {
public static void main(String[] args) {
ServerSocket server = null;
ExecutorService servicePool = Executors.newCachedThreadPool();
try {
server = new ServerSocket(6666);
System.out.println("服务器正在监听...");
int i = 0;
while (true) {
Socket socket = server.accept();// blocking
System.out.println("Receive " + (++i) + " Request");
servicePool.execute(new MyThread(socket, i));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3. SingleClient
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
import javax.swing.text.AbstractDocument.BranchElement;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
public class SingleClient {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Socket socket = null;
BufferedReader br = null;
BufferedWriter bw = null;
BufferedReader sin = null;
PrintWriter pw = null;
System.out.println("User " + i + " :");
try {
socket = new Socket("127.0.0.1", 6666);
System.out.println("连接成功...");
Scanner scanner = new Scanner(System.in);
sin = new BufferedReader(new InputStreamReader(System.in));
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
pw = new PrintWriter(socket.getOutputStream());
while (true) {
System.out.print("请输入字符串:");
String line = sin.readLine();
// String line = scanner.nextLine();
// bw.write(line + "\n");
// bw.flush();
pw.println(line);
pw.flush();
if (line.equals("goodbye"))
break;
System.out.println("结果:" + br.readLine());
}
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (socket != null)
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (pw != null)
pw.close();
if (bw != null)
try {
bw.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
if (br != null)
br.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
4. 运行结果
Server:
服务器正在监听...
Receive 1 Request
Server Thread 1 is running!
Thread 1 :aaaBBByQw
Thread 1 :goodbye
Receive 2 Request
Server Thread 2 is running!
Thread 2 :ttttTTTTbby
Thread 2 :goodbye
Receive 3 Request
Server Thread 3 is running!
.....
Client:
User 0 :
连接成功...
请输入字符串:aaaBBByQw
结果:AAABBBYQW
请输入字符串:goodbye
User 1 :
连接成功...
请输入字符串:ttttTTTTbby
结果:TTTTTTTTBBY
请输入字符串:goodbye
User 2 :
连接成功...
请输入字符串:.....