文章目录
知识点
网络基础知识
ip地址
每个网卡/机器都有一个或多个IP地址
- IPV4:192.168.0.100,每段从0到255
- IPV6: FE80:0000:0000:0000:AAAA:0000:00C2:0002
128bit长,分成8段,每段4个16进制数 - 查询:Windows平台
ipconfig
, Linux/Mac平台ifconfig
端口
port:端口,0-65535
- 0~1023,OS已经占用了,80是Web,23是telnet
- 1024~65535,一般程序可使用(谨防冲突)
- 两台机器通讯就是在IP+Port上进行的
- 在Windows/Linux/Mac上都可以通过
netstat -an
来查询
公网和内网
公网:万维网/互联网
内网:局域网
- 网络是分层的
- 最外层是公网/互联网
- 底下的每层都是内网
- ip地址可以在每个层次的网重用
- tracert 看当前机器和目标机器的访问中继
通讯协议:TCP和UDP
TCP(Transmission Control Protocol)
- 传输控制协议,面向
连接
的协议 - 两台机器的
可靠无差错
的数据传输 双向
字节流传递
UDP(User Datagram Protocol)- 用户数据报协议,面向
无连接
协议 不保证可靠
的数据传输- 速度快,也可以在较差网络下使用
Java UDP编程
基础知识
- 计算机通讯:数据从一个IP的port出发(发送方),运输到另外一个IP的port(接收方)
- UDP:无连接无状态的通讯协议,
- 发送方发送消息,如果接收方刚好在目的地,则可以接受。如果不在,那这个消息就丢失了
- 发送方也无法得知是否发送成功
- UDP的好处就是简单,节省,经济
实现类
- DatagramSocket:通讯的数据管道
- send 和receive方法
- (可选,多网卡)绑定一个IP和Port
- DatagramPacket
- 集装箱:封装数据
- 地址标签:目的地IP+Port
示例代码
UdpRecv
import java.net.*;
public class UdpRecv
{
public static void main(String[] args) throws Exception
{
DatagramSocket ds=new DatagramSocket(3000);
byte [] buf=new byte[1024];
DatagramPacket dp=new DatagramPacket(buf,1024);
System.out.println("UdpRecv: 我在等待信息");
ds.receive(dp);//收到消息则封装到dp,否则阻塞
System.out.println("UdpRecv: 我接收到信息");
String strRecv=new String(dp.getData(),0,dp.getLength()) +
" from " + dp.getAddress().getHostAddress()+":"+dp.getPort();
System.out.println(strRecv);
Thread.sleep(1000);
System.out.println("UdpRecv: 我要发送信息");
String str="hello world 222";
DatagramPacket dp2=new DatagramPacket(str.getBytes(),str.length(),
InetAddress.getByName("127.0.0.1"),dp.getPort());
ds.send(dp2);
System.out.println("UdpRecv: 我发送信息结束");
ds.close();
}
}
UdpSend
import java.net.*;
public class UdpSend
{
public static void main(String [] args) throws Exception
{
DatagramSocket ds=new DatagramSocket();
String str="hello world";
DatagramPacket dp=new DatagramPacket(str.getBytes(),str.length(),
InetAddress.getByName("127.0.0.1"),3000);//本机的3000端口
System.out.println("UdpSend: 我要发送信息");
ds.send(dp);
System.out.println("UdpSend: 我发送信息结束");
Thread.sleep(1000);
byte [] buf=new byte[1024];
DatagramPacket dp2=new DatagramPacket(buf,1024);
System.out.println("UdpSend: 我在等待信息");
ds.receive(dp2);
System.out.println("UdpSend: 我接收到信息");
String str2=new String(dp2.getData(),0,dp2.getLength()) +
" from " + dp2.getAddress().getHostAddress()+":"+dp2.getPort();
System.out.println(str2);
ds.close();
}
}
UdpSend
UdpRecv
接收方必须早于发起方执行
Java TCP 编程
基础知识
TCP协议:有链接、保证可靠的无误差通讯
① 服务器:创建一个ServerSocket,等待连接
软件服务器有两个要求:
- 它能够实现一定的功能
- 它必须在一个公开地址上对外提供服务
② 客户机:创建一个Socket,连接到服务器
③ 服务器:ServerSocket接收到连接,创建一个Socket和客户的Socket建立专线连接,后续服务器和客户机的对话(这一对Socket)会在一个单独的线程(服务器端)上运行
④ 服务器的ServerSocket继续等待连接,返回①
实现类
- ServerSocket: 服务器码头
- 需要绑定port
- 如果有多块网卡,需要绑定一个IP地址
- Socket: 运输通道
- 客户端需要绑定服务器的地址和Port
- 客户端往Socket输入流写入数据,送到服务端
- 客户端从Socket输出流取服务器端过来的数据
- 服务端反之亦然
- 服务端等待响应时,处于阻塞状态
- 服务端可以同时响应多个客户端
- 服务端每接受一个客户端,就启动一个独立的线程与之对应
- 客户端或者服务端都可以选择关闭这对Socket的通道
示例代码
TcpServer和TcpClint交互
//TcpServer.java
import java.net.*;
import java.io.*;
public class TcpServer
{
public static void main(String [] args)
{
try
{
ServerSocket ss=new ServerSocket(8001); //驻守在8001端口
Socket s=ss.accept(); //阻塞,等到有客户端连接上来
System.out.println("welcome to the java world");
InputStream ips=s.getInputStream(); //有人连上来,打开输入流
OutputStream ops=s.getOutputStream(); //打开输出流
//同一个通道,服务端的输出流就是客户端的输入流;服务端的输入流就是客户端的输出流
ops.write("Hello, Client!".getBytes()); //输出一句话给客户端
BufferedReader br = new BufferedReader(new InputStreamReader(ips));
//从客户端读取一句话
System.out.println("Client said: " + br.readLine());
ips.close();
ops.close();
s.close();
ss.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
//TcpClient.java
import java.net.*;
import java.io.*;
public class TcpClient {
public static void main(String[] args) {
try {
Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 8001); //需要服务端先开启
//同一个通道,服务端的输出流就是客户端的输入流;服务端的输入流就是客户端的输出流
InputStream ips = s.getInputStream(); //开启通道的输入流
BufferedReader brNet = new BufferedReader(new InputStreamReader(ips));
OutputStream ops = s.getOutputStream(); //开启通道的输出流
DataOutputStream dos = new DataOutputStream(ops);
BufferedReader brKey = new BufferedReader(new InputStreamReader(System.in)); //封装键盘输入
while (true)
{
String strWord = brKey.readLine();
if (strWord.equalsIgnoreCase("quit"))
{
break;
}
else
{
System.out.println("I want to send: " + strWord);
dos.writeBytes(strWord + System.getProperty("line.separator"));
System.out.println("Server said: " + brNet.readLine());
}
}
dos.close();
brNet.close();
brKey.close();
s.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
多个客户端对应一个服务端
//TcpServer2.java
import java.net.*;
public class TcpServer2
{
public static void main(String [] args)
{
try
{
ServerSocket ss=new ServerSocket(8001);
while(true)
{
Socket s=ss.accept();
System.out.println("来了一个client");
new Thread(new Worker(s)).start();
}
//ss.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
import java.net.*;
import java.io.*;
class Worker implements Runnable {
Socket s;
public Worker(Socket s) {
this.s = s;
}
public void run() {
try {
System.out.println("服务人员已经启动");
InputStream ips = s.getInputStream();
OutputStream ops = s.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(ips));
DataOutputStream dos = new DataOutputStream(ops);
while (true) {
String strWord = br.readLine();
System.out.println("client said:" + strWord +":" + strWord.length());
if (strWord.equalsIgnoreCase("quit"))
break;
String strEcho = strWord + " 666";
// dos.writeBytes(strWord +"---->"+ strEcho +"\r\n");
System.out.println("server said:" + strWord + "---->" + strEcho);
dos.writeBytes(strWord + "---->" + strEcho + System.getProperty("line.separator"));
}
br.close();
// 关闭包装类,会自动关闭包装类中所包装的底层类。所以不用调用ips.close()
dos.close();
s.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Java HTTP 编程
网页访问
网页是特殊的网络服务(HTTP, Hypertext Transfer Protocol)
- 在浏览器输入URL地址
- 浏览器将连接到远程服务器上(IP+80Port)
- 请求下载一个HTML文件下来,放到本地临时文件夹中
- 在浏览器显示出来
HTTP
- 超文本传输协议(HyperText Transfer Protocol)
- 用于从WWW(World Wide Web)服务器传输超文本到本地浏览器的传输协议
- 1989年蒂姆•伯纳斯•李(Tim Berners Lee)提出了一种能让远隔两地的研究者们共享知识的设想
- 借助多文档之间相互关联形成的超文本 (HyperText),连成可相互参阅的 WWW
– 1990年问世,1997年发布版本1.1,2015年发布版本2.0
– 资源文件采用HTML编写,以URL形式对外提供
HTML
-
超文本标记语言(HyperText Markup Language)
-
表单 form
HTTP访问方式
GET:从服务器获取资源到客户端
POST:从客户端向服务器发送数据
- PUT:上传文件
- DELETE:删除文件
- HEAD:报文头部
- OPTIONS:询问支持的方法
- TRACE:追踪路径
- CONNECT:用隧道协议连接代理
HTTP编程
Java HTTP编程 (java.net包)
- 支持模拟成浏览器的方式去访问网页
- URL,Uniform Resource Locator,代表一个资源
- URLConnection
- 获取资源的连接器
- 根据URL的openConnection()方法获URLConnection
- connect方法,建立和资源的联系通道
- getInputStream方法,获取资源的内容
示例代码
Get
import java.io.*;
import java.net.*;
import java.util.*;
public class URLConnectionGetTest
{
public static void main(String[] args)
{
try
{
String urlName = "http://www.baidu.com";
URL url = new URL(urlName);
URLConnection connection = url.openConnection();
connection.connect();
// 打印http的头部信息
Map<String, List<String>> headers = connection.getHeaderFields();
for (Map.Entry<String, List<String>> entry : headers.entrySet())
{
String key = entry.getKey();
for (String value : entry.getValue())
System.out.println(key + ": " + value);
}
// 输出将要收到的内容属性信息
System.out.println("----------");
System.out.println("getContentType: " + connection.getContentType());
System.out.println("getContentLength: " + connection.getContentLength());
System.out.println("getContentEncoding: " + connection.getContentEncoding());
System.out.println("getDate: " + connection.getDate());
System.out.println("getExpiration: " + connection.getExpiration());
System.out.println("getLastModifed: " + connection.getLastModified());
System.out.println("----------");
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
// 输出收到的内容
String line = "";
while((line=br.readLine()) != null)
{
System.out.println(line);
}
br.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
Post
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
public class URLConnectionPostTest
{
public static void main(String[] args) throws IOException
{
String urlString = "https://tools.usps.com/go/ZipLookupAction.action";
Object userAgent = "HTTPie/0.9.2";
Object redirects = "1";
CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
Map<String, String> params = new HashMap<String, String>();
params.put("tAddress", "1 Market Street");
params.put("tCity", "San Francisco");
params.put("sState", "CA");
String result = doPost(new URL(urlString), params,
userAgent == null ? null : userAgent.toString(),
redirects == null ? -1 : Integer.parseInt(redirects.toString()));
System.out.println(result);
}
public static String doPost(URL url, Map<String, String> nameValuePairs, String userAgent, int redirects)
throws IOException
{
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (userAgent != null)
connection.setRequestProperty("User-Agent", userAgent);
if (redirects >= 0)
connection.setInstanceFollowRedirects(false);
connection.setDoOutput(true);
//输出请求的参数
try (PrintWriter out = new PrintWriter(connection.getOutputStream()))
{
boolean first = true;
for (Map.Entry<String, String> pair : nameValuePairs.entrySet())
{
//参数必须这样拼接 a=1&b=2&c=3
if (first)
{
first = false;
}
else
{
out.print('&');
}
String name = pair.getKey();
String value = pair.getValue();
out.print(name);
out.print('=');
out.print(URLEncoder.encode(value, "UTF-8"));
}
}
String encoding = connection.getContentEncoding();
if (encoding == null)
{
encoding = "UTF-8";
}
if (redirects > 0)
{
int responseCode = connection.getResponseCode();
System.out.println("responseCode: " + responseCode);
if (responseCode == HttpURLConnection.HTTP_MOVED_PERM
|| responseCode == HttpURLConnection.HTTP_MOVED_TEMP
|| responseCode == HttpURLConnection.HTTP_SEE_OTHER)
{
String location = connection.getHeaderField("Location");
if (location != null)
{
URL base = connection.getURL();
connection.disconnect();
return doPost(new URL(base, location), nameValuePairs, userAgent, redirects - 1);
}
}
}
else if (redirects == 0)
{
throw new IOException("Too many redirects");
}
//接下来获取html 内容
StringBuilder response = new StringBuilder();
try (Scanner in = new Scanner(connection.getInputStream(), encoding))
{
while (in.hasNextLine())
{
response.append(in.nextLine());
response.append("\n");
}
}
catch (IOException e)
{
InputStream err = connection.getErrorStream();
if (err == null) throw e;
try (Scanner in = new Scanner(err))
{
response.append(in.nextLine());
response.append("\n");
}
}
return response.toString();
}
}
当URL中包含特殊字符(空格、汉字、特殊符号等),需要采用URLEncoder。encode方法进行转义。
HTTP状态返回码:
200,请求成功;
404,请求失败;
500,服务器错误;
·······
Java HTTP 编程 (HttpClient)
HttpClient
- JDK HTTP Client (JDK自带,从9开始)
- Apache HttpComponents的HttpClient (Apache出品)
JDK HttpClient
- JDK 9 新增,JDK10更新,JDK11正式发布
- java.net.http包
- 取代URLConnection
- 支持HTTP/1.1和HTTP/2
- 实现大部分HTTP方法
- 主要类
- HttpClient
- HttpRequest
- HttpResponse
示例代码
Get
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.Charset;
public class JDKHttpClientGetTest {
public static void main(String[] args) throws IOException, InterruptedException {
doGet();
}
public static void doGet() {
try{
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(URI.create("http://www.baidu.com")).build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
catch(Exception e) {
e.printStackTrace();
}
}
}
Post
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class JDKHttpClientPostTest {
public static void main(String[] args) throws IOException, InterruptedException {
doPost();
}
public static void doPost() {
try {
HttpClient client = HttpClient.newBuilder().build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://tools.usps.com/go/ZipLookupAction.action"))
//.header("Content-Type","application/x-www-form-urlencoded")
.header("User-Agent", "HTTPie/0.9.2")
.header("Content-Type","application/x-www-form-urlencoded;charset=utf-8")
//.method("POST", HttpRequest.BodyPublishers.ofString("tAddress=1 Market Street&tCity=San Francisco&sState=CA"))
//.version(Version.HTTP_1_1)
.POST(HttpRequest.BodyPublishers.ofString("tAddress="
+ URLEncoder.encode("1 Market Street", "UTF-8")
+ "&tCity=" + URLEncoder.encode("San Francisco", "UTF-8") + "&sState=CA"))
//.POST(HttpRequest.BodyPublishers.ofString("tAddress=" + URLEncoder.encode("1 Market Street", "UTF-8") + "&tCity=" + URLEncoder.encode("San Francisco", "UTF-8") + "&sState=CA"))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.headers());
System.out.println(response.body().toString());
}
catch(Exception e) {
e.printStackTrace();
}
}
}
HttpComponents
- hc.apache.org, Apache出品
- 从HttpClient进化而来
- 是一个集成的Java HTTP工具包
- 实现所有HTTP方法:get/post/put/delete
- 支持自动转向
- 支持https协议
- 支持代理服务器等
示例代码
Get
import java.io.IOException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class HttpComponentsGetTest {
public final static void main(String[] args) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000) //设置连接超时时间
.setConnectionRequestTimeout(5000) // 设置请求超时时间
.setSocketTimeout(5000)
.setRedirectsEnabled(true)//默认允许自动重定向
.build();
HttpGet httpGet = new HttpGet("http://www.baidu.com");
httpGet.setConfig(requestConfig);
String srtResult = "";
try {
HttpResponse httpResponse = httpClient.execute(httpGet);
if(httpResponse.getStatusLine().getStatusCode() == 200){
srtResult = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");//获得返回的结果
System.out.println(srtResult);
}else
{
//异常处理
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Post
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
public class HttpComponentsPostTest {
public final static void main(String[] args) throws Exception {
//获取可关闭的 httpCilent
//CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build();
//配置超时时间
RequestConfig requestConfig = RequestConfig.custom().
setConnectTimeout(10000).setConnectionRequestTimeout(10000)
.setSocketTimeout(10000).setRedirectsEnabled(false).build();
HttpPost httpPost = new HttpPost("https://tools.usps.com/go/ZipLookupAction.action");
//设置超时时间
httpPost.setConfig(requestConfig);
//装配post请求参数
List<BasicNameValuePair> list = new ArrayList<BasicNameValuePair>();
list.add(new BasicNameValuePair("tAddress", URLEncoder.encode("1 Market Street", "UTF-8"))); //请求参数
list.add(new BasicNameValuePair("tCity", URLEncoder.encode("San Francisco", "UTF-8"))); //请求参数
list.add(new BasicNameValuePair("sState", "CA")); //请求参数
try {
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list,"UTF-8");
//设置post求情参数
httpPost.setEntity(entity);
httpPost.setHeader("User-Agent", "HTTPie/0.9.2");
//httpPost.setHeader("Content-Type","application/form-data");
HttpResponse httpResponse = httpClient.execute(httpPost);
String strResult = "";
if(httpResponse != null){
System.out.println(httpResponse.getStatusLine().getStatusCode());
if (httpResponse.getStatusLine().getStatusCode() == 200) {
strResult = EntityUtils.toString(httpResponse.getEntity());
}
else {
strResult = "Error Response: " + httpResponse.getStatusLine().toString();
}
}else{
}
System.out.println(strResult);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(httpClient != null){
httpClient.close(); //释放资源
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
练习
请编写一个群聊的程序,包括服务端程序和客户端程序。
服务端功能:收到某客户端的信息,将消息在控制台输出,然后,发给其他另外的客户端。
客户端功能:每隔5秒发送一条信息给服务端。然后接收服务器转发过来的消息,并在控制台输出。
要求:最后提交程序源码和运行截图。运行截图在一个画面上,包括服务端截图和2个客户端的截图。
import java.net.*;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class ChatRoomServer {
public static void main(String[] args) {
//线程池
ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newFixedThreadPool(4);
List<Socket> slist=new ArrayList<Socket>();
int num=0;
try {
ServerSocket ss=new ServerSocket(8001);
while(true) {
Socket s=ss.accept();
slist.add(s);
System.out.println("来了一个Client");
executor.execute(new Worker(s,slist,num));
num++;
}
}catch(Exception e) {
e.printStackTrace();
}
}
}
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.List;
class Worker implements Runnable{
Socket s;
List<Socket> slist;
int num;
public Worker(Socket s,List<Socket> slist,int num) {
this.s=s;
this.slist=slist;
this.num=num;
}
@Override
public void run() {
try {
System.out.println("服务人员已经启动");
InputStream ips=s.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(ips));
while(true) {
String strWord=br.readLine();
System.out.println("No."+num+" client said:"+strWord+":"+strWord.length());
if(strWord.equals("quit")) {
break;
}
if(strWord!=null) {
for(int i=0;i<slist.size();i++) {
if(i!=num) {
Socket sc=slist.get(i);
OutputStream ops=sc.getOutputStream();
DataOutputStream dos=new DataOutputStream(ops);
System.out.println("No."+num+" client said "+strWord);
dos.writeBytes("No."+num+" client said "+strWord+System.getProperty("line.separator"));
}
}
}
}
br.close();
//关闭包装类,会自动关闭包装类的底层类。所以不用调用ips.close()
s.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class ChatClient {
public static void main(String[] args) {
try {
Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 8001); //需要服务端先开启
//同一个通道,服务端的输出流就是客户端的输入流;服务端的输入流就是客户端的输出流
InputStream ips = s.getInputStream(); //开启通道的输入流
BufferedReader brNet = new BufferedReader(new InputStreamReader(ips));
OutputStream ops = s.getOutputStream(); //开启通道的输出流
DataOutputStream dos = new DataOutputStream(ops);
String msg="6666";
while(true) {
Thread.sleep(5000);
dos.writeBytes(msg+System.getProperty("line.separator"));
System.out.println("Server said:"+brNet.readLine());
}
}catch(Exception e) {
e.printStackTrace();
}
}
}