TCP 是有连接,可靠传输,面相字节流,全双工。
这里有有连接的是抽象的"连接”主要表达的是通信双方是否保存有对端的信息,如果有则是有连接的,反之则是无连接的。
可靠传输:此处谈到的可靠并不是百分之百可靠能达到对方而是尽可能,相对来说不可靠就是完全不考虑是否能够到达对方。
面向字节流:面向字节流就是TCP传输数据的方式,同文件流/水流是一样的特点,我们可以选择具体传多少,可以一次根据我们的要求传输N个字节。
全双工:一个通信线路既能发送数据也可以接收数据(双向通信)。
TCP中的 socket api 核心类有两个1) ServerSocket(专门给服务器使用的socket对象)2) Socket(既个客户端使用,也会给服务器使用)。
通过上述我们可以得知TCP是面相字节流,因此上传数据的基本单位是Byte。
我们在此处进了解简单的,在这里使用的关于ServerSocket的构造方法和方法。
Socket的方法和构造方法:
下面我们开始构建服务器:
public class severEsocket {
private ServerSocket socket = null;
public severEsocket(int port) throws IOException {
socket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("启动服务器");
while(true){
//通过这种方式可以解决多个客户端可以使用连接一个服务器的问题
Socket Clintsocket = socket.accept();
Thread thread = new Thread(()->{
try {
procession(Clintsocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
thread.start();
}
}
private void procession(Socket clintsocket) throws IOException {
try(InputStream inputStream = clintsocket.getInputStream();
OutputStream outputStream = clintsocket.getOutputStream()){
PrintStream printStream = new PrintStream(outputStream);
Scanner scanner = new Scanner(inputStream);
while(true){
if(!scanner.hasNext()) {
break;
}
String requst = scanner.next();
String response = process(requst);
printStream.println(requst);
printStream.flush();
System.out.printf("[%s:%d]\n", clintsocket.getInetAddress(), clintsocket.getPort());
}
}catch(IOException e){
e.printStackTrace();
}finally{
//通过这种方式避免产生文件资源泄露针对文件描述符表
System.out.printf("[%s:%d] 服务器下线\n", clintsocket.getInetAddress(), clintsocket.getPort());
clintsocket.close();
}
}
private String process(String requst) {
return requst;
}
}
在构造方法中我们要在ServerSocket对象中输入端口号,只要这个构造方法成功了,那么一旦客户端启动且有相同端口号就会连接成功(一个端口号只能连接一个进程,但是一个进程可以连接多个端口号)。
Socket Clintsocket = socket.accept();
这里的accept()方法是接收来自客户端传送的需求,这个方法有一个特殊的作用就是如果客户端一直没有传递需求那么这个方法就会在此一直阻塞,就像如同阻塞队列一样死脑筋,如果没有需求我就一直等,直至需求到来或者进程结束。
clintsocket.close();
这里的close()方法的使用是为了避免是文件描述符妙表空间不足导致文件资源泄露,由于文件资源泄露是有一定的延后性的所以一旦有文件资源泄露问题,那么很有可能是大问题,造成不小的损失。
服务器的构建的步骤大体分为以下几步:
/ 获取到 socket 中持有的流对象
1. 读取请求并解析
2. 根据请求计算响应
3. 把响应写回给客户端
由于我们这里是回显服务器所以我们这里的响应即使字符串客户端所输入的请求。
客户端:
public class Clint {
private Socket socket = null;
public Clint(String IP,int port) throws IOException {
socket = new Socket(IP,port);
}
public void start(){
System.out.println("客户端启动");
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
PrintStream printStream = new PrintStream(outputStream);
Scanner scannerinput = new Scanner(inputStream);
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("请输入->");
String requst = scanner.next();
printStream.println(requst);
printStream.flush();
if(!scannerinput.hasNext()){
break;
}
String response = scannerinput.next() ;
System.out.println(response);
}
}catch(IOException e){
e.printStackTrace();
}
}
}
在这里我们是服务器的回应是返回一个字符串。
TCP客户端的构建相较于服务器就跟家简洁了,
printStream.flush();
客户端和服务器中的flush()方法都是为了解决printStream中的缓冲区问题,因为像这样的IO类型的类都是自带缓冲区的,这个缓冲区会将我们所发送来的请求进行累积,他并不会使我们没输入一个他就返回一个,而是达到一个量的时候在进行返回,因此我们使用flush()来解决这种方法,当然我们也可以使用OutputStream自带的方法进行回应。