Linux/Unix服务端和客户端Socket编程入门实例(含源码下载)
这是服务端MyServer.java
package com.jadyer.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Socket网络编程中的1448问题
* @see ---------------------------------------------------------------------------------------------------------
* @see java.io.InputStream.available()
* @see 该方法返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数
* @see 它没有保证返回的是绝对的字节数,并且有的InputStream实现返回的就不是流中的字节总数,所以不能用它来new byte[count]
* @see 另外要注意的是,用这个方法从本地文件读取数据时,一般不会遇到问题,但在用于网络操作时,就很有可能会发生非预期的结果
* @see 比如说对方发来了1000个字节,但available()却只得到了600,或者200个,甚至0个
* @see 这是因为网络通讯是间断性的,一串字节往往会分几批进行发送,所以available()得到的只是估计字节数
* @see ---------------------------------------------------------------------------------------------------------
* @see java.io.InputStream.read(byte[] b, int off, int len)
* @see 它并不是一定会读取len个字节,它只保证最少读一个字节,最多读len个字节,可参考下方第81行代码的写法读取指定个数的字节
* @see ---------------------------------------------------------------------------------------------------------
* @see 1448问题
* @see 用Socket开发"网络应用"时,有时会遇到这种情况,byte[]中收到的字节中前1448个是有值的,从1449开始竟然都是0x00
* @see 这是因为InputStream().read()每次最多只能获得1448个字节的数据,所以就要考虑多次接收数据(详见下方第90行代码)
* @see ---------------------------------------------------------------------------------------------------------
* @create Jul 27, 2013 12:16:35 PM
* @author 玄玉<>
*/
public class MyServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
//ServerSocket的默认参数空的构造方法用途是,允许服务器在绑定到特定端口之前,先设置一些ServerSocket选项
//否则一旦服务器与特定端口绑定,有些选项就不能在改变了,比如SO_REUSEADDR选项
try {
serverSocket = new ServerSocket();
//设定允许端口重用,无论Socket还是ServetSocket都要在绑定端口之前设定此属性,否则端口绑定后再设置此属性是徒劳的
serverSocket.setReuseAddress(true);
//服务器绑定端口
serverSocket.bind(new InetSocketAddress(8060));
//从连接请求队列中(backlog)取出一个客户的连接请求,然后创建与客户连接的Socket对象,并将它返回
//如果队列中没有连接请求,accept()就会一直等待,直到接收到了连接请求才返回
//Socket socket = serverSocket.accept();
} catch (Exception e) {
System.out.println("服务器启动失败,堆栈轨迹如下");
e.printStackTrace();
//isBound()---判断是否已与一个端口绑定,只要ServerSocket已经与一个端口绑定,即使它已被关闭,isBound()也会返回true
//isClosed()--判断ServerSocket是否关闭,只有执行了close()方法,isClosed()才返回true
//isClosed()--否则即使ServerSocket还没有和特定端口绑定,isClosed()也会返回false
//下面的判断就是要确定一个有引用的,已经与特定端口绑定,并且还没有被关闭的ServerSocket
if(null!=serverSocket && serverSocket.isBound() && !serverSocket.isClosed()){
try {
//serverSocket.close()可以使服务器释放占用的端口,并断开与所有客户机的连接
//当服务器程序运行结束时,即使没有执行serverSocket.close()方法,操作系统也会释放此服务器占用的端口
//因此服务器程序并不一定要在结束前执行serverSocket.close()方法
//但某些情景下若希望及时释放服务器端口,以便其它程序能够占用该端口,则可显式调用serverSocket.close()
serverSocket.close();
System.out.println("服务器已关闭");
} catch (IOException ioe) {
//ignore the exception
}
}
}
System.out.println("服务器启动成功,开始监听" + serverSocket.getLocalSocketAddress());
//服务器开始监听
run(serverSocket);
}
private static void run(ServerSocket serverSocket){
while(true){
Socket socket = null;
try {
socket = serverSocket.accept();
System.out.println("New connection accepted " + socket.getRemoteSocketAddress());
InputStream in = socket.getInputStream();
int dataLen = 0; //报文前六个字节所标识的完整报文长度
int readLen = 0; //已成功读取的字节数
int needDataLen = 0; //剩余需要读取的报文长度,即报文正文部分的长度
byte[] buffer = new byte[6]; //假设报文协议为:前6个字节表示报文长度(不足6位左补0),第7个字节开始为报文正文
while(readLen < 6){
readLen += in.read(buffer, readLen, 6-readLen);
}
dataLen = Integer.parseInt(new String(buffer));
System.out.println("dataLen=" + dataLen);
byte[] datas = new byte[dataLen];
System.arraycopy(buffer, 0, datas, 0, 6);
needDataLen = dataLen - 6;
while(needDataLen > 0){
readLen = in.read(datas, dataLen-needDataLen, needDataLen);
System.out.println("needDataLen=" + needDataLen + " readLen=" + readLen);
needDataLen = needDataLen - readLen;
}
System.out.println("Receive request " + new String(datas, "UTF-8"));
OutputStream out = socket.getOutputStream();
out.write("The server status is opening".getBytes("UTF-8"));
//The flush method of OutputStream does nothing
//out.flush();
} catch (IOException e) {
//当服务端正在进行发送数据的操作时,如果客户端断开了连接,那么服务器会抛出一个IOException的子类SocketException异常
//java.net.SocketException: Connection reset by peer
//这只是服务器与单个客户端通信时遇到了异常,这种异常应该被捕获,使得服务器能够继续与其它客户端通信
e.printStackTrace();
} finally {
if(null != socket){
try {
//与一个客户通信结束后要关闭Socket,此时socket的输出流和输入流也都会被关闭
//若先后调用shutdownInput()和shutdownOutput()方法,也仅关闭了输入流和输出流,并不等价于调用close()
//通信结束后,仍然要调用Socket.close()方法,因为只有该方法才会释放Socket所占用的资源,如占用的本地端口等
socket.close();
} catch (IOException e) {
//ignore the exception
}
}
}
}
}
}