Socket编程:
1. 网络基础知识
如果两台计算机要通过网络进行通信,那么需要满足一些必然的条件:
1. 两台主机它们需要有唯一的标识。用来表示它们所处的身份,他们所在的位置,这就是IP地址。
2. 它们需要有共同的语言,否则就会出现言语不通,无法交流,这就是我们的协议。
3. 每台主机都需要有相应的端口号,一台主机上可以运行多个应用程序,那如何辨别不同应用程序的通信,我们需要使用端口号来进行区分。
这是两台主机通过网络进行通信的必要条件:1. IP地址 2. 协议 3. 端口号
TCP/IP协议:
TCP/IP是目前世界上应用最为广泛的协议。
是以TCP和IP为基础的不同层次上多个协议的集合。
也称为:TCP/IP协议族 或TCP/IP协议栈。
两个主机要进行通信,都要遵循TCP和IP的协议:
TCP: Transmission Control Protocol 传输控制协议
IP: Internet Protocol 互联网协议
在实际的应用当中,一般会将网络分层:
常见的会将网络分为5层。
物理层:是我们用户最直观接触到的,例如网线,网卡,双绞线等等...
而TPC/IP协议是在第4层的传输层
第5层应用层也是用户直接接触到的,应用层有许多的协议,例如:
HTTP超文本传输协议
FTP文件传输协议
SMTP简单邮件传送协议
Telnet远程登陆服务
IP地址:
为实现网络中不同计算机之间的通信,每台机器都必须有一个唯一的标识---IP地址。
端口号:
1. 用于区分不同应用程序。
2. 端口号范围为0~65535,其中0~1023为系统所保留
3. IP地址加上端口号组成了所谓的Socket,Socket是网络上运行的程序之间双向通信链路的终结点,是TCP和UDP的基础。
4. http:80 ftp : 21 telent : 23
Java中网络支持:
1. InetAddress : 用于标识网络上的硬件资源。说白了主要用来标记IP地址相关信息
2. URL : 统一资源定位符,通过URL可以直接读取或写入网络上的数据。
3. Socket : 使用TCP协议实现网络通信的Socket相关的类。
4. Datagram : 使用UDP协议,将数据保存在数据报中,通过网络进行通信。
2. InetAddress类
1. InetAddress类用于表示网络上的硬件资源,表示互联网协议(IP)地址。
InetAddress类没有构造方法,但是有许多静态的方法。详细可查看官方API。
Java提供了InetAddress类来代表IP地址,InetAddress下还有2个子类:Inet4Address、Inet6Address,它们分别代表Internet Protocol version 4(IPv4)地址和Internet Protocol version 6(IPv6)地址。
InetAddress类没有提供构造器,而是提供了如下两个静态方法来获取InetAddress实例:
getByName(String host):根据主机获取对应的InetAddress对象。
getByAddress(byte[] addr):根据原始IP地址来获取对应的InetAddress对象。
InetAddress还提供了如下三个方法来获取InetAddress实例对应的IP地址和主机名:
String getCanonicalHostName():获取此 IP 地址的全限定域名。
String getHostAddress():返回该InetAddress实例对应的IP地址字符串(以字符串形式)。
String getHostName():获取此 IP 地址的主机名。
除此之外,InetAddress类还提供了一个getLocalHost()方法来获取本机IP地址对应的InetAddress实例
3. URL
1. URL(Uniform Resource Locator)统一资源定位符,表示Internet上某一资源的地址。
2. URL由两部分组成:协议名称和资源名称,中间用冒号隔开。
以下为URL中的一些方法:
public static void main(String[]args){
try {
//创建一个URL的实例
URL imooc = new URL("http://www.imooc.com");
//?后面表示参数,#后面表示锚点
URL url = new URL(imooc, "/index.html?username=tom#test");
System.out.println("协议:" + url.getProtocol());
System.out.println("主机:" + url.getHost());
//如果未制定端口号,则使用默认的端口号,此时getPort()方法返回值为-1
System.out.println("端口号:" + url.getPort());
System.out.println("文件路径:" + url.getPath());
System.out.println("文件名:" + url.getFile());
//相对路径及为#号后面的锚点
System.out.println("相对路径:" + url.getRef());
System.out.println("查询字符串:" + url.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
输出结果如下:
使用URL读取网页内容
1. 通过URL对象的openStream()方法可以得到指定资源的输入流。
2. 通过输入流可以读取,访问网络上的数据。
public static void main(String[]args){
try {
URL url = new URL("http://www.baidu.com");
//通过URL的openStream方法获取URL对象所表示的资源的字节输入流
InputStream is = url.openStream();
//将字节输入流转换成字符输入流
InputStreamReader isr = new InputStreamReader(is,"utf-8");
//再为字符输入流添加缓冲,提高读取的效率
BufferedReader br = new BufferedReader(isr);
//读取数据
String data = br.readLine();
while(data != null){
System.out.println(data);
data = br.readLine();
}
br.close();
isr.close();
is.close();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
会将百度页面的html内容显示出来,如下图所示:
4. TCP编程
TCP协议是面向连接,可靠的,有序的,以字节流的方式发送数据。
基于TCP协议实现网络通信的类:
1. 客户端的Socket类
2. 服务器端的ServerSocket类
Socket通信模型:
两台主机进行通信,则其中一台就为服务器端,另一台为客户端。
以下为通信模型:
1. 首先要在Server(服务器)端创建一个ServerSocket,服务器的Socket,绑定相对应的端口,并且再指定的端口进行监听,等待客户端的连接。
2. 当在客户端创建Socket并且向服务器端发送请求,然后与此同时服务器端收到请求并且接受客户端请求的信息
3. 一旦接受到客户端请求之后,会创建一个连接Socket,用来与客户端Socket进行通信。
4. 那么应该如何进行通信,就是通过输入流和输出流,InputStream和OutputStream进行数据的交换,数据的发送,接收以及数据的响应等等。
5. 如果客户端和服务端双方通信完以后,我们需要分别关闭两端的Socket进行通信的断开。
以上为基于TCP的Socket通信,整个通信的过程。
类 ServerSocket : 此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。
类 Socket : 此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
接下来举一个简单的小例子:
登录的过程:
客户端向服务端输入用户名和密码,服务端响应客户端,“表示欢迎”。
流程如下:
代码如下:
服务器端:
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 基于TCP协议的Socket通信,实现用户登陆
* 服务器端
* @author Hubbert
*
*/
public class Server {
public static void main(String[] args) {
try {
//1.创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并侦听此端口。
ServerSocket serverSocket = new ServerSocket(8888);
//2.调用ServerSocket的accept()方法开始侦听,等待客户端的连接
//为了便于查看服务器端的信息
System.out.println("***服务器即将启动,等待客户端的连接***");
Socket socket = serverSocket.accept();
//3.获取输入流,并读取客户端信息
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String info = null;
//循环读取客户端信息
while( (info = br.readLine()) != null ){
System.out.println("我是服务器,客户端说:" + info);
}
//4.获取输出流,相应客户端的请求
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("欢迎您!");
pw.flush();//刷新缓存,向客户端输出信息
socket.shutdownOutput();//关闭输出流
socket.shutdownInput(); //关闭输入流
//5.关闭资源
pw.close();
os.close();
br.close();
isr.close();
is.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 客户端
* @author Hubbert
*
*/
public class Client {
public static void main(String[] args) {
try {
//1.创建客户端Socket,指定服务器地址和端口
Socket socket = new Socket("localhost",8888);
//2.如果服务器一旦接受请求以后,客户端和服务器就建立起了连接,客户端可以向服务器端发送登陆信息
//获取输出流,用来向服务器端发送信息
OutputStream os = socket.getOutputStream();//字节输出流
PrintWriter pw = new PrintWriter(os);//将输出流包装成为打印流
pw.write("用户名:admin,密码:123");
pw.flush();//刷新缓存,向服务器端输出信息
socket.shutdownOutput();//关闭socket输出流
//3.获取输入流,来接受服务器端的相应
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader( new InputStreamReader(is));
String info = null;
while( (info = br.readLine()) != null){
System.out.println("我是客户端,服务器端说:" + info);
}
socket.shutdownInput();
//4.关闭资源
br.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
必须先打开服务器端,点击Run:
然后打开客户端,点击Run:
如图所示可以进行切换Server和Client:
切换到客户端后:
经过TCP Socket的编程以后,即上述中的一个服务器和一个客户端的对话。
但现实生活中,往往是在服务器端运行一个永久的程序,它可以同时跟多个客户端进行通信,并且可以提供多个客户端的请求,提供相对应的服务。
那么我们应该如何实现一个服务端跟多个客户端进行通信呢?应该使用什么技术来实现呢?
-----可以使用多线程服务器
应用多线程来实现服务器与多个客户端之间的通信
基本步骤:
1. 服务器端创建一个ServerSocket,循环调用accep()来侦听客户端发送过来的请求
2. 客户端创建Socket,并请求和客户端的连接。
3. 服务器端接受客户端请求,创建socket与该客户建立专线连接
4. 建立连接的两个socket在一个单独的线程上对话
5. 服务器端继续等待新的连接
代码如下:
//服务器线程类
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
/**
* 服务器线程处理类
* @author Hubbert
*
*/
public class ServerThread extends Thread{
// 和本线程相关的Socket
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;
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);
}
//获取输出流,相应客户端的请求
os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write("欢迎您!");
pw.flush();//刷新缓存,向客户端输出信息
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//5.关闭资源
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();
}
}
}
}
//服务端
package com.imooc.ServerThread;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
//1.创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并侦听此端口。
ServerSocket serverSocket = new ServerSocket(8888);
//为了便于查看服务器端的信息
System.out.println("***服务器即将启动,等待客户端的连接***");
//记录客户端的数量
int count = 0;
Socket socket = null;
//循环侦听等待客户端的连接
while(true){
//2.调用ServerSocket的accept()方法开始侦听,等待客户端的连接
socket = serverSocket.accept();
//3. 创建一个新的线程
ServerThread serverThread = new ServerThread(socket);
//4. 启动线程,执行客户端的通信
serverThread.start();
count++;
System.out.println("客户端的数量为:" + count);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//客户端
package com.imooc.ServerThread;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 客户端
* @author Hubbert
*
*/
public class Client {
public static void main(String[] args) {
try {
//1.创建客户端Socket,指定服务器地址和端口
Socket socket = new Socket("localhost",8888);
//2.如果服务器一旦接受请求以后,客户端和服务器就建立起了连接,客户端可以向服务器端发送登陆信息
//获取输出流,用来向服务器端发送信息
OutputStream os = socket.getOutputStream();//字节输出流
PrintWriter pw = new PrintWriter(os);//将输出流包装成为打印流
pw.write("用户名:admin,密码:123");
pw.flush();//刷新缓存,向服务器端输出信息
socket.shutdownOutput();//关闭socket输出流
//3.获取输入流,来接受服务器端的相应
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader( new InputStreamReader(is));
String info = null;
while( (info = br.readLine()) != null){
System.out.println("我是客户端,服务器端说:" + info);
}
socket.shutdownInput();
//4.关闭资源
br.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
先启动服务端:
再启动客户端:
此时切换到服务端:
此时只有一个用户,看到红色圈那里,还没结束。
此时再次启动客户端,模拟多客户端的连接,在客户端那里修改下用户名和密码:
pw.write("用户名:hubbert,密码:123456");
再次启动客户端:
看到红色那里,服务端还是继续的循环侦听新客户端的连接。
5. UDP编程
UDP协议(用户数据报协议)是无连接,不可靠的,无序的
UDP协议以数据报作为数据传输的载体,也就是说:
进行数据传输时,首先需要将要传输的数据定义成数据报(Datagram),在数据报中指明数据所要达到的Socket(主机地址和端口号),然后再将数据报发送出去。
接下使用代码:
服务器:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class UDPServer {
public static void main(String[] args) throws IOException {
/*
* 接受客户端发送的数据
*/
//1.创建服务器端DatagramSocket,指定端口
DatagramSocket socket = new DatagramSocket(8800);
//2.创建数据报,用于接收客户端发送的数据
byte [] data = new byte[1024]; //创建字节数组,指定接收的数据包的大小
DatagramPacket packet = new DatagramPacket(data, data.length);
//3.接受客户端发送的数据
System.out.println("服务器端已经启动,等待客户端发送数据...");
socket.receive(packet); //此方法在接收数据报之前会一直阻塞
//4.读取数据
String info = new String(data,0,packet.getLength());
System.out.println("我是服务器,客户端说:" + info);
/*
* 向客户端响应数据
*/
//1.定义客户端的地址,端口号,数据
InetAddress address = packet.getAddress();
int port = packet.getPort();
byte[] data2 = "欢迎您".getBytes();
//2. 创建数据报,包含响应的数据信息
DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address,port);
//3.响应客户端
socket.send(packet2);
//4.关闭资源
socket.close();
}
}
客户端:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
public class UDPClient {
public static void main(String[] args) throws IOException {
/*
* 向服务器端发送数据
*/
//1.定义服务器的地址,端口号,数据
InetAddress address = InetAddress.getByName("localhost");
int port = 8800;
byte[]data = "用户名:admin;密码:123".getBytes();
//2.创建数据报,包含发送的信息
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
//3.创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket();
//4.向服务器发送数据报
socket.send(packet);
/*
* 接受服务器端响应的数据
*/
//1.创建数据报,用户接收服务器端响应的数据
byte[] data2 = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(data2,data2.length);
//2.接受服务器端的信息
socket.receive(packet2);
//3.读取数据
String reply = new String(data2,0,packet2.getLength());
System.out.println("我是客户端,服务器说"+reply);
//4.关闭资源
}
}
结果如下:
总结:
1. Socket通信原理
2. 基于TCP的Socket通信
注意点:
1.多线程的优先级
2.是否关闭输出流和输入流
对于同一个socket,如果关闭了输出流,则与该输出流关联的socket也会被关闭,所以一般不用关闭输出流,关闭socket即可。
感谢以下文章: