这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战
在上一篇博客Java网络编程(四)—— ServerSocket(一) - 掘金 (juejin.cn)介绍了什么是服务端Socket以及如何使用服务端Socket,这篇博客继续介绍ServerSocket其他的知识点。
请求队列长度
当服务器进程运行时,可能会同时监听到多个客户的连接请求。例如,每当一个客户进程执行以下代码:
Socket socket = new Socket("123.345.746.2", 80);
复制代码
就意味着在远程123.345.746.2主机的80端口上,监听到了一个客户的连接请求。管理客户连接请求的任务是由操作系统来完成的,操作系统把这些连接请求存储在一个先进先出的队列中。许多操作系统限定了队列的最大长度,一般为50。当队列中的连接请求达到了队列的最大容量时,服务器进程所在的主机会拒绝新的连接请求。只有当服务器进程通过ServerSocket的 accept()方法从队列中取出连接请求,使队列腾出空位时,队列才能继续加入新的连接请求。
对于客户进程,如果它发出的连接请求被加入到服务器的队列中,就意味着客户与服务器的连接建立成功,客户进程从Socket构造方法中正常返回。如果客户进程发出的连接请求被服务器拒绝,Socket构造方法就会抛出 ConnectionException。
ServerSocket构造方法的 backlog 参数用来显式设置连接请求队列的长度,它将覆盖操作系统限定的队列的最大长度。值得注意的是,如果backlog的值设为0或者值大于操作系统限定的队列的最大长度或者没有该参数,backlog 仍然会采用操作系统限定的队列的最大长度。
接受和关闭与客户端的连接
ServerSocket 的 accept()方法从连接请求队列中取出一个客户的连接请求,然后创建与客户连接的Socket对象,之后服务器会从Socket对象中获得输入流和输出流,就能与客户交换数据。但是当服务器正在进行发送数据的操作时,如果客户端断开了连接,那么服务器端会抛出一个IOException的子类SocketException异常,那么服务器就会退出程序,因此我们需要捕获这种异常,使得服务器能够继续与其他客户端进行通信。
可以把与单个客户端通信的代码放入一个try块中,如果遇到异常,那么这个异常会被catch代码块捕获。try代码块应该再跟上一个finally代码块,用来关闭Socket,断开与该客户端的连接。
public void server()
{
while(true)
{
Socket socket = null;
try {
socket = serverSocket.accept();//等待客户连接
System.out.println("已有客户端连接,地址:"+socket.getInetAddress()+" 端口号:"+socket.getPort());
PrintWriter writer = this.getWriter(socket);
BufferedReader reader = this.getReader(socket);
String msg = null;
while ((msg = reader.readLine())!=null)
{
System.out.println(socket.getInetAddress()+" "+socket.getPort()+" 发来的消息:"+msg);
writer.println("server收到了: " + msg);
}
} catch (IOException e) {
//可以选择打印异常,也可以不打印
//e.printStackTrace();
}finally {
try {
if(socket!=null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
复制代码
多线程服务器
一般的Server接收到一个客户连接,就会与该客户进行通信,通信完毕后断开连接,然后再接收下一个客户连接。但是如果同时有多个客户请求连接,这些客户就必须排队等候响应。
许多实际应用要求服务器具有同时为多个客户提供服务的能力。HTTP服务器就是最明显的例子。任何时刻,HTTP服务器都可能接收到大量的客户请求,每个客户都希望能快速得到HTTP服务器的响应。如果长时间让客户等待,会使网站失去信誉,从而降低访问量。因此可以使用多线程的方法使得服务器能够同时接受多个用户请求。
那么需要修改服务器端的代码,当accept()函数不再阻塞时,说明有客户端连入了服务器,那么新建一个线程,并把建立的socket传入线程中进行操作即可,代码如下:
public class ServerDemo {
private int port = 40000;
private ServerSocket serverSocket;
class ThreadServer extends Thread {
Socket socket;
public PrintWriter getWriter(Socket socket) throws IOException {
OutputStream socketoutput = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(socketoutput, true);
return printWriter;
}
public BufferedReader getReader(Socket socket) throws IOException {
InputStream socketinput = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socketinput));
return bufferedReader;
}
public ThreadServer(Socket s) {
this.socket = s;
}
@Override
public void run() {
System.out.println("已有客户端连接,地址:" + socket.getInetAddress() + " 端口号:" + socket.getPort());
try {
PrintWriter writer = this.getWriter(socket);
BufferedReader reader = this.getReader(socket);
String msg = null;
msg = reader.readLine();
while ((msg) != null) {
System.out.println(socket.getInetAddress() + " " + socket.getPort() + " 发来的消息:" + msg);
writer.println("server收到了: " + msg);
msg = reader.readLine();
}
} catch (IOException e) {
} finally {
try {
if (socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public ServerDemo() throws IOException {
serverSocket = new ServerSocket(port);
System.out.println("服务端已启动");
}
public void server()
{
while(true)
{
Socket socket = null;
try {
socket = serverSocket.accept();//等待客户连接
ThreadServer threadServer = new ThreadServer(socket);
threadServer.start();
} catch (IOException e) {
System.out.println("客户端 :"+socket.getInetAddress()+" 断开连接, 服务器继续运行");
}finally {
}
}
}
public static void main(String[] args) throws IOException {
new ServerDemo().server();
}
}
复制代码
客户端的代码不需要任何改动,在这里有一个需要注意的大坑:当设定成多线程模式时,建立的socket关闭代码就不能继续写在主线程的finally块下,否则在新建了一个线程后,主线程继续执行代码,会执行到finally将socket关闭,就会出现问题了!!!这样的话就可以多个客户端同时访问服务器了,效果如下:
关闭服务器Socket
ServerSocket 的 close() 方法使服务器释放占用的端口,并且断开与所有客户的连接。当一个服务器程序运行结束时,即使没有执行 ServerSocket 的 close() 方法,操作系统也会释放这个服务器占用的端口。因此,服务器程序并不一定要在结束之前执行 ServerSocket 的 close() 方法。
在某些情况下,如果希望及时释放服务器的端口,以便让其他程序能占用该端口,则可以显式调用ServerSocket 的 close() 方法。
获取ServerSocket信息
下面两个函数是 ServerSocket 提供的用来获取服务器绑定的IP地址以及绑定的端口号的函数:
public InetAddress getInetAddress()
public int getLocalPort()
复制代码
多数服务器会监听固定的端口,这样才便于客户程序访问服务器。不过匿名端口一般适用于服务器与客户之间的临时通信,通信结束,就断开连接,并且 ServerSocket 占用的临时端口也被释放。
近期评论