网络编程的基础概念
1.端口
用来标识计算机上的网络应用程序,端口是一个虚拟的概念,是16位二进制正整数,故取值为0-65535(十进制)。端口一共可以在一台计算机上存在12w+(2^17)个,因为端口属于传输层,传输层有两个重要协议(TCP和UDP),所以两边各65536个(2^16)。一般来说端口够用,因为计算机上用不到12w多个网络应用。
端口0-1023为知名端口,也叫公认端口,端口1023-49151为注册端口, 比如3306(MySQL),8080(Tomcat),端口49152-65535为动态端口或者私有端口。
Windows查看所有使用的端口: cmd下
netstat -ano
2.ip地址
IP地址是网络中设备的唯一标识,IP地址分为两大类
- IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址的长32bit,也就是4个字节。例如一个采用二进制形式的地址是“11000000 10101000 00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“ . ”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做 “点分十进制法”,这显然比1和0容易记得多
- IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128bit地址长度,每16个字节一组,分成8组十六进制数,这就解决了网络地址资源数量不够的问题
常用DOS命令
ipconfig:查看本机IP地址
ping IP地址:检查网络是否连通
示例:
特殊IP地址
- 127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
- 190.168.0.0 – 192.168.255.255:私有地址,属于非注册地址,专门为组织机构内部使用
3.子网掩码
子网掩码的概念
子网掩码(subnet mask)又叫网络掩码、地址掩码、子网络遮罩,它是一种用来指明一个IP地址的哪些位标识的是主机所在的子网,以及哪些位标识的是主机的位掩码。子网掩码不能单独存在,它必须结合IP地址一起使用。子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部分。
子网掩码的作用
- 子网掩码是一个32位地址,是与IP地址结合使用的一种技术。它的主要作用有两个,一是用于屏蔽IP地址的一部分以区别网络标识和主机标识,并说明该IP地址是在局域网上,还是在远程网上。二是用于将一个大的IP网络划分为若干小的子网络。
- 使用子网是为了减少IP的浪费。因为随着互联网的发展,越来越多的网络产生,有的网络多则几百台,有的只有区区几台,这样就浪费了很多IP地址,所以要划分子网。使用子网可以提高网络应用的效率。
- 通过IP 地址的二进制与子网掩码的二进制进行与运算,确定某个设备的网络地址和主机号,也就是说通过子网掩码分辨一个网络的网络部分和主机部分。子网掩码一旦设置,网络地址和主机地址就固定了。子网一个最显著的特征就是具有子网掩码。与IP地址相同,子网掩码的长度也是32位,也可以使用十进制的形式。例如,为二进制形式的子网掩码:1111 1111.1111 1111.1111 1111.0000 0000,采用十进制的形式为:255.255.255.0。
- 通过计算机的子网掩码判断两台计算机是否属于同一网段的方法是,将计算机十进制的IP地址和子网掩码转换为二进制的形式,然后进行二进制"与"(AND)计算(全1则得1,不全1则得0),如果得出的结果是相同的,那么这两台计算机就属于同一网段。
socket网络编程
Socket编程是在TCP/IP上的网络编程,Java的网络编程主要涉及到的内容是Socket编程。Socket,套接字,就是两台主机之间逻辑连接的端点。TCP/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。Socket是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议、本地主机的IP地址、本地进程的协议端口、远程主机的IP地址、远程进程的协议端口。
Socket,实际上是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。
Socket整体流程
示例:下面是一个客户端和服务器端进行数据交互的简单例子,客户端输入正方形的边长,服务器端接收到后计算面积并返回给客户端,通过这个例子可以初步对Socket编程有个把握。
- 服务器端
-
import java.io.*; import java.net.ServerSocket; import java.net.Socket; public class TestSockeServer { public static void main(String[] args) { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(8080); while (true) { System.out.println("服务器开始运行了,等待客户端连接上来"); Socket socket = serverSocket.accept(); System.out.println(socket.getInetAddress().getHostName()); System.out.println(socket.getLocalAddress()); System.out.println(socket.getLocalPort()); // 推送一条消息过去 String msg = "欢迎您进入我们的QQ"; OutputStream os = socket.getOutputStream(); PrintWriter out = new PrintWriter(os, true); out.println(msg); } } catch (IOException e) { e.printStackTrace(); } finally { try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
-
客户端
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
public class TestSocket {
public static final String DES_IP = "127.0.0.1";
public static final int DES_PORT = 8080;
public static void main(String[] args) {
Socket socket = new Socket();
try {
socket.connect(new InetSocketAddress(DES_IP, DES_PORT));
System.out.println("成功连接到服务器");
InputStream is = socket.getInputStream();
String msg = null;
BufferedReader br = new BufferedReader(new InputStreamReader(is));
while ((msg = br.readLine()) != null ) {
System.out.println("接受到服务器端传递过来的数据,数据是:"+ msg);
}
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("我是一个客户端,我上来了,服务器,你好着吗?");
out.println("第二条数据");
} catch (IOException e) {
e.printStackTrace();
}
}
}
效果:
服务器开始运行了,等待客户端连接上来
127.0.0.1
/127.0.0.1
8080
成功连接到服务器
接受到服务器端传递过来的数据,数据是:欢迎您进入我们的QQ
UDP网络编程
- UDP编程和TCP编程相比,UDP编程就简单得多,因为UDP没有创建连接,数据包也是一次收发一个,所以没有流的概念。
- 在Java中使用UDP编程,仍然需要使用Socket,因为应用程序在使用UDP时必须指定网络接口(IP)和端口号。
- 注意:UDP端口和TCP端口虽然都使用0~65535,但他们是两套独立的端口,即一个应用程序用TCP占用了端口1234,不影响另一个应用程序用UDP占用端口1234。
- 在服务器端,使用UDP也需要监听指定的端口。Java提供了DatagramSocket来实现这个功能。
tcp和udp的区别:
Tcp是面向连接的,udp是无连接的;
Tcp是可靠的,udp是不可靠的;
Tcp只支持点对点通信,udp支持一对一,一对多,多对多的通信模式;
Tcp是面向字节流的,udp是面向报文的;
Tcp有拥塞控制机制;udp没有拥塞控制,适合媒体通信;
Tcp首部开销(20个字节)比udp的首部开销(8个字节)要大;
代码示例:
服务器端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Scanner;
public class TestUDPSever {
public static final String DES_IP = "127.0.0.1";
public static final int DES_PORT = 9999;
public static void main(String[] args) {
DatagramSocket socket = null;
Scanner sc = null;
try {
socket = new DatagramSocket(8888);
// 启动一个接收到线程,来进行数据接受
UDPRreceiveMsg02 udpRreceiveMsg = new UDPRreceiveMsg02(socket);
udpRreceiveMsg.start();
sc = new Scanner(System.in);
while (true) {
System.out.print(">");
String msg = sc.nextLine();
byte[] buf = msg.getBytes();
InetSocketAddress address = new InetSocketAddress(DES_IP, DES_PORT);
DatagramPacket pd = new DatagramPacket(buf, 0, buf.length, address);
socket.send(pd);
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
socket.close();
}
}
}
class UDPRreceiveMsg02 extends Thread {
private DatagramSocket socket;
public UDPRreceiveMsg02(DatagramSocket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
receiveMsg();
} catch (IOException e) {
e.printStackTrace();
}
}
private void receiveMsg() throws IOException {
while (true) {
byte[] buf = new byte[1024];
DatagramPacket pd = new DatagramPacket(buf, 0, buf.length);
// 等待接受数据
socket.receive(pd);
// 将数据转换出来
byte[] msg = pd.getData();
String sendIP = pd.getAddress().getHostAddress();
int sendPort = pd.getPort();
System.out.println("接受到" + sendIP + ":" + sendPort + "发送过来的数据,数据是:" + new String(msg));
}
}
}
客户端
import java.io.IOException;
import java.net.*;
public class TestUDP {
public static void main(String[] args) {
DatagramSocket datagramSocket = null;
try {
// 注意,创建一个udp socket对象,默认占据了8080
datagramSocket = new DatagramSocket(8080);
String msg = "你好啊,socket对象";
byte[] bytes = msg.getBytes();
// 数据报对象
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, new InetSocketAddress("127.0.0.1", 8888));
// 发送数据
datagramSocket.send(packet);
System.out.println("数据已经成功发送");
} catch (IOException e) {
e.printStackTrace();
} finally {
datagramSocket.close();
}
}
}
效果显示:
TCP网络编程
特点:传输数据安全,可靠,面向连接的,点对点,长连接,效率相对较低
-
TCP/IP 模型。一组用于实现网络互连的通信协议,将协议分成四个层次。应用层、传输层、网络层、网络接口层。
-
TCP,Transmission Control Protocol,传输控制协议。是一种面向连接的、可靠的、基于字节流的传输层通信协议。数据大小无限制。建立连接的过程需要三次握手,断开连接的过程需要四次挥手。
-
UDP,User Datagram Protocol,用户数据报协议。是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,每个包的大小64KB。
-
IP协议,Internet Protocol Address,互联网协议地址/网际协议地址。分配给互联网设备的数字标签(唯一标识)。IP地址分为两种。IPV4,分成4段8位的二进制数;IPV6,分成8段十六进制数。
-
Port,端口号。在通信实体上进行网络通讯的程序的唯一标识。
InetAddress 类。
public class Test {
public static void main(String[] args) throws UnknownHostException {
// 获得本地主机地址对象
System.out.println(InetAddress.getLocalHost());
// 根据主机名称获得地址对象
System.out.println(InetAddress.getByName("baidu.com"));
// 获得所有相关地址对象
InetAddress[] all = InetAddress.getAllByName("baidu.com");
for (InetAddress inetAddress : all) {
System.out.println(inetAddress);
}
for (InetAddress inetAddress : all) {
System.out.println("获取IP地址字符串 " + inetAddress.getHostAddress());
// 获得IP地址主机名
System.out.println("获取IP地址主机名 " + inetAddress.getHostName());
}
}
}
结果:
KING-wjh/192.168.56.1
baidu.com/39.156.66.10
baidu.com/39.156.66.10
baidu.com/110.242.68.66
获取IP地址字符串 39.156.66.10
获取IP地址主机名 baidu.com
获取IP地址字符串 110.242.68.66
获取IP地址主机名 baidu.com
封装一个关闭流的通用方法。将 Closeable 接口作为泛型参数。
public class PublicMethod {
public PublicMethod() {
}
public static void closeAll(Closeable... hh) {
for (Closeable closeable : hh) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
TCP 编程实现客户端发送数据给服务器端。
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
// 服务端
public class TestServer {
public TestServer() {
}
public static void main(String[] args) throws IOException {
ServerSocket ssocket = new ServerSocket(9527);
// 与客户端建立连接
Socket socket = ssocket.accept();
System.out.println("等待客户端输入");
InputStream ins = socket.getInputStream();
byte[] bt = new byte[1024];
// 接收客户端发来的数据
int len = ins.read(bt); // 阻塞,接收到数据才往下走
System.out.println(new String(bt, 0, len));
OutputStream ous = socket.getOutputStream();
// 向客户端发送数据
ous.write("不在。".getBytes());
PublicMethod.closeAll(ous, ins, socket, ssocket);
}
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
// 客户端
public class TestClient {
public TestClient() {
}
public static void main(String[] args) throws UnknownHostException, IOException {
// 本地 ip 用来测试
Socket socket = new Socket("127.0.0.1", 9527);
System.out.println("客户端已连接");
OutputStream os = socket.getOutputStream();
// 向服务端发送数据
os.write("在吗?".getBytes());
InputStream ins = socket.getInputStream();
byte[] bt = new byte[1024];
// 接收服务端发来的数据
int len = ins.read(bt); // 阻塞,接收到数据才往下走
System.out.println(new String(bt, 0, len));
PublicMethod.closeAll(ins, os, socket);
}
}
效果显示: