目录
1.TCP协议简介:
2.TCP协议在Java中封装的类以及方法
3.字典服务器
3.1服务器代码:
3.2客户端代码:
1.TCP协议简介:
TCP协议是一种有连接,面向字节流,全双工,可靠的网络通信协议.它相对于UDP协议来说有以下几点好处:
1.它是可靠传输,相比于UDP协议,传输的数据更加可靠.当然这里的可靠是相对的,并不是真的万无一失.我们会在后面的博客中给大家详细介绍TCP协议的可靠传输.
2.它传输的字节流文件没有大小限制,不像UDP协议,一次只能传输64kb的报文数据.
但是也有缺点:
1.开销大,需要对连接的建立与维护,以及确认数据报的确认和重传,会增加网络开销.
2 传输效率低;由于可靠性和流量控制,会对传输效率造成一定的影响
基于这些特性:因此TCP协议不适用于实时性要求高的程序,适合数据库访问,网络游览器,文件传输,电子邮件等应用.
2.TCP协议在Java中封装的类以及方法
TCP协议在Java中有两个api,分别是SeverSocket和Socket
顾名思义,SeverSocket是给服务器用的api,它仅限于服务器使用,而Socket服务器和客户端都可以使用.
SeverSocket构造方法:
方法签名 | 方法说明 |
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
SeverSocket的方法:
法签 名 | 方法说明 |
Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket 对象,并基于该Socket建立与客户端的连接,否则阻塞等待 |
void close() | 关闭此套接字 |
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
Socket的构造方法:
方法签名 | 方法说明 |
Socket(String host, int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的 进程建立连接 |
Socket的方法:
方法签名 | 方法说明 |
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
3.字典服务器
下面我们基于这两个api做一个简单的字典服务器.它类似于翻译软件,当我们去输入中文字符的时候,会返回并且打印与之对应的英文字符.
为了更方便大家理解,我们先做一个在之前的文章提过的回显服务器:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpEchoSever {
ServerSocket serverSocket = null;
public TcpEchoSever(int port) throws IOException { //指定服务器的端口号
serverSocket = new ServerSocket(port);
}
public void strat() throws IOException {
System.out.println("服务器启动");
while (true){
Socket socket = serverSocket.accept(); //通过acccept方法获取到我们的客户端信息并保存到socket中
Thread t = new Thread(()->{ //这里使用多线程是为了让不同的客户端都分配一个线程,以免因为一个客户端让它阻塞而别的客户端不能访问
try {
accress(socket);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
t.start();
}
}
public void accress(Socket socket) throws IOException {
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) { //字节流,有点类似于文件操作
System.out.printf("[客户端连接%s:%d]\n",socket.getInetAddress(),socket.getPort());
while (true) {
{ Scanner inputScnner = new Scanner(inputStream);
if (!inputScnner.hasNext()) {
System.out.println("连接中断");
break;
}
String request = inputScnner.next(); //读取数据
String reqonse = func(request); //把数据进行服务器端的计算请求
PrintWriter writer = new PrintWriter(outputStream);//将响应返回给客户端
writer.println(reqonse);
writer.flush();//这里的flush是为了把缓冲池里的数据给刷新
System.out.printf("[%s:%d]\n,request : %s ,reqonse : %s"
,socket.getInetAddress(),socket.getPort(),request,reqonse);//打印日志
}
}
}finally {
socket.close();//防止客户端过多,造成文件资源泄露,让文件描述符表满了
}
}
public String func(String request){
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoSever tcpEchoSever = new TcpEchoSever(9090);
tcpEchoSever.strat();
}
}
我们直接继承这个服务器代码,并且重写func方法,将我们的字典加入进去
3.1服务器代码:
import java.io.IOException;
import java.util.HashMap;
public class TcpDcarySever extends TcpEchoSever{
HashMap<String,String> hashMap = new HashMap<>();
public TcpDcarySever(int port) throws IOException {
super(port);
hashMap.put("小狗","dog");
hashMap.put("小猫","cat");
hashMap.put("小坤","chincken");
}
@Override
public String func(String request) {
return hashMap.getOrDefault(request,"没找到,请重新查找");
} //重写func方法
public static void main(String[] args) throws IOException {
TcpDcarySever tcpDcarySever = new TcpDcarySever(9091);
tcpDcarySever.strat();
}
}
3.2客户端代码:
步骤都在注释里
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
Socket socket = null;
public TcpEchoClient(String SeverIp,int port) throws IOException { //体现了TCP协议的有连接 得在构造方法中把目的服务器的IP地址和端口号指定
socket = new Socket(SeverIp , port);
}
private void start(){
System.out.println("客户端启动");
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){ //一样是面向字节流 ,所以和文件操作类似
Scanner scanner = new Scanner(System.in);
Scanner inpuscnner = new Scanner(inputStream);
PrintWriter writer = new PrintWriter(outputStream);
while (true){
if(!scanner.hasNext()){
break;
}
String requeset = scanner.next();//从控制台读取数据
writer.println(requeset);//发送请求
writer.flush();//刷新缓冲池
String reqonse = inpuscnner.next();//从服务器端拿到请求并构造成字符串
System.out.println(reqonse);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9091);//指定目标的IP地址和端口号
tcpEchoClient.start();
}
}
结果演示:
启动服务器:
启动客户端观察服务器的情况:
输入中文字符看看:
此时客户端是这样的
为了观察多线程的作用,我们再次启动另一个客户端:
可以看到另一个客户端也脸上了