Java Scoket
整理借鉴自:
Java Scoket https://blog.youkuaiyun.com/yuqnfendetiankong/article/details/76163888
TCP/IP https://blog.youkuaiyun.com/patrick_star_cowboy/article/details/82016692
前言:
Java对网络提供了高速的支持,使得客户端和服务器的沟通变成了现实,而在网络编程中使用最多的就是socket
知识点:
网络基础知识、InetAddress类、URL类、TCP编程、UDP编程。
TCP/IP https://blog.youkuaiyun.com/patrick_star_cowboy/article/details/82016692
JAVA对网络的支持
针对网络通信的不同层次,JAVA提供的网络功能有四大类:
- InetAddress:用于标识网络上的硬件资源。说白了它主要标识IP地址等相关信息
- URL:统一资源定位符,通过URL可以直接读取或写入网络上的数据
- Sockets:使用TCP协议实现网络通信的Socket相关的类
- Datagram:使用UDP协议,将数据保存在数据报中,通过网络进行通信
JAVA中网络相关API的应用
在Java中提供了专门的网络开发程序包---java.net
java的网络编程提供了两种通信协议:TCP(传输控制协议)和UDP(数据报协议)
1.InetAddress
InetAddress是java网络的类。通过该类通过得到ip地址和主机名一些信息。它提供了一些静态方法。
InetAddress下还有2个子类:Inet4Address、Inet6Address,它们分别代表Internet Protocol version 4(IPv4)地址和Internet Protocol version 6(IPv6)地址,不过这两个子类不常用
方法:
getLocalHost()
返回本地主机
getByName()
在给定主机名的情况下,确定主机的IP地址
getByAdress()
在给定原始IP地址的情况下,返回InetAddress的对象
getByAdress()
根据提供的主机名和IP地址,创建InetAddress对象
getHostName()
获取此IP地址的主机名
getHostAddress()
返回IP地址的字符串(以文本表现形式)
测试:
public void InetAddressTest () {
try {
InetAddress address=InetAddress.getLocalHost();
System.out.println("计算机名:"+address.getHostName());
System.out.println("IP地址:"+address.getHostAddress());
byte[] bytes=address.getAddress();//获取IP地址的字节数组
System.out.println("字节形式的数组IP:"+ Arrays.toString(bytes));
System.out.println(address);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
2.URL
JAVA中URL的应用,首先我们需要先了解下URL:
- URL中文名称为统一资源定位符(Uniform Resource Locator),表示Internet上某一资源的地址。
比如说大家经常访问我们的imooc网站,那么在我们的网站上提供了非常多的资源,在访问网站的时候,我们要输入一个地址,也就是网址 www.imooc.com,它呢实际上就是一个url,标识着我们imooc网站上大家共享的资源信息。 - URL由两部分组成:协议名称和资源名称,中间用冒号隔开。我们所说的网址,实际就是HTTP协议,超文本传输协议:Http://www.imooc.com/learn/85 这就是浏览网页所使用到的一个协议。像HTTP就是协议名称,后面的就是资源名称。
- 在java当中,java.net包,提供了URL类来表示URL。
Java的网络类可以让你通过网络或者远程连接来实现应用。而且,这个平台现在已经可 以对国际互联网以及URL资源进行访问了。Java的URL类可以让访问网络资源就像是访问你本地的文件夹一样方便快捷。我们通过使用Java的URL类 就可以经由URL完成读取和修改数据的操作。
打开API文档来查看一下。构造方法,可以根据字符串方式来创建,参数要求不同,也可以根据协议名,主机、端口号、和文件的信息来创建URL。也可以根据一个已存在的URL,以及相应的字符串信息来创建一个新的URL,也就是说根据一个父URL来创建一个子URL,这是我们看到的它的构造方法,这个类还提供了许多其他方法来获取URL的信息。
public void URLTest () {
//创建一个URL的实例
try {
URL imooc=new URL("https://mp.youkuaiyun.com");
//根据已创建的URL,来创建新的URL
URL url=new URL(imooc,"/postedit/80519784?id=1");
//?后面表示参数,#后面表示锚点
System.out.println("协议:"+url.getProtocol());
System.out.println("主机:"+url.getHost());
//如果未指定端口号,根据协议不同使用默认端口号,那我们这里HTTP使用的是80
//此时因为是默认的端口号,所以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来读取网页中的内容
// 使用URL来读取网页中的内容
public void URLTest2 (){
//创建一个URL实例
try {
URL url=new URL("http://www.baidu.com");
//通过url的openstream方法来获取url对象所表示资源的字节输入流
InputStream is=url.openStream();
//之后把它转化成字符输入流
InputStreamReader isr=new InputStreamReader(is);
//为字符输入流添加缓冲
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();
}
}
使用python输出网页信息
import requests
# 打印网页信息
def URLTest():
url='https://www.baidu.com'
a=requests.get(url)
print(a.text)
URLTest()
扩展使用: 结合java.net.URLConnection,发送POST请求到远程服务器
public static String sendPostbyUrl() throws UnsupportedEncodingException, IOException {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
String url = "https://api.jisuapi.com/shouji/query";
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
//设置超时时间
conn.setConnectTimeout(5000);
conn.setReadTimeout(15000);
// 设置通用的请求属性
Map<String,String> header = new HashMap();
header.put("Content-Type","application/json");
if (header!=null) {
for (Map.Entry<String, String> entry : header.entrySet()) {
conn.setRequestProperty(entry.getKey(), entry.getValue());
}
}
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
Map param = new HashMap();
param.put("appkey","yourappkey");
param.put("shouji","13456755448");
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "utf8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
System.out.println(result);
return result;
}
3.通过Socket实现TCP编程
在了解了网络的相关知识,下面我们来看下Socket编程及基于TCP的网络通信。
TCP协议是面向连接、可靠的、有序的,以字节流的方式发送数据。在数据通信之前需要建立连接,保证数据传输的可靠性,那么它的传输是有顺序的,并且以字节流的方式进行传输,在我们JAVA当中如何去基于TCP协议实现网络通信呢?
有这么两个相关的类,基于TCP协议实现网络通信的类:
- 客户端的Socket类
- 服务器端的ServerSocket类
我们来看一下它具体的通信的过程,也就是Socket通信模型。两台主机需要通信,就必然存在着一台主机为服务器端Server,一台为客户端Client。那么通信的时候是这样的,首先我们在服务端,建立一个ServerSocket(服务端倾听Socket),绑定相应的端口,并且在指定的端口进行侦听,等待客户端的连接。当我们在客户端创建Socket并且向服务器端发送请求,服务器收到请求并且接受客户端的请求信息,一旦接受客户端请求信息以后,会创建一个连接Socket,用来与客户端的Socket进行通信。到底该如何进行通信,就是通过相关的输入流和输出流 InputStream,OutpuStream进行数据的交换,发送接收以及数据的响应等等。那么在客户端和服务器端双方通信完以后,我们需要分别关闭两边的Socket及相关资源,进行通信的断开。这是基于TCP-socket通信,整个通信的过程,这点需要各位小伙伴们清楚。
那么具体该怎么进行操作呢?Socket实现通信操作的基本步骤:
- 1.分别在服务器端和客户端完成ServerSocket和Socket的创建,这也是我们实现网络通信的基础
- 2.打开连接到Socket的相关的输入输出流,进行数据的通信
- 3.按照协议对Socket进行读写操作
- 4.在通信完成以后关闭输入输出流,关闭socket
那如何使用SeverSocket和Socket呢?我们可以打开API文档来看一下这里是怎么说明这两个类的。
我们知道编程实现是基于TCP的Socket通信之服务端,下面我们通过socket的一个案例来看一下,如果实现基于TCP的socket通信,我们要实现的是一个用户的登陆。实现的过程,实际上就是用户信息在客户端向服务器端发送过去,服务器端接收到用户信息以后,进行响应这样一来一回的过程,它的运行结果也非常的简单。在客户端向服务端发送登陆请求,提交用户名和密码,那服务器端呢进行响应,欢迎之类的信息。它有一个相对固定的实现的步骤,我们要在服务器端创建一个服务器端的Socket。
服务器端的步骤:
- 1.创建服务器端的ServerSocket对象,绑定监听端口
- 2.调用accept()方法进行侦听客户端的请求,等待客户端的连接
- 3.与客户端建立连接以后,通过输入流读取客户端发送的请求信息
- 4.通过输出流用来响应客户端
- 5.关闭输入输出流以及socket等相应的资源
客户端的步骤:
- 1.创建socket对象,并且指明需要连接的服务器端的地址以及端口号,用来与服务器端进行连接
- 2.连接建立后,获取一个输出流,通过输出流向服务器端发送请求信息
- 3.通过输入流,读取服务器端响应的信息
- 4.关闭相应的资源
仅仅发送信息到服务器端是不够的,如果要实现服务器端对客户端信息的响应,我们该如何做呢?其实也很简单,响应是通过输出流来实现的。
在了解了基于TCP的socket编程以后,我们有的小伙伴们就会发出这样的疑问,那我们所编写的程序只能实现服务器和一个客户端的对话,而在实际的应用中,往往是在服务器中运行一个永久的程序,它可以和多个客户端进行通信,并且可以接受多个客户的请求,提供相应的服务。那么我们如何实现一个服务器和多客户端之间的通信呢?对于这种需求我们又该使用何种技术去实现呢?
我们可以使用之前所讲过的多线程来实现服务器与多客户端之间的通信。它的基本步骤:
- 1.服务器端创建ServerSocket,循环调用accept()方法等待客户端连接。当然由于我们要实现多客户端的通话,也就意味着我们循环等待客户端的连接
- 2.每当客户端创建一个socket并请求和服务端连接,服务器端就会产生一个专门的线程,来响应该客户与之通信
- 3.服务器端接收请求,创建socket与该客户进行连接
- 4.建立连接的两个socket在一个单独的线程上对话,而同时服务器本身在启动线程以后,马上又会进入侦听状态
- 5.服务器继续等待新的连接
具体案例代码:
TCPServerTest (服务器端代码)
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Socket基于TCP通信,服务器端SeverSocket
*/
public class TCPServerTest {
public static void main (String [] a){
try {
// 1.创建一个服务器端ServerSocket,并且绑定指定的端口号
ServerSocket serverSocket = new ServerSocket(8888);
// 客户端计数器
Socket socket = null;
int count = 0;
System.out.println("===================服务端开始启动,等待客户端连接中==================");
while (true){
//2.调用accept()方法开始监听,等待客户端的连接
socket = serverSocket.accept();
TCPServerThread tcpServerThread = new TCPServerThread(socket);
tcpServerThread.start();
count++;
System.out.println("客户端的数量:"+count);
InetAddress address = socket.getInetAddress();
System.out.println("当前客户端的IP地址为:" +address.getHostAddress());
}
// Socket socket=serverSocket.accept();//一旦调用这个方法它就会处于阻塞的状态,等待客户端的请求信息
// //如果客户端发送连接请求,这个时候我们会接收客户端的请求,并且我们看到accpet方法会返回一个socket的实例
// //用来与客户端进行通信
// //一旦与客户端建立socket通信以后,我们下面就需要在客户端和服务器端实现数据的交互,获取客户端提交的登陆
// //信息,那么如何获取呢?需要通过输入输出流来实现。
// //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);
// }
// 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();
}
}
}
TCPServerThread(服务器端线程处理类)
import java.io.*;
import java.net.Socket;
/**
* todo 服务器端线程处理类
*/
public class TCPServerThread extends Thread {
// 每个客户端请求之后,服务器端都会创建一个socket,与之进行通讯,
// 所以我们每个线程都对应一个与本线程相关的socket;
Socket socket=null;
// TCPServerThread构造函数,初始化socket
public TCPServerThread(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();
// 3.获取输入流读取客户端的信息
// 为了提升效率可以把它包装成一个字节输入流
isr = new InputStreamReader(is);
// 我们可以为字节流添加缓冲,以缓冲的方式进行数据的读取
br = new BufferedReader(isr);
String info = null;
while ((info = br.readLine())!= null){
System.out.println("我是服务器端,客户端说"+info);
}
socket.shutdownInput();// socket关闭输入流
// 4.获取输出流,用来服务器端响应客户端的信息
os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write("欢迎您!");
pw.flush();
}catch (IOException e){
e.printStackTrace();
}finally {
// 5.关闭相关的资源,finally是一定会被执行的
try {
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();
}
}
}
}
TCPClient(客户端)
import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* Tcp socket 客户端
*/
public class TCPClient {
public static void main (String[] a){
try {
// 1.创建socket用来与服务器端进行通讯,发送请求建立连接,指定服务器地址和端口
Socket socket = new Socket("127.0.0.1",8888);
// 2.获取输出流用来向服务器端发送登陆的信息
OutputStream os = socket.getOutputStream(); // 字节输出流
PrintWriter pw = new PrintWriter(os);
pw.write("我是张三,请求登陆系统,ps:来自java客户端");
pw.flush();
socket.shutdownOutput();
// 3.获取输入流,用来读取服务器端的响应信息
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));// 添加缓冲
String info = null;
while ((info = br.readLine())!=null){
System.out.println("我是客户端,服务器端说"+info);
}
// 4.关闭其他相关
br.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
TCPClient2(客户端2)
import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* Tcp socket 客户端
*/
public class TCPClient2 {
public static void main (String[] a){
try {
// 1.创建socket用来与服务器端进行通讯,发送请求建立连接,指定服务器地址和端口
Socket socket = new Socket("127.0.0.1",8888);
// 2.获取输出流用来向服务器端发送登陆的信息
OutputStream os = socket.getOutputStream(); // 字节输出流
PrintWriter pw = new PrintWriter(os);
pw.write("我是李四,请求登陆系统,ps:来自java客户端");
pw.flush();
socket.shutdownOutput();
// 3.获取输入流,用来读取服务器端的响应信息
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));// 添加缓冲
String info = null;
while ((info = br.readLine())!=null){
System.out.println("我是客户端,服务器端说"+info);
}
// 4.关闭其他相关
br.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
python客户端
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 文件名:server.py
import socket # 导入 socket 模块
s = socket.socket() # 创建socket对象
host = '127.0.0.1' # host地址 ps: socket.gethostname() 获取本级地址
port = 8888 # 端口号
s.connect((host,port))
s.send('我是王五,请求登陆系统,ps:来自python客户端'.encode()) # 向服务器端发送第一条消息
s.shutdown(socket.SHUT_WR) # 结束输出流
szBuf = s.recv(222222); # 接收服务器端返回
print('我是客户端,服务器端说:'+szBuf.decode()) # 输出服务器端消息
s.close()
服务器端运行结果:
总结一下socket通信的相关内容,本节课的重点是socket通信原理以及基于tcp的socket通信。希望各位小伙伴可以通过示例代码能反复的练习。这里主要有以下几个问题需要和大家多强调以下:
- 1.关于多线程的问题,我们之前在socket编程当中使用到了多线程,比如说服务器需要不停的与多客户端通信,那么也就是需要我们的服务器端需要不停地去监听客户端的请求,这个时候就需要使用多线程,既然是死循环,这个线程就需要注意优先级的问题,如果说没有设置优先级,可能会导致效率低下,速度会比较慢 thread.setPriority(4);这是一个工作上的经验,前面这个方法是设置线程的优先级【1-10】,默认为5,建议适当的降低,否则就会出现运行速度比较慢的问题
- 2.关于TCP的输入流输出流是否关闭问题,对于同一个socket,如果说我们提前把输出流关闭,这个时候会导致与该输出流关联的socket也被关闭,所以一把不用关闭流,直接关闭socket即可
- 3.使用TCP通信传输对象,我们在之前进行用户登陆的时候,客户端向服务器端发送了数据,以及服务器端响应的数据都是通过字符串的形式进行交互的,实际的应用当中,客户端向服务器端发送数据更多的是以对象的形式发送的,我们把用户名和密码封装成user对象,传递的是一个user,这就是我们面向对象的思想。这个时候我们就可以继续使用在IO中所讲到的ObjectInputStream,ObjectOutputStream writeObject来实现对象的传输。
- 4.Socket编程传递文件
之前我们在进行数据传递的时候,传递的是字符串,如果我们想实现文件的传递呢?我现在想在客户端给服务器端发送一个文件,这个时候又该怎么办呢?我们可以结合我们之前IO的相关知识通过输入输出流去读取文件中的数据,并且把它发送给服务器端。