大家都知道在日常的开发中,我们或多或少会接触到 Socket 这个对于通信来说很重要,大家要着重掌握用法和概念,多写写一些小实例,看文档可以很快速掌握
它,在以后的编程中遇到了,就不会在是拦路虎了
Socket 分两个概念来理解,Socket、ServerSocket,ServerSocket首先在服务端监听当前端口,当有Socket连接,ServerSocket会阻塞该Socket的连接请求,
同时在当前服务端建立一个新的Socket,此时客户端和服务端各一个Socket,它们可以互相向流执行写和读操作,双向通信,下面看看大致的交互图:
Socket 交互图:
① Client write\Server read
Server Code :
package engineer.jsp.socket1.util;
/**
* 服务端
* @author Engineer-Jsp
* @date 2014.10.14
*/
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.ServerSocket;
import java.net.Socket;
public class EngineerJspServer {
// 异常尽量网外抛,简单了事
public static void main(String[] args) throws IOException {
// 端口
int port = 5469;
// serverSocket对象监听5469端口
ServerSocket serverSocket = new ServerSocket(port);
// serverSocket接收其他Socket的连接请求,serverSocket的accept()方法为阻塞形式
Socket socket = serverSocket.accept();
// 跟客户端建立好之后,开始获取socket的InputStream,并且读取来自客户端的信息
Reader reader = new InputStreamReader(socket.getInputStream());
char chars[] = new char[64];
int length;
StringBuilder stringBuilder = new StringBuilder();
while((length=reader.read(chars))!=-1){
stringBuilder.append(new String(chars,0,length));
}
System.out.println("From EngineerJspClient"+" "+socket.getInetAddress()+": "+stringBuilder);
reader.close();
socket.close();
serverSocket.close();
}
}
Server从 Socket 的 流 中读取数据的操作也是阻塞式的,如果从流中没有读取到数据程序会一直在那里不动,直到客户端往 Socket 的输出流中写入了数据,或关闭了 Socket 的输出流,客户端的 Socket 也如此,操作完,关闭对应的资源,即关闭对应的 IO 流和 Socket
Client Code:
package engineer.jsp.socket1.util;
/**
* 客户端
* @author Engineer-Jsp
* @date 2014.10.14
*/
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
public class EngineerJspClient {
// 异常尽量网外抛,简单了事
public static void main(String[] args) throws Exception{
// 测试服务器IP
String host = "127.0.01";
// 端口
int port = 5469;
// 与服务器进行连接
Socket socket = new Socket(host,port);
// 连接后,向服务器写数据
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("From EngineerJspClient");
writer.flush();
writer.close();
socket.close();
}
}
在客户端与服务端的流读写,不管是对应关闭还是阻塞中,都要 flush 一下,这样服务端才能收到客户端发送的数据,否则可能会引起两边无限的互相等待
② Client and server simultaneously read and write
Server Code:
package engineer.jsp.socket2.util;
/**
* 服务端
* @author Engineer-Jsp
* @date 2014.10.14
*/
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
public class EngineerJspServer {
// 异常尽量网外抛,简单了事
public static void main(String[] args) throws IOException {
// 端口
int port = 5469;
// serverSocket监听端口5469
ServerSocket serverSocket = new ServerSocket(port);
// serverSocket接收其他Socket连接,accept阻塞
Socket socket = serverSocket.accept();
// 跟客户端建立连接之后,获取socket的流,读取客户端消息
Reader reader = new InputStreamReader(socket.getInputStream());
char chars [] = new char[64];
int length;
StringBuilder stringBuilder = new StringBuilder();
while((length=reader.read(chars))!=-1){
stringBuilder.append(new String(chars,0,length));
}
System.out.println("From EngineerJspClient"+" "+socket.getInetAddress()+": "+stringBuilder);
// 开始执行写操作
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("From EngineerJspServer");
writer.flush();
writer.close();
reader.close();
socket.close();
serverSocket.close();
}
}
从输入流中读取客户端发送过来的数据,往输出流里面写入数据给客户端,关闭对应的资源文件,因为从输入流中读取数据是一个阻塞式操作,在上述的 while 循环中当读到数据的时候就会执行循环体,否则就会阻塞,这样后面的写操作就永远都执行不了了。除非客户端对应的 Socket 关闭了阻塞才会停止, while 循环也会跳出。 针对这种情况,都会约定一个结束标记,当客户端发送过来的数据包含某个结束标记时就说明当前的数据已经发送完毕
给服务端添加读的结束标记
Server Code:
package engineer.jsp.socket3.util;
/**
* 服务端
* @author Engineer-Jsp
* @date 2014.10.14
*/
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
public class EngineerJspServer {
// 异常尽量网外抛,简单了事
public static void main(String[] args) throws IOException {
// 端口
int port = 5469;
// serverSocket监听端口5469
ServerSocket serverSocket = new ServerSocket(port);
// serverSocket接收其他Socket连接,accept阻塞
Socket socket = serverSocket.accept();
// 跟客户端建立连接之后,获取socket的流,读取客户端消息
Reader reader = new InputStreamReader(socket.getInputStream());
char chars [] = new char[64];
int length;
int count;
String str;
StringBuilder stringBuilder = new StringBuilder();
while((length=reader.read(chars))!=-1){
str = new String(chars,0,length);
if((count = str.indexOf("end"))!=-1){
stringBuilder.append(str.substring(0,count));
break;
}
stringBuilder.append(str);
}
System.out.println("From EngineerJspClient"+" "+socket.getInetAddress()+": "+stringBuilder);
// 开始执行写操作
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("From EngineerJspServer");
writer.flush();
writer.close();
reader.close();
socket.close();
serverSocket.close();
}
}
上面代码读到"end"结束
Client Code:
package engineer.jsp.socket2.util;
/**
* 客户端
* @author Engineer-Jsp
* @date 2014.10.14
*/
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.Socket;
public class EngineerJspClient {
// 异常尽量网外抛,简单了事
public static void main(String[] args) throws Exception{
// 测试服务器IP
String host = "127.0.0.1";
// 端口
int port = 5469;
// 连接服务器
Socket socket = new Socket(host,port);
// 向服务器执行写操作
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("From EngineerJspClient");
writer.flush();
// 执行读取服务器消息操作
Reader reader = new InputStreamReader(socket.getInputStream());
char chars [] = new char[64];
int length;
StringBuffer stringBuffer = new StringBuffer();
while((length=reader.read(chars))!=-1){
stringBuffer.append(new String(chars,0,length));
}
System.out.println("From EngineerJspServer"+" "+socket.getInetAddress()+": "+stringBuffer);
writer.close();
reader.close();
socket.close();
}
}
为了避免服务端的while进入无限死循环,Client需要添加标记语
Client Code:
package engineer.jsp.socket3.util;
/**
* 客户端
* @author Engineer-Jsp
* @date 2014.10.14
*/
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.Socket;
public class EngineerJspClient {
// 异常尽量网外抛,简单了事
public static void main(String[] args) throws Exception{
// 测试服务器IP
String host = "127.0.0.1";
// 端口
int port = 5469;
// 连接服务器
Socket socket = new Socket(host,port);
// 向服务器执行写操作
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("From EngineerJspClient");
writer.write("end");
writer.flush();
// 执行读取服务器消息操作
Reader reader = new InputStreamReader(socket.getInputStream());
char chars [] = new char[64];
int length;
int count;
String str;
StringBuffer stringBuffer = new StringBuffer();
while((length=reader.read(chars))!=-1){
str = new String(chars,0,length);
if((count = str.indexOf("end"))!=-1){
stringBuffer.append(str.substring(0,count));
break;
}
stringBuffer.append(str);
}
System.out.println("From EngineerJspServer"+" "+socket.getInetAddress()+": "+stringBuffer);
writer.close();
reader.close();
socket.close();
}
}
③ A plurality of clients connected to the same server
服务端的错误示例代码:
通常我们的服务端会对应多个客户端,以一对多的形式进行交互,这时我们就需要对服务端进行改写,满足我们的需求
下述为错误写法,易犯的错
Server Code:
package engineer.jsp.socket4.util;
/**
* 服务端
* @author Engineer-Jsp
* @date 2014.10.14
*/
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
public class EngineerJspServer1 {
// 异常尽量网外抛,简单了事
public static void main(String[] args) throws IOException {
// 端口
int port = 5469;
// serverSocket监听端口5469
ServerSocket serverSocket = new ServerSocket(port);
// 死循环实现一对多
while(true){
// serverSocket接收其他Socket连接,accept阻塞
Socket socket = serverSocket.accept();
// 跟客户端建立连接之后,获取socket的流,读取客户端消息
Reader reader = new InputStreamReader(socket.getInputStream());
char chars [] = new char[64];
int length;
int count;
String str;
StringBuilder stringBuilder = new StringBuilder();
while((length=reader.read(chars))!=-1){
str = new String(chars,0,length);
if((count = str.indexOf("end"))!=-1){
stringBuilder.append(str.substring(0,count));
break;
}
stringBuilder.append(str);
}
System.out.println("From EngineerJspClient"+" "+socket.getInetAddress()+": "+stringBuilder);
// 开始执行写操作
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("From EngineerJspServer");
writer.flush();
writer.close();
reader.close();
socket.close();
serverSocket.close();
}
}
}
上面的代码中,如果是在服务端一对少的情况下没什么问题,但是如果是多个客户端同时连接服务端的话,会影响到程序性能,因为ServerSocket在监听同一个窗口时,Client的Socket是可以同时并发的,而上面代码的执行步骤是根据先后顺序挨个执行,每执行完一个对客户端的读写操作之后再执行下一个,这样式不科学的,对我们的程序影响极为不好,所以我们需要对其进行改进,改进如下:
Server Code:
package engineer.jsp.socket4.util;
/**
* 服务端
* @author Engineer-Jsp
* @date 2014.10.14
*/
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
public class EngineerJspServer2 {
// 异常尽量网外抛,简单了事
public static void main(String[] args) throws IOException {
// 端口
int port = 5469;
// serverSocket监听5469端口
ServerSocket serverSocket = new ServerSocket(port);
// 死循环一对多
while(true){
// serverSocket接收多个Socket连接,accept阻塞
Socket socket = serverSocket.accept();
// 每接入一个Socket之后, new 一个子线程对其进行优化和处理
new Thread(new MThread(socket)).start();
}
}
static class MThread implements Runnable{
private Socket socket;
public void run() {
try {
ScoketThread();
} catch (Exception e) {
e.printStackTrace();
}
}
public MThread(Socket socket){
this.socket = socket;
}
@SuppressWarnings("unused")
private void ScoketThread() throws IOException{
Reader reader = new InputStreamReader(socket.getInputStream());
char chars [] = new char[64];
StringBuilder stringBuilder = new StringBuilder();
int length;
int count;
String str;
while((length = reader.read(chars))!=-1){
str = new String(chars,0,length);
if((count = str.indexOf("end"))!=-1){
stringBuilder.append(str.substring(0,count));
break;
}
stringBuilder.append(str);
}
System.out.println("From EngineerJspClient"+" "+socket.getInetAddress()+": "+stringBuilder);
// 执行向客户端写操作
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("From EngineerJspServer");
writer.flush();
writer.close();
reader.close();
socket.close();
}
}
}
上面代码用内部类MThread 实现了Runnable接口,ServerSocket每接收一个来自服务端的Socket,new 出一个新的线程来对其进行处理,这样就达到了异步执行的效果,Reader读取数据比较慢,也是不合理的,这是我们考虑需要更强大的读写工具,这里用到 BufferedReader,因为它有个方法 readLine( ),每次读取一行数据
Server Code:
package engineer.jsp.socket4.util;
/**
* 服务端
* @author Engineer-Jsp
* @date 2014.10.14
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
public class EngineerJspServer3 {
// 异常尽量网外抛,简单了事
public static void main(String[] args) throws IOException {
// 端口
int port = 5469;
// serverSocket监听5469端口
ServerSocket serverSocket = new ServerSocket(port);
// 死循环一对多
while(true){
// serverSocket接收多个Socket连接,accept阻塞
Socket socket = serverSocket.accept();
// 每接入一个Socket之后, new 一个子线程对其进行优化和处理
new Thread(new MThread(socket)).start();
}
}
static class MThread implements Runnable{
private Socket socket;
public void run() {
try {
ScoketThread();
} catch (Exception e) {
e.printStackTrace();
}
}
public MThread(Socket socket){
this.socket = socket;
}
@SuppressWarnings("unused")
private void ScoketThread() throws IOException{
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String str;
int length;
while((str = reader.readLine())!=null){
if((length = str.indexOf("end"))!=-1){
stringBuilder.append(str.substring(0,length));
break;
}
stringBuilder.append(str);
}
System.out.println("From EngineerJspClient"+" "+socket.getInetAddress()+": "+stringBuilder);
// 执行向客户端写操作
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("From EngineerJspServer");
writer.write("end\n");
writer.flush();
writer.close();
reader.close();
socket.close();
}
}
}
客户端也要进行更换:
Client Code:
package engineer.jsp.socket4.util;
/**
* 客户端
* @author Engineer-Jsp
* @date 2014.10.14
*/
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
public class EngineerJspClient1 {
// 异常尽量网外抛,简单了事
public static void main(String[] args) throws Exception{
// 测试服务器IP
String host = "127.0.0.1";
// 端口
int port = 5469;
// 连接服务器
Socket socket = new Socket(host,port);
// 向服务器执行写操作
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("From EngineerJspClient");
writer.write("end\n");
writer.flush();
// 执行读取服务器消息操作
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String str;
int length;
StringBuffer stringBuffer = new StringBuffer();
while((str=reader.readLine())!=null){
if((length = str.indexOf("end"))!=-1){
stringBuffer.append(str.substring(0,length));
break;
}
stringBuffer.append(str);
}
System.out.println("From EngineerJspServer"+" "+socket.getInetAddress()+": "+stringBuffer);
writer.close();
reader.close();
socket.close();
}
}
④ Set the timeout time
Socket在读数据的时候是阻塞式的,如果没有读到数据程序会一直阻塞,在同步请求的时候不允许这样的情况发生,需要在请求达到一定的时间后控制阻塞的中断,让程序得以继续运行,设置超时时间大于0,超过设置时间Socket还没有接收到返回的数据的话抛出一个SocketTimeoutException,调用Socket的setSoTimeout()来解决
Client Code:
package engineer.jsp.socket4.util;
/**
* 客户端
* @author Engineer-Jsp
* @date 2014.10.14
*/
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
public class EngineerJspClient2 {
// 异常尽量网外抛,简单了事
public static void main(String[] args) throws Exception{
// 测试服务器IP
String host = "127.0.0.1";
// 端口
int port = 5469;
// 连接服务器
Socket socket = new Socket(host,port);
// 向服务器执行写操作
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("From EngineerJspClient");
writer.write("end\n");
writer.flush();
// 执行读取服务器消息操作
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
socket.setSoTimeout(5*1000);
String str;
int length;
StringBuffer stringBuffer = new StringBuffer();
try {
while((str=reader.readLine())!=null){
if((length = str.indexOf("end"))!=-1){
stringBuffer.append(str.substring(0,length));
break;
}
stringBuffer.append(str);
}
} catch (Exception e) {
System.out.println("Connectionec is Timeout......");
}
System.out.println("From EngineerJspServer"+" "+socket.getInetAddress()+": "+stringBuffer);
writer.close();
reader.close();
socket.close();
}
}
⑤ Receive data garbled
在服务端与客户端的读写操作中,我们需要注意的是保持编码的一致,否则会出现乱码问题
Server Code:
package engineer.jsp.socket5.util;
/**
* 服务端
* @author Engineer-Jsp
* @date 2014.10.14
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
public class EngineerJspServer {
// 异常尽量网外抛,简单了事
public static void main(String[] args) throws IOException {
// 端口
int port = 5469;
// serverSocket监听5469端口
ServerSocket serverSocket = new ServerSocket(port);
// 死循环一对多
while(true){
// serverSocket接收多个Socket连接,accept阻塞
Socket socket = serverSocket.accept();
// 每接入一个Socket之后, new 一个子线程对其进行优化和处理
new Thread(new MThread(socket)).start();
}
}
static class MThread implements Runnable{
private Socket socket;
public void run() {
try {
ScoketThread();
} catch (Exception e) {
e.printStackTrace();
}
}
public MThread(Socket socket){
this.socket = socket;
}
@SuppressWarnings("unused")
private void ScoketThread() throws IOException{
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
StringBuilder stringBuilder = new StringBuilder();
String str;
int length;
while((str = reader.readLine())!=null){
if((length = str.indexOf("end"))!=-1){
stringBuilder.append(str.substring(0,length));
break;
}
stringBuilder.append(str);
}
System.out.println("From EngineerJspClient"+" "+socket.getInetAddress()+": "+stringBuilder);
// 执行向客户端写操作
Writer writer = new OutputStreamWriter(socket.getOutputStream(),"GBK");
writer.write("From EngineerJspServer");
writer.write("end\n");
writer.flush();
writer.close();
reader.close();
socket.close();
}
}
}
Client Code:
package engineer.jsp.socket5.util;
/**
* 客户端
* @author Engineer-Jsp
* @date 2014.10.14
*/
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
public class EngineerJspClient {
// 异常尽量网外抛,简单了事
public static void main(String[] args) throws Exception{
// 测试服务器IP
String host = "127.0.0.1";
// 端口
int port = 5469;
// 连接服务器
Socket socket = new Socket(host,port);
// 向服务器执行写操作
Writer writer = new OutputStreamWriter(socket.getOutputStream(),"UTF-8");
writer.write("From EngineerJspClient");
writer.write("end\n");
writer.flush();
// 执行读取服务器消息操作
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(),"GBK"));
socket.setSoTimeout(5*1000);
String str;
int length;
StringBuffer stringBuffer = new StringBuffer();
try {
while((str=reader.readLine())!=null){
if((length = str.indexOf("end"))!=-1){
stringBuffer.append(str.substring(0,length));
break;
}
stringBuffer.append(str);
}
} catch (Exception e) {
System.out.println("Connectionec is Timeout......");
}
System.out.println("From EngineerJspServer"+" "+socket.getInetAddress()+": "+stringBuffer);
writer.close();
reader.close();
socket.close();
}
}
源码地址:http://download.csdn.net/detail/jspping/8042081