笔者今天写socket通讯时发现read方法会出现阻塞,查找资料后终于找到了原因,在此记录下载,希望能帮到其他的朋友,也避免自己以后再犯这个错误
socket通讯有用字节流的,有用字符流的,字符流是对字节流的包装,
字节流:
服务端
package bybyte;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
//此方法在没有客户端连接时会一直处于阻塞状态
Socket socket = serverSocket.accept();
InputStream is =socket.getInputStream();
OutputStream os = socket.getOutputStream();
//读数据
byte[]
readbuf = new byte[1024];
StringBuilder stringBuilde
r = new StringBuilder();
int len = is.read(readbuf);
while(len!=-1)
{
stringBuilder.append(new String(readbuf,0,len));
len = is.read();
}
System.out.println("message from client:"+stringBuilder.toString());
//写数据
os.write("Hello,client".getBytes());
os.flush();
//shutdownOutput会关闭输出流,但是连接还是建立着的,相当于提示客户端服务器输出完毕了。
//这样客户端才能在read方法里读出-1来,退出循环
socket.shutdownOutput();
os.close();
is.close();
socket.close();
serverSocket.close();
}
}
客户端:
package bybyte;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class SocketClient {
public static void main(String[] args) throws UnknownHostException, IOException {
//以这种方式创建的socket会自动去连本地的9999端口
Socket socket = new Socket("127.0.0.1",9999);
//写数据
OutputStream os =socket.getOutputStream();
os.write("hello,server,I'm client".getBytes());
os.flush();
//shutdownOutput会关闭输出流,但是连接还是建立着的,相当于提示服务器我客户端输出完毕了。
//这样服务器才能在read方法里读出-1来,退出循环,不加shutdown服务器会在read方法处阻塞
socket.shutdownOutput();
//读数据
InputStream is = socket.getInputStream();
byte[] readbuf = new byte[1024];
StringBuilder sb = new StringBuilder();
int len = is.read(readbuf);
while(len!=-1)
{
sb.append(new String(readbuf,0,len));
len = is.read(readbuf);
}
System.out.println("message from server:"+sb.toString());
}
}
注:客户端socket通过输出流写数据时,数据会传输到服务端,服务段通过read方法会读取到数据,第一次读取完后,因为没有到流的末尾,所以执行while循环,此时却无数据可读,read就会处于阻塞状态。利用shutdownoutput方法使得服务端在读完数据后识别出流的末尾,read方法返回-1,自然就退出循环了。
字符流:
服务端:
package bystring;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
public static void main(String[] args) throws IOException {
ServerSocket serversocket = new ServerSocket(9999);
Socket accept = serversocket.accept();
//把字节流封装成字符流
BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));
PrintWriter pw = new PrintWriter(accept.getOutputStream());
//读数据
//此方法会读取一行,一行的判断标准是是否读取到了"\r"或"\n"
String message = br.readLine();
System.out.println("message from client:"+message);
pw.write("hello,client,I'm server \n");
pw.flush();
pw.close();
br.close();
serversocket.close();
accept.close();
}
}
客户端:
package bystring;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
/**客户端
*/
public class SocketClient {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket socket = new Socket("127.0.0.1",9999);
//封装为字符流
PrintWriter pw = new PrintWriter(socket.getOutputStream());
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//写数据
//在写数据是一定要加上"\n"或"\r",不然服务器读数据readline因为没有独好换行符会一直处于阻塞状态
pw.write("hello,server,I'm client \n");
pw.flush();
//读数据
String message = br.readLine();
System.out.println("message from server :"+message);
br.close();
pw.close();
socket.close();
}
}
注:在实际开发中可考虑把服务端放置在一个线程里,死循环里调用accept方法,因为accept方法是阻塞的,只有在客户端进行连接时才执行后续代码,才需要cpu资源。在阻塞时是不会消耗cpu资源的。
在实际生产中进行数据传输时,应该使用这种模式:规定一定长度的头(比如10个字节长度),头里的数据保存着实际要传输数据的字节长度,则读数据时,只需先读取出头里的数据变换为整数length,在读取length个字节长度的数据即为要传输的实际数据
当一个客户端需要和多个服务器中的一个建立链接时
//服务器的ip和port组成的map
public void SocketClient(Map<String,String> addressMap) {
int sum=0;
for(Map.Entry<String, String> entry:addressMap.entrySet())
{
try {
Socket socket = new Socket(entry.getKey(), Integer.parseInt(entry.getValue()));
//只要与其中一个建链就不再连接其他服务器了了
break;
} catch (Exception e) {
System.out.println("与服务器("+entry.getKey()+")端口("+entry.getValue()+")建链失败");
}
}
if(sum==addressMap.size())
{
System.out.println("与所有的服务器建立连接失败");
}
笔者学识有限,若有不当或错误的地方,希望大家能指正,我会尽快进行修改