TCP流套接字编程
TCP流套接字编程
TCP和UDP差距是很大的,在数据传输方面,UDP是面向数据报的,而TCP是面向字节流的的,下面列出了使用TCP来实现网络编程所依赖的Socket类,通过这些类和具体的例子,来详细的讲解TCP网络编程。
ServerSocket API
ServerSocket 是Java提供给我们创建TCP服务端Socket所使用的API。
ServerSocket 的构造方法如下:
构造方法 | 方法说明 |
---|---|
ServerSocket(int port) | 创建一个服务端的Socket对象,并绑定到指定的端口 |
ServerSocket 方法:
方法 | 方法说明 |
---|---|
Socket accept() | 在客户端连接后,通过accept()方法,可以拿出内核中已经于客户端建立好的连接对象,这个对象就是Socket对象,如果没有建立好的连接,则阻塞等待 |
void close() | 关闭此套接字 |
Socket API
在Java中,也给我们提供了一个Socket类,而这个 Socket 既会给客户端使用又会给服务器使用,以上这个 Server Socket 和 Socket 这两个类都是用来表示系统中的Socket文件的,也就是抽象了网卡这样的概念。
Socket 构造方法:
构造方法 | 方法说明 |
---|---|
Socket(String host, int port) | 创建一个客户端的Socket,并指定对端主机的IP,对端进程的端口号,然后建立连接 |
Socket 方法:
方法 | 方法说明 |
---|---|
InetAddress getInetAddress() | 返回Socket所连接的地址 |
InpuStream getInputStream() | 返回Socket的输入流 |
OutputStream getOutpubStream() | 返回此Socket的输出流 |
注意:UDP是无连接的,而TCP是有连接的
这里的“有连接”和“无连接” 不是传统意义上的连接,而是通信双方都保存了对端的信息,而UDP呢,它是每发送一次数据,都要指定一次对端的IP和端口号,然后将生成的数据报作为send的参数发出去,下图就是UDP中的发送数据报的核心代码;
对于TCP来说,并不需要,前提是需要先把连接给建立上。而这个连接如何建立,是不需要我们通过代码来实现的,而是操作系统的内核自动负责完成的,对于客户端这边来说,主要是“发起连接”动作,在服务器这边的应用程序是没有任何感知的,因为,系统内核就直接完成了建立连接的流程(这个流程就是三次握手),完成这个建立连接的流程之后,建立好的连接就会放在内核中的一个队列里排队(每一个ServerSocket都有这样的队列),服务器这边的应用程序想要和客户端进行通信,就会通过accept()方法,将建立好的连接对象拿到应用程序中,这样就可以进行通信,如果队列中没有建立好的连接的话,accept()方法就会阻塞等待,这个队列呢就是一种生产者消费者模型。
下面通过两个示例来讲解这些方法的使用:
示例:回显服务器
服务器端
一:建立连接
public class TcpEchoServer {
private ServerSocket socket = null;
public TcpEchoServer(int port) throws IOException {
//创建服务器端的Socket对象
socket = new ServerSocket(port);
}
//启动服务器方法
public void start() throws IOException {
System.out.println("服务器启动");
//1.通过accept方法将建立好的连接拿到应用程序中,如果没有,则阻塞等待
Socket clientSocket = socket.accept();
//通过这个方法,处理拿到的连接
process_connection(clientScoket);
}
二:计算请求,返回响应
private void process_connection(Socket clientSocket){
//打印连接上的对端信息
System.out.printf("客户端上线:[%s:%d]\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
//通过clientSocket中的流对象来使双方进行通信
try(InputStream is = clientSocket.getInputStream();
OutputStream os = clientSocket.getOutputStream()) {
//因为服务器和客户端可能不止一次进行交互,所以通过while进行循环交互
while(true) {
//通过Scanner的方式将流对象中的请求数据读取出来
Scanner scanner = new Scanner(is);
//判断scanner是否有下一个数据,如果没有,连接结束
if(!scanner.hasNext()) {
System.out.printf("客户端下线:[%s:%d]\n",clientSocket.getInetAddress(),clientSocket.getPort());
break;
}
//拿到客户端请求,通过next()方法,约定读到"\n"结束
String request = scanner.next();
//通过这个方法根据请求计算相应
String response = calculator_response(request);
//返回服务器处理的响应
//2.通过PrintWriter进行发送
PrintWriter printWriter = new PrintWriter(os);
//这里的println可不是打印到控制台,而是发送给OutputStream对象,然后发送给客户端
printWriter.println(response);
//通过flush方法刷新缓冲区,如果没有这个操作的话,写入的数据很可能会在缓冲区中,而没有被发送出去
printWriter.flush();
//打印一下这次交互的内容
System.out.printf("[%s:%d], request:%s, response:%s\n",clientSocket.getInetAddress(),
clientSocket.getPort(), request, response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private String calculator_response(String request) {
//因为我们写的是回显服务器,所以请求是什么,响应就是什么
return request;
}
public static void main(String[] args) {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9999);
try {
tcpEchoServer.start();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
客户端
一、建立连接
public class TcpEchoClient {
private Socket socket = null;
public TcpEchoClient(String ip, int port) {
try {
//指定服务器的地址和端口号
//当我们new这个对象时,系统内核就会自动帮我们完成建立连接的流程;
socket = new Socket(ip, port);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
二、发起请求,获取响应
//客户端启动方法
public void start() {
try(InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream()) {
Scanner scanner = new Scanner(System.in);
PrintWriter printWriter = new PrintWriter(os);
Scanner in = new Scanner(is);
while(true) {
//1.向服务器发起请求
System.out.println("请输入请求->:");
String request = scanner.next();
printWriter.println(request);
printWriter.flush();
//2.读取服务器返回来的响应
String response = in.next();
System.out.println("response:" + response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9999);
tcpEchoClient.start();
}
}
运行之后:
但是,上述代码还存在一个问题,现在是一个服务器和一个客户端进行交互,如果多个客户端和一个服务器进行交互,那么这个bug就出来了!!!
下图为IDEA如何同时运行多个进程的操作步骤
同时运行多个客户端,分析一下代码会出现什么问题!!!
运行完之后,可以看到,虽然启动了两个客户端,但是,在服务器这边,并没有显示第二个客户端上线,这是为什么呢?下面就结合代码分析一下原因
如果想要在处理第一个客户端请求的过程中,accept快速的接收到第二个客户端建立起的连接,那么,就需要使用到并发编程!!!
利用线程池实现并发编程
利用线程池的方式分配一个新的线程去执行process_connection方法中的处理请求的逻辑,让主线程继续去获取到系统队列中已经建立好连接的其他客户端,然后再通过线程池分配新的线程去执行,利用这样的方式,就可以实现多个客户端与服务器进行交互。
public void start() throws IOException {
System.out.println("服务器启动");
//创建一个线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
while(true) {
//1.通过accept方法将建立好的连接拿到应用程序中
Socket clientSocket = socket.accept();
//通过这个方法,使用新的线程处理拿到的连接
threadPool.submit(new Runnable() {
@Override
public void run() {
process_connection(clientSocket);
}
});
}
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!