一、API介绍
ServerSocket
- 构造方法
方法签名 | 方法说明 |
---|---|
ServerSocket(int port) | 创建⼀个服务端流套接字Socket,并绑定到指定端⼝ |
关于此构造方法的注意事项:
Server的构造方法不止这一个,但是表格中的是最常用的。 ServerSocket(int
port) 不用指定服务器的IP地址,它会自动监听所有网络接口,在运行之前它的IP地址用通配符表示,服务器实际运行的IP地址取决于服务器的运行环境。 如果是在本地电脑,那么IP地址就是“127.0.0.1”,如果使用的是公网,那么IP地址就是公网IP,因此程序用就不用自己手动设置服务器的IP地址了
- 类方法
方法签名 | 方法说明 |
---|---|
Socket accept | 开始监听指定端口(ServerSocket创建绑定的端口),有客户端连接后,返回一个服务端的Socket对象,并基于该Socket建立与客户但的连接,否则进入阻塞等待 |
void close() | 关闭套接字 |
注意:
只要使用套接字就会消耗文件资源,也就是会占用文件资源描述符。如果使用完套接字,不手动close()关闭,有时出现大量文件资源申请,文件资源描述符被使用完就可能出现文件资源泄露。为了保证代码的健壮性,一定要记得判断是否需要手动关闭文件资源。
Socket
- 构造方法
方法签名 | 方法说明 |
---|---|
Socket(String host, int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接 |
- 类方法
方法签名 | 方法说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
in getPort() | 返回套接字所连接的端口号 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
二、创建简单的回显服务器
回显服务器
(Echo Server)是一种简单的服务器应用程序,它的功能是接收客户端发送的数据,并将这些数据原样返回给客户端。这种服务器通常用于测试和调试网络应用程序。
服务器端
public class Server {
//用于与客户端建立连接
private ServerSocket serverSocket = null;
public Server(int port) throws IOException {
//这里只用设置端口号即可,不用设置IP地址,IP地址根据运行环境而定
//因为是在本地运行,所以运行时IP地址时“127.0.0.1”
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
while (true) {
//与UDP协议不同,TCP协议需要和客户端建立连接
Socket clientSocket = serverSocket.accept();
System.out.printf("服务器和客户端建立连接 [%s:%d]\n",
clientSocket.getInetAddress(),
clientSocket.getPort());
//使用线程池,确保一个服务器可以同时处理多个客户端发来的请求
Thread thread = new Thread(() -> {
try {
//正式进行数据传输
processConnection(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
});
//记得启动线程!
thread.start();
}
}
private void processConnection(Socket clientSocket) throws IOException {
//TC面向字节流,进行传输,所以使用OutputStream/InputStream
try(OutputStream outputStream=clientSocket.getOutputStream();
InputStream inputStream=clientSocket.getInputStream()) {
//用于获取客户端的请求
Scanner scanner=new Scanner(inputStream);
//用于发送响应给客户端
PrintWriter printWriter=new PrintWriter(outputStream);
//循环处理请求与响应
while(true){
//1、获取客户端请求
if(!scanner.hasNext()){
//如果没有得到客户端的请求,说明客户端下线了
break;
}
//得到的请求放到request中
String request=scanner.next();
//2、根据请求,生成响应
String response=process(request);
//3、把响应发送给客户端
printWriter.println(response);
printWriter.flush();//记得刷新缓冲区!!!详细解释见代码块下方注意事项
//4、打印工作日志
System.out.printf("[%s:%d] 请求=%s 响应=%s\n",
clientSocket.getInetAddress(),
clientSocket.getPort(),
request,
response);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//打印客户端下线信息
System.out.printf("客户端下线 [%s:%d]\n",
clientSocket.getInetAddress(),
clientSocket.getPort());
//只要是套接字,就会消耗资源占位符,如果用完没有及时关闭,可能出现文件资源泄露。
if(clientSocket!=null&&!clientSocket.isClosed()){
clientSocket.close();
}
}
}
//因为是回显服务器,所以这里直接返回相同的内容,在实际开发中这一步可能是比较复杂的
private String process(String request){return request;}
public static void main(String[] args) throws IOException {
//自己指定一个端口号最好是大于1023的(当然也要小于65535)
Server server=new Server(9090);
server.start();
}
}
注意事项:
PrintWriter的发送数据设置了缓冲区。 设置一个缓冲区的原因是避免频繁进行数据IO,因为这样消耗大量资源。通过缓冲区,数据堆积到一定大小在进行IO那么消耗成本就会小很多。但是在我们这个回显服务器中,需要立刻看到响应,所以需要用到PrintWriter中的flush()方法,调用这个方法可以刷新缓冲区,把所有的数据都进行IO。
另外, 除了用PrintWriter进行数据IO ,还可以使用outputStream
的write()
方法,但是需要把数据转化成字节数组:
客户端
public class Client {
//用于接收服务器的响应,或者发送请求给服务器
private Socket socket=null;
//两个参数分别是,需要连接的服务器的IP地址以及端口号,端口号刚才设置了是9090,由于是在本地运行所以ip默认是“127.0.0.1”
public Client(String serverIP,int port) throws IOException {
//和指定的服务器建立连接
socket=new Socket(serverIP,port);
}
public void start() throws IOException {
System.out.println("客户端启动!");
//与UDP不同,TCP面向字节流进行传输
try(OutputStream outputStream=socket.getOutputStream();
InputStream inputStream=socket.getInputStream()){
//用户通过控制台输入,提供请求
Scanner userIN=new Scanner(System.in);
//用于发送请求
PrintWriter printWriter=new PrintWriter(outputStream);
//用于接收响应
Scanner recieve=new Scanner(inputStream);
//循环运行
while(true){
//1、获取用户请求
System.out.print("->");
String request=userIN.next();
//2、把请求发送给客户端
printWriter.println(request);
printWriter.flush();//注意记得刷新缓冲区!
//3、接收服务器发来的响应
if(!recieve.hasNext()){
//没有获取到服务器的响应,可能是网络出现问题,或者服务器挂了
break;
}
//正确获取到响应
String response=recieve.next();
//4、把响应呈现给用户(打印)
System.out.printf("响应:%s\n",response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
Client client=new Client("127.0.0.1",9090);
client.start();
}
}
最后,如果有任何关于代码或概念上的问题,欢迎私信或者评论交流。关于TCP协议的相关机制,可以参阅: 网络编程专栏 大家的支持是我前行的巨大动力!