【网络编程socket】BIO & Socket和ServerSocket的简单例子
文章目录
概述
ServerSocket是基于BIO的
1、构造ServerSocket
ServerSocket的构造方法有以下几种重载形式:
ServerSocket()throws IOException
ServerSocket(int port) throws IOException
ServerSocket(int port, int backlog) throws IOException
ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException
在以上构造方法中,参数port指定服务器要绑定的端口(服务器要监听的端口),参数backlog指定客户连接请求队列的长度,参数bindAddr指定服务器要绑定的IP地址。
1.1 、绑定端口
除不带参数的构造方法以外,其他构造方法都会使服务器与特定端口绑定,该端口由参数port指定。
如果端口被其他服务进程占用,或是,在某些系统中,若没有以超级用户身份运行服务器程序,操作系统不允许服务器绑定到1-1023的端口时,会抛出BindException。
1.2、设定客户连接请求队列的长度
当服务器进程运行时,可能会同时监听到多个客户的连接请求。管理客户端连接请求的任务是由操作系统来完成的。操作系统将连接请求存储在一个先进先出队列中。许多操作系统限定了队列的最大长度,一般为50
。当队列中的连接请求达到了队列的最大容量时,服务器进程所在的主机会拒绝新的连接请求。只有当服务器进程通过ServerSocket的accept()方法从队列中取出连接请求,使队列腾出空位时,队列才能继续加入新的连接请求。
对于客户进程,如果它发出的连接请求被加入到服务器的队列中,就意味着客户与服务器的连接建立成功,客户进程从Socket构造方法中正常返回。如果客户进程发出的连接请求被服务器拒绝,Socket构造方法就会抛出ConnectionException。
ServerSocket构造方法的backlog参数用来显式设置连接请求队列的长度,它将覆盖操作系统限定的队列的最大长度。
在以下几种情况,仍然采用操作系统限定的队列最大长度:
-
backlog参数的值大于操作系统限定的队列的最大长度;
-
backlog参数的值小于或等于0;
-
在ServerSocket构造方法中没有设置backlog参数。
1.3、设定绑定的IP地址
若主机只有一个地址,则服务器默认绑定该地址;若主机有多个地址,则可以调用ServerSocket(int port, int backlog, InetAddress bindAddr)构造方法设置主机ip地址。
1.4、默认构造方法的作用
ServerSocket有一个不带参数的默认构造方法。通过该方法创建的ServerSocket不与任何端口绑定,接下来还需要通过bind()方法与特定端口绑定。
这个默认构造方法的用途是,允许服务器在绑定到特定端口之前,先设置ServerSocket的一些选项。因为一旦服务器与特定端口绑定,有些选项就不能再改变了。
2、接收和关闭与客户的连接
ServerSocket的accept()方法从连接请求队列中取出一个客户的连接请求,然后创建与客户连接的Socket对象,并将它返回。如果队列中没有连接请求,accept()方法就会一直等待,直到接收到了连接请求才返回。
服务器从Socket对象中获得输入流和输出流
,就能与客户交换数据。当服务器正在进行发送数据的操作时,如果客户端断开了连接,那么服务器端会抛出一个IOException的子类SocketException异常:java.net.SocketException: Connection reset by peer。
3、关闭ServerSocket
ServerSocket的close()方法使服务器释放占用的端口,并且断开与所有客户的连接。当一个服务器程序运行结束时,即使没有执行ServerSocket的close()方法,操作系统也会释放这个服务器占用的端口。因此,服务器程序并不一定要在结束之前执行ServerSocket的close()方法。
在某些情况下,如果希望及时释放服务器的端口,以便让其他程序能占用该端口,则可以显式调用ServerSocket的close()方法。
ServerSocket的isClosed()方法判断ServerSocket是否关闭,只有执行了ServerSocket的close()方法,isClosed()方法才返回true;否则,即使ServerSocket还没有和特定端口绑定,isClosed()方法也会返回false。
ServerSocket的isBound()方法判断ServerSocket是否已经与一个端口绑定,只要ServerSocket已经与一个端口绑定,即使它已经被关闭,isBound()方法也会返回true。
4、获取ServerSocket的信息
- public InetAddress getInetAddress():获取服务器绑定的ip地址;
- public int getLocalPort():获取服务器绑定的端口;
在构造ServerSocket时,如果把端口设为0,那么将由操作系统为服务器分配一个端口(称为匿名端口),程序只要调用getLocalPort()方法就能获知这个端口号。多数服务器会监听固定的端口,这样才便于客户程序访问服务器。匿名端口一般适用于服务器与客户之间的临时通信,通信结束,就断开连接,并且ServerSocket占用的临时端口也被释放。
5、ServerSocket选项
ServerSocket有以下3个选项。
- SO_TIMEOUT:表示等待客户连接的超时时间。
- SO_REUSEADDR:表示是否允许重用服务器所绑定的地址。
- SO_RCVBUF:表示接收数据的缓冲区的大小。
5.1、SO_TIMEOUT选项
- 设置该选项:public void setSoTimeout(int timeout) throws SocketException
- 读取该选项:public int getSoTimeout () throws IOException
SO_TIMEOUT表示ServerSocket的accept()方法等待客户连接的超时时间,以毫秒为单位。 如果SO_TIMEOUT的值为0,表示永远不会超时,这是SO_TIMEOUT的默认值。
当服务器执行ServerSocket的accept()方法时,如果连接请求队列为空,服务器就会一直等待,直到接收到了客户连接才从accept()方法返回。如果设定了超时时间,那么当服务器等待的时间超过了超时时间,就会抛出SocketTimeoutException,它是InterruptedException的子类。
5.2、SO_REUSEADDR选项
- 设置该选项:public void setResuseAddress(boolean on) throws SocketException
读取该选项:public boolean getResuseAddress() throws SocketException
这个选项与Socket的SO_REUSEADDR选项相同,用于决定如果网络上仍然有数据向旧的ServerSocket传输数据,是否允许新的ServerSocket绑定到与旧的ServerSocket同样的端口上。SO_REUSEADDR选项的默认值与操作系统有关,在某些操作系统中,允许重用端口,而在某些操作系统中不允许重用端口。
当ServerSocket关闭时,如果网络上还有发送到这个ServerSocket的数据,这个ServerSocket不会立刻释放本地端口,而是会等待一段时间,确保接收到了网络上发送过来的延迟数据,然后再释放端口
许多服务器程序都使用固定的端口。当服务器程序关闭后,有可能它的端口还会被占用一段时间,如果此时立刻在同一个主机上重启服务器程序,由于端口已经被占用,使得服务器程序无法绑定到该端口,服务器启动失败,并抛出BindException。
为了确保一个进程关闭了ServerSocket后,即使操作系统还没释放端口,同一个主机上的其他进程还可以立刻重用该端口,可以调用ServerSocket.setResuseAddress(true)方法
5.3、SO_RCVBUF选项
- 设置该选项:public void setReceiveBufferSize(int size) throws SocketException
- 读取该选项:public int getReceiveBufferSize() throws SocketException
SO_RCVBUF表示服务器端的用于接收数据的缓冲区的大小,以字节为单位。一般说来,传输大的连续的数据块(基于HTTP或FTP协议的数据传输)可以使用较大的缓冲区,这可以减少传输数据的次数,从而提高传输数据的效率。而对于交互式的通信(Telnet和网络游戏),则应该采用小的缓冲区,确保能及时把小批量的数据发送给对方。
5.4、设定连接时间、延迟和带宽的相对重要性
public void setPerformancePreferences(int connectionTime,int latency,int bandwidth)
该方法的作用与Socket的setPerformancePreferences()方法的作用相同,用于设定连接时间、延迟和带宽的相对重要性。
6、创建多线程服务器
许多实际应用要求服务器具有同时为多个客户提供服务的能力。HTTP服务器就是最明显的例子。任何时刻,HTTP服务器都可能接收到大量的客户请求,每个客户都希望能快速得到HTTP服务器的响应。如果长时间让客户等待,会使网站失去信誉,从而降低访问量。
可以用并发性能来衡量一个服务器同时响应多个客户的能力。一个具有好的并发性能的服务器,必须符合两个条件:
- 能同时接收并处理多个客户连接;
- 对于每个客户,都会迅速给予响应。
用多个线程来同时为多个客户提供服务,这是提高服务器的并发性能的最常用的手段。
以下将按照3中方式来实现EchoServer,它们都使用多线程。
- 为每个客户分配一个工作线程。
- 使用线程池,由其中的工作线程来为客户服务。
可以利用JDK的Java类库中现成的线程池,由它的工作线程来为客户服务。
6.1、 为每个客户分配一个线程
服务器的主线程负责接收客户的连接,每次接收到一个客户连接,就会创建一个工作线程,由它负责与客户的通信。
public static void start(){
try{
ServerSocket serverSocket = new ServerSocket(PORT);
System.out.println("server listen on port:" + PORT);
while (true){
try {
Socket client = serverSocket.accept();
System.out.println("receive client connect, localPort=" + client.getPort());
//每次都new一个新的线程
new Thread(new EchoServer.HandlerServer(client)).start();
}catch (Exception e){
System.out.println("client exception,e=" + e.getMessage());
}
}
}catch(Exception e){
System.out.println("server exception,e=" + e.getMessage());
}
}
6.2、使用JDK类库提供的线程池
以此,提供一个完整的例子:
在这里插入代码片
上一篇: win10 中使用vs code 编译 latex
下一篇: 题解 CF1391D 【505】
推荐阅读
-
【网络编程socket】BIO & Socket和ServerSocket的简单例子
-
【网络编程01】socket的基础知识-简单网络通信程序
-
linux网络编程之用socket实现简单客户端和服务端的通信(基于TCP)
-
Linux网络编程(一):一个简单的socket程序
-
Erlang中的socket编程简单例子
-
Windows下C语言的Socket编程例子(TCP和UDP)
-
Windows下C语言的Socket编程例子(TCP和UDP)
-
简单socket编程服务端和客户端流程以及TCP类的封装
-
网络编程(InetAddress类、Socket和ServerSocket、实现客户端和服务器之间的双向通信)
-
linux网络编程之用socket实现简单客户端和服务端的通信(基于UDP)