本博文简要阐述java.net包下的基于TCP/IP的Socket编程以及其源码分析
public static voidmain(String args[]) throws IOException {
ServerSocket server = null;
server =new ServerSocket(4444);// 设置server的端口
Socketsocket = null;
int number= 0;
booleanflag = true;
try{
while (flag) {
socket = server.accept();//监听连接,方法阻塞,能够建立多个长连接
number++;
createHandlerThread(socket,number);// 为每一个连接建立线程
}
} finally{
server.close();
}
}
private static voidcreateHandlerThread(Socket socket, int number) {
DataAcceptServer das = new DataAcceptServer();
Handlerhandler = das.new Handler(socket, number);
Thread t =new Thread(handler);
t.start();
}
public class Handlerimplements Runnable {
privateSocket socket = null;
privateint number = 0;
publicHandler(Socket socket, int number) {
this.socket = socket;
this.number = number;
}
@Override
publicvoid run() {
System.out.println("get the connetion withclient" + number);
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(newInputStreamReader(
socket.getInputStream(), "UTF-8")); //获取socket的接收流
out = new PrintWriter(newOutputStreamWriter(
socket.getOutputStream(), "UTF-8"));//获取socket的发送流
} catch (IOException e) {
e.printStackTrace();
}
String getline = null;
boolean end = false;
while (!end) {
try {
getline =in.readLine(); // 获取消息
} catch (IOException e){
e.printStackTrace();
break;
}
System.out.println("getmessage from client" + number + " :"
+ getline);
out.write("ok\n"); //返回ok
out.flush();
if (getline.equals("byebye")){
end =true;
}
}
try {
in.close();
out.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("release the connetion withclient" + number);
}
}
public static voidmain(String args[]) throwsUnknownHostException,
IOException {
Socketsocket = new Socket("127.0.0.1", 4444); //设置server的ip和端口
//socket.setSoTimeout(10000);//设置超时时间
BufferedReader sin = new BufferedReader(newInputStreamReader(
System.in));
PrintWriter out = new PrintWriter(newOutputStreamWriter(
socket.getOutputStream(),"UTF-8"));//获取socket的发送流
BufferedReader in = new BufferedReader(newInputStreamReader(
socket.getInputStream(),"UTF-8"));//获取socket的接收流
Stringreadline = null;
readline =sin.readLine();
while(!readline.equals("byebye")) {//以byebye终止连接
out.println(readline);
out.flush();
System.out.println("Client:" +readline);
System.out.println("Server:" +in.readLine());
readline = sin.readLine();
}
out.close();
in.close();
socket.close();
}
写网络编程这一章,初衷是为了讲诉HttpClient、Jetty、Netty的使用心得,NIO和BIO的比较,Thrift和Avro等,无奈今天被合作伙伴鄙视了,他看到我用了Apache的Httpclient(C)+Jetty(S),说:你只不过是传一些数据,用Socket多简单迅速,我们的量一天十几T,难怪我这边一堆的timeout。后来他告诉我他用得是“c调用linux的Socket接口”写的自己的库,很骄傲表示速度杠杠的(工程量变大是一弊端),于是我也看了一下java.net基础包下的东西,会有两章,本章主要是将应对TCP/IP的Socket部分。
java.net包含以下几类:DatagranSocket一类,Socket/ServerSocket一类,InetXAddress一类,URL相关一类以及其他,本章主要讲诉的是Socket/ServerSocket
Socket编程,无非就是一个Socket(C)去连接一个ServerSocket(S),所以编写了两个简单的程序并进行分析,功能有一个DataAcceptServer,可以创建4444端口,能够最多接受50个连接;两个DataSender,创建长连接,知道发送byebye为止。为方便,使用UTF-8字符流。程序如下:
//Server
importjava.io.BufferedReader;
importjava.io.IOException;
importjava.io.InputStreamReader;
importjava.io.OutputStreamWriter;
importjava.io.PrintWriter;
importjava.net.ServerSocket;
importjava.net.Socket;
publicclass DataAcceptServer {
}
ServerSocket类在java.net下并不起眼,也不完成主要的工作(SocketImpl的子类PlainSocketImpl和它的子类SocksSocketImpl完成了最重要的工作)
第一步:ServerSocket server = newServerSocket(4444)
1、转化为ServerSocket(int port, intbacklog, InetAddress bindAddr)构造方法,port(必需)+ 最大连接数(默认50)+服务地址(默认本机)。ip+端口会以InetSocketAddress类形式存在。
2、SecurityManager security =System.getSecurityManager();security会去检测此port是否可用
3、bind(),由PlainSocketImpl中的native方法socketBind来完成,也就是使用系统调用来完成创建和绑定端口的工作
4、listen(),由PlainSocketImpl中的native方法socketListen来完成,也就是使用系统调用来完成设置最大连接数,创建queuefor incoming connectionindications(a request toconnect),其中的count参数就是最大连接数。如果thequeue满了了话,将会refuse后面的connection
第二步:Socket socket= server.accept();
1、accept(SocksSocketImpl s),是一个阻塞方法,由PlainSocketImpl的native方法socketAccept来完成,也就是使用系统调用来监听是否有连接进来,直到有一个连接
2、响应连接,创建Socket对象,同时赋予其一个非常最要的对象SocksSocketImpl,它完成了我们所见到socket对象的所有的工作。
第三步:socket.getOutputStream()和socket.getInputStream()来获取SocksSocketImpl对象的输入输出流。接下来的工作就使用流来处理了。
//client
importjava.io.BufferedReader;
import java.io.IOException;
importjava.io.InputStreamReader;
importjava.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
importjava.net.UnknownHostException;
public class DataSender {
}
Socket类就更为简单了,但是背后的秘密也是值得挖掘,他完成的主要是创建对某个服务的连接,并传输数据或者命令
第一步:Socketsocket = new Socket("127.0.0.1", 4444);
1、首先为自己创建一个SocksSocketImpl对象
2、bind(),由PlainSocketImpl中的native方法socketBind来完成,也就是使用系统调用来完成绑定端口的工作
3、connect(),由SocksSocketImpl对象的connect来进入(因为涉及到安全验证和各种不同的connect方法如代理等),最终由PlainSocketImpl的socketConnect来完成,也是使用系统调用来完成connect的工作。
第二步:socket.getOutputStream()和socket.getInputStream()来获取SocksSocketImpl对象的输入输出流。接下来的工作就使用流来处理了。
看到这里,其实socket编程最核心的工作都并不是由它本身来完成,而是使用非java方法来完成,JNI调用系统C库(会在之后的章程介绍),因此我认为,在承受范围之内,慢并不是java的错,而是我实现的有问题。
最后总结一张图:
由于博主知识有限,如有误,请指正点评,欢迎交流