Java网络编程
1.基本网络概念
1.1网络
1.1.1网络是相互发送和接受数据的计算机和其他设备的集合
每一个设备就是一个网络节点(node)
每一个计算机是一个主机(host)
1.1.2每个网络节点有地址
以太网分配物理地址
Internet服务器分配Internet地址
地址可以有标识名字,但是不与地址锁定
1.1.3数据交换使用包交换
包有发送人和目标地址信息
可以互不干扰的共用线缆
可以进行安全校验
需要提供协议定义数据传递规则
1.1.4网络的分层
网络协议栈
TCP/IP四层模型
Java网络类只工作在应用层和传输层上
主机网络层,连接硬件
网际层,用于路由和寻址,支持不同类型的主机网络层相互对话,包含IPv4和IPv6两个协议,以下是IPv4数据报结构
传输层,负责确保包以发送的顺序接受,保证数据没有丢失或破坏,包含对丢失或破坏的数据进行有序重发的TCP(传输控制协议)协议和检测破坏包、但不保证有序的UDP(数据报协议)协议
应用层,向用户传输数据的层,用于Web的HTTP、用于电子邮件的SMTP、POP和IMAP、用于文件传输的FTP、FASP和TFTP、用于文件访问的NFS、用于文件共享的Gnutella和BitTorrent、用于语音通信的回话启动协议SIP和Skype
1.2IP、TCP和UDP
1.3.1IP
允许任意两点之间有多个路由
TCP确认连接两端收到IP包
Java在传输层只支持TCP和UDP
1.3.2IP地址
IPv4地址,4字节地址,例如192.168.6.190
包的首部保护目的地址和源地址
IPv4地址已经用完,目前在向IPv6地址过渡
IPv6地址,16字节地址,例如FEDC:7654:3210:FEDC:BA98:7654:3210
1.3.3域名(DNS)
用于记忆主机名,并在使用时转换为ip地址
IP地址会随着时间变换
127.0.0.1表示本地回送地址
IPv6的本地回送地址为::1
1.3.4端口
每台计算机有65535个端口
1到1023保留给已知服务
已知端口分配
1.3Internet
1.3.1是一个给予IP的网络
1.3.2Internet地址分块
由ISP分配IPv4地址
固定了前24位,称为/24
最低地址表示网络本身
最高地址是这个网络的广播地址
1.3.3网络地址转换
由于外网地址不够用,需要使用NAT网络地址转换,会导致外网地址和内网地址不同
1.3.4防火墙
唯一Internet和本地网络直接的硬件和软件会检查所以进出的数据,保证数据合法性,称为防火墙
1.3.5代理服务器
外部请求通过代理服务器想内部机器进行请求,外部主机只能看到代理服务器,无法知道内部机器的主机名和IP地址,保护内部机器
可以控制访问情况
可以实现本地缓存
但是无法应对所有协议,可以通过HTTP介入自定义协议,例如SOAP
1.4客户/服务器模型
1.4.1服务端发送数据,客户端接收数据
1.4.2对等端通过服务器程序实现相互通信
1.5Internet标准
1.5.1IETF标准包括TCP/IP、MIME和SMTP
1.5.2W3C标准包括HTTP、HTML和XML
2.流
2.1输出流
2.1.1write(int b)一次写入1字节,开销很大
2.1.2write(byte[] data)或write(byte[] data,int offset,int length)比一次写入一字节快得多
2.1.3flush()强迫缓冲的流发送数据,应当在关闭流前刷新输出
2.1.4close()关闭流,java7带资源的try
try(OutputStream out = new FileOutputStream("/a.txt")){ //处理输出流... }catch(IOException ex){ System.err.println(ex.getMessage); }
2.2输入流
2.2.1read()一次读取1字节
2.2.2read(byte[] input)或read(byte[] input,int offset,int length)尝试读取指定数组的input
后续字节未到达而失败,可以读取知道数组填满
后续字节已丢失而失败,先判断是否读完
int bytesRead = 0; int byteToRead = 1024; byte[] input = new byte[byteToRead ]; while(true){ int result = in.read(input,bytesRead ,bytesToRead-bytesRead ); if(result == -1){break;} bytesRead += result; }
2.2.3available()确定不阻塞的情况下有多少字节可以读取
2.2.4skip()跳过数据不进行读取
2.2.5标记和重置
mark(int readAheadLimit)标记流的当前位置,参数为可以重置的字节数限制大小,只能有一个标记,已有标记会被新标记替换
reset()把流重置到之前标记的位置
markSupported()检查这个流是否支持标记和重置
2.3过滤器流
2.3.1过滤器读取过程
2.3.2将过来串链在在一起
过滤器通过构造函数与流连接,为了避免使用到前面的流的read方法,需要有意的重写底层输入流的引用
InputStream in = new InputStream("a.txt"); in = new BufferedInputStream(in);
2.3.3缓冲流
public BufferedInputStream(InputStream in, int size)
参数1为底层流,向其读出或写入未缓冲的数据
参数2为缓冲区的字节数,输入缓存默认为2048字节,输出缓存默认为512字节
2.3.4PrintStream过滤器输出流,是有害的
输出与平台有关
使用平台默认编码方式
吞掉了所有异常
2.3.5数据流DataInputStream
用二进制格式读/写Java基本数据类型和字符串
2.4阅读器和书写器
2.4.1书写器
OutputStreamWriter构造函数知道了要写入的输出流和编码方式
2.4.2阅读器
InputStreamReader根据知道编码将字节转换为字符
2.4.3过滤器阅读器和书写器
OutputStreamWriter和InputStreamReader相当于输入和输出流上的装饰器,把面向字节的接口改为面向字符的接口
2.4.4PrintWriter
正确处理多字节字符集和国际化文本
3.线程
3.1运行线程
3.1.1进程->进程池->线程->线程池->NIO
3.1.2把线程要做的工作放在run方法中
3.1.3派生Tread
派生子类,覆盖run方法
3.1.4实现Runnable接口
实现Runnable接口来避免覆盖表中Tread方法
3.2从线程返回信息
3.2.1竞态条件
得到的结果是正确还是一次取决于线程个数,cup和磁盘的速度,系统的cpu个数以及不同线程分配的时间,这些称为竞态条件
3.2.2轮询
设置标志值,直到这个值改变,代表线程结束,使用返回值
3.2.3回调
当线程结束时,告诉主程序线程结束
线程结束时调用回调对象的实例方法传递结果
避免在构造函数中启动线程
和轮询相比不会浪费cpu而且更灵活
对实例感兴趣的可以添加到回调的对象列表来完成注册,例如事件的监听,又叫观察者模式
3.2.4Future、Callable和Executor
创建ExecutorService,会为你创建线程
提交一个Callable任务,得到一个Future
向Future请求线程任务结果,如果有就得到,没有就等待
public class test { public static void main(String[] args) throws ExecutionException, InterruptedException { //任务分解 MyTask myTask1 = new MyTask(); MyTask myTask2 = new MyTask(); //创建2个线程 ExecutorService service = Executors.newFixedThreadPool(2); Future<Integer> future1 = service.submit(myTask1); Future<Integer> future2 = service.submit(myTask2); System.out.println(Math.max(future1.get(), future2.get())); } } class MyTask implements Callable { @Override public Object call() throws Exception { //线程任务 return null; } }
3.3同步
3.3.1线程通过共享内容、文件句柄、socket和其他资源使得程序更高效,但是需要保证线程不同时访问同一个资源
3.3.2同步块
将程序包在synchronized块中,能防止对同一个对象同步的其他线程使用这个共享资源
只要有多个线程共享资源,必须考虑同步
导致冲突的可以是一个静态类变量,也可以是实例对象
3.3.3同步方法
通过synchronized修饰符修饰方法
会降低代码速度
增加死锁可能性
如果只是类的实例需要同步,可能不能保护真正需要保护的对象
3.3.4同步的替代方法
使用局部变量替代字段
基本类型的方法参数可以在单独的线程中安全的修改
String参数是安全的,因为是不可变字符串
StringBuffer参数是不安全的,因为是可变字符串
构造函数一般不需要单选线程安全问题,因为构造函数返回前线程没有这个对象的引用
利用private和final使字段不可变,并且不编写可能改变它们的方法
将非线程安全的类用作线程安全类的私有字段,让包含类以线程安全的方式访问非安全类
使用atomic包中的线程安全但可变的类,例如AtomicInterger、AtomicLong、AtomicBoolean,AtomicInterger代替int[],不使用引用变量,而是把对象存储在AtomicStampedReference中
映射和列表使用Collections.synchronizedList()方法进行同步
3.4死锁
3.4.1两个线程需要独占访问同样的资源集,每个线程分别有其不同的子集的锁,就会发生死锁
3.4.2确保相同顺序请求资源解决死锁问题
3.5线程调度
3.5.1优先级
设置优先级,范围0-10,默认为5,
Thread t =new Thread(); t.setPriority(8);
3.5.2抢占
抢占式调度,高优先级线程准备运行时,jvm主动暂停正在运行的低优先级线程,将CPU控制权交给其他线程
协作式调度,等待正在运行的线程自己暂停,将CPU控制权交给其他线程
3.5.3阻塞
任何时候线程必须停下来等待它没有的资源时,就会发生阻塞
让线程字段放弃CPU控制权,最常见的方式是对I/O阻塞
线程进行同步方法或代码块时会产生对锁的阻塞
3.5.4放弃
Thread.yield()显示放弃正在运行的线程,拥有获得的锁,不应该做任何同步
3.5.5休眠
放弃只是线程愿意暂停,休眠是直接暂停,拥有获得的锁,不应该做任何同步
Thread.sleep(100);
唤醒线程
Thread.interrupted();
3.5.6连接线程
无限等待被连接线程
public final void join()
等待连接线程指定时间后,执行本线程
public final synchronized void join(long millis)
3.5.7等待一个对象
处于等待状态的线程会释放等待对象的锁,直到得到其他线程的通知(唤醒)
Thread t =new Thread(); t.wait();
通知等待这个对象的线程
Thread t =new Thread(); t.notify();
3.5.8结束
run方法返回时,线程将撤销,其他线程可以接管CPU
3.6线程池和Exeutor
Exeutor可以很容易建立线程池,只需要将任务作为一个Runnable对象提交给这个线程,就可以得到Future对象,用来检查任务进度
4.Internet地址
4.1InetAddress类
4.1.1DNS将人民可以记忆的主机名和计算机可以记忆的IP地址关联在一起
4.1.2InetAddress包含一个主机名和一个IP地址
4.1.3创建新的InetAddress对象
根据域名获取地址
InetAddress address=InetAddress.getByName("www.baidu.com");
获取主机名
InetAddress address=InetAddress.getByName("www.baidu.com");
address.getHostName();
4.1.4缓存
DNS查找开销很大,InetAddress会缓存查找的结果
对于不成功的DNS查询只缓存10秒,API可以设置查找成功或失败的缓存时间
4.1.5按IP地址查找
IP地址查找可能不准确,主机名比IP地址稳定,优先使用主机名进行DNS查找
4.1.6安全性问题
任意DNS查找会泄露信息,要禁止任意的DNS查找
4.1.7获取方法
没有改变InetAddress对象的字段,InetAddress是不可变,是线程安全的
getHostName()在不字段主机名时才会联系DNS
getCanonicalHostName()知道主机名也会联系DNS
InetAddress.getLocalHost()获取本地主机
4.1.8地址类型
isAnyLocalAddress()是否通配地址,通配地址可以匹配本地系统中的任何地址
isLoopbackAddress()是否回送地址,回送地址直接在IP层连接同一台计算机,而不使用任何物理硬件
isLinkLocalAddress()是否IPv6链接地址,用于帮助IPv6网络实现自配置
isSiteLocalAddress()是否IPv6本地网站地址,可以通过路由器在网站或校园内转发
isMulticastAddress()是否组播地址,组播会将内容广播给所有预定的计算机
isMCGlobal()是否全球组播地址,以FF0E或FF1E开头
isMCOrgLocal()是否组播范围组播地址,以FF08或FF18开头
isMCSiteLocal()是否网站范围组播地址,以FF05或FF15开头
isMCLinkLocal()是否子网范围组播地址,以FF02或FF12开头
isMCNodeLocal()是否本地接口组播地址,以FF01或FF11开头
4.1.9测试可达性
isReachable(int timeout)测试特定节点是否可以和本机建立网络连接
4.1.10Object方法
equals(Object obj)是否有相同的IP地址
4.2Inet4Address和Inet6Address
4.2.1通过比较getAddress()返回的字节数组大小可以区分IPv4和IPv6
4.3NetworkInterface类
4.3.1NetworkInterface类表示一个本地IP地址
4.3.2工厂方法
getName()获取知道名字的网络接口
getByInetAddress()与知道IP地址绑定的网络接口
getNetworkInterfaces()列出本机所有网络接口
4.3.3获取方法
getInetAddresses()绑定接口的所有IP地址
4.4一些有用的程序
4.4.1检查已知地址是否垃圾邮件发送者
4.4.2通过离线处理日志文件提升WEB服务器的性能
5.URL和URI
5.1URI
5.1.1统一资源标识符是才有特定语法标识资源的字符串
5.1.2URI组成
模式:模式特定部分
http://www.baidu.com/find?id=101 模式为http、授权机构为www.baidu.com、路径为/find、查询为id=101
5.1.3URLs
URI只能标识资源,URL既能标识资源又能获取资源
语法:protocol://userInfo@host:port/path?query#fragment
protocol协议
host服务器名字、userInfo用户信息、port端口,共同构成权威机构
path路径,执行服务器上的特定目录
query查询字符串向服务器提供附加参数
fragment骗的执行远程资源的某个特定部分
5.1.4相对URL
继承服文档的部分信息的不完整的URL称为相对URL
<a href="a.html">
相对链接以"/"开头,标识相对于文档根目录而不是当前文件
<a href="/a.html">
5.2URL类
5.2.1使用策略设计模式,协议处理器就是策略,URL类构成上下文,通过它选择不同的策略
5.2.2URL是不可变的,即线程安全的
5.2.3创建新的URL
有多种不同的构造函数,根据需要创建
5.2.4从字符串构造URL
URL url=new URL("http://www.baidu.com");
5.2.5由组成部分构造URL
会使用默认端口80
URL url=new URL("http","www.baidu.com","/index.html#intro");
5.2.6构建相对URL
URL url=new URL("http://www.baidu.com/index.html#intro"); URL url2=new URL(url,"index2.html");
5.2.7其他URL对象来源
5.2.8从URL获取数据
openStream()可以获得一个输入流,可以由此读取数据
openConnection()为指定URL打开一个socket
getContent()向URL请求内容,很难一次获得哪种对象
getContent(Class[] classes)以指定格式返回URL的内容
5.2.9分解URL
getProtocol()获取协议
getHost()获取主机名
getPort()获取端口号,没有显示指定时返回-1
getDefaultPort()获取默认端口
getFile()获取路径部分
getPath()获取路径部分,不包括查询字符串
getRef()获取片段标识部分
getQuery()获取查询字符串
getUserInfo()获取用户信息
getAuthority()获取授权机构
5.2.10相等性和比较
equals()不会比较标识的资源,会阻塞
5.2.11比较
toURI()将URL对象转换为对应的URI对象
5.3URI类
5.3.1URI是对URL的抽象,不计包括统一资源定位符(URL),还包括统一资源名(URN)
5.3.2构造一个URI
URI uri=new URI("http://www.baidu.com/index.html#intro");
5.3.3URI的各部分
语法:模式:模式特定部分:片段
5.3.4解析相对URI
在相对和觉得URI直接进行转换
5.3.5相等性和比较
URI可以排序
5.3.6字符串表示
5.4x-www-form-urlencoded
5.4.1不同操作系统会导致URL的不同
5.4.2URLEncoder
对URL进行编码
5.4.3URLDecoder
对URL进行解码
5.5代理
5.5.1系统属性
设置代理服务器域名和IP地址
5.5.2Proxy类
为不同的远程主机选择不同的代理服务器
5.5.3ProxySelector类
根据协议、主机、路径、日期时间和其他表中来选择不同的代理
5.6通过GET与服务端程序通信
5.7访问口令保护的网站
5.7.1Authenticator类
为使用HTTP认证自我保护的网站提供用户名和口令
5.7.2PasswordAuthentication类
包含用户名和口令的两个只读属性的类
5.7.3JPasswordField类
以稍安全的方式询问用户口令
6.HTTP
6.1HTTP协议
6.1.1响应码
100-199提供信息的响应
200-299成功
300-399重定向
400-499客户端错误
500-599服务器错误
6.1.2kepp-Alive
重用socket连接
6.2HTTP方法
6.2.1GEY
6.2.2POST
6.2.3PUT
6.2.4DELETE
6.3请求主体
6.3.1起始行
方法、路径和查询字符串,以及HTTP版本
6.3.2HTTP首部
6.3.3空行
6.3.4主体
6.4Cookie
6.4.1小文本串在连接直接储存持久的客户端状态,小文本串称为cookie
6.4.2CookieManager储存获取cookie的类
6.4.3CookieSore本地存放和获取Cookie
7.URLConnection
7.1打开URLConnection
7.1.1构造URL对象
7.1.2获取URLConnection对象
7.1.3配置URLConnection
7.1.4读取首部字段
7.1.5获得输入流读取数据
7.1.6获得输出流写入数据
7.1.7关闭连接
7.2读取服务器的数据
7.2.1构造URL对象
7.2.2获取URLConnection对象
7.2.3获取输入流
7.2.4读取数据
7.3读取首部
7.3.1获取指定的首部字段
7.3.2获取任意首部字段
7.4缓存
7.4.1Java的Web缓存
7.5配置连接
7.5.1protected URL url获取url
7.5.2protected boolean connected连接是否打开
7.5.3protected boolean allowUserInteraction是否允许用户交互
7.5.4protected boolean doInput是否允许读取服务器
7.5.5protected boolean doOutput是否运行将输出发回服务器
7.5.6protected boolean ifModifiedSince是否在指定时间后有所修改
7.5.7protected boolean useCaches是否可以使用缓存
7.5.8超时
查询和修改连接的超时值
7.6配置客户端请求HTTP首部
7.7向服务器写入数据
7.8URLConnection的安全考虑
7.9猜测MIME媒体类型
7.10HttpURLConnection
7.10.1请求方法
7.10.2HEAD请求HTTP首部
7.10.3DELETE删除WEB服务器上指定URL的文件
7.10.4.PUT将一个文件存放在web服务器上
7.10.5OPTIONS询问某个特定URL支持哪些选项
7.10.6TRACE请求HTTP首部
7.10.7端口与服务器的连接
7.10.8处理服务器响应
错误条件
重定向
代理
流模式
8.客户端Socket
8.1使用Socket
8.1.1Socket是两台注解直接的一个连接,可以完成7个基本操作:连接远程机器、发送数据、接受数据、关闭连接、绑定端口、监听入站数据、在绑定端口上接受来自远程机器的连接
8.2用Telnet研究协议
8.2.2用Telnet研究协议,了解协议如何操作
8.2.3用Socket从服务器读取
try (Socket socket = new Socket("www.baidu.com",8080)){
//设置连接超时时间
socket.setSoTimeout(1000);
InputStream inputStream = socket.getInputStream();
} catch (Exception e) {
e.printStackTrace();
}
8.2.4用Socket写入服务器
OutputStream outputStream = socket.getOutputStream();
8.2.5半关闭Socket
shutdownInput()只关闭输入流
isInputShutdown()输入流是否打开
8.3构造和连接Socket
8.3.1基本构造函数
购货函数返回前可以获得与远程主机建立的活动的网络连接,可以用这个对象确定是否允许某个端口建立连接
8.3.2选择从那个本地接口连接
Socket(String host, int port, InetAddress localAddr, int localPort)
8.3.3构造但不连接
通过无参构造获得Socket,再通过地址建立连接,并设置超时等待时间,来支持不同类型的Socket
try (Socket socket = new Socket()){ SocketAddress address=new InetSocketAddress("www.baidu.com",8080); socket.connect(address,10000); } catch (Exception e) { e.printStackTrace(); }
8.3.4Socket地址
getRemoteSocketAddress()获取连接系统的地址
getLocalSocketAddress()返回发起连接的地址
8.3.5代理服务器
Socket(Proxy proxy)创建一个未连接的Socket,指定一个代理服务器连接
8.3.6获取Socket的信息
getInetAddress()获取远程主机
getPort()获取远程端口
getLocalAddress()本地连接地址
getLocalPort()本地连接端口
8.3.7关闭还是连接
isClosed()检查Socket是否关闭,从未连接也是false
isConnected()检查Socket是否从未连接过远程主机
//检查Socket是否打开 if (socket.isConnected() && !socket.isConnected()) { }
8.3.8toString()
用于调试
8.4设置Socket选项
8.4.1TCP_NODELAY是否关闭Socket的缓冲,游戏程序需要设置为ture
8.4.2SO_BINDADDR指定Socket关闭时是否将尚未发送的数据报进行发送(延迟时间不为0),默认系统在延迟时间为设置不为0时会在Socket关闭后尝试发送剩余数据
8.4.3SO_TIMEOUT设置读取数据的超时时间,默认为0,不限制
8.4.4SO_RCVBUF控制用于网络输入的建议的接收缓冲区大小,SO_SNDBUF控制用于网络输入的建议的发送缓冲区大小,两者相斥,会使用较小的设置值。如果带宽大,数据传输率小,应该增加缓冲区大小,相反则减少
8.4.5SO_KEEPALIVE打开后,客户端会2小时发送一个数据报,确保服务器未崩溃
8.4.6SO_OOBINLINE是否接收紧急数据
8.4.7SO_REUSEADDR是否运行其他Socket绑定到已知端口
8.4.8IP_TOS服务类型
服务类型储存在IP首部
8.5Socket异常
8.5.1BindException端口正在使用或没有足够权限
8.5.2ConnectException主机忙或没有进程监听端口
8.5.3NoRouteToHostException连接超时
8.5.4ProtocolException数据违反TCP/IP规范
8.6GUI应用中的Socket
8.6.1whois简单目录服务协议
8.6.2网络客户库
9.服务器Socket
9.1使用ServerSocket
9.1.1创建一个迭代服务器
accept()是阻塞的
try (ServerSocket serverSocket = new ServerSocket(8080)) {
while (true) {
try (Socket socket = serverSocket.accept()) {
Writer out = new OutputStreamWriter(socket.getOutputStream());
//向客户端回送消息
out.flush();
socket.close();
} catch (IOException e) {
}
}
} catch (IOException e) {
System.out.println(e);
}
9.1.2提供二进制数据
使用写byte数组的输出流
9.1.3多线程服务器
多线程服务器
客户端没有使用try-with-resources是因为客户端Socket不可了try块,而放在单独的线程中。如果使用了,主线程一旦到达while循环末尾就会关闭socket,,而次数新线程还没有用完这个Socket
public class test { public static void main(String[] args) { try (ServerSocket serverSocket = new ServerSocket(8080)) { while (true) { try (Socket socket = serverSocket.accept()) { Thread task = new MyThread(socket); task.start(); } catch (IOException e) { } } } catch (IOException e) { System.out.println(e); } } } class MyThread extends Thread { private Socket socket; public MyThread(Socket socket) { this.socket = socket; } @Override public void run() { try { Writer out = new OutputStreamWriter(socket.getOutputStream()); //向客户端回送消息 out.flush(); socket.close(); } catch (IOException e) { System.out.println(e); } finally { try { socket.close(); } catch (IOException e) { //忽略 } } } }
线程池服务器
public class test { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(50);//生成包含50个线程的线程池 try (ServerSocket serverSocket = new ServerSocket(8080)) { while (true) { try { Socket socket = serverSocket.accept(); Callable<Void> task = new MyThread(socket); pool.submit(task); } catch (IOException e) { } } } catch (IOException e) { System.out.println(e); } } } class MyThread implements Callable<Void> { private Socket socket; public MyThread(Socket socket) { this.socket = socket; } @Override public Void call() { try { Writer out = new OutputStreamWriter(socket.getOutputStream()); //向客户端回送消息 out.flush(); socket.close(); } catch (IOException e) { System.out.println(e); } finally { try { socket.close(); } catch (IOException e) { //忽略 } } return null; } }
9.1.4用Socket写入服务器
获取输入流进行写入
9.1.5关闭服务器Socket
关闭Socket而非ServerSocket
9.2日志
9.2.1日志记录内容
请求日志
服务器错误日志
9.2.2如何记录日志
为每一个类创建日志根据,日志工具是线程安全的
捕获RuntimeException能够避免服务器崩溃,使服务器继续处理其他请求
public class test {
private final static Logger errorLogger = Logger.getLogger("errors");
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(50);//生成包含50个线程的线程池
try (ServerSocket serverSocket = new ServerSocket(8080)) {
while (true) {
try {
Socket socket = serverSocket.accept();
Callable<Void> task = new MyThread(socket);
pool.submit(task);
} catch (IOException e) {
errorLogger.log(Level.SEVERE, "accept errer", e);
} catch (RuntimeException e) {
errorLogger.log(Level.SEVERE, "unexpected errer" + e.getMessage(), e);
}
}
} catch (IOException e) {
errorLogger.log(Level.SEVERE, "Couldn't start server", e);
} catch (RuntimeException e) {
errorLogger.log(Level.SEVERE, "Couldn't start server:" + e.getMessage(), e);
}
}
}
9.3构造服务器Socket
9.3.1创建一个保存50个入站连接的服务器Socket
ServerSocket serverSocket = new ServerSocket(8080,50)
9.3.2构造但不绑定端口
允许程序在绑定端口之前设置服务器选项
9.4获得服务器Socket的有关信息
9.4.1getInetAddress()获取服务器使用的地址
9.4.2getLocalPort()获取服务器使用的端口
9.5Socket选项
9.5.1SO_TIMEOUT等待连接超时时间
9.5.2SO_REUSEADDR是否允许新的Socket绑定到之前使用过的端口上
9.5.3SO_RCVBUF缓冲区大小
9.5.4服务类型
低成本、高可靠、最大吞吐量、最小延迟
9.6HTTP服务器
9.6.1单文件服务器
9.6.2重定向服务器
9.6.3功能完备的HTTP服务器
10.安全Socket
10.1保护通信
对称密钥和非对称密钥与中间人攻击
10.2创建安全客户端Socket
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); try (Socket socket = factory.createSocket("www.baidu.com", 8080)) {
10.3选择密码组
密码组保护:协议、密钥交换算法、加密算法和校验和
10.4事件处理器
实现HandshakeCompletedListener接口跨越知道SSLSocket的握手结束
10.5会话管理
相同回话使用一组相同的公开密钥和私有密钥
10.6客户端模式
设置Socket是否需要自行认证,仅用于高安全性的内部应用
10.7创建安全服务器Socket
10.7.1步骤
10.8配置SSLServerSocket
10.8.1选择密码组
10.8.2会话管理
10.8.3客户端模式
11.非阻塞I/O
11.1一个实例客户端
11.1.1通道
利用通道不需要获取输入或输出流,可以直接写入通道本身,不是写入字节数组,而是写入ByteBuffer对象
11.1.2缓冲区
缓冲区可以重用,再次使用前需要清空缓冲区
try { SocketAddress address = new InetSocketAddress(8080); SocketChannel client = SocketChannel.open(address);//打开通道 ByteBuffer buffer = ByteBuffer.allocate(74);//创建ByteBuffer对象 WritableByteChannel out = Channels.newChannel(System.out);//将数据封装在通道中 client.configureBlocking(false);//设置为非阻塞模式 while (true) { int n = client.read(buffer); if (n > 0) { buffer.flip();//回绕,使输出通道从数据的开头写入 out.write(buffer);//将数据写入 buffer.clear();//清空缓冲区便于复用 } else if (n == -1) { break;//服务器故障 } } } catch (IOException e) { errorLogger.log(Level.SEVERE, "accept errer", e); }
11.2一个示例服务器
public class test { private final static Logger errorLogger = Logger.getLogger("errors"); public static void main(String[] args) { ServerSocketChannel serverChannel; Selector selector; try { serverChannel = ServerSocketChannel.open();//打开通道 serverChannel.bind(new InetSocketAddress(8080));//绑定端口 serverChannel.configureBlocking(false);//设置为非阻塞模式 selector = Selector.open();//迭代处理准备好的连接代替为连接分配线程 serverChannel.register(selector, SelectionKey.OP_ACCEPT);//注册监听通道的选择器,关注的操作为通道是否准备好接受一个新连接 } catch (IOException e) { errorLogger.log(Level.SEVERE, "accept errer", e); return; } while (true) { try { selector.select();//检查是否有可操作的数据 } catch (IOException e) { errorLogger.log(Level.SEVERE, "accept errer", e); break; } Set<SelectionKey> readyKeys = selector.selectedKeys();//找到了就绪的通道 Iterator<SelectionKey> iterator = readyKeys.iterator(); if (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove();//删除已经处理的键,避免处理2次 try { if (key.isAcceptable()) {//服务器通道 ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel client = server.accept(); client.configureBlocking(false); SelectionKey key2 = client.register(selector, SelectionKey.OP_WRITE); //为客户端建立缓冲区 ByteBuffer buffer = ByteBuffer.allocate(74); //读取数据 buffer.flip();//回绕,使输出通道从数据的开头写入 key2.attach(buffer);//将数据写入 } else if (key.isWritable()) {//客户端通道 SocketChannel client = (SocketChannel) key.channel(); //向客户端写入数据 ByteBuffer buffer = (ByteBuffer) key.attachment(); if (!buffer.hasRemaining()) { buffer.rewind();//用下一行重新填充缓冲区 //将数据放入缓冲区 buffer.flip();//准备从缓冲区写入 } client.write(buffer); } } catch (IOException e) { key.cancel(); try { key.channel().close(); } catch (IOException ex) { errorLogger.log(Level.SEVERE, "accept errer", ex); } } } } } }
11.3缓冲区
11.3.1流是基于字节的,通道是基于块的,通道支持对统一对象的读/写,缓冲区可以看着固定大小的元素列表
11.3.2缓冲区的4个关键部分
位置,缓冲区将读取或写入的下一个位置
容量,缓冲区可以保存的元素的最大书面
限度,缓冲区可访问数据的末尾位置
标记,缓冲区客户端指定的所以
rewind()将位置设为0,但不改变限度,允许重新读取缓冲区
flip()将限度设置为读取位置,位置设置为0,用于排课刚刚填充的缓冲区
11.3.3创建缓冲区
分配,allocate()用于输入,创建后备数组
ByteBuffer buffer=ByteBuffer.allocate(100);
直接分配,allocateDirect(),不创建后备数组
包装,wrap()用于输出,对数组操作结束前不要包装数组
byte[] bytes = "som data".getBytes("UTF-8"); ByteBuffer buffer=ByteBuffer.wrap(bytes);
填充,向缓冲区放入数据
排空,从缓冲区获取数据,需要进行回绕,使位置回到0
11.3.4批量方法
批量填充和排空相应元素数组
11.3.5数据转换
11.3.6视图缓冲区,确定知道读取的数据是一种特定的基本数据类型,可以创建视图缓冲区
11.3.7压缩缓冲区,压缩时将缓冲区剩余数据移到开头,释放空间
11.3.8复制缓冲区,建立缓冲区的副本将相同信息分发到多个通道
11.3.9分片缓冲区,分片是原缓冲区的子序列,只保护当前位置到限度的所有元素
11.3.10标记和重置,用于重新读取数据
11.3.11Object方法
相等:类型相同,剩余个数相同,相对位置元素相等,不考虑位置之前的元素和日历、限度、标记
11.4通道
11.4.1SocketChannel
连接,有参构造建立连接,无参构造不立即连接
读取,一个源填充多个缓冲区,称为散布
写入,将多个缓冲区数据写入一个Socket,称为聚焦
关闭
11.4.2ServerSocketChannel
用于接收入站连接
创建服务器Socket通道,open()
接受连接,accept()
11.4.3Channels类
工具类,将传统的基于I/O的流、阅读器和书写器包装在通道中
11.4.4异步通道
11.4.5Socket选项
11.5就绪选择
11.5.1Selector类
选择读写时不阻塞的Socket
open()创建选择器
register()注册通道
准备好的通道在使用后,需要删除这个键
11.5.2SelectorKey类
相当于通道的指针
channel()获取通道
attachement()获取储存的对象
cancel撤销注册
12.UDP
12.1UDP协议
12.2UDP客户端
public static void main(String[] args) { try (DatagramSocket socket = new DatagramSocket((0))) { socket.setSoTimeout(10000);//设置超时时间 InetAddress host = InetAddress.getByName("ww.baidu.com"); DatagramPacket request = new DatagramPacket(new byte[1], 1, host, 8080);//发送数据包 DatagramPacket response = new DatagramPacket(new byte[1024], 1024);//接受数据包 socket.send(request);//发送数据 socket.receive(response);//接收响应 String result = new String(response.getData(), 0, response.getLength(), "UTF-8"); } catch (IOException e) { e.printStackTrace(); } }
12.3UDP服务器
public static void main(String[] args) { try (DatagramSocket socket = new DatagramSocket((8080))) { while (true) { try { DatagramPacket request = new DatagramPacket(new byte[1024], 1024);//发送数据包 socket.receive(request);//接收响应 String daytime = new Date().toString(); byte[] data = daytime.getBytes("UTF-8"); DatagramPacket response = new DatagramPacket(data, data.length, request.getAddress(), request.getPort());//接受数据包 socket.send(response);//发送数据 } catch (IOException | RuntimeException ex) { ex.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } }
12.4DatagramPacket类
12.4.1数据包结构
12.4.2构造函数
接收数据包的构造函数
public DatagramPacket(byte buf[], int offset, int length)
发送数据包的构造函数
public DatagramPacket(byte buf[], int length,InetAddress address, int port)
12.4.3get方法
获取注解地址、端口、数据报数据、数据字节数、开始填充数据报的位置
12.4.4set方法
设置有效载荷,其他途径发送大数据、发往的地址、发往的端口、发送数据的成都
12.5DatagramSocket类
12.5.1构造函数
public DatagramSocket()创建一个绑定到匿名端口的Socket
public DatagramSocket(int port)创建一个指定端口的Socket
public DatagramSocket(int port, InetAddress laddr)用于多宿主主机
12.5.2发送和接收数据报
send()发送数据
receive()接收数据
12.5.3管理连接
12.5.4Socket选项
12.6一些有用的应用程序
12.6.1简单的UDP客户端
12.6.2UDPServer
12.6.3UDP Echo客户端
12.7DatagramChannel
12.7.1用于非阻塞UDP应用程序
12.7.2使用DatagramChannel
打开Socket、接收、发送、连接、读取、写入、关闭
12.7.3Socket选项
13.IP组播
13.1组播
13.1.1组播比单播的点通信宽,但比广播通信窄而且目标更明确
13.1.2组播地址与组
组播地址又叫组播组,是一组共享一个组播地址的主机
13.1.3客户端和服务器
使用UDP,需要额外设置TTL
13.1.4路由器和路由
最大限制在于是否有特殊的组播路由器
13.2使用组播Socket
13.2.1构造函数
13.2.2与组播组通信
加入组
离开组并且关闭连接
发送组播数据
回送模式
网络接口
13.3两个简单示例