Java Socket通信之TCP协议
文章目录
- 一、 Java流套接字通信模型
- 1.TCP模型
- 2.TCP Socket常见API
- ServerSocket API
- Socket API
- 二、TCP流套接字编程
- 1.回显服务器
- 2.多线程服务器
- 三、TCP中的长短连接
- 四、协议
- 1. 为什么需要协议?
- 2. 封装/分用 VS 序列化/反序列化
- 3. 自定义协议
紧接着 Java Socket通信之UDP协议 再来!
一、 Java流套接字通信模型
1.TCP模型
TCP的整个通信流程如下如所示:
服务器的任务:
①创建ServerSocket对象并绑定一个端口,成为listenSocket(类似于大街拉拢客人的中介)。
②listenSocket调用ServerSocket的accept()方法,把内核建立好的连接拿到代码中进行处理。如果接收到来自客户端的请求时,accept()会返回一个Socket实例,称为clientSocket(类似于中介把客人介绍给前台小姐姐,由她负责之后的业务流程,中介继续去拉拢客人),如果没有接收到请求,则一直处于阻塞等待状态。
③使用clientSocket的getInputStream和OutputStream得到字节流对象,可进行字节的读取和写入。
④当客户端断开连接后,服务器要及时关闭clientSocket,否则会出现文件资源泄露的情况。
客户端的任务:
①创建一个Socket对象,同时指定服务器的IP和Port (建立TCP连接三次握手,由内核完成,用户感知不到)。
②客户端通过Socket对象getInputStream和getOutputStream和服务器进行通信。
关于端口被占用问题:
通常情况下,两个进程无法绑定到同一个端口号!(除特殊情况:eg. Linux中 fork系统调用)
解决办法:如果占用端口的进程A不需要运行,就可以关闭A后,再启动需要绑定该端口的进程B;如果需要运行A进程,则可以修改进程B的绑定端口,换为其他没有使用的端口。
2.TCP Socket常见API
ServerSocket API
ServerSocket 是创建TCP服务端Socket的API;
ServerSocket构造方法:
方法 | 说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字socket,并绑定到响应端口上 |
ServerSocket方法:
方法 | 说明 |
---|---|
Socket accept() | 开始监听指定端口(创建时绑定的端口),如果有客户端连接,返回一个socket对象,如果没有,一直处于阻塞等待状态。(用户代码调用accept,才是真的把连接拿到用户代码中) |
void close() | 关闭此套接字(否则随着连接的增多,socket文件会出现文件资源泄露的情况) |
Socket API
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端socket。不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对方端信息,即用来与对方收发数据的。
注:Socket是服务器和客户端都需要使用;ServerSocket是只在服务器中使用的。
Socket 构造方法:
方法 | 说明 |
---|---|
Socket(String host, int post) | 创建一个客户端流套接字socket,并与对应IP和port的进程建立连接 |
Socket 方法:TCP Socket 是基于字节流的,进行具体读写时和文件类似。
方法 | 说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
二、TCP流套接字编程
1.回显服务器
服务器代码:
/*** 代码有几个值得注意的点:* ① 将构造好的响应写会给客户端时,要用println* ② 返回响应时要用flush()将缓冲区中的数据冲刷到内存中* ③ 当前简易的程序是一个服务器只能同时为一个客户端收发数据,如果再有一个客户端要通讯,需等待上一个客户端释放完成之后再通讯* 关键问题在于:如果第一个客户端没有退出,此时服务器的逻辑一直在processConnection内部打转,没有机会再次调用到accept,* 也没有办法处理第二个连接~* 解决办法:使用多线程!* 主线程里面循环调用accept()*/
package TCPEcho;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.Scanner;public class TcpEchoServer {private ServerSocket listenSocket = null;//构造函数--端口绑定public TcpEchoServer(int port) throws IOException {listenSocket = new ServerSocket(port);}public void start() throws IOException {//accept()相当于一个监听程序,TCP是一个有连接的操作,首先要建立连接//如果客户端没有发来请求,accept()阻塞等待//如果服务器接收到客户端发来的请求,accept()会返回一个Socket对象//客户端和服务器的进一步交互就交给clientSocket来完成了while (true) {Socket clientSocket = listenSocket.accept();processConnection(clientSocket);}}//处理一个请求,这个请求中可能涉及客户端和服务器的多次交互private void processConnection(Socket clientSocket) throws IOException {String log = String.format("[%s:%d]客户端已上线!",clientSocket.getInetAddress().toString(),clientSocket.getPort());System.out.println(log);try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){while(true) {//1.接收请求并解析//①可以使用inputStream的read()方法读取数据到byte[]中,然后在转成String//②第一种方法比较麻烦,还可以借助Scanner来完成这个工作Scanner scanner = new Scanner(inputStream);//如果连接关闭,才会触发到这个情况!(读取到EOF)if (!scanner.hasNext()) {log = String.format("[%s:%d]客户端已下线!",clientSocket.getInetAddress().toString(), clientSocket.getPort());System.out.println(log);break;}String request = scanner.next();//2.根据请求计算响应String response = process(request);//3.构造响应并返回PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(response);printWriter.flush(); //刷新log = String.format("[%s:%d],request: %s, response: %s",clientSocket.getInetAddress().toString(), clientSocket.getPort(),request, response);System.out.println(log);}}catch(IOException e){e.printStackTrace();}finally{//当前的clientSocket声明周期不是跟随整个程序,而是和连接有关//因此需要每个服务器连接结束后进行close()操作,否则随着连接的增多,socket文件就可能出现资源泄露的情况clientSocket.close();}}//实现回显服务器(客户端发啥,服务器返回啥)private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);tcpEchoServer.start();}
}
客户端代码:
package TCPEcho;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 {private Socket socket = null;private String serverIP;private int serverPort;public TcpEchoClient(String serverIP, int serverPort) throws IOException {this.serverIP = serverIP;this.serverPort = serverPort;this.socket = new Socket(serverIP,serverPort); //socket创建的同时,就和服务器尝试建立连接}public void start() throws IOException {try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){while(true){//1.从键盘上读取用户输入内容Scanner scanner = new Scanner(System.in);System.out.println("->");String request = scanner.next();if(request.equals("exit")){System.out.println("程序退出!");break;}//2.将读取的内容构造成请求发送给服务器PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(request);printWriter.flush();//3.从服务器读取响应并解析Scanner responseScanner = new Scanner(inputStream);String response = responseScanner.next();//4.把结果显示到界面String log = String.format("request:%s, response:%s",request,response);System.out.println(log);}}catch(IOException e){e.printStackTrace();}finally{socket.close();}}public static void main(String[] args) throws IOException {TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9090);tcpEchoClient.start();}
}
2.多线程服务器
上面的例子,我们只能实现一个客户端一个服务器这样的通信方式,如果多个客户端共同发来请求,就要用到多线程;更进一步,线程的创建和销毁都需要消耗资源,为了避免频繁创建销毁线程,我们也可以使用线程池进行代码的改进。
服务器代码:
//使用线程:
package TCPThreadEcho;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.Scanner;public class TcpThreadEchoServer {private ServerSocket listenSocket = null;public TcpThreadEchoServer(int port) throws IOException {listenSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动!");while (true) {//在这个代码中,通过创建线程,就能保证在调用完accept()之后就能立刻再次返回调用accept().Socket clientSocket = listenSocket.accept();//创建一个线程来给客户端提供服务Thread t = new Thread() {@Overridepublic void run() {try {processConnection(clientSocket);} catch (IOException e) {e.printStackTrace();}}};t.start();}}private void processConnection(Socket clientSocket) throws IOException {String log = String.format("[%s:%d]客户端已上线!",clientSocket.getInetAddress().toString(),clientSocket.getPort());System.out.println(log);try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){while(true){Scanner scanner = new Scanner(inputStream);if(!scanner.hasNext()){log = String.format("[%s:%d]客户端已下线!",clientSocket.getInetAddress().toString(),clientSocket.getPort());System.out.println(log);break;}String request = scanner.next();//2.根据请求计算响应String response = process(request);//3.构造响应并返回PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(response);printWriter.flush();//打印log = String.format("[%s:%d],request:%s, response:%s",clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response);System.out.println(log);}}catch(IOException e){e.printStackTrace();}finally{clientSocket.close();}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpThreadEchoServer tcpThreadEchoServer = new TcpThreadEchoServer(9090);tcpThreadEchoServer.start();}
}
改进:
//使用线程池:
package TCPThreadPoolEcho;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.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TcpThreadPoolEchoServer {private ServerSocket listenSocket = null;public TcpThreadPoolEchoServer(int port) throws IOException {listenSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动!");//使用线程池!!ExecutorService executorService = Executors.newCachedThreadPool();while (true) {//在这个代码中,通过创建线程,就能保证在调用完accept()之后就能立刻再次返回调用accept().Socket clientSocket = listenSocket.accept();//创建一个线程池来给客户端提供服务executorService.submit(new Runnable() {@Overridepublic void run() {try {processConnection(clientSocket);} catch (IOException e) {e.printStackTrace();}}});}}private void processConnection(Socket clientSocket) throws IOException {String log = String.format("[%s:%d]客户端已上线!",clientSocket.getInetAddress().toString(),clientSocket.getPort());System.out.println(log);try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){while(true){Scanner scanner = new Scanner(inputStream);if(!scanner.hasNext()){log = String.format("[%s:%d]客户端已下线!",clientSocket.getInetAddress().toString(),clientSocket.getPort());System.out.println(log);break;}String request = scanner.next();//2.根据请求计算响应String response = process(request);//3.构造响应并返回PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(response);printWriter.flush();//打印log = String.format("[%s:%d],request:%s, response:%s",clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response);System.out.println(log);}}catch(IOException e){e.printStackTrace();}finally{clientSocket.close();}}public String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpThreadPoolEchoServer tcpThreadPoolEchoServer = new TcpThreadPoolEchoServer(9090);tcpThreadPoolEchoServer.start();}}
客户端代码:
package TCPThreadEcho;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 TcpThreadEchoClient {public Socket socket = null;public TcpThreadEchoClient(String serverIp, int serverPort) throws IOException {this.socket = new Socket(serverIp, serverPort);}public void start(){try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){while (true){//从键盘接收请求内容Scanner scanner = new Scanner(System.in);String request = scanner.next();if(request.equals("exit")){System.out.println("程序退出");break;}//构造请求并发送PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(request);printWriter.flush();//接收响应并解析Scanner responseScanner = new Scanner(inputStream);String response = responseScanner.next();String log = String.format("[%s:%d],request:%s,response:%s",socket.getInetAddress().toString(),socket.getPort(),request,response);System.out.println(log);}} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpThreadEchoClient tcpThreadEchoClient = new TcpThreadEchoClient("127.0.0.1", 9090);tcpThreadEchoClient.start();}
}
针对线程特别多的情况如何处理?(高并发解决!)
①协程代替线程,完成并发
②IO多路复用机制,完成并发 IO多路复用机制
③使用分布式,提供更多的硬件资源
TCP协议的抓包工具:tcpdump(Linux)、wireShark跨平台、图形化工具。
三、TCP中的长短连接
TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:
短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。
长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。
两者对比如下:
①建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。
②主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。
③两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等。
基于BIO(同步阻塞IO)的长连接会一直占用系统资源。对于并发要求很高的服务端系统来说,这样的消耗是不能承受的。实际应用时,服务端一般是基于NIO(即同步非阻塞IO)来实现长连接,性能可以极大的提升。
四、协议
1. 为什么需要协议?
对于客户端及服务端应用程序来说,请求和响应,需要约定一致的数据格式:
①客户端发送请求和服务端解析请求要使用相同的数据格式。
②服务端返回响应和客户端解析响应也要使用相同的数据格式。
③请求格式和响应格式可以相同,也可以不同。
④约定相同的数据格式,主要目的是为了让接收端在解析的时候明确如何解析数据中的各个字段。
⑤可以使用知名协议(广泛使用的协议格式),如果想自己约定数据格式,就属于自定义协议。
2. 封装/分用 VS 序列化/反序列化
一般来说,在网络数据传输中,发送端应用程序,发送数据时的数据转换格式,对发送数据时的数据包装动作来说:
如果是使用知名协议,这个动作也称为封装
如果是使用小众协议(包括自定义协议),这个动作也称为序列化,一般是将程序中的对象转换为特定的数据格式。
接收端应用程序,接收数据时的数据转换格式,即对接收数据时的数据解析动作来说:
如果是使用知名协议,这个动作也称为分用
如果是使用小众协议(包括自定义协议),这个动作也称为反序列化,一般是基于接收数据特定的格式,转换为程序中的对象。
3. 自定义协议
在数据传输的过程中,除了UDP和TCP协议外,程序还存在应用层自定义协议;对于协议来说,重点需要约定好如何解析,一般是根据字段的特点来设计协议:
对于定长的字段:可以基于长度约定,如int字段,约定好4个字节即可
对于不定长的字段:
①可以约定字段之间的间隔符,或最后一个字段的结束符,如换行符间隔,\n符号结束等
②除了该字段“数据”本身,再加一个长度字段,用来标识该“数据”长度;即总共使用两个字段:
“数据”字段本身,不定长,需要通过“长度”字段来解析;
“长度”字段,标识该“数据”的长度,即用于辅助解析“数据”字段。
实际开发过程中,自定义协议(除了文本+分隔符)
还有文本格式(xml, json…)这种把请求/响应当成字符串处理,基本单位是字符。
还有二进制格式(protobuffer,thift…)把请求/响应当成二进制数据处理,基本单位是字节。
Java Socket通信之TCP协议相关推荐
- Java学习系列(十六)Java面向对象之基于TCP协议的网络通信
TCP/IP的网络分层模型:应用层(HTTP/FTP/SMTP/POPS...),传输层(TCP协议),网络层(IP协议,负责为网络上节点分配唯一标识),物理层+数据链路层). IP地址用于标识网络中 ...
- flex java socket通信
引用:http://developer.51cto.com/art/201003/189791.htm Java socket通信如何进行相关问题的解答呢?还是需要我们不断的学习,在学习的过程中会遇到 ...
- Java Socket通信实现多人多端网络画板聊天室
老规矩,先上实现的效果展示! Java Socket通信实现多人多端网络画板聊天室 本文介绍了一个基于Socket实现网络画板聊天室的完整过程,聊天室具备多人文本对话.同步绘图等功能. 初尝试 Soc ...
- python实现淘宝客服自动回复语_Python+Socket实现基于TCP协议的客户与服务端中文自动回复聊天功能示例...
本文实例讲述了Python+Socket实现基于TCP协议的客户与服务端中文自动回复聊天功能.分享给大家供大家参考,具体如下: [吐槽] 网上的代码害死人,看着都写的言之凿凿,可运行就是有问题. 有些 ...
- 使用JAVA实现Socket通信,TCP、UDP简析。
Socket通信是一种非常重要的通信方式,它使用起来简单方便,也很容易学会,下面就我所知道的简单记录一下. 首先是UDP的方式.(通过Android与PC端进行通信) 发送端: package com ...
- python连接fanuc机器人、fanuc机器人以太网通信、发那科机器人以太网通信 fanuc socket 、fanuc TCP协议 通信 fanuc机器人与PC通讯
fanuc机器人以太网连接,fanuc tcp协议,读取位置.报警信息,读写数值寄存器.位置寄存器.IO,清除报警.----沟通(tian531200 v同号) R寄存器读取 位置寄存器读取 关节位置 ...
- Java Socket编程 - 基于TCP方式的二进制文件传输【转】http://blog.csdn.net/jia20003/article/details/8248221...
此人博客挺好的,推荐一个! 一个基于Java Socket协议之上文件传输的完整示例,基于TCP通信完成. 除了基于TCP的二进制文件传输,还演示了JAVA Swing的一些编程技巧,Demo程序 实 ...
- Java Socket编程 - 基于TCP方式的二进制文件传输
一个基于Java Socket协议之上文件传输的完整示例,基于TCP通信完成. 除了基于TCP的二进制文件传输,还演示了JAVA Swing的一些编程技巧,Demo程序 实现主要功能有以下几点: 1. ...
- java:socket通信
基于tcp协议,建立稳定连接的点对点的通信. 实时,快速,安全性高,占用系统资源高,效率低 请求-响应模式(request, response) 客户端: 在网络通讯中,第一次主动发起通讯的程序叫做客 ...
最新文章
- pwn with glibc heap(堆利用手册)
- “自拍神器”贴心实用功能大曝光
- 识别强直性脊柱炎高效和疾病特定的基质改变
- [C语言]关于指针和int型的一道题目
- GridView 添加分害线
- dsniff 和 Ettercap 和 bettercap 详解 - 网络嗅探工具包
- k8s安装sqlite3_kubernetes环境部署单节点redis数据库的方法
- 第十天:SwiftGoodAsOldPhones
- php 远程图片大小,PHP下载远程图片并保存到本地方法总结
- 如何使frame能居中显示
- 7-11 分段计算居民水费
- 视频教程-CCNA视频----从零开始学CCNA实验视频课程(加强版)-思科认证
- 【Codecs系列】HEVC标准(九):环路滤波技术之SAO
- livereload_使用LiveReload节省编码时间和精力
- 仿微信二维码极速扫描(MLKit及CameraX初体验),安卓消息分发机制
- 浅谈社交行业风控如何做以及黑产危害
- WIN7安装官方漏洞补丁,错误代码0x80240037的解决方法
- Vue:如何制作表格数据分页查询
- Echarts3实例 map地图值渲染
- Co Attention注意力机制实现
热门文章
- 虚拟机 网络不可达 连不上网 Destination Host Unreachable
- 打开服务器无线网连接,笔记本打开无线网络的方法【图文教程】
- 1623 三人国家代表队
- 3分钟整明白 缓存热点 是咋回事
- css如何设置透明度?设置透明度的两种方法(代码实例)
- 我读《计算机科学概论》第12版 J.Gleen.Brookshear Dennis Brylow
- POST和GET区别 -- 面试重点之一
- 给定一个十进制整数,如何转成二进制形式?如何转成十六进制形式?
- 华尔街大鳄丢币,加密世界“丢份”
- Windows下的chcp命令(更改该控制台的活动控制台代码页)