这里写目录标题
TCP/UDP网络编程知识总结
网络通信基础知识
概念:两台或多台设备进行数据的传输。
网络
概念:两台或多台设备通过一定物理设备连接来构成网络。
网络的分类:
- 局域网:(范围逐渐变大),一个小型区域
- 城域网:一个城市
- 广域网:一个国家(典型代表是局域网)
网络编程的目的
网络编程的目的:直接或间接地通过网络协议与其他计算机实现数据交换进行通讯
要想实现网络编程,需要关注两个问题:
1.如何准确定位网络上的一台或多台主机,定位主机上的特定应用
2.找到主机以后怎么进行可靠高效的数据传输
IP地址
- 概念:唯一标识主机的。
- 查看ip地址:ipconfig
- 在代码中查看本机ip :InetAddress.getLocalHost()
- IP地址表示:4个字节(32位)表示,一个字节的范围是0~255;
- IP地址的格式:网络地址+主机地址。
- IPV6地址(解决了IPV4在网络地址上的资源有限问题),IPV6可以实现多种接入设备连入互联网。 IPV6是由128位(128/16 = 8字节)表示一个地址。
域名
- www.baidu.com着就是百度的域名,他可以理解成是IP地址的映射(因为ip地址不容易记忆,所以就有域名的出现)
端口
概念:用于标识计算机上某个特定的网路程序。
端口号:范围0~65535.
电脑浏览器如果想访问一个网站需要通过(IP+端口号)
在网络开发中最好不要使用0~1024号端口,因为这些端口可能已经被其他服务器使用了
- 一个服务要接收和发送数据的话,需要有一个端口。端口就类似于人的耳朵,我们要听懂别人的话,必须要有耳朵
网络通信协议
TCP协议于UDP协议的区别
- TCP类似于打电话(稳定性传输)
- UDP类似于发短信(不可靠的)
TCP 协议:传输控制协议
就是确定是否连接成功,需要进行三次确认。
- TCP协议的优点:需要进行三次握手(是可靠的)
- 缺点就是效率低。
UDP协议:用户数据协议
- UDP协议无法保证传输的数据一定被接收到
- UDP协议就是没有确认对方是否能够接收到消息,就直接传输信息
- 比如说:你发送信息给别人,但是这个电话号码可能停机了或者说注销了,你不能确保别人能接收消息,类似于UDP协议
假如说现在有一个人也想要打电话给kim,由于kim这个时候在和tom打电话,他们两个如果电话没有挂掉,这个人的电话是打不通的,就类似于TCP协议要释放已经建立的连接
tom发信息给kim,另外一个人同样也可以发信息给kim,就类似于UDP协议不需要释放资源
简单的API使用
InetAddress
一个InetAddress类的对象就当于是一个IP地址
相关方法
getByName(String host) 、 getLocalHost()
两个常用方法:getHostName() 获取域名 / getHostAddress() 获取主机地址
实例
代码
package 网络编程;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class API {
public static void main(String[] args) throws UnknownHostException {
//获取本机的IntetAddress的对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
//根据指定主机名,获取InetAddress对象
InetAddress host1 = InetAddress.getByName("LAPTOP-612T08NP");
System.out.println(host1);
//根据域名返回InetAddress对象,比如百度的域名
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println(host2);
//根据域名获取Ip
String hostAddress = host2.getHostAddress();
System.out.println("host2 对应的IP = "+ hostAddress);
}
}
常用的方法就是上面四种,分别来获取IP和对应的域名。
Socket理解
- Socket就类似于通信的通道,发送数据是通过socket
- TCP编程:可靠的,但是效率低
- UDP编程:不可靠的,但是效率高
简单理解就是数据两端的插头,让客户端与服务器端可以很好的取得连接。目的就是为了通信,而通信的目的就是用来发送数据和接收数据。
TCP网络通信案例
案例一:
使用字节流。编写一个服务器端和一个客户端,服务器端在9999端口进行监听,客户端连接到服务器端发送一个“hello world”然后退出,服务器端接收到客户端的信息输出并退出。
服务端:
package 网络编程;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketTCPServer {
public static void main(String[] args) throws IOException {
//在本机9999端口接听,等待连接
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务器,在9999端口监听,等待连接。。。");
//当没有客户端连接999端口时,程序会阻塞,等待连接
//如果有客户端连接。则会返回Socket对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("socket = "+socket.getClass());
}
}
此时9999端口已经占用了,如果再次点击运行,程序会报错。
案例二:TCP编写(字节读写)
案例要求:
客户端:
- 连接服务端(IP,端口)
- 连接上后,生成Socket,通过socket.getOutputStream()函数创建输出流
- 通过输出流,写入数据到数据通道(服务器端来接收)
package 网络编程;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class SocketTCPClient {
public static void main(String[] args) throws IOException {
//连接TCP端(ip,端口)
//连接本机9999端口,如果连接成功,返回socket对象
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
System.out.println("客户端 socked返回"+socket.getClass());
//连接成功后,生成socket对象,通过socket.getOutputStream()(输出流)输出hello server
//得到和socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//通过输出流,写入数据到数据通道
outputStream.write("hello server".getBytes());
//关闭流对象的socket,
outputStream.close();
socket.close();
System.out.println("客户端退出!");
}
}
服务端
- 在本机的9999 端口监听,等待连接
- 当没有客户端连接9999 端口时,程序会阻塞,等待连接
- 通过socket.getInputStream();函数来读取客户端写入到数据通道的数据,然后显示出来。
package 网络编程;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketTCPServer {
public static void main(String[] args) throws IOException {
//在本机9999端口接听,等待连接
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务器,在9999端口监听,等待连接。。。");
//当没有客户端连接999端口时,程序会阻塞,等待连接
//如果有客户端连接。则会返回Socket对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务器 socket = "+socket.getClass());
//通过输入流来读取客户端传来的数据
InputStream inputStream = socket.getInputStream();
//IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1){
System.out.println(new String(buf,0,readLen));//根据
}
//关闭流
inputStream.close();
socket.close();
serverSocket.close();
}
}
注意事项
- 运行程序的时候一定是先运行server段,再运行client客户端
案例三:
服务端
package 网络编程;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketTCPServer {
public static void main(String[] args) throws IOException {
//在本机9999端口接听,等待连接
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务器,在9999端口监听,等待连接。。。");
//当没有客户端连接999端口时,程序会阻塞,等待连接
//如果有客户端连接。则会返回Socket对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务器 socket = "+socket.getClass());
//通过输入流来读取客户端传来的数据
InputStream inputStream = socket.getInputStream();
//IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1){
System.out.println(new String(buf,0,readLen));//根据
}
//获取socket相关联的输出流
OutputStream outputStream = socket.getOutputStream();
//写入数据到数据通道
outputStream.write("hello,client".getBytes());
//关闭流
inputStream.close();
socket.close();
serverSocket.close();
//关闭输出流
outputStream.close();
}
}
- server端有输出,但是没有退出,好像是卡到了什么地方。clint并没有接收到server端发送的数据。
- 如何解决上面的问题,就是给两个端设置结束标志。socket。shutdownOutput();关闭了输出的结束。
代码优化
服务端:
package 网络编程;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketTCPServer {
public static void main(String[] args) throws IOException {
//在本机9999端口接听,等待连接
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务器,在9999端口监听,等待连接。。。");
//当没有客户端连接999端口时,程序会阻塞,等待连接
//如果有客户端连接。则会返回Socket对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("服务器 socket = "+socket.getClass());
//通过输入流来读取客户端传来的数据
InputStream inputStream = socket.getInputStream();
//IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1){
System.out.println(new String(buf,0,readLen));//根据
}
//获取socket相关联的输出流
OutputStream outputStream = socket.getOutputStream();
//写入数据到数据通道
outputStream.write("hello,client".getBytes());
//设置结束标志
socket.shutdownOutput();
//关闭流
inputStream.close();
socket.close();
serverSocket.close();
//关闭输出流
outputStream.close();
}
}
客户端:
package 网络编程;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class SocketTCPClient {
public static void main(String[] args) throws IOException {
//连接TCP端(ip,端口)
//连接本机9999端口,如果连接成功,返回socket对象
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
System.out.println("客户端 socked返回"+socket.getClass());
//连接成功后,生成socket对象,通过socket.getOutputStream()(输出流)输出hello server
//得到和socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//通过输出流,写入数据到数据通道
outputStream.write("hello server".getBytes());
//设置结束标志
socket.shutdownOutput();
//获取和socket关联的输入流,读取数据(字节),并显示
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1){
System.out.println(new String(buf,0,readLen));//根据
}
//关闭流对象的socket,
outputStream.close();
socket.close();
System.out.println("客户端退出!");
//关闭输入流
inputStream.close();
}
}
案例四:文件的传输
分析:将客户端(Client)的图片上传到服务器(server),本质就是一个图片上传。同时服务器要恢复消息。
上传逻辑
- 先将磁盘的图片通过文件输入流上传到程序中,程序中使用字节数组来保存。然后程序中使用socket将其传输到服务端。
代码
代码都写了详细的注释
- stramutils工具类,主要是讲文件的二进制码转换成字节,存到数组里面。
package upload;
/**
* 工具类的作用:
* 处理各种情况的用户输入,并且能按照程序员对的需求,得到用户的控制台输入
*/
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Scanner;
import java.util.*;
public class StreamUtils {
//静态属性
private static Scanner scanner = new Scanner(System.in);
public static byte[] streamToByteArray(InputStream is) throws Exception{
ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
byte[] b = new byte[1024];//字节数组
int len;
while((len=is.read(b))!=-1){//循环读取
bos.write(b,0, len);//把读取到的数据,写入bos
}
byte[] array = bos.toByteArray();//然后将bos转成字节数组bos.close();
return array;
}
/**
* 功能:读取键盘输入的一个菜单项,值:1-5范围
*/
public static char readMenuSelection() {
char c;
for (; ; ) {
String str = readKeyBoard(1, false);
c = str.charAt(0);//将字符串转换为字符
if (c != '1' && c != '2' && c != '3' && c != '4' && c != '5') {
System.out.println("选择错误,请重新输入:");
} else break;
}
return c;
}
/**
* 功能:读取键盘输入的一个字符
*/
public static char readChar() {
String str = readKeyBoard(1, false);//就是一个字符
return str.charAt(0);
}
/**
* 功能;读取键盘输入的一个字符,如果直接回车,则返回指定的默认值
* defaultValue 指定的默认值
* return 默认值或者输入的字符
*/
public static char readChar(char defaultValue) {
String str = readKeyBoard(1, true);//要么是空字符,要么??
return str.length() == 0 ? defaultValue : str.charAt(0);
}
/**
* 读取键盘输入的整型,长度少于两位
* return 整数
*/
public static int readInt() {
int n;
for (; ; ) {
String str = readKeyBoard(2, false);
try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.println("选择错误,请重新输入:");
}
}
return n;
}
/**
* 功能:读取键盘输入的整数或者默认值,如果直接回车,则返回默认值
* defaultValue 指定的默认值
* return 整数或者默认值
*/
public static int readInt(int defaultValue) {
int n;
for (; ; ) {
String str = readKeyBoard(2, false);
if (str.equals("")) {
return defaultValue;
}
try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.println("选择错误,请重新输入:");
}
}
return n;
}
/**
* 功能:读取键盘输入的指定长度的字符串
* limit:限制的长度
* return:指定长度的字符串
*/
public static String readString(int limit) {
return readKeyBoard(limit, false);
}
/**
* 功能:读取键盘输入的指定长度的字符串或者默认值,如果直接回车,返回默认值
* limit:限制的长度
* defaultValue 指定的默认值
* return 指定长度的字符串
*/
public static String readString(int limit, String defaultValue) {
String str = readKeyBoard(limit, true);
return str.equals("") ? defaultValue : str;
}
/**
* 功能:读取键盘输入的确认选项,Y或N
* 将小的功能,封装到一个方法中
* return Y/N
*/
public static char readConfirmSelection() {
System.out.println("请输入你的选择(Y/N)");
char c;
for (; ; ) {//无限循环
//在这里,将接收到字符,转换程大写字母
//y=>Y,n=>N
String str = readKeyBoard(1, false).toUpperCase();
c = str.charAt(0);
if (c == 'Y' || c == 'N') {
break;
} else {
System.out.print("选择错误,请重新输入:");
}
}
return c;
}
/**
* 功能:读取键盘输入,如果输入为空,或者输入大于limit的长度,就会提示重新输入
*/
private static String readKeyBoard(int limit, boolean blankReturn){
//定义了字符串
String line="";
//scanner.hasNextLine()判断有没有下一行
while (scanner.hasNextLine()){
line=scanner.nextLine();//读取下一行
//如果Line.length=0,即用户没有输入任何内容,直接回车
if(line.length()==0){
if(blankReturn) return line;
else continue;//如果blackRuturn=false,不接受空串,必须输入内容
}
//如果用户输入的内容大于了limit,就提示重写输入
//如果用户输入的内容哦个>0<=limit,我就接受
if(line.length()<1 || line.length()>limit){
System.out.println("输入长度(不大于 "+ limit + ")错误,请重新输入:");
continue;
}
break;
}
return line;
}
}
- server端
package upload;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPFileUploadServer {
public static void main(String[] args) throws Exception {
//服务端在本机监听8888端口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端在8888端口监听");
//等待客户端连接,如果有客户端连接,就会有一个socke;
Socket socket = serverSocket.accept();
//读取客户端发送的数据
//通过socket得到一个输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
//读取
byte[] bytes = StreamUtils.streamToByteArray(bis);
//完成将得到的byte数组,写入到指定的路径,就得到一个文件了
String destFilePath = "D:\\2111.png";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
bos.write(bytes);//把字节数组写进去
bos.close();
//关闭其他资源
bis.close();
socket.close();
serverSocket.close();
}
}
client端:
package upload;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class TCPFiledUploadClient {
public static void main(String[] args) throws Exception {
//客户端代码
//客户端连接服务端,得到socket对象
Socket socket = new Socket(InetAddress.getLocalHost(),8888);
//创建读取磁盘文件的输入流
String filePath = "D:\\111.png";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
//bytes 就是filepath对应的字节数组
byte[] bytes = StreamUtils.streamToByteArray(bis);
//通过socket获取输出流,将bit数据发送给服务端
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
bos.write(bytes);//将文件对应的字节数组的内容,写入到数据通道
//这里要设置一个结束标志
bis.close();//关闭输入流
socket.shutdownOutput();//设置写入数据的结束标记。这个很关键
//关闭相关的流
bos.close();
socket.close();
}
}
注意事项
效果
优化
图片复制以后,会给客户端返回一句话“收到图片”;
客户端接收并打印
netstat指令
- Listening 表示某个端口在监听。
- 如果有一个外部程序(客户端)连接到该端口,就会显示一条连接信息。
- ctrl+c退出程序
UDP网络通信
基本流程
- 核心的两个类:DatagramSocket与DatagramPacket
- 建立发送端和接收端(没有服务端和客户端这一说法)
- 发送数据前,建立数据包DatagramPacket对象
- 调用DatagramSocket的发送,DatagramPacket对象
- 关闭DatagramSocket
UDP网络编程案例
- 接收端A接收数据的代码
package UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
//创建一个DatagramSocket对象,准备在9999的端口接收数据
DatagramSocket socket = new DatagramSocket(9999);
//构建一个DatagramPacket对象,准备接收数据
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//调用接收方法,
//当有数据包发送到本机的9999端口时,就会接收到数据,如果没有数据发送到9999端口,就会阻塞
System.out.println("接收端A 等待接收数据");
socket.receive(packet);
//可以把数据packet进行拆包,取出数据并显示
int length = packet.getLength();//实际接收到的数据长度
byte[] data = packet.getData();//接收到的数据
String s = new String (data,0,length);
System.out.println(s);
//关闭资源
socket.close();
}
}
注意事项
- DatagramSocket既可以接收数据也可以发送数据
发送数据端B发送代码:
package UDP;
import java.io.IOException;
import java.net.*;
public class UDPSenderB {
public static void main(String[] args) throws IOException {
//创建DatagramSocket对象,准备发送和接收数据
DatagramSocket socket = new DatagramSocket(9998);
//将要发送的数据封装到DatagramPacket对象
byte[] data = "hello 渣渣鑫".getBytes();
//DatagramPacket(字节数组(要发送的内容),长度,主机ip,端口)
DatagramPacket packet = new DatagramPacket(data,data.length, InetAddress.getByName("192.168.56.1"),9999);
//发送
socket.send(packet);
socket.close();
}
}