日升时奋斗,日落时自省
目录
1、网络编程基本概念
2、UDP数据报套接字编程
2.1、UDP相关API
2.1.1、DatagramSocket API
2.1.2、DatagramPacket API
2.2、UDP版本服务器
2.3、UDP版本客户端
2.4、UDP连接操作
2.5、翻译业务
2.6、总结
3、TCP流套接字编程
3.1、TCP相关API
3.2、TCP版本服务器
3.3、TCP版本的客户端
3.4、TCP连接操作
3.4.1多线程TCP服务器
3.4.2、线程池TCP服务器
网络编程指的是网络上的主机,通过不同的进程,以编程的方式进行实现网络通信
详细下来就是我们只要满足进程不同就行;所以即使是同一个主机,只要是不同进程,基于网络来传输数据,也属于网络编程
(1)发送端和接收端
发送端:数据的发送方进程 ,称为发送端,发送端就是网络通信的源主机
接收端:数据的接收方进程, 称为接收端,接收端就是网络通信中的目的主机
注:发送端和接收端只是相对的。
(2)请求和响应
一般情况,获取一个网络资源,涉及到两次网络数据传输
首先: 请求数据发送
然后:响应数据发送
例如 :餐馆买饭,客人发出请求(我要吃什么),餐馆针对请求做出处理(后厨颠勺中),最后做好针对客人进行响应(做好了给你)
(3)客户端和服务端
服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务
客户端:获取服务的一方进程,称为客户端
注:我们常说的客户端(发送端)和服务器(接收端),但是理解不单就是客户与服务器交互,如果是服务器与服务器交互谁又是客户端(发送端)其实就是看谁给谁发送请求,发送请求的就是发送端,接收请求的就是接收端
基于UDP来编写一个简单的客户端服务器进行网络通信程序;需要有一定的网络基础
首先就是要认识进行网络编程要使用的API
核心内容 Socket API,操作系统给应用程序提供的网络编程API;Socket这里翻译成套接字,它是计算机之间进行通信的一种约定或一种方式;虽然socket英译为“插座”,但是在计算机领域里它叫“套接字”。
可以认为socket API是和传输层密切相关的,传输层提供了两个最核心的协议UDP和TCP所以socket也提供了两种风格的API
DatagramSocket使用这个类,表示一个socket对象,代表socket文件;原因:在操作系统中,把这个socket对象也是当成了一个文件来处理,相当于是文件描述符表上的一项,提到这里就会想到普通文件也就是对应的硬件设备(硬盘),socket文件,对应的硬件设备(网卡)一切都是文件而起。
一个socket对象,就可以和另外一台主机进行通信,如果要和多个不同的主机通信,需要创建多个socket对象。
DatagramSocket是UDP Socket用于发送和接收UDP数据报 (这里稍微有点绕口,多看两遍)
这个绕口的话是解释:DatagramSocket与下面马上要提到DatagramPacket对比理解,实际意思就是DatagramSocket里面是数据报作为参数也就是DatagramPacket类的(它就是作参数的)
(1)无参数版本的构造方法,没有指定端口,系统会自动分配一个空闲的端口
(2)一个参数版本的构造方法,有一个端口号,此时就是为了socket对象能和指定的端口号(简单的数字范围在1024-65535之间),关联起来;我们知道端口号是和进程相关连的,为了确定是哪个进程,本质上不是进程和端口号建立联系,而是进程中的socket对象和端口建立了联系
(3)需要了解的方法 一个receive(接收)另一个是send(发送)
receive方法此处有一个DatagramPacket的参数,但是这里填的相当于是一个空对象,receive方法内部会对参数的这个空对象进行内容填充(将接收来的数据填充),从而构造出结果数据,参数也是一个“输出型参数” 如果没有接收到数据 会进行阻塞等待
send方法也有一个DatagramPacket参数,这里的参数就有数据了,从此套接字发送数据报
最后一个使用需用方法就是close 关闭此数据报套接字
DatagramPacket是UDP socket发送和接收的数据报
DatagramPacket表示UDP中传输的一个报文,构造这个对象可以指定一些具体的数据进去,这些数据后面实现UDP的时候会展示
(1)2个参数版本的构造方法
把buf这个缓冲区给设置进去,数据存储先放在缓存区上,既然是文件就需要涉及到文件操作,当然内部会自己处理,我们直接使用网络编程相关API就可以了(最后一个参数是缓冲区设置的长度)
(2)4个参数版本的构造方法
构造缓冲区、起始位置、缓冲区长度、地址(这个地址包括两个内容IP地址 和 端口号port)
这里提到一个InetSocketAddress 的API 它也有自己得构造方法,里面包含两个参数一个IP地址,另一个是端口号
我们这里做的是自己本机电脑的服务器,就是本机连接本机 称为回显服务器(echo server)
一个普通的服务器的构造需要涉及这几步:收到请求,根据请求计算响应,返回响应
Echo server 省略了其中的“根据请求计算响应”,实现的这个程序是请求啥返回啥,主要针对socket API基本用法的理解,这里不写“请求计算响应”不是它不重要,它是最重要的,但是实现需要对应的业务,这个等socket会用了再实现一个比较简单业务给友友们理解
提示:我们这里自定义了一个类 叫做: UdpEchoServer 以下UDP服务器代码都是包含在这个类中
第一步:
网络编程操作本身就是操作网卡的,但是网卡不方便直接操作,在操作系统内核中,使用了一个特殊的叫做“socket”这样的文件来抽象表示网卡,因此进行网络通信是势必要有一个Socket
private DatagramSocket socket=null;
第二步:
这里是我们自己实现一个UDP版本的服务器,socket当前置空的原因是为了在该类的构造方法中添加输入自己的端口号,创建socket对象的同时,给他绑定一个端口号;
public UdpEchoServer(int port) throws SocketException {
//构造方法中有一个参数就是 端口号 在socket创建对象的时候传入端口号
socket=new DatagramSocket(port);
}
为什么要关联一个具体端口号???
因为客户端是需要通过服务器的端口号来给服务器发送请求,那前面不是说如果不分配的话操作系统会分配一个随机的端口号吗?如果是这样的话,客户端就不知道这个端口号是什么了,也就没有办法进行通信了。
第三步:
思路:
(1)创建一个执行start方法,因为客户端肯定是不会就发发送一次请求的,会有多次,所以服务器一般都是7*24小时工作的,这里会写一个while(true)循环保持服务器不会断开.
(2)那循环内需要的就是网络所需的基础 : 接收请求 , 根据请求计算响应 , 返回响应(这里我们细说)
接收请求:
针对UDP来说传输数据的基本单位是"数据报" 对应的类就是DatagramPacket,首先就是读取客户端的请求,需要使用receive方法 ,该方法的参数是一个输出型参数,这个参数就要是一个空的DatagramPacket对象,需要DatagramPacket对象创建对象
DatagramPacket对象中传了两个参数 ,一个缓冲区,另一个该对象缓冲区的的长度;创建对象后传参给receive,如果客户端有数据传入receive内部会针对参数对象填充数据(该数据来自网卡),如果客户端没有数据传入receive会进行阻塞
根据请求计算响应:
此时DatagramPacket是一个特殊的对象,并不直接进行数据处理,可以把来面的数据转化为字符串进行处理
获取字符串之后,就可以进行处理了(这里获取字符串的原因就是为了便于我们做处理);
因为我们这里是走一下流程,所以没有写请求处理,知道要写这个就行.
返回响应:
把响应写回客户端,使用方法 send的参数也是DatagramPacket 对象,需要把返回对象构造好,此处构造对象就不在是空字节数了,而是使用响应数据来构造的
到了这里UDP的客户端就写完了.(以上都是图片方便看解析,下面附有代码)
public void start() throws IOException {
System.out.println("服务器启动");
//服务器不是只给一个客户端提供服务系统就完了,一定会有很多的客户端
while(true){
/*
* 只要客户端来了,就提供服务
* 1、读取客户端发来的请求是啥
* receive 方法的参数是一个输出型参数 需要先构造一个空白的DatagramPacket对象 ,交给receive来进行填充
* */
DatagramPacket requestPacker=new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacker); //将接受传来的数据放入的当前的缓冲区
//此时这个 DatagramPacket 是一个特殊的对象, 并不方便直接进行处理 可以把这里包含的数据拿出来,构造成一个字符串
String requset =new String (requestPacker.getData(),0,requestPacker.getLength());
//2、根据请求计算响应, 由于此处是回显服务器 ,响应和请求相同
String response=process(requset);
/*
* 3、把响应写回到客户端 ,send的参数也是 DatagramPacket 需要把这个 Packet对象构造好
* 此处构造的响应对象 不能是用空的字节数 构造了,而是要使用响应数据来构造
* */
DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacker.getSocketAddress());
socket.send(responsePacket);
//4、打印一下, 当前这次请求响应处理中间结束
System.out.printf("[%s: %d] req:%s ,resp:%s\n",responsePacket.getSocketAddress().toString(),
responsePacket.getPort(),requset,response);
}
}
public String process(String requset) {
return requset;
}
最后一行还进行了打印,这里也对其进行解释:
main方法操作:
这里的客户端是为了连接上面的服务器,待这里客户端写好以后友友们就可以自行尝试。
提示:这里客户端创建一个自定义类我这里叫做: UdpEchoClient 以下所有UDP客户端代码都在这个类中
第一步:
创建我们需要的变量 UDP客户端也需要socket文件来传输数据 ,需要创建一个 socket对象,同时还要知道服务器的IP地址和 端口号才能发送数据。
private DatagramSocket socket=null;
private String serverIP=null;
private int serverPort=0;
/*
* 一次通信 ,需要有两个 IP 两个端口
* 客户端 IP 是 127.0.0.1
* 客户端的 port 是系统自动分配的
* 服务器 IP 和 端口 也需要告诉客户端 ,才能顺利把这个消息发给服务器
* */
/*
* 客户端这里为啥没有 个一个具体的端号 因为服务器不是本机 客户的主机上可能已经运行了很多的程序就会导致你设置的端口 号是被重复,会产生冲突
* */
public UdpEchoClient(String serverIP ,int serverPort) throws SocketException {
socket=new DatagramSocket();
this.serverIP=serverIP;
this.serverPort=serverPort;
}
这里可能会有友友们问到为啥服务器有自己自己的端口号,这里为什么没有提到客户端具体的端口号??
答案:客户端的端口号是系统自动分配的(分配一个空闲的端口号),那可以自己给客户端定义一个具体的端口号吗?勉强可以,因为客户端上有可能有很多程序,每个程序都有自己的端口号,如果自己定义就可能会产生冲突;
举个例子:你去吃饭,人多的时候就会给你一个牌牌,叫到你,你来领餐,但是此时你就是想要66号牌牌,但是可能这个牌牌已经被人领走了(该饭店就是一个客户端,餐牌就是端口号,客户端会系统自动分配端口号,你强行要一个餐牌就可能已经被(其他程序)拿走了造成冲突)
第二步:
写一个start方法进行运行 :思路服务器大同小异(反向操作)
while(true)循环中执行:写数据进行判断结束、发送数据、接收数据、控制台打印
写数据进行判断:
发送数据:
接收数据:
控制台打印:
以上图片解析比较零散,以下附有以上解释的代码
public void start() throws IOException {
System.out.println("客户端启动 ");
Scanner scanner=new Scanner(System.in);
while(true){
//1、从控制台读取发送的数据
System.out.println("> ");
String request=scanner.next();
if(request.equals("exit")){
System.out.println("结束");
break;
}
/*
* 2、构造成UDP 请求 并发送
* 构造这个Packet 的时候 ,需要把serverIP 和 port 都传入过来 但是此处IP地址是一个32位的二级制的整数
* 上述的IP地址是一个字符串 需要使用 InetAddress.getByName来进行一个转换
* */
DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(serverIP),serverPort);
socket.send(requestPacket);
//3、读取服务器的UDP 响应 并解析
DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
String response=new String(responsePacket.getData(),0,responsePacket.getLength());
//4、把解析好的结果显示出来
System.out.println(response);
}
}
main方法操作:
和UDP服务器操作基本相同,但是需要 服务器的IP地址和服务器的端口号
首先这里先说怎么检测你的服务器和客户端都是写好的可以运行了:
以上连接操作是怎么进行,代码详细分解
以下带有业务的服务器继承了上面UDP版本服务器进行的
//字典服务器 用来继承既可以
//DictServer 来说 和 EchoServer 相比 大部分的东西都是一样的
//主要是请求计算机响应是不一样的
public class UdpDictServer extends UdpEchoServer {
//这里是一个简单的翻译 使用到了Map 第一个泛型参数是接收单词 第二个泛型参数是 翻译过来的汉语
private Map dict=new HashMap<>();
//应用服务器这里需要继承父类 构造方法里面就可以写对应的东西
public UdpDictServer(int port) throws SocketException {
super(port);
/*
* 给当前 继承服务器 设置内容
* 此处可以无限键值对 以下就是翻译内容 当然这里只是为了方便友友们看, 实际自己想实现点简单的业务也是可以的
* */
dict.put("cat","小猫");
dict.put("dog","小狗");
dict.put("fuck","卧槽");
}
//继承了也就需要 重写 UdpEchoServer的方法
public String process(String request){
//查词典的过程
return dict.getOrDefault(request,"当前单词没有查到结果");
//这里就是 看是否是默认值 , 如果有就是用当前值 如没有的话就是用默认值
}
public static void main(String[] args) throws IOException {
//本次只需要掉用该服务器就可以了
UdpDictServer server=new UdpDictServer(9090);
server.start();
}
}
这里就是简单的业务实现,上面给了注释,代码不多也很好理解,重点理解前面的服务器和客户端代码,这里就不难看懂了。
以下演示以下,该带有业务的服务器跑起来 与客户端 联用
(1)服务器的端口 是固定指定的,目的是为了方便客户找到服务器程序
(服务器是我们自己手里的机器,上面运行啥,都是我们控制的,指定安排空闲端口即可)
从哪里看 ,可以有两个地方看: 任务管理器友友们应该都很熟悉(Ctrl+alt+del 后点击任务管理器)以下点击就能查看PID(Process ID)其实是进程号(也是独一无二的)
另一种方法就是命令行查看 涉及cmd命令 netstat -ano (命令行打开操作: win+r 弹出框输入cmd)
这里只截取了一部分作为演示,能理解就行
(2)客户端的端口 是由系统分自动分配的,如果手动指定,可能能会造成其他程序的端口的冲突
(客户端上的程序是不可控的,系统分配是最好的,服务器上的端口号是可控的,因为程序员可以自己看见,或者自查)
(3)端口冲突
端口冲突会有以下提醒 ,这里以服务器作为当前的端口冲突来解释
上面已经提及了两个服务器,一个UPD版本的服务器,另一个翻译业务的服务器 两个服务器同时用一个9090端口号就会产生冲突
TCP API主要涉及两个类
ServerSocket 专门给服务器使用的Socket对象
Socket 是既能给 客户端是用也能给服务器使用 (知道这里不是很好理解,往后面看结合代码理解)
TCP和UDP传输数据不一样 UDP是“数据报” 但是TCP不是 而是以“字节”的方式 流式传输
ServerSocket一个参数构造方法
创建一个服务器套接字Socket,并绑定到指定端口
使用方法就有所变化 ,检测方法Socket.accept()开始监听指定端口,有客户端;连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接 ,否则阻塞等待 ,这里明显与UDP的服务器不同了,体现了TCP的有连接的特性
关闭此套接字 close()方法
这里相当于是连接,看是否连接上客户端,TCP 有一个特性就是有连接的,例如电话一样,需要有人接,所以这个accept就是监听有没有人接电话,没有的话就先进行阻塞(这里只是以电话举例,不是说电话就是这么搞的)
Socket在服务器这边,是由accept返回的,在客户端这边,咱们代码里构造的时候制定了一个IP地址和端口号,有了这两个信息就能和服务器建立连接了。
既然是以字节的方式进行传输的,那就会涉及到IO文件操作 InputStream对应getInputStream方法和OutputStream对应的getOutputStream方法 进一步通过Socket对象,获取到内部的流对象,借助流对象来进行发送接收
提示:这里需要我们自定义一个类叫做:TcpEchoServer来写TCP版本服务器的代码
第一步:就是创建一个ServerSocket 对象 创建一个构造方法进行传参一个具体的端口号
//相当于是 接收客户来
private ServerSocket serverSocket=null;
public TcpEchoServer(int port) throws IOException {
//传入一个端口号
serverSocket =new ServerSocket(port);
}
第二步:那服务器ServerSocket对象已经创建好了,需要一个方法来进行服务器start方法
public void start() throws IOException {
System.out.println("启动服务器");
while(true){
//使用这个 clientSocket 和 具体的的客户端进行交流
//具体进行服务器 这里用作监听 客户端是否上线
Socket clientSocket =serverSocket.accept();
//如果上线 accept方法 阻塞结束 进行连接操作
processConnection(clientSocket);
}
}
每次创建或者返回一个 Socket对象 ,(Socket就是文件)每次创建一个clientSocket对象,就要占用一个文件描述符表,使用完毕之后就会进行“释放” ,但是不建议直接在这里“释放”,在finally里面“释放”
到了这里就会友友问 :ServerSocket 与Socket都是创建对象这不是多此一举吗?答案:这里其实我也不知道(凡是存在必有道理),我们先按照这样的方法来。
但是这里还是要说一下,这里该如何理解。
第三步:processConnection如何来连接客户端操作
思路:
(1)可以显示一下 因为已经接收到了客户端 ,那这里打印一次客户端已上线
(2)Socket返回套接字输入流,返回套接字输出流,因为TCP是流式操作的
(3)处理肯定不是一个请求和响应所以需要循环操作,之后读取请求,判断请求数据是否存在,不存在那连接也就断开了 ,如果存在跳过即可,进行数据输入
(4)根据请求构造响应
(5)这里返回响应结果想要是一个字符串,但是接收来的是字节流 需要 稍作转换,转换成字符串
(6)服务器打印
以下是附上processConnection代码
private void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d] 客户端上线! \n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
//基于上述socket 对象和 客户端 进行通信
//以下使用 try() 是java语法 try with resources
try (InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream=clientSocket.getOutputStream()){
//由于要处理多个请求和 响应 也是使用 循环进行的
while(true){
//1、读取请求
Scanner scanner=new Scanner(inputStream);
if(!scanner.hasNext()){
System.out.printf("[%s:%d]客户端下线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
//注意 !此处使用next 是一直 读取到换行符 或者 空格 或者 其他空白字符tab 但是 最终返回结果里不包含前面提到的这几个类别
String request=scanner.next();
//2、根据请求构造响应
String response=process(request);
//3、返回响应结果
//OutputStream 没有write String功能 这里需要转换 可以把String 里的字节数组转换出来 进行写入
//也可以用字符流来转化一下
PrintWriter printWriter=new PrintWriter(outputStream);
//此处使用 println来写入 让结果带有一个 \n 换行 方便对端来接收解析
printWriter.println(response);
//前面IO文件操作 中知道在字符写文件的时候 是需要刷新的所以这里也会进行
printWriter.flush();
System.out.printf("[%s:%d] req:%s ,resp :%s \n",clientSocket.getInetAddress().toString(),clientSocket.getPort()
,request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
//此处需要处理一下
clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public String process(String request) { //这里也只是走个过程,并没有真的对请求进行处理
return request;
}
main方法操作:
提示:自定义一个TCPEchoClient类 以下TCP客户端代码都在该类中
如果友友们,从前面看到这里了,基本不管是创建服务器还是创建一个客户端都是创建Socket对象,TCP客户端代码就没有TCP服务器代码麻烦了。
(1)创建Socket对象 写一个构造方法接收服务器的IP地址和 端口号
//定义一个Socket对象用来接收 IP地址和 端口号
private Socket socket=null;
public TcpEchoClient(String serverIp ,int serverPort) throws IOException {
//Socket构造方法 能够识别点分十进制格式的 IP地址 比DatagramPacket更方便
//new 这个对象的同时 就会进行TCP连接操作
socket=new Socket(serverIp,serverPort);
}
(2)写一个客户端启动start方法
客户端:用户写入数据、接收套接字输入流和输出流、连接结束的判断 、数据发送、接收数据
此处就不在详细解释了,基本和以上代码相似,代码配有详细的注释
public void start(){
System.out.println("客户端启动");
Scanner scanner=new Scanner(System.in);
//接收套接字的输入流 和 输出流
try(InputStream inputStream=socket.getInputStream();
OutputStream outputStream=socket.getOutputStream()){
while(true){
System.out.println(">");
//1. 先从键盘上读取用户数据内容
String request=scanner.next();
//判定输入一个算是解除连接 我们这里给了一个exit 表示结束连接 就相当于是 挂电话的按键
if(request.equals("exit")){
System.out.println("goodbye");
break;
}
//2 把读到的内容构造请求发送给服务器 这里和TCP服务器是一样的 都需要进行转换将字节转化为字符串
PrintWriter printWriter=new PrintWriter(outputStream);
//发送给服务器 printWriter 的 println方法本身就会起到发送的作用
printWriter.println(request);
//此处需要刷新缓冲区 确保数据是发出去了,
printWriter.flush();
//3 服务器的响应 Scanner本身就参数就是字节输入流参数
Scanner respScanner=new Scanner(inputStream);
//进行读取
String response=respScanner.next();
//4 把响应内容显示到界面上
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
代码执行作以下分析:
以上代码是写完了,但是这里还有几个想要提醒要点
(1)疑惑:其中使用了printWriter类来转换字节流变成字符串是否还是同一个文件描述符表,OutputStream相当于一个文件描述符(socket文件)通过OutputStream就可以往这个 文件描述符表中写入数据,但是当前的OutputStream自身方法不方便 写字符串需要进行转换 ,PrintWriter对象来表示(对应的是同一个文件描述符表)
(2)以上能看发送是通过PrintWriter类中println方法进行发送的,细节问:如果不用println,直接用print发送行不行???
答案:不行(图解)
连接之后就发现问题了,不能多个客户端进行连接,如何解决呢?答案:多线程
以前面的TCP服务器作为地基 这里有start方法内部需要稍作修改即可
public void start() throws IOException {
System.out.println("启动服务器");
while(true){
//使用这个 clientSocket 和 具体的的客户端进行交流
//具体进行服务器 这里用作监听 客户端是否上线
Socket clientSocket =serverSocket.accept();
//如果上线 accept方法 阻塞结束 进行连接操作
Thread thread=new Thread(()->{
processConnection(clientSocket);
});
thread.start();
}
}
为什么多线程操作加在这里不用过多的解释,那多线程为什么不包含以下的代码呢
Socket clientSocket =serverSocket.accept();
这里在while循环中,所以每次多线程执行并且在当前while循环中结束,再进行下一次while循环
多线程已经能解决问题了,但这里还不打算演示,因为我们的客户端最多也就只能开几个,确实带的动,C10K(1w客户端)问题解决了,但是如果更多呢C10M(1kw客户端)问题呢,创建或者销毁这么多线程也是要有开销的,多线程提到开销问题优化措施就会想到线程池。
修改多线程修改代码位置相同
public void start() throws IOException {
System.out.println("启动服务器");
//创建线程池 这里创建的不是固定数量线程的池子, 是根据工作需要创建的线程池
ExecutorService service=Executors.newCachedThreadPool();
while(true){
//使用这个 clientSocket 和 具体的的客户端进行交流
//具体进行服务器 这里用作监听 客户端是否上线
Socket clientSocket =serverSocket.accept();
//如果上线 accept方法 阻塞结束 进行连接操作
//提交任务
service.submit(()->{
processConnection(clientSocket);
});
}
}
这里就没有什么过多的解释了,如果对线程池不是很理解的友友(可以看下这篇博客)
现在演示,使用多线程之后,客户端有怎么样的结果
注 :TCP版本服务器和客户端没有写相关业务,友友们可以模仿这UDP翻译业务也给TCP写一个,基本是一样的写法,这里再TCP版本中就不在重复进行了。