think in java 读书笔记 2 —— 套接字
目录
概要
有关套接字的详细介绍,可以看下《think in java》15.2章节。
1. 套接字基本知识
“套接字”或者“插座”(Socket)也是一种软件形式的抽象,用于表达两台机器间一个连接的“终端”。针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”。线缆的每一端都插入一个“套接字”或者“插座”里。当然,机器之间的物理性硬件以及电缆连接都是完全未知的。抽象的基本宗旨是让我们尽可能不必知道那些细节。
在Java 中,我们创建一个套接字,用它建立与其他机器的连接。从套接字得到的结果是一个InputStream 以及OutputStream(若使用恰当的转换器,则分别是Reader 和Writer),以便将连接作为一个IO 流对象对待。有两个基于数据流的套接字类:ServerSocket,服务器用它“侦听”进入的连接;以及Socket,客户用它初始一次连接。一旦客户(程序)申请建立一个套接字连接,ServerSocket 就会返回(通过accept()方法)一个对应的服务器端套接字,以便进行直接通信。从此时起,我们就得到了真正的“套接字-套接字”连接,可以用同样的方式对待连接的两端,因为它们本来就是相同的!此时可以利用getInputStream()以及getOutputStream()从每个套接字产生对应的InputStream 和OutputStream 对象。这些数据流必须封装到缓冲区内。
对于Java 库的命名机制,ServerSocket(服务器套接字)的使用无疑是容易产生混淆的又一个例证。大家可能认为ServerSocket 最好叫作“ServerConnector”(服务器连接器),或者其他什么名字,只是不要在其中安插一个“Socket”。也可能以为ServerSocket 和Socket 都应从一些通用的基础类继承。事实上,这两种类确实包含了几个通用的方法,但还不够资格把它们赋给一个通用的基础类。相反,ServerSocket 的主要任务是在那里耐心地等候其他机器同它连接,再返回一个实际的Socket。这正是“ServerSocket”这个命名不恰当的地方,因为它的目标不是真的成为一个Socket,而是在其他人同它连接的时候产生一个Socket 对象。
然而,ServerSocket 确实会在主机上创建一个物理性的“服务器”或者侦听用的套接字。这个套接字会侦听进入的连接,然后利用accept()方法返回一个“已建立”套接字(本地和远程端点均已定义)。容易混淆的地方是这两个套接字(侦听和已建立)都与相同的服务器套接字关联在一起。侦听套接字只能接收新的连接请求,不能接收实际的数据包。所以尽管ServerSocket 对于编程并无太大的意义,但它确实是“物理性”的。
创建一个ServerSocket 时,只需为其赋予一个端口编号。不必把一个IP 地址分配它,因为它已经在自己代表的那台机器上了。但在创建一个Socket 时,却必须同时赋予IP 地址以及要连接的端口编号(另一方面,从ServerSocket.accept()返回的Socket 已经包含了所有这些信息)。
2. 交互过程
连接建立好后,服务器端和客户端的输入流和输出流就互为彼此,即一端的输出流是另一端的输入流。
3. 一个简单的服务器和客户机程序
这个例子将以最简单的方式运用套接字对服务器和客户机进行操作。服务器的全部工作就是等候建立一个连接,然后用那个连接产生的Socket 创建一个InputStream 以及一个OutputStream。在这之后,它从InputStream 读入的所有东西都会反馈OutputStream,直到接收到行中止(END)为止,最后关闭连接。客户机连接与服务器的连接,然后创建一个OutputStream。文本行通过OutputStream 发送。客户机也会创建一个InputStream,用它收听服务器说些什么(本例只不过是反馈回来的同样的字句)。服务器与客户机(程序)都使用同样的端口号,而且客户机利用本地主机地址连接位于同一台机器中的服务器(程序),所以不必在一个物理性的网络里完成测试(在某些配置环境中,可能需要同真正的网络建立连接,否则程序不能工作——尽管实际并不通过那个网络通信)。
下面是服务器程序:
/**
* @Title: JabberServer.java
* @Description: TODO
* @author :Xingle
* @date 2014-7-22 下午12:50:53
* @version
*/ package com.xingle_test.socket; import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket; /**
* 一个简单的服务器和客户机程序
*
* @ClassName: JabberServer TODO
* @author Xingle
* @date 2014-7-22 下午12:50:53
*/
public class JabberServer { // Choose a port outside of the range 1-1024:
public static final int POST = 8080; public static void main(String[] args) throws IOException {
ServerSocket s = new ServerSocket(POST);
System.out.println("Started :" + s);
try {
// Blocks until a connection occurs:
Socket socket = s.accept();
try {
System.out.println("Connection acception:" + socket);
BufferedReader in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
// Output is automatically flushed by PrintWriter:
PrintWriter out = new PrintWriter(new BufferedWriter(
(new OutputStreamWriter(socket.getOutputStream()))),
true);
while (true) {
String str = in.readLine();
if (str.equals("END"))
break;
System.out.println("Echoing:" + str);
out.println(str);
}
// Always close the two sockets...
} finally {
System.out.println("service closing...");
socket.close();
}
} finally {
s.close();
}
}
}
下面是客户程序的源码:
/**
* @Title: JabberClient.java
* @Description: TODO
* @author :Xingle
* @date 2014-7-22 下午1:53:35
* @version
*/ package com.xingle_test.socket; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket; /**
*
* @ClassName: JabberClient TODO
* @author Xingle
* @date 2014-7-22 下午1:53:35
*/
public class JabberClient { public static void main(String[] args) throws IOException {
// testing on one machine w/o a network:
InetAddress addr = InetAddress.getByName(null);
// Alternatively, you can use
// the address or name:
// InetAddress addr =
// InetAddress.getByName("127.0.0.1");
// InetAddress addr =
// InetAddress.getByName("localhost");
System.out.println("addr=" + addr);
Socket socket = new Socket(addr, JabberServer.POST);
// Guard everything in a try-finally to make sure that the socket is closed:
try {
System.out.println("socket=" + socket);
BufferedReader in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
// Output is automatically flushed by PrintWriter:
PrintWriter out = new PrintWriter(new PrintWriter(
new OutputStreamWriter(socket.getOutputStream())), true);
for (int i = 0; i < 10; i++) {
out.println("howdy:"+i);
String str = in.readLine();
System.out.println(str);
}
out.println("END");
} finally {
System.out.println("closing...");
socket.close();
}
} }
然后先运行服务器端,再运行客户端
客户端的执行结果:
addr=localhost/127.0.0.1
socket=Socket[addr=localhost/127.0.0.1,port=8080,localport=58540]
howdy:0
howdy:1
howdy:2
howdy:3
howdy:4
howdy:5
howdy:6
howdy:7
howdy:8
howdy:9
closing...
服务器端的执行结果:
Started :ServerSocket[addr=0.0.0.0/0.0.0.0,localport=8080]
Connection acception:Socket[addr=/127.0.0.1,port=58540,localport=8080]
Echoing:howdy:0
Echoing:howdy:1
Echoing:howdy:2
Echoing:howdy:3
Echoing:howdy:4
Echoing:howdy:5
Echoing:howdy:6
Echoing:howdy:7
Echoing:howdy:8
Echoing:howdy:9
service closing...
4. 服务多个客户
最基本的方法是在服务器(程序)里创建单个ServerSocket,并调用accept()来等候一个新连接。一旦accept()返回,我们就取得结果获得的Socket,并用它新建一个线程,令其只为那个特定的客户服务。然后再调用accept() ,等候下一次新的连接请求。
对于下面这段服务器代码,可发现它与JabberServer.java 例子非常相似,只是为一个特定的客户提供服务的所有操作都已移入一个独立的线程类中:
服务器代码:
package com.xingle_test.socket; import java.io.*;
import java.net.ServerSocket;
import java.net.Socket; /**
* 服务器端
* @ClassName: MultiJabberServer
* @author Xingle
* @date 2014-7-23 下午9:10:23
*/
public class MultiJabberServer {
static final int PORT = 8080; public static void main(String[] args) throws IOException {
ServerSocket s = new ServerSocket(PORT);
System.out.println("Server Started");
try {
while (true) {
// Blocks until a connection occurs:
Socket socket = s.accept();
try {
new ServeOneJabber(socket);
} catch (IOException e) {
// If it fails, close the socket,
// otherwise the thread will close it:
socket.close();
}
}
} finally {
s.close();
}
}
} class ServeOneJabber extends Thread {
private Socket socket;
private BufferedReader in;
private PrintWriter out; public ServeOneJabber(Socket s) throws IOException {
socket = s;
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// Enable auto-flush:
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream())), true);
// If any of the above calls throw an
// exception, the caller is responsible for
// closing the socket. Otherwise the thread
// will close it.
start(); // Calls run()
} public void run() {
try {
while (true) {
String str = in.readLine();
if (str.equals("END"))
break;
System.out.println("Echoing: " + str);
out.println(str);
}
System.out.println("closing...");
} catch (IOException e) {
} finally {
try {
socket.close();
} catch (IOException e) {
}
}
}
}
和以前一样,我们创建一个ServerSocket,并调用accept()允许一个新连接的建立。但这一次,accept() 的返回值(一个套接字)将传递给用于ServeOneJabber 的构建器,由它创建一个新线程,并对那个连接进行控制。连接中断后,线程便可简单地消失。
如果ServerSocket 创建失败,则再一次通过main()掷出违例。如果成功,则位于外层的try-finally 代码块可以担保正确的清除。位于内层的try-catch 块只负责防ServeOneJabber 构建器的失败;若构建器成功,则ServeOneJabber 线程会将对应的套接字关掉。
为了证实服务器代码确实能为多名客户提供服务,下面这个程序将创建许多客户(使用线程),并同相同的服务器建立连接。每个线程的“存在时间”都是有限的。一旦到期,就留出空间以便创建一个新线程。允许创建的线程的最大数量是由final int maxthreads 决定的。大家会注意到这个值非常关键,因为假如把它设得很大,线程便有可能耗尽资源,并产生不可预知的程序错误。
客户端程序:
package com.xingle_test.socket; import java.io.*;
import java.net.InetAddress;
import java.net.Socket; /**
* 客户端
* @ClassName: MultiJabberClient
* @author Xingle
* @date 2014-7-23 下午9:13:10
*/
public class MultiJabberClient {
static final int MAX_THREADS = 4; public static void main(String[] args) throws IOException,
InterruptedException {
InetAddress addr = InetAddress.getByName(null);
while (true) {
if (JabberClientThread.threadCount() < MAX_THREADS)
new JabberClientThread(addr);
Thread.currentThread().sleep(100);
}
}
} class JabberClientThread extends Thread {
private Socket socket;
private BufferedReader in;
private PrintWriter out;
private static int counter = 0;
private int id = counter++;
private static int threadcount = 0; public static int threadCount() {
return threadcount;
} public JabberClientThread(InetAddress addr) {
System.out.println("Making client " + id);
threadcount++;
try {
socket = new Socket(addr, MultiJabberServer.PORT);
} catch (IOException e) {
// If the creation of the socket fails,
// nothing needs to be cleaned up.
}
try {
in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
// Enable auto-flush:
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream())), true);
start();
} catch (IOException e) {
// The socket should be closed on any
// failures other than the socket
// constructor:
try {
socket.close();
} catch (IOException e2) {
}
}
// Otherwise the socket will be closed by
// the run() method of the thread.
} public void run() {
try {
for (int i = 0; i < 5; i++) {
out.println("Client " + id + ": " + i);
String str = in.readLine();
System.out.println(str);
}
out.println("END");
} catch (IOException e) {
} finally {
// Always close it:
try {
socket.close();
} catch (IOException e) {
}
threadcount--; // Ending this thread
}
}
}
首先执行服务器端程序,再执行客户端程序,先看下客户端结果:
Making client 0
Client 0: 0
Client 0: 1
Client 0: 2
Client 0: 3
Client 0: 4
Making client 1
Client 1: 0
Client 1: 1
Client 1: 2
Client 1: 3
Client 1: 4
Making client 2
Client 2: 0
Client 2: 1
Client 2: 2
Client 2: 3
Client 2: 4
Making client 3
Client 3: 0
Client 3: 1
Client 3: 2
Client 3: 3
Client 3: 4
Making client 4
Client 4: 0
Client 4: 1
Client 4: 2
Client 4: 3
Client 4: 4
Making client 5
Client 5: 0
Client 5: 1
Client 5: 2
Client 5: 3
Client 5: 4
Making client 6
Client 6: 0
Client 6: 1
Client 6: 2
Client 6: 3
Client 6: 4
Making client 7
Client 7: 0
Client 7: 1
.
.
.
(截取一部分)
再看下服务器端console:
Server Started
Echoing: Client 0: 0
Echoing: Client 0: 1
Echoing: Client 0: 2
Echoing: Client 0: 3
Echoing: Client 0: 4
closing...
Echoing: Client 1: 0
Echoing: Client 1: 1
Echoing: Client 1: 2
Echoing: Client 1: 3
Echoing: Client 1: 4
closing...
Echoing: Client 2: 0
Echoing: Client 2: 1
Echoing: Client 2: 2
Echoing: Client 2: 3
Echoing: Client 2: 4
closing...
Echoing: Client 3: 0
Echoing: Client 3: 1
Echoing: Client 3: 2
Echoing: Client 3: 3
Echoing: Client 3: 4
closing...
Echoing: Client 4: 0
Echoing: Client 4: 1
Echoing: Client 4: 2
Echoing: Client 4: 3
Echoing: Client 4: 4
closing...
Echoing: Client 5: 0
Echoing: Client 5: 1
Echoing: Client 5: 2
Echoing: Client 5: 3
Echoing: Client 5: 4
closing...
Echoing: Client 6: 0
Echoing: Client 6: 1
Echoing: Client 6: 2
Echoing: Client 6: 3
Echoing: Client 6: 4
closing...
Echoing: Client 7: 0
Echoing: Client 7: 1
Echoing: Client 7: 2
.
.
.
(截取一部分)
JabberClientThread 构建器获取一个InetAddress,并用它打开一个套接字。大家可能已看出了这样的一个套路:Socket 肯定用于创建某种Reader 以及/或者Writer(或者InputStream 和/或OutputStream)对象,这是运用Socket 的唯一方式(当然,我们可考虑编写一、两个类,令其自动完成这些操作,避免大量重复的代码编写工作)。同样地,start()执行线程的初始化,并调用run()。在这里,消息发送给服务器,而来自服务器的信息则在屏幕上回显出来。然而,线程的“存在时间”是有限的,最终都会结束。注意在套接字创建好以后,但在构建器完成之前,假若构建器失败,套接字会被清除。否则,为套接字调用close()的责任便落到了run()方法的头上。
threadcount 跟踪计算目前存在的JabberClientThread 对象的数量。它将作为构建器的一部分增值,并在run()退出时减值(run()退出意味着线程中止)。在MultiJabberClient.main()中,大家可以看到线程的数量会得到检查。若数量太多,则多余的暂时不创建。方法随后进入“休眠”状态。这样一来,一旦部分线程最后被中止,多作的那些线程就可以创建了。大家可试验一下逐渐增大MAX_THREADS,看看对于你使用的系统来说,建立多少线程(连接)才会使您的系统资源降低到危险程度。
think in java 读书笔记 2 —— 套接字的更多相关文章
- think in java 读书笔记 3 —— 数据报
目录 think in java 读书笔记 1 ——移位 think in java 读书笔记 2 —— 套接字 think in java 读书笔记 3 —— 数据报 概要 1. 数据报基本知识 2 ...
- think in java 读书笔记 1 ——移位
目录 think in java 读书笔记 1 ——移位 think in java 读书笔记 2 —— 套接字 think in java 读书笔记 3 —— 数据报 在Think in Java中 ...
- Thinking In Java读书笔记--对象导论
Thinking In Java读书笔记--对象导论[对象]服务提供者==>将对象看做一个服务提供者[程序员分类][类创造者]/[客户端程序员] [访问控制存在的原因?][1]客户端程序员无法触 ...
- head first java读书笔记
head first java读书笔记 1. 基本信息 页数:689 阅读起止日期:20170104-20170215 2. 标签 Java入门 3. 价值 8分 4. 主题 使用面向对象的思路介绍J ...
- Java读书笔记1
Java逍遥游记读书笔记 前言 必须先来一句,这是入门级别,高手勿喷~ 写Android的时候总有一些语句不是很理解,其实大部分是Java的内容,所以想系统的学下Java. 这本书——<Java ...
- java读书笔记二
这是我的一些读书笔记: 我研究了一下面向对象: 面向对象符合人类看待事物的一般规律,对象的方法的实现细节是包装的,只有对象方法的实现者了解细节 我觉得面向过程是由过程.步骤.函数组成,过程是核心,面向 ...
- Effective Java读书笔记完结啦
Effective Java是一本经典的书, 很实用的Java进阶读物, 提供了各个方面的best practices. 最近终于做完了Effective Java的读书笔记, 发布出来与大家共享. ...
- Head First Java 读书笔记(完整)
第0章:学习方法建议 该如何学习Java? 1.慢慢来.理解的越多,就越不需要死记硬背.时常停下来思考. 2.勤作笔记,勤做习题. 3.动手编写程序并执行,把代码改到出错为止. 需要哪些环境和工具? ...
- UNIX网络编程读书笔记:套接口选项
概述 有很多方法来获取和设置影响套接口的选项: getsockopt和setsockopt函数 fcntl函数 ioctl函数 getsockopt和setsockopt函数 这两个函数仅用于套接口. ...
随机推荐
- Task<TResult>的使用
https://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).aspx Represents an asynchronous operatio ...
- CUBRID学习笔记 5 错误码
服务器错误码 AS Error Code Number CAS Error Code Error Message Note -1000 CAS_ER_DBMS "CUBRID DBMS Er ...
- Python入门-多行语句
1. Python语句中一般以新行作为为语句的结束符. 但是我们可以使用斜杠( \)将一行的语句分为多行显示. 2. 语句中包含[], {} 或 () 括号就不需要使用多行连接符. days = [' ...
- HDU 5877 Weak Pair(弱点对)
HDU 5877 Weak Pair(弱点对) Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 262144/262144 K (Jav ...
- TestNg测试框架使用笔记
Gradle支持TestNG test { useTestNG(){ //指定testng配置文件 suites(file('src/test/resources/testng.xml')) } } ...
- Codeforces Round #379 (Div. 2) C. Anton and Making Potions 二分
C. Anton and Making Potions time limit per test 4 seconds memory limit per test 256 megabytes input ...
- Python基础学习笔记(二)变量类型
参考资料: 1. <Python基础教程> 2. http://www.runoob.com/python/python-chinese-encoding.html 3. http://w ...
- swipejs的bug
Github:https://github.com/thebird/Swipe 以下bug的修复方式皆来自于网上. 现在最新的版本是2.0,bug如下: 1.触摸后不会自动播放 修复方式, funct ...
- linux 命令行模式下,浏览网页
Ubuntu自带最新版的Gnome桌面,拥有大量的服务和桌面应用程序,让您仅通过一张安装光盘就可以体验到无比舒适的操作环境.下文介绍的在ubuntu下使用终端命令行上网的方法. 第一步,需要安装一个名 ...
- java位运算符常见用法
1. 判断int型变量a是奇数还是偶数 a&1 = 0 偶数 a&1 = 1 奇数 2. 求平均值,比如有两个int类型变量x.y,首先要求x+y的和,再除以2,但是有可能x+y的结果 ...