文章目录
- 1. 服务器Socket简介
- 2. 使用ServerSocket
- 3. 用Socket写入服务器
- 4. 关闭服务器Socket
1. 服务器Socket简介
博客Socket(一)~Socket(二)从客户端的角度讨论了Socket,客户端就是向监听连接的服务器打开一个Socket程序。不过只有客户端Socket还不够,如果不能和服务器通信,客户端没有什么用处,所以服务器也要创建Socket,编写服务器时,无法预先了解哪个主机会联系你,即使确实知道,你也不清楚哪个主机希望和你连接。换句话说,服务器就像坐在电话旁等电话一样,不知道谁会打来电话,或者什么时间打电话,所以只用Socket类是不能做到这一点的。对于接受连接的服务器,Java提供了一个ServerSocekt类表示服务器Socket。服务器Socket在服务器上运行 ,监听入站TCP连接。每个服务器Socket监听服务器上的一个特定端口,当远程主机上的一个客户端尝试连接这个端口时,服务器就会被唤醒,协商建立客户端和服务器之间的连接,并返回一个常规的Socket对象,表示两个主机之间的Socket。换句话说,服务器Socket等待连接,而客户端Socket发起连接,一旦ServerSocket建立了连接,服务器会使用一个常规的Socket对象向客户端发送数据,数据总是通过常规的Socket传送。
2. 使用ServerSocket
ServerSocket类包含了使用Java编写服务器所需的全部内容,其中包括创建新ServerScoket对象的构造函数、在指定端口监听连接的方法、配置各个服务器Socket选项的方法,以及其他一些常见的方法,如tostring()。在java中程序的生命周期如下:
- 使用一个ServerSocket()构造函数在一个特定的端口创建一个新的ServerSocket
- ServerSocket使用其accept()方法监听这个端口的入站连接。accept()会一直阻塞,直到一个客户端尝试建立连接,此时accept()返回一个连接客户端和服务器的Socket对象
- 根据服务器的类型,会调用Socket的getInputStream()方法或getOutputStream()方法,或者这两个方法都使用,以获取与客户端通信的输入和输出流
- 服务器和客户端根据已协商的协议交互,直到要关闭连接
- 服务器或客户端(或二者)关闭连接
- 服务器返回步骤2,等待下一次连接
下面代码是daytime服务器的一个简单本地实现,首先是服务端代码
class myserver extends Thread{
@Override
public void run(){
try {
System.out.println("服务器端开始运行:"+System.currentTimeMillis());
ServerSocket serverSocket=new ServerSocket(8080);
Socket connection=serverSocket.accept();
OutputStream outputStream=connection.getOutputStream();
Writer writer=new OutputStreamWriter(outputStream);
Date now=new Date();
writer.write(now.toString()+" 你龙哥服务器 "+"\r\n");
writer.flush();
connection.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
我们首先让服务端运行,然后用telnet测试一下
然后是客户端代码
class User extends Thread{
@Override
public void run(){
try {
System.out.println("客户端开始运行:"+System.currentTimeMillis());
Socket socket=new Socket();
SocketAddress socketAddress=new InetSocketAddress("127.0.0.1",8080);
socket.connect(socketAddress);
InputStream inputStream=socket.getInputStream();
InputStreamReader reader=new InputStreamReader(inputStream);
int c;
StringBuilder mystring=new StringBuilder();
while((c=reader.read())!=-1){
mystring.append((char)c);
}
System.out.println(mystring);
inputStream.close();
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
最后是main函数代码
public static void main(String[] args) throws InterruptedException {
myserver server=new myserver();
server.start();
Thread.sleep(3000);
User user=new User();
user.start();
}
查看运行结果
3. 用Socket写入服务器
在目前为止所有的例子中,服务器只写入客户端Socket,而没有从客户端读取,不过,大多数协议要求服务器同时读写客户端。这并不难,与从前一样,仍然要接受一个连接,不过这一次要同时获取一个InputStream和一个OutputStreamn。使用InputStream读取客户端,另外使用OutputStream写入客户端,关键是理解协议:明确何时写入和何时读取。RFC 862定义的echo协议是最简单的交互式TCP服务器,客户端打开指向echo服务器端口7的Socket,并发送数据,服务器将数据发回。这个过程一直继续,知道客户端关闭连接为止。 与Telnet的从控制体接受输入,把它发送给服务器,然后等待服务器读取返回一行输出不同,echo它会回显收到的每一个字节,它并不关心这些字节是否表示采用某种编码的字符,或者是划分为多行,与很多协议不同,echo没有指定锁步行为,即客户端发送一个请求,然后在发送更多数据之前会等待完整的服务器响应。echo协议需要客户端自己关闭连接。
下面是echo服务器的简单实现
public static void main(String[] args) throws InterruptedException {
int port;
try{
port=8080;
}catch (RuntimeException e){
port=7;
}
System.out.println("Listening for connections on port "+port);
ServerSocketChannel serverSocketChannel;
Selector selector;
try{
serverSocketChannel =ServerSocketChannel.open();
ServerSocket ss=serverSocketChannel.socket();
InetSocketAddress address=new InetSocketAddress(port);
ss.bind(address);
serverSocketChannel.configureBlocking(false);
selector=Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}catch (IOException e){
e.printStackTrace();
return;
}
while (true){
try{
selector.select();
}catch (IOException e){
e.printStackTrace();
break;
}
}
Set<SelectionKey> readyKeys=selector.selectedKeys();
Iterator<SelectionKey> iterator=readyKeys.iterator();
while(iterator.hasNext()){
SelectionKey key=iterator.next();
iterator.remove();
try{
if(key.isAcceptable()){
ServerSocketChannel server=(ServerSocketChannel) key.channel();
SocketChannel client=server.accept();
System.out.println("Accepted connection from:"+client);
client.configureBlocking(false);
SelectionKey clientkey=client.register(selector,SelectionKey.OP_WRITE|SelectionKey.OP_READ);
ByteBuffer buffer=ByteBuffer.allocate(100);
clientkey.attach(buffer);
}
if(key.isAcceptable()){
SocketChannel client=(SocketChannel) key.channel();
ByteBuffer output=(ByteBuffer) key.attachment();
client.read(output);
}
if(key.isWritable()){
SocketChannel client=(SocketChannel) key.channel();
ByteBuffer output=(ByteBuffer) key.attachment();
output.flip();
client.write(output);
output.compact();
}
} catch (ClosedChannelException e) {
throw new RuntimeException(e);
} catch (IOException e) {
key.cancel();
try {
key.channel().close();
}catch (IOException ex){}
}
}
}
当你运行这段代码时,它将创建一个简单的服务器端,用于监听指定的端口(默认为8080)上的连接。服务器使用非阻塞模式进行处理。
- 首先,它尝试将端口设置为8080,如果出现RuntimeException异常,则将端口设置为7。
- 然后,它打印一条消息,表示服务器正在监听指定端口上的连接。
- 接下来,它创建一个ServerSocketChannel对象和一个Selector对象。它打开ServerSocketChannel并获取关联的ServerSocket对象。然后,它将ServerSocket绑定到指定的端口,并将ServerSocketChannel设置为非阻塞模式。最后,它将ServerSocketChannel注册到Selector上,关注连接请求事件。
- 然后,代码进入一个无限循环,不断调用
selector.select()
方法来检查是否有就绪的事件发生。如果发生了IOException异常,循环会中断。- 在循环中,它获取就绪的SelectionKey集合,并遍历每个SelectionKey。根据SelectionKey的不同状态,它执行相应的操作:
- 如果SelectionKey是可接受的(isAcceptable),表示有新的连接请求,它会接受连接,打印连接信息,并将新连接的SocketChannel注册到Selector上,关注读写事件。
- 如果SelectionKey是可读的(isReadable),表示有数据可读取,它会读取数据到ByteBuffer中。
- 如果SelectionKey是可写的(isWritable),表示可以写入数据,它会从ByteBuffer中取出数据并写入到SocketChannel中。
- 在处理每个SelectionKey时,如果发生了ClosedChannelException异常,它会抛出RuntimeException异常。如果发生了IOException异常,它会取消该SelectionKey并关闭相应的通道。
- 这个循环将一直执行,直到程序被终止。这样,服务器就可以接受并处理来自客户端的连接和数据。
注意:上面的代码涉及JavaNIO的知识
4. 关闭服务器Socket
如果使用完一个服务器Socket,就应当把它关闭,这个关闭Socket是不一样的,关闭ServerSocket会释放本地主机的一个端口,运行另一个服务器绑定到这个端口。他还会中断该ServerSocket已经接受的目前处于打开状态的所有Socket。程序结束时,服务器Socket会自动关闭,但手动关闭服务也是没有什么坏处,如下:
ServerSocket server=null;
try{
server=new ServerSocket(port);
}finally{
if(server!=null)
{
try{
server.close();
}catch(IOException ex){
}
}
}
可以使用无参数的ServerSocket()构造函数稍加改进,无参数构造函数不抛出任何异常,也不绑定到一个端口,你需要之后调用bind()方法来绑定一个Socket地址
ServerSocket server=new ServerSocket();
try{
SocketAddress address=new InetSocketAddress(port);
server.bind(address);
}finally{
if(server!=null)
{
try{
server.close();
}catch(IOException ex){
}
}
}