更新日期:2010年8月18日
1.什么是socket
socket用中文意思就是我们常说的”套接字“,用于描述IP地址和端口,是运行在网络上的两个程序间双向通讯连接的末端,它提供客户端和服务器端的连接通道。Socket绑定于特定端口这样TCP层就知道将数据提供给哪个应用程序。
Java有一套功能强大而又易于使用的网络开发 API,这个API是一系列的接口和类,在java.net和javax.net包中可以找到它们。套接字一般分为两种:流套接字(Stream Socket)和数据报套接字(Datagram Sockets)
端口是为了唯一标识每台计算机唯一服务的。另外端口号是从0~65535之间的,前1024个端口已经被Tcp/Ip 作为保留端口,因此你所分配的端口只能是1024个之后的。
2.Socket编程实现原理
从连接的建立到连接的结束,每个Socket应用都大致包含以下几个基本步骤:
1、服务器端socket绑定于特定端口,服务器侦听socket等待连接请求;
2、客户端向服务器和特定端口提交连接请求;
3、服务器接受连接,产生一新的socket,绑定到另一端口,由此socket来处理和客户端的交互,服务器继续侦听原socket来接受其他客户端的连接请求;
4、连接成功后客户端也产生一socket,并通过它来与服务器端通讯(注意:客户端socket并不与特定端口绑定);
5、接下来,服务器端和客户端就通过读取和写入各自的socket来进行通讯。
注意:本文中只讲解基于TCP的Socket应用
3.重要的API
3.1 InetAddress类
描述:该类在套接字编程中份演着目的地角色,通常作为参数传递给流套接字类和数据报套接字的构造方法或其它方法。
构造方法:该类只有一个默认的不带参数的构造方法。
不能直接用new创建一个InetAddress对象。象这样做
InetAddress ia = new InetAddress ();
就是错误的。但是我们可以通过它的5个静态工厂方法获得一个InetAddress对象或对象数组。
重要方法:
static InetAddress[] getAllByName(String host);
通过传递参数host(主机名),返回一个InetAddress对象数组的引用。如果指定的主机没有IP地址存在,则方法抛出一个UnknownHostException 异常对象。
static InetAddress getByAddress(byte[] addr);
通过传递一个用字节数组表示的二进制IP地址参数(4字节对应Ipv4,16字节对应Ipv6),返回一个InetAddress对象的引用。如果返回对象的数组既不是4字节也不是16字节,则方法抛出一个UnknownHostException 异常对象。
static InetAddress getByAddress(String host, byte[] addr);
创建一个基于给定主机名和给定字节数组组成的IP地址的InetAddress对象。如果返回对象中的数组既不是4字节也不是16字节,则方法抛出一个UnknownHostException 异常对象。
static InetAddress getByName(String host);
返回一个与给定主机名的InetAddress对象。如果指定的主机没有IP对应,则方法抛出一个UnknownHostException异常对象。
static InetAddress getLocalHost();
返回一个包含本地主机的IP地址的InetAddress对象。
上面讲到的方法均提到返回一个或多个InetAddress对象的引用,实际上每一个方法都要返回一个或多个Inet4Address/Inet6Address对象的引用。一旦获得了InetAddress对象,你就可以使用其他非静态方法得到你想要的东东了。
3.2 Socket类
描述:Socket是建立网络连接时使用的。在连接成功时,服务端和客户端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。
构造方法:该类有多个构造方法,常用的有两个:Socket(InetAddress addr, int port) 和 Socket(String host, int port)
两个构造方法都创建了一个基于指定接服务器主机名或InetAddress和服务器端口号的流套接字。对于第一个InetAddress子类对象通过 addr参数获得服务器主机的IP地址,对于第二个函数host参数包被分配到InetAddress对象中,如果没有IP地址与host参数相一致,那么将抛出UnknownHostException异常对象。
如果创建连接成功,java网络API将在客户端的流套接字中捆绑客户程序的IP地址和任意一个端口号,否则两个函数都会抛出一个IOException对象。
重要方法:
InputStream getInputStream();
获得网络连接输入,同时返回一个IutputStream对象实例。
OutputStream getOutputStream();
获得网络连接输出,连接的另一端将得到输入,同时返回一个OutputStream对象实例。
必须捕获两个方法可能产生的IOException。怎么操作这些流对象就看你要作什么了!:)
3,3 ServerSocket类
描述:该类用于服务器端。
构造方法:该类有四个构造方法,常用的有:
ServerSocket(int port)
通过指定监听端口号来创建ServerSocket对象,如果在这时出现错误将抛出IOException异常对象,否则将创建ServerSocket对象并开始准备接收连接请求。接下来无限循环调用accept()方法。
重要方法:
Socket accept();
调用开始后accept()方法将导致调用线程阻塞直到连接建立。在建立连接后accept()返回一个最近创建的Socket对象,该Socket对象绑定了客户程序的IP地址和端口号。
4.注意事项
(1) 在服务端和客户端均未发送数据时,不要在同时在两端的while()循环里读取 InputStream。否则将造成阻塞。
(2) 如果用字符流处理数据,在数据末尾加上 “\r\n”,并记得 flush()一下。
5.程序举例
5.1 服务器与单个客户端通信例子
服务器端(ServerExam2.java)
服务器不断接收客户机所写入的信息,只到客户机发送"End"字符串就退出程序
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(5678);
Socket client = server.accept();
//建立缓冲并把原始的字节流转变为Unicode
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter out = new PrintWriter(client.getOutputStream());
while (true) {
String str = in.readLine();
System.out.println(str);
//服务器也会做出"Receive"为回应,告知客户机已接收到消息
out.println("has receive....");
out.flush();
//服务器不断接收客户机所写入的信息,只到客户机发送"End"字符串就退出程序
if (str.equals("end"))
break;
}
client.close();
}
客户端(ClientExam2.java)
接受客户键盘输入,并把该信息输出,然后输出"End"用来做退出
static Socket server;
public static void main(String[] args) throws Exception {
server = new Socket(InetAddress.getLocalHost(), 5678);
BufferedReader in = new BufferedReader(new InputStreamReader(server
.getInputStream()));
PrintWriter out = new PrintWriter(server.getOutputStream());
BufferedReader wt = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String str = wt.readLine();
out.println(str);
out.flush();
if (str.equals("end")) {
break;
}
System.out.println(in.readLine());
}
server.close();
}
这个程序只是简单的两台计算机之间的通讯。如果是多个客户同时访问一个服务器呢,则只能接收一个客户端。
5.2 服务器与多个客户端排队通信
服务器端(ServerExam3.java)
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(5678);
while (true) {
Socket client = server.accept();
// 建立缓冲并把原始的字节流转变为Unicode
BufferedReader in = new BufferedReader(new InputStreamReader(client
.getInputStream()));
PrintWriter out = new PrintWriter(client.getOutputStream());
while (true) {
String str = in.readLine();
System.out.println(str);
// 服务器也会做出"Receive"为回应,告知客户机已接收到消息
out.println("has receive....");
out.flush();
// 服务器不断接收客户机所写入的信息,只到客户机发送"End"字符串就退出程序
if (str.equals("end"))
break;
}
client.close();
}
}
客户端与ClientExam3.java相同
说明:
与ServerExam2.java基本相同,仅只是加了一个外层的While循环。这个循环的目的就是当一个客户进来就为它分配一个Socket直到这个客户完成一次和服务器的交互,这里也就是接受到客户的"End"消息。那么现在就实现了多客户之间的交互了。但是问题又来了,这样做虽然解决了多客户,可是是排队执行的。也就是说当一个客户和服务器完成一次通讯之后,下一个客户才可以进来和服务器交互。
例3:通过线程并发通信
服务器端(ServerExam4.java)
创建线程要么直接继承Thread要么实现Runnable接口,重写run方法。
public class ServerExam4 extends Thread {
private Socket client;
public ServerExam4(Socket c) {
this.client = c;
}
public void run() {
try {
BufferedReader in = new BufferedReader(
new InputStreamReader(client.getInputStream()));
PrintWriter out = new PrintWriter(client.getOutputStream());
while (true) {
String str = in.readLine();
System.out.println(str);
out.println("has receive...");
out.flush();
if (str.equals("end"))
break;
}
client.close();
} catch (IOException ex) {
} finally {
}
}
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(5678);
while (true) {
ServerExam4 mu = new ServerExam4(server.accept());
mu.start();
}
}
}
说明
通过构造函数传递引用和客户Socket建立了联系。这样每个线程就有了一个通讯管道。同样我们可以填写run方法。把之前的操作交给线程来完成。这样多客户并行的Socket就建立起来了。
总结
1.项目代码:SocketProject_20100818.zip
2.开发环境:jdk1.6,myeclipse7.5
参考文献
1.我的Java Socket学习历程.
2.java socket开发入门.
3.一篇不错的介绍Java Socket编程的文章.
阅读(1133) | 评论(0) | 转发(0) |