前言
前文中我们介绍了UDP Socket相关的构造方法和方法,并实现了UDP的回显服务器和客户端。
本篇将介绍TCP Socket,并使用TCP Socket api实现服务器和客户端的通信
一.TCP Socket的常见方法
1.ServerSocket
ServerSocket是创建TCP服务端Socket的API
1)ServerSocket构造方法
方法签名 | 方法说明 |
ServerSocket(int port) | 创建⼀个服务端流套接字Socket,并绑定到指定端⼝ |
2)ServerSocket方法
方法签名 | 方法说明 |
Socket accept() | 开始监听指定端⼝(创建时绑定的端⼝),有客⼾端连接后,返回⼀个服务端Socket对象,并基于该 Socket建⽴与客⼾端的连接,否则阻塞等待 |
void close() | 关闭此套接字 |
TCP建立连接的流程是操作系统内核完成的,代码感知不到,accept()操作是内核已经完成了连接建立的操作,accept()是确定对该连接进行处理(确认操作)。
如果没有客户端请求,则会阻塞。
accept会返回一个Socket对象,服务器每调用一次accept都会产生一个新的Socket对象,来和客户端进行“一对一服务”。
2.Socket
Socket 是客⼾端Socket,或是服务端中接收到客⼾端建⽴连接(accept⽅法)的请求后,返回的服务端Socket。
不管是客⼾端还是服务端Socket,都是双⽅建⽴连接以后,保存的对端信息,及⽤来与对⽅收发数据的。
1)Socket构造方法
方法签名 | 方法说明 |
Socket(String host,int Port) | 创建⼀个客⼾端流套接字Socket,并与对应IP的主机上,对应端⼝的进程建⽴连接 |
2)Socket方法
方法签名 | 方法说明 |
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输⼊流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
二.TCP服务器端实现
1.代码实现
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.security.Provider;
import java.util.Scanner;
public class TcpServer {
private ServerSocket serverSocket=null;
public TcpServer(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while(true){
Socket client= serverSocket.accept();
processConnection(client);
}
}
private void processConnection(Socket client) {
System.out.printf("[%s :%d]客户端上线",client.getInetAddress(),client.getPort());
try(InputStream inputStream=client.getInputStream();//TCP是全双工通信,既能读,也能写
OutputStream outputStream=client.getOutputStream();
PrintWriter printWriter=new PrintWriter(outputStream)){
while(true){
//1.读取请求并解析(为方便读,可使用Scanner)
Scanner scanner=new Scanner(inputStream);
if(!scanner.hasNext()){
//如果Scanner读不出下一个数据了,说明客户端关闭了连接,导致服务器读到了“末尾”
break;
}
//2.根据请求计算响应
String request=scanner.next();
String response=process(request);
//3.把响应写回客户端
printWriter.println(response);
//4.打印日志
System.out.printf("[%s :%d], rep=%s,reps=%s\n",client.getInetAddress(),client.getPort(),
request,response);
}
}catch (IOException e){
e.printStackTrace();
}
System.out.printf("[%s :%d]服务器下线",client.getInetAddress(),client.getPort());
}
private String process(String response){
return response;
}
public static void main(String[] args) throws IOException {
TcpServer server=new TcpServer(9090);
server.start();
}
}
2.代码解析
1.建立ServerSocket类,需填入参数来绑定程序的端口号,new ServerSocket(port)后于客户端建立起连接。
2.创建start()方法启动服务器,使用accept()确认对连接客户端进行处理,将处理过程传入processConnection()函数中
3.因TCP服务器是面向字节流,因此需要使用输入流与输出流进行数据的发送与接收。(在try中定义是因为try结束后会自动释放文件资源)
4.进行数据的读取,读取完后对数据进行处理(process()中处理,因本代码为回显服务器,只是返回客户端请求,无更多逻辑),处理完后生成响应,并写回客户端,最后打印日志
注:此处服务器代码存在三个问题,需结合客户端代码进行解释与修改
三.TCP客户端实现
1.代码实现
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 TcpCilent {
private Socket socket=null;
public TcpCilent(String ServerIP,int Port) throws IOException {
socket=new Socket(ServerIP,Port);
}
public void strat(){
System.out.println("客户端启动");
try(InputStream inputStream= socket.getInputStream();
OutputStream outputStream=socket.getOutputStream();
Scanner scanner=new Scanner(inputStream);
PrintWriter printWriter=new PrintWriter(outputStream);
Scanner scannerIn=new Scanner(System.in)){
while(true){
//1.从控制台读取信息
String request=scannerIn.next();
//2.将数据发送给服务器
printWriter.println(request);
//3.从服务器获取响应
if(!scannerIn.hasNext()){
break;
}
String response=scanner.next();
//4.打印日志
System.out.println(response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpCilent cilent=new TcpCilent("127.0.0.1",9090);
cilent.strat();
}
}
2.代码解析
1.客户端创建Socket类,需填入服务器的ip和端口号,在new时系统内核会自与对应的服务器连接上
2.与服务器同理,因TCP面向字节流,需要通过输入流与输出流来实现数据的发送与接收。
3.从控制台中读取数据(我们输入的),然后将其写入到服务器,在获取到服务器的响应,最后打印日志。
四.问题与解决
问题一:客户端发送数据之后,并没有任何响应(缓冲区问题)
运行上述代码,服务器成功打印出客户端上线和下线
但是客户端发送数据,服务器并无响应
原因是像PrintWriter这样的类,以及很多IO流中的类,都是自带“缓冲区”的。
引入缓冲区后,进行写入操作时不会立即触发IO,而是会将数据先放入缓冲区,等到缓冲区的数据积攒一波后,在统一发送(因为频繁的IO操作开销大,这是一个优化)
解决上诉问题,只要通过flush刷新缓冲区就可解决。
问题二:服务器代码未对client进行close()操作
这里的client是“连接级别”的数据,随着客户端断开连接,这个client也就不再使用了。
即使是同一个客户端再次连接,也是一个新的client,和旧的不是同一个
因此,这里的client就应该主动关闭掉。
可以在finally关闭
问题三:尝试多个客户端来同时连接服务器
作为一个服务器,就是要同时给多个客户端进行服务的
当前代码,只能启动一个客户端,如何启动多个客户端?
要修改一些配置(如图)
此时,我们发现,我们可以启动多个客户端
但是,利用新创建的客户端向服务器发送数据,却没有响应。
若把第一个客户端关掉,服务器就会立刻针对客户端2的请求进行响应。
此时,我们的服务器只能同时给一个客户端进行服务,这是不科学的。
原因是当第一个客户端连接上服务器,服务器代码就会进入processConnection内部的while循环,此时第二个客户端无法执行到accept,使用操作积压在内核缓冲区中。
解决方法是将双重while循环改为单层while循环,我们可以利用多线程
这样,问题就得到了解决
两个客户端是不同的端口
可以共同使用服务器。
完整代码
1.客户端代码
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 TcpCilent {
private Socket socket=null;
public TcpCilent(String ServerIP,int Port) throws IOException {
socket=new Socket(ServerIP,Port);
}
public void strat(){
System.out.println("客户端启动");
try(InputStream inputStream= socket.getInputStream();
OutputStream outputStream=socket.getOutputStream();
Scanner scanner=new Scanner(inputStream);
PrintWriter printWriter=new PrintWriter(outputStream);
Scanner scannerIn=new Scanner(System.in)){
while(true){
//1.从控制台读取信息
String request=scannerIn.next();
//2.将数据发送给服务器
printWriter.println(request);
printWriter.flush();
//3.从服务器获取响应
if(!scannerIn.hasNext()){
break;
}
String response=scanner.next();
//4.打印日志
System.out.println(response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpCilent cilent=new TcpCilent("127.0.0.1",9090);
cilent.strat();
}
}
2.服务器端代码
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.security.Provider;
import java.util.Scanner;
public class TcpServer {
private ServerSocket serverSocket=null;
public TcpServer(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while(true){
Socket client= serverSocket.accept();
Thread t=new Thread(()->{
try {
processConnection(client);
} catch (IOException e) {
e.printStackTrace();
}
});
t.start();
}
}
private void processConnection(Socket client) throws IOException {
System.out.printf("[%s :%d]客户端上线\n",client.getInetAddress(),client.getPort());
try(InputStream inputStream=client.getInputStream();//TCP是全双工通信,既能读,也能写
OutputStream outputStream=client.getOutputStream();
PrintWriter printWriter=new PrintWriter(outputStream)){
while(true){
//1.读取请求并解析(为方便读,可使用Scanner)
Scanner scanner=new Scanner(inputStream);
if(!scanner.hasNext()){
//如果Scanner读不出下一个数据了,说明客户端关闭了连接,导致服务器读到了“末尾”
break;
}
//2.根据请求计算响应
String request=scanner.next();
String response=process(request);
//3.把响应写回客户端
printWriter.println(response);
printWriter.flush();
//4.打印日志
System.out.printf("[%s :%d], rep=%s,reps=%s\n",client.getInetAddress(),client.getPort(),
request,response);
}
}catch (IOException e){
e.printStackTrace();
}finally {
System.out.printf("[%s :%d]服务器下线\n",client.getInetAddress(),client.getPort());
client.close();
}
}
private String process(String response){
return response;
}
public static void main(String[] args) throws IOException {
TcpServer server=new TcpServer(9090);
server.start();
}
}
以上便是全部内容,如有不对,欢迎指正
标签:java,Socket,Javaee,TCP,client,服务器,import,客户端 From: https://blog.csdn.net/yc_xym/article/details/143086674