网络编程套接字之TCP
目录
一、TCP流套接字编程
ServerSocket
Socket
TCP长短连接
二、TCP回显服务器客户端
服务器
客户端
一、TCP流套接字编程
我们来一起学习一下TCP socket api的使用,这个api与我们之前学习的IO流操作紧密相关,如果对IO流还不太熟悉的,可以看看这篇IO流操作
ServerSocket
顾名思义,ServerSocket是创建TCP服务器的Socket对象
构造方法 | 作用 |
---|---|
ServerSocket(int port) | 创建一个服务器套接字Socket,并指定端口号 |
方法 | 作用 |
---|---|
Socket accept() | 开始监听指定端口,有客户端连接时,返回一个服务端Socket对象,并基于该Socket对象与客户端建立连接,否则阻塞等待 |
void close() | 关闭此套接字 |
Socket
我们这里的Socket既是客户端的Socket,也可能是服务器接收到客户端连接后,返回的服务器Socket,不论是那个Socket,都是双方建立连接后,保存对方信息,进行收发数据的。
构造方法 | 作用 |
---|---|
Socket(String host,int port) | 创建一个客户端Socket对象,并与对应IP主机,对应端口的进程进行连接 |
方法 | 作用 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回套接字的输入流 |
OutputStream getOutputStream() | 返回套接字的输出流 |
TCP长短连接
顾名思义,我们的TCP的长短连接,就表示我们TCP建立连接后,什么时候关闭连接就决定了是长连接还是短链接。
短连接: 在每次接收到数据并返回响应后,关闭连接。也就是说短连接只能收发一次数据。
长连接: 一直保持连接的状态,不关闭连接,双方可以不停的收发数据。
二、TCP回显服务器客户端
服务器
public class TCPEchoServer {
protected ServerSocket socket;
public TCPEchoServer(int port) throws IOException {
if(port<1025||port>65535){
throw new BindException("端口号必须在1025-65535之间");
}
this.socket=new ServerSocket(port);
}
//开始处理客服端的连接请求
public void start() throws IOException {
System.out.println("服务器已启动等待客户端连接");
while (true) {
//接收客户端请求
Socket clientSocket = socket.accept();
//接收到客户端连接之后交给专门的方法进行数据处理
processConnection(clientSocket);
}
}
protected void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d] 客户端上线了.\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
//所有的通讯数据都封装在Socket对象中,对应的就是入参clientSocket.TCP传输形式是字节流
//1.通过输入输出流获取发送数据
try(InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream=clientSocket.getOutputStream()){
//2.循环读取客户端发来的数据
while (true) {
//通过Scanner简化数据的读取
Scanner scanner=new Scanner(inputStream);
if(!scanner.hasNextLine()){
System.out.printf("[%s:%d] 客户端下线了.\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
//获取用户发来的具体内容
String request=scanner.nextLine();
//根据请求计算响应
String response=process1(request);
//把响应写入输出流
PrintWriter writer=new PrintWriter(outputStream);
//一次读一行,不能用write
writer.println(response);
//刷新缓冲区相当于系统把数据交给了网卡
writer.flush();
//打印日志
System.out.printf("[%s:%d] request = %s, response = %s\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort(), request, response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected String process1(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TCPEchoServer server=new TCPEchoServer(9090);
server.start();
//2121五21232312112112
}
}
客户端
public class TCPchoClient {
//声明Socket对象
private Socket socket;
public TCPchoClient(String serverIp,int serverPort) throws IOException {
//校验
if (serverIp == null || serverIp.isEmpty()) {
throw new RuntimeException("Ip地址不能为空");
}
if (serverPort < 1025 || serverPort > 65535) {
throw new RuntimeException("端口号必须在1025-65535之间");
}
this.socket = new Socket(serverIp,serverPort);
}
public void start(){
System.out.println("客户端已启动");
//从Socket对象获取输入和输出流对象
try(InputStream inputStream=this.socket.getInputStream();
OutputStream outputStream=this.socket.getOutputStream()) {
//循环接受用户的输入
while (true) {
System.out.println("->");
Scanner scanner=new Scanner(System.in);
String request=scanner.nextLine();
//非空校验
if (request == null || request.isEmpty()) {
System.out.println("输入内容不能为空");
continue;
}
//2.把用户的输入封装到输入流,可以用PrintWriter简化
PrintWriter writer=new PrintWriter(outputStream);
//3.把数据写入到输出流
writer.println(request);
//强制刷新缓冲区
writer.flush();
//接受返回数据
Scanner responseScanner=new Scanner(inputStream);
//解析响应数据
String response = responseScanner.nextLine();
//打印日志
System.out.printf("request=%s,response=%s\n",request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TCPchoClient client=new TCPchoClient("127.0.0.1",9090);
client.start();
}
}
当我们客户端连接上这个服务器的时候,就执行到processConnection方法的while循环中,只要该方法不结束,我们的accpet就无法获取到第二个客户端socket对象。
那么我们如何解决这个问题呢?
使用多线程,我们的主线程专门负责进行accept,每收到一个连接,创建新线程,由新线程来负责处理这个新的客户端。
public class TCPThreadServer extends TCPEchoServer{
public TCPThreadServer(int port) throws IOException {
super(port);
}
@Override
public void start() throws IOException {
System.out.println("服务器已经启动,等待客户端连接");
//循环接收客户端的连接请求
while (true) {
Socket clientSocket = socket.accept();
//为每一个客户端连接创建一个线程
Thread thread = new Thread(() -> {
//子线程处理连接
processConnection(clientSocket);
});
thread.start();
}
}
public static void main(String[] args) throws IOException {
TCPThreadServer threadServer=new TCPThreadServer(9090);
threadServer.start();
}
}