Socket编程
网络编程入门
软件结构
- C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件。
- 特点: 客户端和服务器是分开的,需要下载客户端,对网络要求相对低, 服务器压力小,开发和维护成本高,相对稳定
B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等。
特点:没有客户端,只有服务器,不需要下载客户端,直接通过浏览器访问, 对网络要求相对高, 服务器压力很大,相对不稳定,开发和维护成本低,
两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机在网络中的通信的程序。
网络编程三要素
协议
网络通信协议:通信协议是计算机必须遵守的规则,只有遵守这些规则,计算机之间才能进行通信。这就好比在道路中行驶的汽车一定要遵守交通规则一样,协议中对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守,最终完成数据交换。
java.net
包中提供了两种常见的网络协议的支持:
- TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
- TCP协议特点: 面向连接,传输数据安全,传输速度低
- 例如: 村长发现张三家的牛丢了
- TCP协议: 村长一定要找到张三,面对面的告诉他他家的牛丢了 打电话: 电话一定要接通,并且是张三接的
- 连接三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
- 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。 你愁啥?
- 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。我愁你咋地?
- 第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。你再愁试试
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。
- UDP:用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中**,例如视频会议、QQ聊天等。**
- UDP特点: 面向无连接,传输数据不安全,传输速度快
- 例如: 村长发现张三家的牛丢了
- UDP协议: 村长在村里的广播站广播一下张三家的牛丢了,信息丢失,信息发布速度快
IP地址
- IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。
**IP地址分类 **
IPv4:是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d
的形式,例如192.168.65.100
。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。
IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。有资料显示,全球IPv4地址在2011年2月分配完毕。
为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。
常用命令
1 2 3
| ping 空格 IP地址 ping 220.181.57.216 ping www.baidu.com
|
特殊的IP地址
- 本机IP地址:
127.0.0.1
、localhost
。
端口号
网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。
- 端口号:用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
利用协议
+IP地址
+端口号
三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
小结
- 协议: 计算机在网络中通信需要遵守的规则,常见的有TCP,UDP协议
- TCP: 面向连接,传输数据安全,传输速度慢
- UDP: 面向无连接,传输不数据安全,传输速度快
- IP地址: 用来标示网络中的计算机设备
- 分类: IPV4 IPV6
- 本地ip地址: 127.0.0.1 localhost
- 端口号: 用来标示计算机设备中的应用程序
- 端口号: 0–65535
- 自己写的程序指定的端口号要是1024以上
- 如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
InetAddress类
InetAddress类的概述
InetAddress类的方法
- static InetAddress getLocalHost() 获得本地主机IP地址对象
- static InetAddress getByName(String host) 根据IP地址字符串或主机名获得对应的IP地址对象
- String getHostName();获得主机名
- String getHostAddress();获得IP地址字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class InetAddressDemo01 { public static void main(String[] args) throws Exception { InetAddress ip1 = InetAddress.getLocalHost(); System.out.println("ip1: "+ip1);
InetAddress ip2 = InetAddress.getByName("www.baidu.com"); System.out.println("ip2:"+ip2);
String hostName = ip1.getHostName(); System.out.println("hostName:"+hostName);
String hostAddress = ip1.getHostAddress(); System.out.println("hostAddress:"+hostAddress); } }
|
TCP通信程序
TCP协议概述
TCP概述
- TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收器端建立逻辑连接,然后再传输数据。它提供了两台计算机之间可靠无差错的数据传输。TCP通信过程如下图所示:
TCP协议相关的类
- Socket : 一个该类的对象就代表一个客户端程序。
- Socket(String host, int port) 根据ip地址字符串和端口号创建客户端Socket对象
* 注意事项:只要执行该方法,就会立即连接指定的服务器程序,如果连接不成功,则会抛出异常。
如果连接成功,则表示三次握手通过。
OutputStream getOutputStream();
获得字节输出流对象
InputStream getInputStream();
获得字节输入流对象
void close();
关闭Socket, 会自动关闭相关的流,关闭通过Socket获得流,也会关闭Socket
- ServerSocket : 一个该类的对象就代表一个服务器端程序。
ServerSocket(int port);
根据指定的端口号开启服务器。
Socket accept();
等待客户端连接并获得与客户端关联的Socket对象 如果没有客户端连接,该方法会一直阻塞
void close();
关闭ServerSocket,一般不关闭
TCP通信案例1
需求
路径
- 客户端实现步骤
- 创建客户端Socket对象并指定服务器地址和端口号
- 调用Socket对象的getOutputStream方法获得字节输出流对象
- 使用字节输出流对象的write方法往服务器端输出数据
- 关闭Socket对象断开连接。
- 服务器实现步骤
- 创建ServerSocket对象并指定端口号(相当于开启了一个服务器)
- 调用ServerSocket对象的accept方法等待客端户连接并获得对应Socket对象
- 调用Socket对象的getInputStream方法获得字节输入流对象
- 调用字节输入流对象的read方法读取客户端发送的数据
实现
客户端代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Client { public static void main(String[] args) throws Exception{ Socket socket = new Socket("127.0.0.1",6666); System.out.println(socket);
OutputStream os = socket.getOutputStream();
os.write("服务器,你好,今晚约吗?".getBytes());
socket.close();
} }
|
服务端代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Server { public static void main(String[] args) throws Exception{ ServerSocket ss = new ServerSocket(6666);
Socket socket = ss.accept(); System.out.println(socket);
InputStream is = socket.getInputStream();
byte[] bys = new byte[8192]; int len = is.read(bys); System.out.println(new String(bys,0,len));
ss.close();
} }
|
TCP通信案例2
需求
- 客户端向服务器发送字符串数据,服务器回写字符串数据给客户端(模拟聊天)
路径
- 客户端实现步骤
- 创建客户端Socket对象并指定服务器地址和端口号
- 调用Socket对象的getOutputStream方法获得字节输出流对象
- 使用字节输出流对象的write方法往服务器端输出数据
- 调用Socket对象的getInputStream方法获得字节输入流对象
- 调用字节输入流对象的read方法读取服务器端返回的数据
- 关闭Socket对象断开连接。
- 服务器实现步骤
- 创建ServerSocket对象并指定端口号(相当于开启了一个服务器)
- 调用ServerSocket对象的accept方法等待客端户连接并获得对应Socket对象
- 调用Socket对象的getInputStream方法获得字节输入流对象
- 调用字节输入流对象的read方法读取客户端发送的数据
- 调用Socket对象的getOutputStream方法获得字节输出流对象
- 调用字节输出流对象的write方法往客户端输出数据
- 关闭Socket和ServerSocket对象
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
public class Client { public static void main(String[] args) throws Exception{ Socket socket = new Socket("127.0.0.1",6666); while (true) { OutputStream os = socket.getOutputStream(); Scanner sc = new Scanner(System.in); System.out.println("请输入向服务器发送的数据:"); String str = sc.nextLine(); os.write(str.getBytes());
InputStream is = socket.getInputStream(); byte[] bys = new byte[1024]; int len = is.read(bys); System.out.println(new String(bys,0,len)); }
} }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
public class Server { public static void main(String[] args) throws Exception{ ServerSocket ss = new ServerSocket(6666); Socket socket = ss.accept();
while (true) { InputStream is = socket.getInputStream(); byte[] bys = new byte[1024]; int len = is.read(bys); System.out.println(new String(bys,0,len));
OutputStream os = socket.getOutputStream(); Scanner sc = new Scanner(System.in); System.out.println("请输入向客户端发送的数据:"); String str = sc.nextLine(); os.write(str.getBytes()); }
} }
|
综合案例
文件上传案例
需求
分析
【客户端】输入流,从硬盘读取文件数据到程序中。
【客户端】输出流,写出文件数据到服务端。
【服务端】输入流,读取文件数据到服务端程序。
【服务端】输出流,写出文件数据到服务器硬盘中。
【服务端】获取输出流,回写数据。
【客户端】获取输入流,解析回写数据。
实现
拷贝文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| public class Client { public static void main(String[] args) throws Exception { FileInputStream fis = new FileInputStream("day12\\aaa\\hb.jpg");
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
OutputStream os = socket.getOutputStream();
byte[] bys = new byte[8192]; int len;
while ((len = fis.read(bys)) != -1) { os.write(bys,0,len); } socket.close(); fis.close(); } } public class Server { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(8888);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream("day12\\aaa\\hbCopy2.jpg");
byte[] bys = new byte[8192]; int len;
while ((len = is.read(bys)) != -1) { fos.write(bys,0,len); } fos.close(); socket.close(); } }
|
文件上传成功后服务器回写字符串数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| public class Client { public static void main(String[] args) throws Exception { FileInputStream fis = new FileInputStream("day12\\aaa\\hb.jpg");
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
OutputStream os = socket.getOutputStream();
byte[] bys = new byte[8192]; int len;
while ((len = fis.read(bys)) != -1) { os.write(bys, 0, len); }
socket.shutdownOutput();
System.out.println("============客户端开始接受服务器返回的数据=============="); InputStream is = socket.getInputStream();
int read = is.read(bys); System.out.println("服务器回写的数据是:" + new String(bys, 0, read));
socket.close(); fis.close(); } }
public class Server { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(8888);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream("day12\\aaa\\hbCopy5.jpg");
byte[] bys = new byte[8192]; int len;
while ((len = is.read(bys)) != -1) { fos.write(bys, 0, len); }
System.out.println("======服务器开始回写数据给客户端======="); OutputStream os = socket.getOutputStream();
os.write("恭喜您,上传成功!".getBytes());
fos.close(); socket.close(); } }
|
优化文件上传案例
1 2 3 4 5 6 7 8 9 10
| 1.文件名固定----->优化 自动生成唯一的文件名 2.服务器只能接受一次 ----> 优化 死循环去接收请求,建立连接 3.例如:如果张三先和服务器建立连接,上传了一个2GB字节大小的文件 李四后和服务器建立连接,上传了一个2MB字节大小的文件
李四就必须等张三上传完毕,才能上传文件
优化---->多线程优化 张三上传文件,开辟一条线程 李四上传文件,开辟一条线程
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| public class Server { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(8888);
while (true) { Socket socket = ss.accept();
new Thread(new Runnable() { @Override public void run() {
try { InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream("day12\\aaa\\"+System.currentTimeMillis()+".jpg");
byte[] bys = new byte[8192]; int len;
while ((len = is.read(bys)) != -1) { fos.write(bys, 0, len); }
System.out.println("======服务器开始回写数据给客户端======="); OutputStream os = socket.getOutputStream();
os.write("恭喜您,上传成功!".getBytes());
fos.close(); socket.close();
} catch (IOException e) {
} } }).start();
} } }
public class Client { public static void main(String[] args) throws Exception { FileInputStream fis = new FileInputStream("day12\\aaa\\hb.jpg");
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
OutputStream os = socket.getOutputStream();
byte[] bys = new byte[8192]; int len;
while ((len = fis.read(bys)) != -1) { os.write(bys, 0, len); }
socket.shutdownOutput();
System.out.println("============客户端开始接受服务器返回的数据=============="); InputStream is = socket.getInputStream();
int read = is.read(bys); System.out.println("服务器回写的数据是:" + new String(bys, 0, read));
socket.close(); fis.close(); } }
|
模拟B\S服务器 扩展
需求
- 模拟网站服务器,使用浏览器访问自己编写的服务端程序,查看网页效果。
分析
- 准备页面数据,web文件夹。
- 我们模拟服务器端,ServerSocket类监听端口,使用浏览器访问,查看网页效果
实现
浏览器工作原理是遇到图片会开启一个线程进行单独的访问,因此在服务器端加入线程技术。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| public class Demo { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(9999);
while (true) { Socket socket = ss.accept();
new Thread(new Runnable() { @Override public void run() { try { InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = br.readLine();
String[] arr = line.split(" ");
String path = arr[1].substring(1); System.out.println("浏览器需要访问的页面路径是:" + path);
FileInputStream fis = new FileInputStream(path);
OutputStream os = socket.getOutputStream();
byte[] bys = new byte[8192]; int len;
os.write("HTTP/1.1 200 OK\r\n".getBytes()); os.write("Content-Type:text/html\r\n".getBytes()); os.write("\r\n".getBytes());
while ((len = fis.read(bys)) != -1) { os.write(bys, 0, len); }
fis.close(); socket.close(); } catch (IOException e) {
} } }).start(); } }
private static Socket method01() throws IOException { ServerSocket ss = new ServerSocket(9999); Socket socket = ss.accept(); InputStream is = socket.getInputStream(); byte[] bys = new byte[8192]; int len = is.read(bys); System.out.println(new String(bys, 0, len));
return socket; } }
|
访问效果:
图解: