Socket网络编程
网络模型
1. OSI(开放系统互连)参考模型
分为:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
2. TCP/IP参考模型
分为:应用层、 、传输层、网际层、主机至网络层
网络通讯三要素
1. IP地址 网络中的标识,本地回路地址127.0.0.1 主机名localhost
2. 端口号 用于标识进程的逻辑地址 有效端口 0--655365 0--1024系统使用或保留端口
3. 传输协议  通讯的规则,常见的有
- TCP(分装数据包,无需建立连接,大小限制在64k内,不可靠,速度快)
- UDP(建立连接形成传输通道,进行大量数据传输,三次握手安全可靠,效率稍低)
(三次握手:1在没在 2在 3我知道了)
获取IP地址对象
1. 获取本机主机IP地址对象
`InetAddress ip = InetAddress.getLocalHost();`
2. 获取其他主机IP地址对象(192.168.xxx.255是广播)
- `String ip = "192.168.4.16";`
`String[] ip_split = ip.split("\\.");`
`int i = 0;`
`byte[] bytes = new byte[4];`
`bytes[0] = (byte)Integer.parseInt(ip_split[0]);`
`bytes[1] = (byte)Integer.parseInt(ip_split[1]);`
`bytes[2] = (byte)Integer.parseInt(ip_split[2]);`
`bytes[3] = (byte)Integer.parseInt(ip_split[3]);`
`InetAddress ip = InetAddress.getByAddress(bytes) ;`
- `InetAddress ip = InetAddress.getByName("主机名/IP地址");`
- `InetAddress[] ip = InetAddress.getAllByName("主机名/IP地址");`部分网址有多个ip地址,此方法返回对象数组
`ip.getHostAddress()` 获取IP地址
`ip.getHostName()` 获取主机名
public static void main(String[] args) throws UnknownHostException {
//获取本机主机IP地址对象
InetAddress ip = InetAddress.getLocalHost();
System.out.println(ip.getHostAddress());
System.out.println(ip.getHostName());
//获取其他主机IP地址对象
ip = InetAddress.getByName("DESKTOP-MLPB9BK");
System.out.println(ip.getHostAddress());
System.out.println(ip.getHostName());
ip = InetAddress.getByName("www.baidu.com");
System.out.println(ip.getHostAddress());
System.out.println(ip.getHostName());
}
DNS 域名解析服务器,存储了IP与域名的对应关系
Socket是为网络服务提供的一种机制。通信两端都有Socket.网络通信就是Socket之间的通信,数据在两Socket间通过IO传输
UDP协议传输
UDP 对应Socket对象 DatagramSocket
-
发送端
- 创建UDP的Socket服务,(是否指定端口号均可)
DatagramSocket ds = new DatagramSocket([端口号,可有可无]);
- 将要发送到数据封装到数据包中
byte[] buf = "UDP传输演示,发送端数据".getBytes();
DatagramPacket dp = new DatagramPacket(byte[],byte[].length, InetAddress.getLocalHost(), 9999);
- 通过UDP的Socket服务的send()方法将数据包发送
ds.send(dp);
- 关闭Socket服务
ds.close();
- 创建UDP的Socket服务,(是否指定端口号均可)
-
接收端
- 建立Socket服务,因是接收数据,必须要绑定端口与发送方数据包指定的端口号相同
DatagramSocket ds = new DatagramSocket(9999);
- 创建数据包,用于存储收到数据。方便用数据包的方法解析这些数据
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
- 通过UDP的Socket服务的receive方法将接收到的数据存储到数据包中
ds.receive(dp);
阻塞式 - 通过数据包对象的方法解析数据包的数据
dp.getAddress().getHostAddress()
获取发送方IP地址
dp.getAddress().getHostName()
)获取发送方主机名
new String(dp.getData(),0,dp.getLength())
获取发送方数据
dp.getPort()
获取发送方端口号 - 关闭Socket服务
ds.close();
- 建立Socket服务,因是接收数据,必须要绑定端口与发送方数据包指定的端口号相同
例
UDP发送端
public static void main(String[] args) throws IOException {
//1.创建UDP的Socket服务
DatagramSocket ds = new DatagramSocket();
//2.将要发送到数据封装到数据包中
byte[] buf = "UDP传输演示,发送端数据".getBytes();
DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 9999);
//3.通过UDP的Socket服务的send()方法将数据包发送
ds.send(dp);
//4.关闭Socket服务
ds.close();
}
UDP接收端
public static void main(String[] args) throws IOException {
//1.建立Socket服务,因是接收数据,要绑定端口
DatagramSocket ds = new DatagramSocket(9999);
//2.创建数据包,用于存储收到数据。方便用数据包的方法解析这些数据
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
//3.通过UDP的Socket服务的receive方法将接收到的数据存储到数据包中
ds.receive(dp);//阻塞式
//4.通过数据包对象的方法解析数据包的数据
System.out.println(dp.getAddress().getHostAddress());
System.out.println(dp.getAddress().getHostName());
System.out.println(new String(dp.getData(),0,dp.getLength()));
System.out.println(dp.getPort());
//5.关闭Socket服务
ds.close();
}
TCP协议传输
TCP 对应Socket对象 两个: 客户端Socket 服务端ServerSocket
-
发送端
- 创建TCP客户端socket服务。使用的是Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9090);
Socket socket = new Socket("192.168.1.111", 9090);
Socket socket = new Socket();
SocketAddress address = new InetSocketAddress("ip/主机名", 9090);
socket.connect(address)
- 如果连接建立成功,说明数据传输通道建立
该通道是socket流,是底层建立的,既有输入又有输出。从Socket获取输入/输出流流对象
通过getOutputStream()
和getInputStream()
来获取两个字节流
获取Socket输出流对象, 通过该流把数据发送给服务器
OutputStream out = ocket.getOutputStream();
- 使用输出流,将数据写出。
out.write(String.getBytes());
- 关闭资源,断开连接
socket.close();
- 创建TCP客户端socket服务。使用的是Socket对象
-
服务端
- 创建Socket服务,通过ServerScoket对象。服务器必须对外提供一个端口,否则客户端无法连接。
ServerSocket serverSocket = new ServerSocket(9090);
- 获取连接过来的客户端对象。
Socket socket = serverSocket.accept();
- 通过客户端对象获取Scoket流读取客户端发来的数据。
InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
String str = new String(buf,0,len);
- 关闭资源。关客户端,关服务端。
serverSocket.close();
socket.close();
- 创建Socket服务,通过ServerScoket对象。服务器必须对外提供一个端口,否则客户端无法连接。
例
TCP客户端
public static void main(String[] args) throws IOException {
// 建立TCP客户端与服务器的连接, 指定服务器的IP地址与程序对应的端口号
Socket socket = new Socket(InetAddress.getLocalHost(), 9090);
//InetAddress address = InetAddress.getByName(IP/主机名) ;
//Socket socket = new Socket(address, 9090);
// 获取Socket输出流对象, 通过该流把数据发送给服务器
OutputStreamWriter socketOut = new OutputStreamWriter(socket.getOutputStream());
//BufferedWriter bw = new BufferedWriter(socketOut);
// 获取Socket输入流对象, 通过该流获得服务器发送给客户端的数据
BufferedReader socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 获取键盘输入流对象,读取数据
BufferedReader keyboardReader = new BufferedReader(new InputStreamReader(System.in));
String line = null;
System.out.print("我说:");
while ((line = keyboardReader.readLine()) != null) {
socketOut.write(line + "\n");//阻塞方法,加入"\n"结束标记,告诉服务器我发送完毕,使用BufferedWriter,可以用bw.newLine()代替
bw.write(line);
// 刷新,见BufferWriter
socketOut.flush();
// 读取服务器端返回的数据
line = socketReader.readLine();
System.out.println("服务器:" + line);
System.out.print("我说:");
}
socket.close();
}
TCP服务端
public static void main(String[] args) throws IOException {
// 创建服务器端, 注册当前程序的端口号
ServerSocket serverSocket = new ServerSocket(9090);
// 接受客户端的连接,产⽣生⼀一个Socket
Socket socket = serverSocket.accept();
// 获取Socket的输⼊入流, 就是通过这个输入流获得客户端发送给服务器的数据
BufferedReader socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 获取Socket的输出流, 就是通过该输出流, 服务器把数据发送给客户端
OutputStreamWriter socketOut = new OutputStreamWriter(socket.getOutputStream());
//BufferedWriter bw = new BufferedWriter(socketOut);
// 获取键盘的输⼊入流,通过该输入流读取键盘上输入的数据
BufferedReader keyboardReader = new BufferedReader(new InputStreamReader(System.in));
// 不断读取客户端数据
String line = null;
while ((line = socketReader.readLine()) != null) {
System.out.println("客户端:" + line);
System.out.print("我说:");
line = keyboardReader.readLine();
socketOut.write(line + "\n");//阻塞方法,加入"\n"结束标记,告诉客户端我发送完毕,使用BufferedWriter,可以用bw.newLine()代替
bw.write(line);
socketOut.flush();//见BufferWriter
}
// 关闭
serverSocket.close();
}
TCP多线程—多用户上传
客户端
public class TransClient {
public static void main(String[] args) throws IOException {
// 建立TCP客户端与服务器的连接, 指定服务器的IP地址与程序对应的端口号
Socket socket = new Socket(InetAddress.getLocalHost(), 9090);
//InetAddress address = InetAddress.getByName(IP/主机名) ;
//Socket socket = new Socket(address, 9090);
// 获取Socket输出流对象, 通过该流把数据发送给服务器
OutputStream outputStream = socket.getOutputStream();
InputStream is = new FileInputStream("D:/1.mp4");
byte[] bytes = new byte[256];
int len = is.read(bytes);//返回读取到的有效字节数
while(len != -1) {
outputStream.write(bytes,0,len);
len = is.read(bytes);
}
socket.close();
}
}
服务端
public class TransServer {
public static void main(String[] args) throws IOException{
// 创建服务器端, 注册当前程序的端口号
ServerSocket serverSocket = new ServerSocket(9090);
while(true) {
Socket socket = serverSocket.accept();
new Thread(new Runnable() {
@Override
public void run() {
OutputStream os = null;
int connt = 0;
try {
// 获取Socket的输⼊入流, 就是通过这个输入流获得客户端发送给服务器的数据
InputStream inputStream = socket.getInputStream();
File f = new File("D:/","1.mp4");
while(f.exists()) {
f = new File("D:/","1("+(++connt)+").mp4");
}
os = new FileOutputStream(f);
byte[] bytes = new byte[2048];
int a = inputStream.read(bytes);
while(a != -1)
{
os.write(bytes,0,a);
a = inputStream.read(bytes);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
// 接受客户端的连接,产生一个Socket
}
}
}
最常见的客户端:
浏览器 :IE
最常见的服务端:
服务器:Tomcat
为了了解其原理:
-
自定义服务端
public class MyTomcat { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(9090); Socket socket = serverSocket.accept(); InputStream in = socket.getInputStream(); byte[] buf = new byte[2048]; int len = in.read(buf); String text = new String(buf,0,len); System.out.println(text); StringBuffer reply = new StringBuffer(); // 必须添加的响应头 reply.append("HTTP/1.1 200 OK\\nContent-type:text/html\n\n");// 必须添加的响应头 InputStream is = new FileInputStream("D:/2.html"); buf = new byte[2048]; len = is.read(buf);//返回读取到的有效字节数 while(len != -1) { reply.append(new String(buf,0,len)); len = is.read(buf); } OutputStream outputStream = socket.getOutputStream(); //此处不修改编码格式,需要html中指定 outputStream.write(reply.toString().getBytes("GB2312")); socket.close(); serverSocket.close(); } }
-
使用已有的客户端IE,了解一下客户端给服务端发了什么请求?
-
客户端发送的请求是:
//以下是请求行 //格式为:请求方式 / 请求的资源路径 / http协议版本。 GET / HTTP/1.1 //以下是请求消息头 //格式为:属性名:属性值 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* //支持解析的数据类型 Accept-Language: zh-cn,zu;q=0.5 //支持的语言 Accept-Encoding: gzip, deflate //支持的压缩包 User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; InfoPath.2)//用户信息 Host: 127.0.0.1:9090 //要访问的主机 //Host: www.huyouni.com:9090 Connection: Keep-Alive(或者close) //空行 //请求体(比如注册信息)。
-
自定义客户端
public class MyBrowser { public static void main(String[] args) throws UnknownHostException, IOException { Socket s = new Socket("192.168.1.100",8080); //模拟浏览器,给tomcat服务端发送符合http协议的请求消息。 PrintWriter out = new PrintWriter(s.getOutputStream(),true); out.println("GET /myweb/1.html HTTP/1.1"); out.println("Accept: */*"); out.println("Host: 192.168.1.100:8080"); out.println("Connection: close"); out.println(); out.println(); InputStream in = s.getInputStream(); byte[] buf = new byte[1024]; int len = in.read(buf); String str =new String(buf,0,len); System.out.println(str); s.close(); //http://192.168.1.100:8080/myweb/1.html } }
-
服务端发回应答消息。
//以下是应答行 //http的协议版本 应答状态码 应答状态描述信息 HTTP/1.1 200 OK //以下是应答消息属性信息 //属性名:属性值 Server: Apache-Coyote/1.1 ETag: W/"199-1323480176984" Last-Modified: Sat, 10 Dec 2011 01:22:56 GMT Content-Type: text/html Content-Length: 199 Date: Fri, 11 May 2012 07:51:39 GMT Connection: close //空行 //应答体。 <html> <head> <title>这是我的网页</title> </head> <body> <h1>欢迎光临</h1> <font size='5' color="red">这是一个tomcat服务器中的资源。是一个html网页。</font> </body> </html>
URL统一资源定位器
-
URL对象创建
URL url = new URL("http://192.168.1.100:8080/myweb/1.html?name=list");
-
常用方法
String getFile() 获取此 URL的文件名 String getHost() 获取此 URL的主机名(如适用) String getPath() 获取此 URL的路径部分 int getPort() 获取此 URL的端口号 String getProtocol() 获取此 URL的协议名称 String getQuery() 获取此 URL的查询部分 URLConnection openConnection() 返回一个URLConnection实例,表示与URL引用的远程对象的URL InputStream openStream() 打开与此 URL ,并返回一个 InputStream ,以便从该连接读取,相当于 URLConnection conn = url.openConnection();
InputStream in = conn.getInputStream();
的简写
URL统一资源定位器简易浏览器
String str_url = "http://192.168.1.100:8080/myweb/1.html";
URL url = new URL(str_url);
//获取url对象的Url连接器对象。将连接封装成了对象:java中内置的可以解析的具体协议的对象+socket.
URLConnection conn = url.openConnection();
InputStream in = conn.getInputStream();
//InputStream in = url.openStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
String text = new String(buf,0,len);
System.out.println(text);
in.close();
网络结构
-
C/S client/server
特点:
该结构的软件,客户端和服务端都需要编写。
可发成本较高,维护较为麻烦。
好处:
客户端在本地可以分担一部分运算。 -
B/S browser/server
特点:
该结构的软件,只开发服务器端,不开发客户端,因为客户端直接由浏览器取代。
开发成本相对低,维护更为简单。
缺点:
所有运算都要在服务端完成。