网络编程涉及到了最基础的Socket编程,以及基于次的网络服务。下面将介绍在Java中如何实现Socket以及一些简单的网络客户端和服务端。
Socket
- 连接
Socket类是Java中实现的基本的网络编程类,可以通过地址和端口来进行初始化,连接到该服务器的端口上,也可以使用无参构造函数,再调用connect()方法连接上服务器。两者的区别是当直接使用带参构造函数去初始化Socket时,进程会一直阻塞直到连接上服务器,而如果是在使用无参构造函数创建Socket后调用connect()方法,可以设置连接的时间限制,当超过时间后就会抛出IOException。
Socket s1=new Socket(String host,int port);
Socket s2=new Socket();
s2.connect(host,port,1000);//时限为1秒钟
- 通信
当Socket对象连接上服务器后,就可以通过socket.getInputStream()和socket.getOutputStream()与服务器进行数据传输。这两个方法返回的是(In|Out)putStream对象,可以使用输入输出流的处理方法来进行读写。最后通过socket.close()来关闭。 - 其余方法
此外,Socket类还实现了一些有助于平常使用的方法。
public InetAddress getInetAddress();//获取socket连接的服务器地址
public int getPort();//获取socket连接的服务器端口
public InetAddress getLocalAddress();//获取socket连接的本地的地址
public int getLocalPort();
public boolean isClosed();
...
ServerSocket
- 创建
服务器程序会在某个端口等待用户程序的连接,在创建ServerSocket的时候,可以进行端口指定。
创建完ServerSocket对象后,通过调用s.accept()方法来等待用户程序连接上服务器。该方法阻塞进程直到用户连接,并返回一个Socket对象。获取到Socket对象后就可以通过输入输出流向用户程序进行读写。
ServerSocket ss=new ServerSocket(port);
Socket s=ss.accept();
InputStream in=s.getInputStream();
OutputStream out=s.getOutputStream();
- 并发处理
当要同时处理多个用户程序连接服务器的时候,可以在获得Socket对象后创建线程来处理该Socket的读写。
while(ture){
Socket s=ss.accept();
Thread t=new Thread(
public void run(){
//input and output
}
);
t.start();
}
- 半关闭
Socket对象提供了只关闭输入/输出流其中之一而不断开连接的方法,即shutdown(In|Out)put(),在断开后,任何输入或输出都将会被抛弃。断开后没有办法再重新连接上,因此这种方法只适用于一站式的服务。
因特网地址
Java中提供了InetAddress和InetSocketAddress类来表示因特网地址。其中InetSocketAddress类单纯地用来表示一个地址,即ip:port。InetAddress用来表示一个ip,并提供了用于获得域名对应的ip等功能。
public boolean isReachable(int timeout) throws IOException;
public String getHostName();
public static InetAddress getByName(String host);
//获取一个域名所有的ip地址
public static InetAddress[] getAllByName(String host);
//获取本地的ipv4地址
public static InetAddress getLocalHost() throws UnknownHostException;
...
获取Web服务
使用Socket只能与指定的ip:port进行通信,而无法获得服务器特定目录下的资源,即无法通过URL获得资源(?)。URI是统一资源标识符,其中能够定位到数据的称为URL(统一资源定位符),不能定位到数据的称为URN(统一资源名称)。
Java提供了URL类对URL进行访问,提供了URI类对标识符进行解析。
-
URI
一个URI具有这样的格式,[scheme:]schemeSpecificPart[#fragment],其中[...]表示可选的部分,并且:和#可以被包含在标识符内。包含[scheme:]部分的为绝对URI,否则为相对URI,其中schemeSpecificPart以/开头的绝对URI为不透明的,例如mailto:cay@test.com。所有透明的绝对URI和所有相对URI都是分层的,如https://test.com/index、../../java/net/Socket.html#Socket()。一个分层的URI的schemeSpecificPart具有这样的格式,[//authority][path][?query],基于服务器的URI的authority部分具有这样的格式[user-info@]host[:port]。
URI类提供了解析标识符和处理相对(绝对)标识符的功能。其中resolve()方法提供了解析相对URI,将其生成绝对URI的方法,relativize()方法提供了相对化方法,将绝对URI生成相对URI。比如,b/c以https://a.com/b为参数调用resolve()将得到https://a.com/b/c;https://a.com/b/c以https://a.com/b为参数调用relativize()将得到c。
public String getSchemeSpecificPart();
public String getAuthority();
public String getUserInfo();
public String getHost();
public int getPort();
...
-
URL
URL类提供了从给定url获得资源的方法。URL提供了与URI类似的解析标识符的方法,此外还可以通过调用openStream()获得InputStream对象来获取资源内容。不过URL类没有提供输出的方法,想要和服务器进一步交互,可以调用openConnection()方法来获得URLConnection对象来进一步操作。
URL url = new URL(urlString);
InputStream in = url.openStream();
-
URLConnection
该类提供了向服务器输出和接收服务器输入的功能。操作URLConnection需要遵循一下几个步骤:
⑴ 调用URL的openConnection()来获得URLConnection对象。
⑵ 设置参数和请求的属性。
⑶ 调用connect()连接远程资源。
⑷ 建立连接后就可以查询头信息和访问资源数据。
- 参数和请求头属性
该类提供了一下方法设置参数和请求头属性(比如http header),setter都有对应的getter。默认情况下,建立的连接只允许从服务器接收输入流,如果想要向服务器发送数据,需要先设置setDoOutput(true)。修改请求头属性使用了键值对来进行修改,如果一个键可以有多个对应的值,则将值用list包装即可。
public void setAllowUserInteraction();
public void setDoInput();
public void setDoOutput();
public void setIfModifiedSince();//连接只对某个特定日期以来修改过的数据感兴趣
public void setUseCaches();
//修改请求头属性
public void addRequestProperty(String key, String value);
- 建立连接后的查询访问
建立连接后,该类还提供了如下方法进行头部的信息查询。
public Object getContent();
public String getHeaderField();
public InputStream getInputStream();
public OutputStream getOutputStream();
public String getContentEncoding();
public int getContentLength();
public String getContentType();
public long getDate();
public long getExpiration();
public long getLastModifed();
-
提交表单
想服务器提交表单有Get和Post两种方法。
- Get
对于Get方法来说,只需要遵循下面的规则将参数附在URL结尾处。
⑴ 保留字符A-Z、a-z、0-9、.、-、~、_。
⑵ 用+字符替换所有空格。
⑶ 将其他所有字符编码为UTF-8,并将每个字节都编码为%后面紧跟一个两位的十六进制数字。
例如San Francisco,CA,可以使用San+Francisco%2+CA。 - Post
Post方法将参数写入到输出流中,不在URL上显示长串的参数键值对。
URL url=new URL(urlString);
URLConnection connection=url.openConnection();
connection.setDoOutput(true);
PrintWriter out=new PrintWriter(connection.getOutputStream(),"utf-8");
out.print(name1+"="+URLEncoder.encode(value1,"utf-8")+"&");
out.print(name2+"="+URLEncoder.encode(value2,"utf-8"));
out.close();
- 重定向
服务器程序相应可能会产生重定向,重定向一般都是自动处理的,但是有些情况下,比如http与https之间的重定向因为安全原因不被支持,所以需要自己手动进行重定向。下面是手动处理重定向的步骤。
//在连接前关闭自动重定向
connection.setInstanceFollowRedirects(false);
//在发送请求之后获取相应码
int responseCode=connection.getResponseCode();
//如果是HttpURLConnection.HTTP_MOVED_PERM
//或HttpURLConnection.HTTP_MOVED_TEMP
//或HttpURLConnection.HTTP_SEE_OTHER
//就获取Location响应头,获得重定向URL,断开连接,创建到新的URL连接
String location=connection.getHeaderField("Location");
if(location!=null){
URL base=connection.getURL();
connection.disconnect();
connection=(HttpURLConnection) new URL(base,location).openConnection();
}
HTTP
HTTP有0.9、1.0、1.1、2.0这几个版本。
- HTTP 0.9
HTTP 0.9是第一个HTTP协议的版本,只支持GET请求,不支持请求头且没有协议头,只支持纯文本内容。该版本的协议就已经支持了无状态特性,事务结束就关闭请求,访问地址不存在也不会返回错误信息。 - HTTP 1.0
HTTP 1.0在0.9的版本上支持了请求和响应头部、响应以一个响应状态行开始、响应信息不仅限于超文本、支持了POST请求。该版本默认使用了短连接,每次请求建立一个TCP连接。 - HTTP 1.1
新增了keep alive、chunked编码传输、字节范围请求、请求流水线等特性。还支持了HOST域(即一个IP下可能有多个服务器,通过主机名指定)、增加了connect/trace/delete/put/options的请求方法。
- keep alive
该版本的HTTP协议默认使用长连接,即事务完成后不断开连接,除非客户端或服务器主动断开。 - chunked编码传输
该特性使得传输对象可以分块传输并标明长度,当长度表明为0时说明传输结束。 - 字节范围请求
当客户端已有请求对象的一部分内容时,可以向服务器请求其余部分的内容,而不用重新传输整个对象。
- HTTP 2.0
该协议是下一代HTTP协议,以下是其特性。
- 多路复用,将HTTP 1.1的包分为两帧,头部放在header帧,数据放在data帧,以实现加大带宽,降低延迟的目的。
- 所有通信在一个连接上完成,该连接可以承载任意数量的双向传输,每个消息被分成多帧传输,到达后根据头部的信息进行重新组装。
- 头部压缩,当一个客户端向同一服务器请求时,数据包的头部是相似的,HTTP 2.0提供了压缩的方法。
- 随时复位,HTTP 2.0可以随时停止一个TCP连接的传输,在不中断的情况下传输新的内容。
- 服务器端推流,客户端向服务器请求一个资源,服务器判断该客户端可能用到的其他资源,一并发送给客户端,客户端进行缓存。
- 优先权和依赖,可以指明哪些资源优先级更高,可以更快得到处理。
HTTP包结构
一个HTTP包由一下部分构成:起始行、一个或多个头域、一个空行表示头域的结束、实体消息。
GET http://download.microtool.de:80/somedata.exe
Host: download.microtool.de
Accept:*/*
Pragma: no-cache
Cache-Control: no-cache
Referer: http://download.microtool.de/
User-Agent:Mozilla/4.04[en](Win95;I;Nav)
Range:bytes=554554-
HTTP包的第一行为起始行,请求包的起始行包含请求方法、URL、HTTP版本;响应包的起始行包含HTTP-Version Status-Code Reason-Phrase,HTTP-Version表示支持的HTTP版本、Status-Code表示三位数字的结果代码、Reason-Phrase对结果代码的文本描述等。
上述请求包中Host、Accept、Pragma就为头域,通过<key>:<value>的形式表示。
头域可以分为通用头域、请求头域、响应头域和实体头域。
- 通用头域:
Date表示消息发送的时间、Pragma实现特定的指令比如Pragma:no-cache、Connection表示连接状态等。 - 请求头域:
Host表示请求资源的URL、Accept表示自己接收什么文件、Accept-Charset表示接收文件的编码格式、Authorization用来将身份验证信息发给服务器等。 - 响应头域:
Age、Location、Proxy-Authenticate、Public等。 - 实体头域:请求消息和响应消息都可以包含实体信息,实体信息一般由实体头域和实体组成,
Content-Type实体的介质类型、Content-Length表示实际传送的实体长度等。











网友评论