文章目录
1、写在前面
软件结构:
- CS 结构:客户端-服务器结构
- BS 结构:浏览器-服务器结构
网络通信协议:
- TCP/IP协议:因特网连接协议
tcp/ip协议分为4层,链路层、传输层、网络层和应用层。每一层完成不同的功能,共同作用完成网络传输服务。传输层协议有TCP、UDP等。
- UDP: 面向无连接的通信协议,例如视频会议、qq发送文字。大小被限制在64k以内。
- TCP: 面向连接的通信协议,每次连接需要经过三次握手,例如下载文件、网页浏览。
ip协议:
- ip 编址:ipv4是一个32位的二进制数,常用a.b.c.d这样的四个字节表示。
- ip 协议:是TCP/IP体系中的网络层协议。对上可载送传输层各种协议的信息,对下可将IP信息包放到链路层。ip地址就是ip协议中的一部分。
- 相关指令:ipconfig 、ping
端口号:
- 端口号:一台计算机对应一个ip地址,一台计算机有多个端口号对应多个服务或应用。
- 常用端口号:80是HTTP默认端口号、22是scp/ssh的tcp端口号
- 指令:“netstat -n”显示地址和端口信息
java TCP通信:
- 通信方式:通过Socket套接字传输数据,套接字的输入流用InputStream接收、输出流用OutputStream接收。
- Scoket类参数:String host 主机名称或ip地址、int port端口号。
- 成员方法:close关闭套接字
2、使用soket套接字
2.1、使用soket套接字通信
客户端
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",7890);
OutputStream o = socket.getOutputStream(); // 获取输出流
o.write("你好,服务器".getBytes());
socket.close();
}
}
服务端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
* 服务端
* 客户端发送消息过来,服务端获取到客户端对象、消息,通过输入流将消息接收到
* 先运行服务端,再运行客户端就可以接收到消息:你好,服务器
**/
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(7890);
Socket socket = server.accept(); // 获取到客户端对象
InputStream is = socket.getInputStream(); // 输入流对象
byte[] bytes = new byte[1024];
int l = is.read(bytes); // 返回字节的实际长度
System.out.println(new String(bytes,0,l));
OutputStream os = socket.getOutputStream();
os.write("收到".getBytes());
socket.close();
server.close();
}
}
2.2、文件上传
客户端
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
// 客户端
public class TCPClient {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("d:\\1.png");
Socket socket = new Socket("127.0.0.1",8888);
OutputStream os = socket.getOutputStream();
// 读取本地文件
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes))!=-1){
os.write(bytes,0,len);
}
// fis.read(bytes)是读取本地文件,结束标记是读取到-1
// socket.shutdownOutput(); 就是结束标记
socket.shutdownOutput();
// 读取服务器回写数据
InputStream is = socket.getInputStream();
while ((len = is.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
fis.close();
socket.close();
}
}
服务端
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
// 服务器
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8888);
Socket socket = server.accept();
// 获取输入流
InputStream is = socket.getInputStream();
// 判断文件夹是否存在
File file = new File("d:\\upload");
if(!file.exists()){
file.mkdirs();
}
// 将文件数据保存到磁盘上
FileOutputStream fos = new FileOutputStream(file+"\\1.jpg");
int len = 0;
byte[] bytes = new byte[1024];
while ((len = is.read(bytes))!=-1){
fos.write(bytes,0,len);
}
socket.getOutputStream().write("上传成功".getBytes());
fos.close();
socket.close();
server.close();
}
}
2.3.1、防止同名文件覆盖
在服务器端,把文件名改成这个
// 自定义一个命名规则,防止同名文件覆盖
String fileName = "itcast"+System.currentTimeMillis()+new Random().nextInt(999999)+".jpg";
2.3.2、让服务器一直处于监听状态
2.3.3、利用多线程,提高效率
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
// 服务器
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8888);
while (true){
Socket socket = server.accept();
// 有一个客户端上传文件,就开启一个线程
new Thread(new Runnable() {
@Override
public void run() {
try {
// 获取输入流
InputStream is = socket.getInputStream();
// 判断文件夹是否存在
File file = new File("d:\\upload");
if(!file.exists()){
file.mkdirs();
}
// 自定义一个命名规则,防止同名文件覆盖
String fileName = "itcast"+System.currentTimeMillis()+new Random().nextInt(999999)+".jpg";
// 将文件数据保存到磁盘上
FileOutputStream fos = new FileOutputStream(file+"\\"+fileName);
int len = 0;
byte[] bytes = new byte[1024];
while ((len = is.read(bytes))!=-1){
fos.write(bytes,0,len);
}
socket.getOutputStream().write("上传成功".getBytes());
fos.close();
socket.close();
}catch (IOException e){
System.out.println(e);
}
}
}).start();
}
}
}
2.3、模拟BS服务器
先创建一个简单的服务器
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
// BS版本的TCP服务器
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8080);
Socket socket = server.accept();
// 获取输入流
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = 0;
while ((len=is.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
}
}
把一个html文件放进项目目录下
启动这个服务器,用浏览器访问这个html
查看服务器后台,我们可以看到socket getInputStream收到的内容
接下来我们提取getInputStream收到的内容的第一排:
GET /java_socket/src/socket03/baidu.html HTTP/1.1
并获取对应的路径,把对应的资源写入到输出流
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
// BS版本的TCP服务器
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8080);
Socket socket = server.accept();
// 获取输入流
InputStream is = socket.getInputStream();
// 网络输入流转为字符串缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 读取客户端请求信息的第一行
String line = br.readLine();
// 利用空格切割出地址
String[] arr = line.split(" ");
// 把路径最前面的"/"去掉
String path = arr[1].substring(1);
System.out.println(path);
// 读取对应路径下的文件
FileInputStream fileInputStream = new FileInputStream(path);
// 写入客户端
OutputStream os = socket.getOutputStream();
// 写入http相应头(固定写法)
os.write("HTTP/1.1 200 OK\r\n".getBytes());
os.write("Content-Type:text/html\r\n".getBytes());
os.write("\r\n".getBytes());
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fileInputStream.read(bytes))!=-1){
os.write(bytes,0,len);
}
fileInputStream.close();
socket.close();
server.close();
}
}
启动这个main方法,现在通过浏览器就能访问到对应的html了
如果我们这个html里面有图片等,我们会发现显示不出来,这是因为页面中有图片,那么浏览器会单独开启一个线程读取图片,所以我们需要写一个多线程循环,让服务器处于监听状态,客户端请求一次,服务器就回写一次。
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
// BS版本的TCP服务器
public class TCPServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8080);
while (true){
new Thread(new Runnable() {
@Override
public void run() {
try{
Socket socket = server.accept();
// 获取输入流
InputStream is = socket.getInputStream();
// 网络输入流转为字符串缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 读取客户端请求信息的第一行
String line = br.readLine();
// 利用空格切割出地址
String[] arr = line.split(" ");
// 把路径最前面的"/"去掉
String path = arr[1].substring(1);
System.out.println(path);
// 读取对应路径下的文件
FileInputStream fileInputStream = new FileInputStream(path);
// 写入客户端
OutputStream os = socket.getOutputStream();
// 写入http相应头(固定写法)
os.write("HTTP/1.1 200 OK\r\n".getBytes());
os.write("Content-Type:text/html\r\n".getBytes());
os.write("\r\n".getBytes());
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fileInputStream.read(bytes))!=-1){
os.write(bytes,0,len);
}
fileInputStream.close();
socket.close();
}catch (Exception e){
System.out.println(e);
};
}
});
}
}
}