一、概述
网络编程就是在网络通信协议下,实现两个或两个以上的设备之间的数据交换。我们要做的就是把数据发送到指定的位置,或者接收到指定的数据。
网络编程三要素:IP地址
+端口
+协议
二、相关名词解释
1、计算机网络
计算机网络就是把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使计算机间可以方便地互相传递信息、共享硬件、软件等资源。
计算机网络是现代通信技术与计算机技术相结合的产物,有以下主要功能:
- 资源共享
- 信息传输与集中处理
- 均衡负载与分布处理
- 综合信息服务
2、IP地址和端口号
网络通信表面上是计算机之间的通信,而内部则表现为不同计算机上应用程序间的通信。因此实现计算机间通信首先就要找到网络上的目标计算机,再找到该计算机上的相应程序;
要找到网络上的目标计算机,这就需要了解IP地址的概念。
IP地址
:一个32位二进制数表示,用于唯一地标识网络中的一个通信实体。(即设备的唯一标识)
但是由于IP地址不容易记忆,所以为了方便记忆,有创造了另外一个概念——域名
,例如baidu.com等。一个IP地址可以对应多个域名,一个域名只能对应一个IP地址。
在网络中传输的数据,全部是以IP地址作为地址标识,所以在实际传输数据以前需要将域名转换为IP地址,实现这种功能的服务器称之为DNS
服务器,也就是通俗的说法叫做域名解析器。
可以这样理解:IP相当于我们的QQ号,域名相当于这个QQ号的昵称(假设昵称唯一),我们在搜索QQ用户时一般是搜QQ号,当我们知道该QQ号的昵称时就可以直接搜昵称,记住昵称比记住QQ号要简单;
IP地址和域名解决了在网络中找到一个计算机的问题。接下来就需要找到该计算机中相应的应用程序,这就需要知道另一个概念——端口。
端口
:一个16位二进制数表示,用于唯一标识计算机中的一个应用程序。(端口号必须位于0-65535之间)
端口分类:
- 公认端口:0~1023 被预定义服务通信占用的端口(服务器端使用)
- 注册端口:1024~49151 分配给用户或应用程序(服务器端使用)
- 动态/私有端口:49152~65535 客户端进程运行时动态选择
3、通信协议:TCP/UDP
通信协议,就是计算机网络中连接和通信时所遵守的规则,它规定数据的传输格式、传输速率等。
常见的协议有TCP和UDP;
TCP
:传输控制协议
特点:
- 面向连接
- 提供可靠交付,实现无差错的数据传输
- 适用于重要数据的传输
- 传输大小无限制
TCP协议中,在发送数据的准备阶段,客户端与服务器端要进行三次交互,以保证连接的可靠;
三次握手:
①、客户端向服务器发出连接请求,等待服务器确认
②、服务器端向客户端回送一个响应,通知客户端接收到了连接请求
③、客户端再次向服务器端发送确认信息,确认连接
UDP
:用户数据报协议
特点:
- 无连接传输
- 不保证可靠交付
- 消耗资源小,通信效率高
- 适用于普通数据的传输,传输大小64KB以下
4、URL
统一资源定位器,可以由协议名、主机名、端口号、资源路径组成(很多时候端口都是隐藏的),网络中资源的唯一标识,用于定位网上的资源。
举一个例子
https:// www.baidu.com /index.html
http:HTTP超文本传输协议,也就是网页在网上传输的协议。
www:这个是服务器名
www.baidu.com:这个是域名,是用来定位网站的独一无二的名字。
/:这个是根目录,也就是说,通过网站名找到服务器,然后在服务器存放网页的根目录。
index.html:这个是根目录下的默认网页。
主机名、服务器名、域名、IP地址、网站名概念区分
网站名、主机名、域名在字符串形式上是一样的;
域名等效于IP地址,是网站中计算机的唯一标识;
域名是主机在公网环境下的标识,主机名是主机在局域网中的标识。
域名的范围大于主机名,一个域名下可以有多个主机名;
三、InetAddress类
InetAddress类主要表示IP地址;
InetAddress类没有提供公共的构造器,要通过以下静态方法获取InetAddress实例。
static InetAddress getByName(String host)
:给定主机名的情况下获取InetAddress实例。
static InetAddress getByAddress(byte[] IP)
:在给定IP地址的情况下,返回InetAddress实例
static InetAddress getLocalHost()
:返回本地主机。
其他方法:
String getHostAddress()
:返回IP地址字符串。
String getHostName()
:获取此IP地址的主机名/域名。
boolean isReachable(int timeout)
:测试是否可以到达该地址。
String getCanonicalHostName()
:获取此IP地址的完全限定域名。
class InetAddressTest{
public static void main(String[] args) throws UnknownHostException, MalformedURLException {
InetAddress address1 = InetAddress.getByName("www.baidu.com");//百度网址
System.out.println("InetAddress: "+address1);
System.out.println("域名: " +address1.getHostName());//获得主机名/域名
System.out.println("IP: "+address1.getHostAddress());//对象所含IP地址
System.out.println(address1.getCanonicalHostName());//IP地址的完全限定域名
InetAddress address2 = InetAddress.getLocalHost();//本机域名和IP地址
System.out.println("InetAddress: "+address2);
System.out.println("主机名: " +address2.getHostName());//获得主机名/域名
System.out.println("IP: "+address2.getHostAddress());//对象所含IP地址
System.out.println(address2.getCanonicalHostName());//IP地址的完全限定域名
byte[] addr = {36, (byte) 152,44,95};//超过128的要强制转换为byte类型,这里强制转换不会影响最终IP的值
InetAddress address3 = InetAddress.getByAddress(addr);
System.out.println("InetAddress: "+address3);
System.out.println("主机名: " +address3.getHostName());//获得主机名/域名
System.out.println("IP: "+address3.getHostAddress());//对象所含IP地址
System.out.println(address3.getCanonicalHostName());//IP地址的完全限定域名
byte b= (byte) 152;
System.out.println(b);//-104
}
}
四、URL类
class URLTest{
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("https://www.sojson.com/yasuoyihang.html");
System.out.println("域名: " + url.getHost());// www.sojson.com
System.out.println("端口: "+url.getPort());// -1
System.out.println("协议: "+url.getProtocol()); //https
System.out.println("路径: "+url.getPath());// /yasuoyihang.html
System.out.println("资源: "+url.getFile());// /yasuoyihang.html
}
}
五、网络编程步骤
网络编程都是由客户端和服务器端组成。
Ⅰ、客户端网络编程步骤
客户端(Client)是指网络编程中首先发起连接请求的程序。
①、建立网络连接
建立网络连接,实际上就是创建客户端的套接字对象socket,后面的一系列操作都与这个socket有关;
②、交换数据
连接建立以后,就可以交换数据了。交换数据按照"请求-响应"模型进行,由客户端发送一个请求数据到服务器,服务器反馈一个响应数据给客户端。
③、关闭连接
数据交换完成以后,需要关闭网络连接,释放程序占用的端口、内存等系统资源。
Ⅱ、服务器端网络编程步骤
服务器端(Server)是指在网络编程中被动等待连接的程序。
①、监听端口
服务器端属于被动等待连接,所以服务器端启动以后,只需要监听本地计算机的某个固定端口即可。
②、获取连接
当客户端连接到服务器端时,服务器端就可以获得一个连接,这个连接包含客户端的信息,例如客户端IP地址等等;
③、交换数据
服务器端获得客户端的连接后,可以进行数据交换。
④、关闭连接
同样数据交换完成以后,需要关闭系统资源。
六、TCP网络编程
TCP是一种可靠的网络协议,它在两端各建立一个Socket对象,形成虚拟链路,通过Socket产生的IO流进行数据传输。
Java中java.net.Socket
类代表客户端连接
,又叫客户端套接字(套接字是指两台设备间通信的端点,是包含IP和端口号的网络单位)
java.net.ServerSocket
类代表服务器端连接
,又叫服务器端套接字;
Socket方法
构造:Socket(String host,int port)
:创建指定主机名/IP地址、程序端口号的套接字对象;
getOutputStream()
:获取该套接字的字节输出流对象;
getInputStream()
:获取该套接字的字节输入流对象;
ServerSocket方法
构造:ServerSocket(String host,int port)
:创建绑定端口号的服务器端套接字对象;
accept()
:监听并获取请求该服务器的客户端套接字对象;
客户端
class CilentA {
public static void main(String[] args) {
String serverIP = "172.22.41.130";//服务器IP
int port = 10000;//端口号
Socket socket = null;
try {
//1、创建Socket对象
socket = new Socket(serverIP, port);//连接到IP地址是172.22.41.130的计算机的10000号端口
//Socket socket=new Socket("LAPTOP-VJJP1MBG",10000);//也可指定服务器主机名
//2、通过Socket对象获取输出流对象,并进行数据交流
//向服务器发送请求
OutputStream outputStream = socket.getOutputStream();
String content = "客户端:" + new Scanner(System.in).nextLine();//如果用next()就只能读取一个字符串,空格无效,nextLine()读取到换行为止,发送包括空格在内的内容
outputStream.write(content.getBytes());
//输出服务器的响应
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = 0;
len = inputStream.read(bytes);
System.out.println(new String(bytes, 0, len));
} catch (IOException e) {
e.printStackTrace();
} finally {
//3、关闭资源
if (socket != null) {//关闭了socket,由socket产生的输入输出流也会关闭
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务器端
class ServerA {
public static void main(String[] args) throws IOException {
int port = 10000;
ServerSocket serverSocket = null;
try {
//①、实例化serverSocket对象,通过该对象获取Socket对象
serverSocket = new ServerSocket(port);
Socket socket = serverSocket.accept();
//②、通过Socket对象获取输入流对象,并进行数据交流
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = 0;
len = inputStream.read(bytes);
System.out.println(new String(bytes, 0, len));
OutputStream outputStream = socket.getOutputStream();
String content = "服务器端:响应请求";
outputStream.write(content.getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭ServerSocket
if (serverSocket != null) {
serverSocket.close();
}
}
}
}
注意:要先启动服务器端程序,再启动客户端程序,否则会异常;
以上只是进行了一次数据交换,现实中一次肯定是不够的,我们可以复用Socket连接,实现建立一次连接,进行多次数据交换,做法是将数据交换的逻辑写到一个循环中。
客户端
class MulSocketClient {
public static void main(String[] args) {
Socket socket = null;
String serverIP = "127.0.0.1";
int port = 10000;
Scanner scan = new Scanner(System.in);
String context = null;
try {
socket = new Socket(serverIP, port);
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
byte[] b = new byte[1024];
int len = 0;
while (true) {
//发送数据
context = scan.nextLine();
outputStream.write(context.getBytes());
//接收数据
if ((len = inputStream.read(b)) != -1) {
String context2=new String(b, 0, len);
if(context2.equals("exit")){
break;
}
System.out.println("服务器反馈:"+context2);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭连接
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
服务器端
class MulSocketServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
int port = 10000;
Scanner scan = new Scanner(System.in);
try {
//建立连接
serverSocket = new ServerSocket(port);
System.out.println("服务器已启动:");
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
byte[] b = new byte[1024];
int len = 0;
String context = "服务器接收成功!";
while (true) {
if ((len = inputStream.read(b)) != -1) {
String context2=new String(b, 0, len);
if(context2.equals("退出886")){
outputStream.write("exit".getBytes());
break;
}
System.out.println("客户端发送内容为:" + context2);
}
//向客户端发送反馈内容
outputStream.write(context.getBytes());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭连接
if (serverSocket != null) {
serverSocket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
如何使服务器端支持多个客户端同时工作?
每当服务器端接收到一个连接时,启动一个专门的线程处理;
即服务器端代码修改:
class MulSocketServer2 {
public static void main(String[] args) {
ServerSocket serverSocket = null;
int port = 10000;
Scanner scan = new Scanner(System.in);
try {
//建立连接
serverSocket = new ServerSocket(port);
System.out.println("服务器已启动:");
while (true) {
Socket socket = serverSocket.accept();
System.out.println(socket.getInetAddress());
new Thread(new Runnable() {
@Override
public void run() {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
byte[] b = new byte[1024];
int len = 0;
while (true) {
if ((len = inputStream.read(b)) != -1) {
String context2 = new String(b, 0, len);
if (context2.equals("退出886")) {
outputStream.write("exit".getBytes());
break;
}
System.out.println("客户端发送内容为:" + context2);
}
//向客户端发送反馈内容
outputStream.write("服务器接收成功!".getBytes());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭连接
if (serverSocket != null) {
serverSocket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
七、UDP网络编程
UDP协议不可靠,它在通信实例两端各建立一个socket,只是发送、接收数据报对象。
DatagramSocket类
UDP编程中,用DatagramSocket
类实现服务器与客户端的套接字。
虽然UDP方式的网络通讯不需要建立专用的网络连接,但是毕竟还是需要发送和接收数据,以及接收数据时的监听。
类比于TCP中的网络连接,该类既可以用于实现客户端连接,也可以用于实现服务器端连接。
方法:
构造:
DatagramSocket()
:创建客户端连接对象,在该连接中,不指定服务器端的IP和端口,所以UDP方式的网络连接更像一个发射器,而不是一个具体的连接;
DatagramSocket(String port)
:制订端口号创建服务器端连接对象;
void send(DatagramPacket p)
:发送数据;
void receive(DatagramPacket p)
:接收数据;
DatagramPacket类
在UDP方式的网络编程中,在发送数据时,需要将需要发送的数据内容首先转换为byte数组,然后将数据内容、服务器IP和服务器端口号一起构造成一个DatagramPacket
类型的对象,再调用socket中的send方法发送该对象即可。
构造:
DatagramPacket(byte[] data,int data.length,InetAddress address,int serverPort)
而在接收数据时,首先构造一个数据缓冲字节数组,该数组用于存储接收的服务器端反馈数据,该数组的长度必须大于或等于服务器端反馈的实际有效数据的长度。然后以该缓冲数组为基础构造一个DatagramPacket数据包对象,最后socket的receive方法接收数据即可。接收到的服务器端反馈数据存储在DatagramPacket类型的对象内部,再通过调用DatagramPacket的getData()方法将对象中的内容读取出来;
构造:
DatagramPacket(byte[] b, int b.length)
byte[] getData()
:将DatagramPacket对象读取出来;
客户端:
class SimpleUDPClient {
public static void main(String[] args) {
DatagramSocket ds = null; //连接对象
DatagramPacket sendDp; //发送数据包对象
DatagramPacket receiveDp; //接收数据包对象
String serverHost = "127.0.0.1"; //服务器IP
int serverPort = 10001; //服务器端口号
try {
ds = new DatagramSocket();//建立连接
//初始化发送数据
String content = "发出请求"; //转换为字符串
byte[] data = content.getBytes();
InetAddress address = InetAddress.getByName(serverHost);
sendDp = new DatagramPacket(data, data.length, address, serverPort);//初始化发送包对象
ds.send(sendDp);//发送
//初始化接收数据
byte[] b = new byte[1024];
receiveDp = new DatagramPacket(b, b.length);
ds.receive(receiveDp);//接收
//读取反馈内容,并输出
byte[] response = receiveDp.getData();
int len = receiveDp.getLength();
String s = new String(response, 0, len);
System.out.println("服务器端:" + s);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭连接
ds.close();
} catch (Exception e) {
}
}
}
}
服务器端:
class SimpleUDPServer {
public static void main(String[] args) {
DatagramSocket ds = null; //连接对象
DatagramPacket sendDp; //发送数据包对象
DatagramPacket receiveDp; //接收数据包对象
final int PORT = 10001; //端口
try {
ds = new DatagramSocket(PORT);//建立连接,监听端口
System.out.println("服务器端已启动:");
//初始化接收数据
byte[] b = new byte[1024];
receiveDp = new DatagramPacket(b, b.length);
ds.receive(receiveDp);//接收
InetAddress clientIP = receiveDp.getAddress();//读取反馈内容,并输出
int clientPort = receiveDp.getPort();
byte[] data = receiveDp.getData();
int len = receiveDp.getLength();
System.out.println("客户端IP:" + clientIP.getHostAddress());
System.out.println("客户端端口:" + clientPort);
System.out.println("客户端:" + new String(data, 0, len));
//发送反馈
String response = "OK";
byte[] bData = response.getBytes();
sendDp = new DatagramPacket(bData, bData.length, clientIP, clientPort);
ds.send(sendDp); //发送
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭连接
ds.close();
} catch (Exception e) {
}
}
}
}
服务器端支持多个客户端同时工作
UDP方式的网络编程由于不建立虚拟的连接,所以在实际使用时和TCP方式存在很多的不同,最大的一个不同就是**“无状态”。该特点指服务器端只是接收信息而无法识别出是谁发送的**,所以回送信息给客户端时的需要加上客户端IP、端口号(通过getAddress()、getPort()方法获取),因此也就不需要使用多线程就能实现服务器端支持多个客户端同时工作的目的;
客户端:
class MulUDPClient {
public static void main(String[] args) {
DatagramSocket ds = null; //连接对象
DatagramPacket sendDp; //发送数据包对象
DatagramPacket receiveDp; //接收数据包对象
String serverHost = "127.0.0.1"; //服务器IP
int serverPort = 10001; //服务器端口号
try {
ds = new DatagramSocket();//建立连接
while (true) {
String content = new Scanner(System.in).nextLine();
byte[] data = content.getBytes();
InetAddress address = InetAddress.getByName(serverHost);
sendDp = new DatagramPacket(data, data.length, address, serverPort);//初始化发送包对象
ds.send(sendDp);//发送
//初始化接收数据
byte[] b = new byte[1024];
receiveDp = new DatagramPacket(b, b.length);
ds.receive(receiveDp);//接收
//读取反馈内容,并输出
byte[] response = receiveDp.getData();
int len = receiveDp.getLength();
String s = new String(response, 0, len);
if(s.equals("exit")){
break;
}
System.out.println("服务器端:" + s);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭连接
ds.close();
} catch (Exception e) {
}
}
}
}
服务器端:
class MulUDPServer {
public static void main(String[] args) {
DatagramSocket ds = null; //连接对象
DatagramPacket sendDp; //发送数据包对象
DatagramPacket receiveDp; //接收数据包对象
final int PORT = 10001; //端口
try {
ds = new DatagramSocket(PORT);//建立连接,监听端口
System.out.println("服务器端已启动:");
while (true) {
//接收
byte[] b = new byte[1024];
receiveDp = new DatagramPacket(b, b.length);
ds.receive(receiveDp);//接收
InetAddress clientIP = receiveDp.getAddress();
//读取反馈内容,并输出
int clientPort = receiveDp.getPort();
byte[] data = receiveDp.getData();
int len = receiveDp.getLength();
String s = new String(data, 0, len);
System.out.println("客户端:" + s);
//发送反馈
String response = "接收成功";
if(s.equals("exit")){
response="exit";
}
byte[] bData = response.getBytes();
sendDp = new DatagramPacket(bData, bData.length, clientIP, clientPort);
ds.send(sendDp); //发送
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//关闭连接
ds.close();
} catch (Exception e) {
}
}
}
}