网络编程两大步骤
一、如何定位网络上的一台或者多台主机:
网络层主要负责网路主机的定位,数据传输传输的路由,由ip地址可以唯一的确定internet上的一台主机。
二、如何在定位之后进行数据的传输;
在传输层则提供面向应用的可靠(TCP)或者非可靠(UDP)的数据传输机制
对于客户端/服务器(C/S)结构。 即通信双方一方作为服务器等待客户提出请求并予以响应。
客户则在需要服务时向服务器提出申请。服务器一般作为守护进程始终运行,监听网络端口,
一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的
客户也能及时得到服务。
对于浏览器/服务器(B/S)结构。 客户则在需要服务时向服务器进行请求。服务器响应后及时返回,
不需要实时监听端口。
Tcp协议是一种面向链接的保证数据可靠传输的协议,得到的是一个顺序的无差错对的数据流,发送方和接收方创建socket连接的方式是,
服务端开启服务等待,等待客户端发送链接,创建链接后双方可进行发送或者接收操作。
UDP协议是一种无连接协议,每个数据包都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,
因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
在网络编程中,网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
Socket套接字是通信的对象,它支持TCP/IP协议的网络通信。它是网络通信过程中端点的抽象表示,
包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,
远地进程的协议端口。
Socket本质是编程接口(API),对TCP/IP协议进行封装提供借口给程序员使用,这就是Socket编程接口;
服务端使用ServerSocket绑定IP和端口,使用Accept监听端口是否有客户端发送连接请求,一旦有客户端发送连接请求,
服务端就回送连接信息,正式建立连接。Server端和Client端都可以通过Send,Write等方法与对方通信。
对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:
1、创建Socket;
2、打开连接到Socket的输入/出流;
3、按照一定的协议对Socket进行读/写操作;
4、关闭Socket。
基于TCP的socket实现
服务端
public class TcpSocketServer {
public static void main(String[] args) {
try {
// 创建服务端socket 绑定端口
ServerSocket serverSocket = new ServerSocket();
//绑定ip
serverSocket = new ServerSocket(8088, 10, InetAddress.getByName("192.168.0.110"));
// 创建客户端socket 用户下面接收客户端socket对象
Socket socket = new Socket();
System.out.println("等待客户端连接...");
//循环监听等待客户端的连接
while(true){
// 监听客户端 没有接受到数据才会停在此处 接受到往下执行
socket = serverSocket.accept();
//发送内容实现线程的创建
ServerThread thread = new ServerThread(socket);
thread.start();
//获取客户端的ip
InetAddress address=socket.getInetAddress();
System.out.println("当前链接的客户端的IP:"+address.getHostAddress());
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
服务端执行线程
public class ServerThread extends Thread{
private Socket socket = null;
public ServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
InputStream is=null;
InputStreamReader isr=null;
BufferedReader br=null;
OutputStream os=null;
// * PW支持两个直接对文件写操作的构造方法:
// * PrintWriter(File f)传文件名
// * PrintWriter(String s)传路径
// * PrintWriter给人一种可以直接对文件进行操作的假象
// * PW是一个高级流
// * 实际上PW包装了字节流、字符流和字符缓冲流。
// * PW负责自动行刷新
// * bw负责提高效率
// * osw负责读字符
// * fos负责写字节
// * 最后PW能够按行很快地把一个字符串变成字节写在文件中
PrintWriter pw=null;
try {
is = socket.getInputStream();
isr = new InputStreamReader(is);
br = new BufferedReader(isr);
String info = null;
while((info=br.readLine())!=null){
System.out.println("客户端:"+info);
}
//非关闭连接 仅关闭一方的发送状况
socket.shutdownInput();
os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write("服务器欢迎你1");
pw.flush();
} catch (Exception e) {
// TODO: handle exception
} finally{
//关闭资源
try {
if(pw!=null)
pw.close();
if(os!=null)
os.close();
if(br!=null)
br.close();
if(isr!=null)
isr.close();
if(is!=null)
is.close();
if(socket!=null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端
public class TcpSocketClient {
public static void client() throws InterruptedException {
try {
// 和服务器创建连接
Socket socket = new Socket("192.168.0.111", 8088);
// 要发送给服务器的信息
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("订单状态以改变");
//flush方法是用于将输出流缓冲的数据全部写到目的地。
//所以一定要在关闭close之前进行flush处理,即使PrintWriter有自动的flush清空功能
pw.flush();
socket.shutdownOutput();
// 从服务器接收的信息
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String info = null;
while ((info = br.readLine()) != null) {
System.out.println("我是客户端,服务器返回信息:" + info);
}
br.close();
is.close();
os.close();
pw.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
在执行顺序上,如果先执行了客户端会直接请求,但是因为服务端还没有执行监听端口等待连接,客户端会出现连接异常或者超时
所以最好先执行服务端在执行客户端。
服务端:
public class UdpSocketServer {
DatagramSocket socket = null;
public static void main(String[] args) {
try {
// 要接收的报文
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
// 创建socket并指定端口 ip
InetSocketAddress isa = new InetSocketAddress("192.168.0.110", 8088);
socket = new DatagramSocket(isa);
// 接收socket客户端发送的数据
while(true) {
//如果没有收到会一致阻塞
socket.receive(packet);
String receiveMsg = new String(packet.getData(), 0, packet.getLength());
System.out.println("消息长度" + packet.getLength());
System.out.println("客户端:" + receiveMsg);
msg=receiveMsg;
}
// 关闭socket
// socket.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
客户端
public class UdpSocketClient {
public static void Client(String ip,String msg) {
try {
// 要发送的消息
String sendMsg = msg;
System.out.println(sendMsg);
// 获取服务器的地址 得到本地主机地址
InetAddress addr = InetAddress.getByName(ip);
// 创建packet包对象,封装要发送的包数据和服务器地址和端口号
DatagramPacket packet = new DatagramPacket(sendMsg.getBytes(),
sendMsg.getBytes().length, addr, 8088);
// 创建Socket对象
DatagramSocket socket = new DatagramSocket();
// 发送消息到服务器
socket.send(packet);
System.out.println("继续发送");
// 关闭socket
socket.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
因为TCP的可靠性这里选用Tcp进行传输,传输原理,连接建立后,客户端发送请求传输的文件名或者绝对路径+文件名到服务端,
服务端就会根据文件名或者绝对路径创建文件对象,判断文件是否存在,存在则以字节或者字符形式传输至客户端,客户端在指定目录保存
客户端:
public class FileTcpSocketClient {
//image文件名
public static void Imageclient(String image) throws InterruptedException {
try {
// 和服务器创建连接
Socket socket = new Socket("192.168.0.106", 8089);
// ===================发送给服务器的信息=================
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write(image);
//flush方法是用于将输出流缓冲的数据全部写到目的地。
//所以一定要在关闭close之前进行flush处理,即使PrintWriter有自动的flush清空功能
pw.flush();
socket.shutdownOutput();
// ========================从服务器接收文件====================
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
FileOutputStream fos=null;
//文件名
String fileName = dis.readUTF();
System.out.println("文件名"+fileName);
//文件长度
long fileLength = dis.readLong();
System.out.println("文件大小"+fileLength);
//下面三行是保存的路径 可以自己设置
String staticPath = ClassUtils.getDefaultClassLoader().getResource("static/img").getPath();
String path1=staticPath.substring(1, staticPath.length());
System.out.println(path1);
//指定保存目录 判断是否存在在 不存在则创建
File directory = new File(path1);
if(!directory.exists()) {
directory.mkdir();
}
//与系统有关的默认名称分隔符。此字段被初始化为包含系统属性 file.separator 值的第一个字符。在 UNIX 系统上,此字段的值为 '/';在 Microsoft Windows 系统上,它为 '\'。
//directory.getAbsolutePath() 获取指定目录绝对路径
File file = new File(directory.getAbsolutePath() + File.separatorChar + fileName);
if(!file.exists()) {
fos = new FileOutputStream(file);
// 开始接收文件
byte[] bytes = new byte[1024];
int length = 0;
while ((length = dis.read(bytes, 0, bytes.length)) != -1) {
fos.write(bytes, 0, length);
fos.flush();
}
System.out.println("======== 文件接收成功 [File Name:" + fileName + "]");
}
is.close();
os.close();
pw.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端:
public class FileTcpSocketServer extends Thread{
@Override
public void run() {
try {
// 创建服务端socket
ServerSocket serverSocket = new ServerSocket();
//绑定ip 端口
serverSocket.bind(new InetSocketAddress("192.168.0.106",8089));
// serverSocket = new ServerSocket(8089, 3, InetAddress.getByName("127.0.0.1"));
// 创建客户端socket
Socket socket = new Socket();
System.out.println("等待客户端连接...");
//循环监听等待客户端的连接
while(true){
// 监听客户端 没有接受到数据才会停在此处
socket = serverSocket.accept();
FileThread thread = new FileThread(socket);
thread.start();
//获取客户端的ip
InetAddress address=socket.getInetAddress();
System.out.println("当前链接的客户端的IP:"+address.getHostAddress());
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
文件传输线程
public class FileThread extends Thread{
private Socket socket = null;
public FileThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
InputStream is=null;
InputStreamReader isr=null;
BufferedReader br=null;
OutputStream os=null;
FileInputStream fis=null;
DataOutputStream dos=null;
String fileName=null;
// * PW支持两个直接对文件写操作的构造方法:
// * PrintWriter(File f)传文件名
// * PrintWriter(String s)传路径
// * PrintWriter给人一种可以直接对文件进行操作的假象
// * PW是一个高级流
// * 实际上PW包装了字节流、字符流和字符缓冲流。
// * PW负责自动行刷新
// * bw负责提高效率
// * osw负责读字符
// * fos负责写字节
// * 最后PW能够按行很快地把一个字符串变成字节写在文件中
PrintWriter pw=null;
try {
is = socket.getInputStream();
isr = new InputStreamReader(is);
br = new BufferedReader(isr);
String info = null;
while((info=br.readLine())!=null){
System.out.println("客户端:"+info);
fileName=info;
}
//非关闭连接 仅关闭一方的发送状况
socket.shutdownInput();
os = socket.getOutputStream();
pw = new PrintWriter(os);
System.out.println(fileName);
//下面三行设置查找文件路径
String staticPath = ClassUtils.getDefaultClassLoader().getResource("static/img").getPath();
String path1=staticPath.substring(1, staticPath.length());
System.out.println(path1+fileName);
File file=new File(path1+fileName);
String responseMsg="请求的文件不存在";
System.out.println("7");
if(!file.exists())
{
System.out.println("不存在");
pw.write(responseMsg);
pw.flush();
}else {
System.out.println("存在");
fis = new FileInputStream(file);
// 文件名和长度
dos = new DataOutputStream(os);
dos.writeUTF(file.getName());
dos.flush();
dos.writeLong(file.length());
System.out.println(file.length());
dos.flush();
// 开始传输文件
System.out.println("======== 开始传输文件 ========");
byte[] bytes = new byte[1024];
int length = 0;
long progress = 0;
while ((length = fis.read(bytes, 0, bytes.length)) != -1) {
dos.write(bytes, 0, length);
dos.flush();
progress += length;
System.out.print("| " + (100 * progress / file.length()) + "% |");
}
System.out.println();
System.out.println("======== 文件传输成功 ========");
}
} catch (Exception e) {
// TODO: handle exception
} finally{
//关闭资源
try {
if(pw!=null)
pw.close();
if(os!=null)
os.close();
if(br!=null)
br.close();
if(isr!=null)
isr.close();
if(is!=null)
is.close();
if(socket!=null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}