Java中的网络编程—Socket通信
一、网络基础知识
两台计算机要想通过网络进行通信,那么他们必须满足ip地址、协议、端口号这三个必然的条件
1、两台主机必须表明所在的身份和位置,也就是ip地址
2、必须有共同的语言,不然无法交流,也就是我们所说的协议
3、需要有相应的端口号,一台主机可以运行多个应用程序,怎么辨别不同通信的程序,那么就需要端口号来区分
TCP/IP协议
TCP/IP是目前世界上应用最为广泛的协议,是以TCP和IP为基础的不同层次上多个协议的集合,也称TCP/IP协议族或TCP/IP协议栈
TCP:Transmission Control Protocol 传输控制协议
IP:Internet Protocol 互联网协议
TCP/IP模型
一般来说,会将网络进行分层,常见的会把网络分为五层,叫TCP/IP模型
IP地址
为实现网络中不同计算机之间的通信,每台机器都必须有一个唯一的标识---IP地址
IP地址格式:数字型,如:192.168.0.1
IPv4:32位的二进制
端口
1、用于区分不同应用程序
2、端口号范围为0~65535,其中0~1023为系统所保留
3、IP地址和端口号组成了所谓的Socket,Socket是网络上运行的程序之间双向通信链接的终结点,是TCP和UDP的基础
4、http:80 ftp:21 telnet:23
二、Java中网络相关API的应用
java中的网络支持
针对网络通信的不同层次,java提供的网络功能有四大类:
1、InetAddress:用于标识网络上的硬件资源
2、URL:统一资源定位符,通过url可以直接读写或写入网络上的数据
3、Socket:使用TCP协议实现网络通信的Socket相关的类
4、Datagram:使用UDP协议,将数据保存在数据报中,通过网络进行通信
InetAddress类
1、InetAddress类用于标识网络上的硬件资源,表示互联网协议(IP)地址
2、实例如下
输入:
package rmd_intl_app.Test;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
/**
* @Description: InetAddress类
*/
public class InetAddressTest {
public static void main(String[] args) throws UnknownHostException {
//获取本机InetAddress类的操作实例
InetAddress address = InetAddress.getLocalHost();
System.out.println("address计算机名:"+address.getHostName());
System.out.println("addressIP地址:"+address.getHostAddress());
byte[] bs = address.getAddress();//获取字节数组形式的ip地址
System.out.println("字节数组的形式的IP:"+Arrays.toString(bs));
System.out.println("直接输出InetAddress对象:"+address);
//根据计算机名获取InetAddress实例
InetAddress address2 = InetAddress.getByName("MS-20160808TVQL");
System.out.println("address2获取计算机名:"+address2.getHostName());
System.out.println("address2IP地址:"+address2.getHostAddress());
//根据ip地址获取InetAddress实例
InetAddress address3 = InetAddress.getByName("192.168.0.235");
System.out.println("address3计算机名:"+address3.getHostName());
System.out.println("address3IP地址:"+address3.getHostAddress());
}
}
输出:
address计算机名:MS-20160808TVQL
addressIP地址:192.168.0.235
字节数组的形式的IP:[-64, -88, 0, -21]
直接输出InetAddress对象:MS-20160808TVQL/192.168.0.235
address2获取计算机名:MS-20160808TVQL
address2IP地址:192.168.0.235
address3计算机名:MS-20160808TVQL
address3IP地址:192.168.0.235
URL
1、URL(Uniform Resoure Locator)统一资源定位符,表示Internet上某一资源的地址
2、URL由两部分组成,协议名称和资源名称,中间用冒号隔开
3、在java.net包中,提供了URL类来表示URL
4、实例如下
输入:
package rmd_intl_app.Test;
import java.net.MalformedURLException;
import java.net.URL;
/**
* @Description: url
*/
public class Urltest {
public static void main(String[] args) {
try {
//创建一个url实例
URL url = new URL("https://www.taobao.com/");
//?后面是参数,#后面是锚点
URL u = new URL(url, "index.html?username=taobao#test");
System.out.println("协议:"+u.getProtocol());
System.out.println("主机:"+u.getHost());
//如果没有指定端口号,则返回默认端口号,此时getPort()返回值为-1
System.out.println("端口:"+u.getPort());
System.out.println("文件路径:"+u.getPath());
System.out.println("文件名称:"+u.getFile());
System.out.println("相对路径:"+u.getRef());
System.out.println("查询字符串:"+u.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
输出:协议:https
主机:www.taobao.com
端口:-1
文件路径:/index.html
文件名称:/index.html?username=taobao
相对路径:test
查询字符串:username=taobao
使用URL读取网页内容
1、通过urll对象的openStream()方法可以得到指定资源的输入流
2、通过输入流可以读取、访问网络上的数据
3、实例如下
输入:
package rmd_intl_app.Test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
/**
* @Description: 使用URL读取网页内容
*/
public class UrlTest02 {
public static void main(String[] args) {
try {
//创建url一个实例
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 (IOException e) {
e.printStackTrace();
}
}
}
输出:
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>©2017Baidu<a href=http://www.baidu.com/duty/>使用百度前必读</a> <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>京ICP证030173号 <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
三、通过Socket实现TCP编程
Socket通信
TCP协议是面向连接,可靠的、有序的、以字节流的方式发送数据
基于TCP协议实现网络通信的类:客户端的Socket类和服务器端的ServerSocket类
Socket通信模型
三个步骤:建立连接(客户端和服务器端进行建立通信连接)、开始通信(客户端向服务器发送请求,服务器端响应请求,然后返回请求响应)、结束通信(关闭通信和连接请求)
Socket通信实现步骤
1、创建socket和ServerSocket
2、打开连接到Socket输入/输出流
3、按照协议对Socket进行读/写操作
4、关闭输入/输出流,关闭Socket
服务器端
1、创建ServerSocket对象,绑定监听端口
2、通过accept方法监听客户端请求
3、连接建立后,通过输入流读取客户端发送的请求信息
4、通过输出流向客户端发送响应信息
5、关闭相关资源
6、实例如下
package rmd_intl_app.Test;
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;
/**
* @Description: 服务器端
*/
public class Server {
public static void main(String[] args) {
try {
System.out.println("服务器端已经启动,等待连接...........");
//1、创建ServerSocket服务器对象,指定绑定的端口并监听
ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();//进行监听,等待客户端连接
//2、字节输入流
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);//将字节流转换为字符流
BufferedReader br = new BufferedReader(isr);//为输入流添加缓冲
String info = null;
//3、循环读取客户端信息
while ((info = br.readLine()) != null) {
System.out.println("我是服务器,客户端说:"+info);
}
socket.shutdownInput();//关闭输入流
//4、获取输出流,响应客户端请求
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("我是服务器,非常欢迎你!");
pw.flush();
//5、关闭资源
pw.close();
os.close();
br.close();
isr.close();
is.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
1、创建Socket对象,指明需要连接的服务器的地址和端口号
2、连接建立后,通过输出流向服务器端发送请求信息
3、通过输入流获取服务器响应的信息
4、关闭相关资源
5、实例如下
package rmd_intl_app.Test;
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;
/**
* @Description: 客户端
*/
public class Client {
public static void main(String[] args) {
try {
//1、创建socket客户端,指定服务器地址和端口
Socket socket = new Socket("192.168.0.235",8080);
//2、获取输出流,向服务器发送信息
OutputStream os = socket.getOutputStream();//字节输出流
PrintWriter pw = new PrintWriter(os);//将输出流包装为打印流
pw.write("hello word");
pw.flush();
//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();
}
}
}
多线程下的通信
应用多线程来实现服务器与多客户端之间的通信
基本步骤:
1、服务器端创建ServerSocket,循环调用accept()等待客户端连接
2、客户端创建一个socket并请求和服务器端连接
3、服务器端接受客户端请求,创建socket与该客户端建立专线连接
4、建立两个连接的socket在一个单独的线程上对话
5、服务器端继续等待新的连接
实例如下:
1、服务器线程处理类
package rmd_intl_app.Test;
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;
/**
* @Description: 服务器线程处理类
*/
public class ServerThread extends Thread{
//和本线程相关的socket
Socket socket = null;
public ServerThread(Socket socket){
this.socket = socket;
}
//线程执行的操作,响应客户端的请求
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;
//3、循环读取客户端信息
while ((info = br.readLine()) != null) {
System.out.println("我是服务器,客户端说:" + info);
}
socket.shutdownInput();//关闭输入流
//获取输出流,响应客户端请求
os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write("我是服务器,非常欢迎你!");
pw.flush();
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(pw != null){
pw.close();
}
if(pw != null){
os.close();
}
if(pw != null){
br.close();
}
if(pw != null){
isr.close();
}
if(pw != null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2、服务器端
package rmd_intl_app.Test;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Description: 服务器端
*/
public class Server {
public static void main(String[] args) {
try {
//创建ServerSocket服务器对象,指定绑定的端口并监听
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("服务端已经启动,等待客户端连接.....");
Socket socket = null;
//记录客户端的数量
int count = 0;
while(true){
//进行监听,等待客户端连接
socket = serverSocket.accept();
//创建一个新线程
ServerThread thread = new ServerThread(socket);
//启动线程
thread.run();
count++;//统计客户端的数量
System.out.println("客户端的数量为:"+count);
InetAddress address = socket.getInetAddress();
System.out.println("客户端IP为:"+address.getHostAddress());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3、客户端
package rmd_intl_app.Test;
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;
/**
* @Description: 客户端
*/
public class Client {
public static void main(String[] args) {
try {
//1、创建socket客户端,指定服务器地址和端口
Socket socket = new Socket("192.168.0.235",8080);
//2、获取输出流,向服务器发送信息
OutputStream os = socket.getOutputStream();//字节输出流
PrintWriter pw = new PrintWriter(os);//将输出流包装为打印流
pw.write("hello word");
pw.flush();
//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();
}
}
}
注意事项:
1、资源必须释放,关闭资源的代码可写的严谨点,且倒序以此关闭
2、服务器端优先启动
3、多线程下客户端可以多次启动,体验多线程效果
四、通过Socket实现UDP编程
UDP编程
UDP协议(用户数据报协议)是无连接、不可靠、无序的,相对来说传输速度比较快
UDP协议以数据报作为数据传输的载体。就是进行数据传输时,首先需要将要传输的数据定义成数据报(Datagram),在数据报中指明数据所要达到的Socket(主机地址和端口号),然后在将数据报发送过去
操作相关类:
DatagramPacket:表示数据报包
DatagramSocket:进行端到端通信的类
UDP通信模型
服务器端实现步骤
1、创建DatagramSocket,指定端口号
2、创建DatagramPacket
3、接受客户端发送的数据信息
4、读取数据
5、实例如下
package rmd_intl_app.Test;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* @Description: 服务器端
*/
public class UDPServer {
public static void main(String[] args) throws IOException {
/**
* 服务器端接受数据
*/
//1、创建服务器DatagramSocket,指定端口
DatagramSocket socket = new DatagramSocket(8080);
//2、创建数据报,用于接受客户端发送来的数据
byte[] b = new byte[2014];//创建字节数组,指定接受数据包的大小
DatagramPacket packet = new DatagramPacket(b, b.length);
//3、接受客户端发送来的数据
System.out.println("服务器已经启动,等待连接.....");
socket.receive(packet);//此方法在接受数据之前会一直堵塞
//4、读取数据
String info = new String(b,0,packet.getLength());
System.out.println("我是服务器,客户端说:"+info);
/**
* 服务器端响应数据
*/
//1、定义服务器的地址,端口号,数据
InetAddress address = packet.getAddress();
int port = packet.getPort();
byte[] by = "欢迎您!".getBytes();
//2、创建数据报,包含响应的数据信息
DatagramPacket packet2 = new DatagramPacket(by, by.length, address, port);
//3、响应客户端
socket.send(packet2);
//4、关闭资源
socket.close();
}
}
客户端实现步骤
1、定义发送信息
2、创建DatagramPacket,包含将要发送的信息
3、创建DatagramSocket
4、实例如下
package rmd_intl_app.Test;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* @Description: 客户端
* @author lc
* @date 2017年6月24日
*/
public class UDPClient {
public static void main(String[] args) throws IOException {
/**
* 向服务器端发送数据
*/
//1、定义服务器的地址,端口号,数据
InetAddress address = InetAddress.getByName("192.168.0.235");
int port = 8080;
byte[] data = "您好,我是客户端".getBytes();
//2、创建数据报,包含发送的数据信息
DatagramPacket packet = new DatagramPacket(data, data.length, address,port);
//3、创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket();
//4、向服务器端发送数据报
socket.send(packet);
/**
* 接受服务器端响应的数据
*/
//1、创建数据报,用于接受服务器端响应的数据
byte[] b = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(b,b.length);
//2、接受服务器端响应的数据
socket.receive(packet2);
//3、读取数据
String info = new String(b,0,packet2.getLength());
System.out.println("我是客户端,服务器说:"+info);
//4、关闭资源
socket.close();
}
}
(以上是个人学习笔记,不对之处望指正)