目录
TCP流套接字编程
一.API介绍
ServerSocket类
构造方法:
编辑方法:
Socket类
构造方法:
方法:
二、TCP连接
三、通过TCP实现回显服务器
TCP服务端:
1.创建Socket对象
2.构造方法
3.start方法
TCP客户端:
1.创建Socket对象
2.构造方法:
3.start方法
服务端参考代码:
客户端参考代码:
参考结果:
TCP流套接字编程
一.API介绍
有两个关键的类:
ServerSocket:是创建TCP服务端Socket的API。给服务器使用的类,使用这个类,绑定端口号.
(马路牙子上的拉客销售)
Socket:既会给客户端使用,又会给服务器使用。是客户端的Socket,又是服务器接收到客户端accept请求后,返回的服务端Socket.
(售楼处的销售顾问)
ServerSocket类
构造方法:
方法:
Socket类
既会给客户端使用,又会给服务器使用
作用:双方建立连接后,保存对端信息,用来与对方收发数据。
构造方法:
方法:
二、TCP连接
对于UDP来说,是无连接的,每次发送数据时,都需要手动在send方法中指定目标地址;
但是TCP是有连接的,需要提前把连接功能建立起来,
连接如何建立,不需要代码干预,是系统内核自动负责完成的。
我们要做的就是:客户端:发起“建立连接”的动作;
服务器:把建立好的连接从内核中拿到应用程序里。
三、通过TCP实现回显服务器
TCP服务端:
1.创建Socket对象
2.构造方法
3.start方法
要循环的读取客户端的连接.因此要在循环中
任务1:接听连接,通过调用accept方法
任务2:建立连接,通过实现processConnection方法,建立连接
修改1:将连接改成多线程模式
当多个客户端同时发出请求时,由于单线程,这能处理一个客户端请求,将连接改成多线程,同时获取多用户的请求;但是还有一个问题,每来一个客户端,就创建一个线程,客户端结束,就销毁这个线程,当多个客户端发起请求时,就要创建和销毁大量的线程,开销就会较大;
可以用线程池的方法来创建多线程,提前将线程创建好,需要时就直接用,不用时就先放着,不需要创建和销毁。
线程池解决了线程频繁创建和销毁的开销,但是当多个线程被创建,而没有被销毁,就会导致当前服务器上积累了的大量的线程,这对于服务器的负担会非常严重。
为了解决这个问题,还可以引入其他方案:
1、引入协程。(轻量级线程)本质上还是线程,用户可以通过手动调度的方法,让一个线程并发的执行多个任务。(解决了线程调度的开销)
2.IO多路复用。(系统内核级别的机制)让一个线程负责处理多个Socket对象,这里需要Socket数据不是同时需要被处理的。
4.processConnection方法
输出客户端IP和端口号,表示客户端上线
通过调用Socket的getInputStream方法获取客户端请求,和getOutputStream返回响应
任务2.1 读取客户端请求
读取结束,则表明客户端下线,显示客户端IP和端口号
2.2,请求并解析 注意:next读取到空白字符才结束
2.3根据请求解析响应
这里的process方法建议写成public,方便之后子类通过继承TcpEchoServer方法,实现特有的功能
2.4把响应返回给客户端, 通过PrintfWriter的println方法返回.
修改2:手动刷新缓冲区
这里的println会有缓冲区,当读取到客户的请求时,不会计算响应,而是等缓冲区满了,才一起处理,因此,每次请求,要手动刷新缓冲区,才能保证客户端的每次请求都能及时响应
TCP客户端:
1.创建Socket对象
2.构造方法:
3.start方法
因为TCP是以字节流的形式发送请求的,要用InputStream来获取用户的输入; 获取到服务端的响应,也要用OutputStream来接收
任务1:获取用户输入请求
2.将请求发送给服务端 通过PrintWriter的println方法发送,这样可以获取到y用户输入的 "\n"
修改1:手动刷新缓冲区
客户端这里的println也有缓冲区的问题,每次输入后,也要手动刷新,将请求发送给服务端
3.从服务端获取响应 通过Scanner读取OutPutStream的输出流来获取服务器返回的响应
4.显示响应
修改2:对Socket手动关闭
目前这个代码会出现文件资源泄露,创建的Socket,没有进行close(),
这里的关闭,只是关闭了输出流和输入流,并没有关闭Socket
因此,为了避免文件资源泄露,当客户端断来连接后,要在客户端的finally代码块中手动关闭
服务端参考代码:
class TcpEchoServer1{
//创建ServerSocket对象
private ServerSocket serverSocket=null;
//提供客户端端口号
public TcpEchoServer1(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务端启动");
ExecutorService pool = Executors.newCachedThreadPool();
while(true){
//1.通过accpet方法 接听连接
Socket clientSocket = serverSocket.accept();
//2.通过实现processConnection方法 建立连接
// processConnection(clientSocket);
pool.submit(new Runnable(){//通过线程池,创建多条线程
@Override
public void run() {
processConnection(clientSocket);
}
});
}
}
//TCP是以字节流的方式发送请求的,要用InputStream来读取发送的请求,
// 用OutputStream来 返回响应
private void processConnection(Socket clientSocket) {
System.out.printf("[%s,%d] 客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());
try(InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream=clientSocket.getOutputStream()) {
//循环读取客户端的请求,并返回响应
while(true){
//1.读取请求
Scanner scan=new Scanner(inputStream);
if(!scan.hasNext()){
//读取完毕,客户端断开连接,产生 读取完毕
System.out.printf("[%s,%d] 客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());
break;
}
//2,请求并解析 注意:next读取到空白字符才结束
String request=scan.next();
//3.根据请求,计算响应
String response=process(request);
//4.把响应返回给客户端
//给outputStream套上一层,可以获取 \n 更方便操作
PrintWriter printWriter=new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();//手动刷新缓冲区
System.out.printf("[%s,%d] req:%s, resp:%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),
request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String process(String request) {
//这里还是什么都不做,就返回请求
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer1 tcpEchoServer1 = new TcpEchoServer1(9090);
tcpEchoServer1.start();
}
}
客户端参考代码:
class TcpEchoClient1{
//1.创建Socket对象
private Socket clientSocket=null;
//传入服务端的IP和端口号
public TcpEchoClient1(String serverIp,int serverPort) throws IOException {
clientSocket=new Socket(serverIp,serverPort);
}
public void start() throws IOException {
System.out.println("客户端启动");
try(InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream=clientSocket.getOutputStream();
Scanner scanNetWork=new Scanner(inputStream);
Scanner scanConsole=new Scanner(System.in);
PrintWriter printWriter=new PrintWriter(outputStream) ) {
while(true){
System.out.print("->");
if(!scanConsole.hasNext()){
break;//客户端请求输入结束
}
//1.获取请求
String request=scanConsole.next();
//2.将请求发送给服务端 通过PrintWriter的println方法发送
printWriter.println(request);
printWriter.flush();
//3.获取响应
String response=scanNetWork.next();
//4.输出响应
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
clientSocket.close();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient1 tcpEchoClient1 = new TcpEchoClient1("127.0.0.1", 9090);
tcpEchoClient1.start();
}
}
参考结果:
可以同时启动多个客户端,与同一个服务端建立连接