在之前的文章《网络编程杂谈之TCP协议》中,我们阐述了TCP协议的基本概念,TCP作为一种可靠的、面向连接的数据传输协议,确保了数据在发送和接收之间的可靠性、顺序性和完整性,特点可以概括如下:
1、面向连接:在进行数据传输之前,TCP需要客户端和服务器之间建立一个连接,这个连接包括一系列的握手和协商步骤,以确保通信双方都准备好进行数据传输。
2、可靠性:TCP是一种可靠的协议,它使用各种机制来确保数据的可靠传输,包括数据分段的确认和重传机制,以及流量控制等多种手段。
3、顺序性:TCP保证数据段的到达顺序与发送顺序相同,即使数据在传输过程中被拆分成多个数据包,接收方也会将它们按照正确的顺序重新组装,比如说链接的一方发了ABC,那么接收的一方收到的也一定是ABC。
4、流量控制:TCP使用滑动窗口协议来实现流量控制,确保了发送方不会以超过接收方处理能力的速度发送数据,从而避免了数据丢失和网络拥塞。
5、拥塞控制:TCP还具有拥塞控制机制,它可以检测到网络中的拥塞并采取相应的措施来减轻拥塞,从而实现降低发送速率和重新发送丢失的数据包。
6、面向字节流:TCP是面向字节流的协议,它不会保留消息边界。这意味着接收方需要自行解析和分割接收到的字节流,以还原原始消息。
7、可靠的错误检测和纠正:TCP具有强大的错误检测和纠正机制,它可以检测并纠正在数据传输过程中出现的错误,以确保数据的完整性。
8、全双工通信:TCP支持全双工通信,所谓全双工是指建立连接后,通信双方可以同时发送和接收数据,而不需要等待对方的响应。
9、Socket(套接字):TCP使用端口号来标识不同的应用程序或服务,通信的两端通过IP地址和端口号来建立连接,而套接字(Socket)就是对其中任意一端的抽象,分为服务器端套接字(Server Socket)或客户端套接字(Client Socket),分别用于服务器和客户端的通信。
总的来说,TCP协议作用于传输层且适用于大多数需要可靠数据传输的应用程序,如文件传输、上位机通信等,并可以做为其他应用层协议的实现基础,如HTTP、MQTT等。
在代码实现层面,Socket是指一种编程接口(API),不同开发语言基本上都围绕Socket提供了一组用于创建、连接、发送和接收数据的API。下面我们以Java为例,通过java.net包下提供的Socket操作API与 java.io包下提供的IO操作API, 实现一个基本的TCP服务端与客户端的监听、链接并进行消息收发的示例。
服务端
在TCP服务端的实现中我们需要先定义一个ServerSocket对象,并实现对指定IP与端口号的绑定与监听,这里需要注意的是如果一直没有客户端链接,serverSocket.accept会一直处在阻塞状态,一旦有客户端链接事件发生才会向下执行,而服务需要满足与多个客户端进行链接,这也是为什么我们需要一个while (true)去一直轮询执行,因为我们其实是不知道客户端什么时候会链接上来的,下面的读写操作也是同一个道理, 所以为了不影响主线程accept新的客户端,我们把完成链接的Socket开启一个独立的线程或者抛给线程池来处理后续IO读写操作,这是一种典型的BIO(Blocking I/O)即阻塞IO的处理模式。
publicclassBioServer{
publicstaticvoidmain(String[]args)throwsIOException{
ExecutorServiceexecutor=newThreadPoolExecutor(2,4,1000,TimeUnit.SECONDS,newArrayBlockingQueue(10),
newThreadPoolExecutor.CallerRunsPolicy());
ServerSocketserverSocket=newServerSocket();
serverSocket.bind(newInetSocketAddress("127.0.0.1",9091));//绑定IP地址与端口,定义一个服务端socket,开启监听
while(true){
Socketsocket=serverSocket.accept();//这里如果没有客户端链接,会一直阻塞等待,一旦有客户端链接就会向下执行
executor.execute(newBioServerHandler(socket));//我们把链接Socket抛给线程池
}
}
}
读写操作是通过Socket获取InputStream与OutputStream来完成的,这里的Input与Output是站在你程序的视角来区分的,所以Input是收,Output是发,同理inputStream.read作为IO读操作也是阻塞的,程序只有接受到数据时才会向下执行,由于我们不确定Socket链接的读写操作何时发生,也只能依靠 while (true)轮询执行。
publicclassBioServerHandlerimplementsRunnable{
privatefinalSocketsocket;
publicBioServerHandler(Socketsocket){
this.socket=socket;
}
@Override
publicvoidrun(){
//TODOAuto-generatedmethodstub
try{
while(true){
byte[]r服务器托管网bytes=newbyte[1024];
InputStreaminputStream=socket.getInputStream();//通过IO输入流接受消息
intrlength=inputStream.read(rbytes,0,1024);//读操作阻塞,一旦接受到数据向下执行并返回接收到的数据长度
byte[]bytes=newbyte[rlength];
System.arraycopy(rbytes,0,bytes,0,rlength);
Stringmessage=newString(bytes);
System.out.printf("Client:%s%n",message);
PrintStreamwriter=newPrintStream(socket.getOutputStream());//通过IO输出流发送消息
writer.pr服务器托管网intln("HelloBIOClient");
}
}catch(IOExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
}
客户端
publicclassBioClient{
publicstaticvoidmain(String[]args)throwsIOException,InterruptedException{
Socketsocket=newSocket();
socket.connect(newInetSocketAddress("127.0.0.1",9091));
while(true){
if(!socket.isConnected()){
System.out.print("connecting...");
continue;
}
PrintStreamwriter=newPrintStream(socket.getOutputStream());
writer.write("HelloBIOServer".getBytes(StandardCharsets.UTF_8));
BufferedReaderreader=newBufferedReader(newInputStreamReader(socket.getInputStream()));
Stringmessage=reader.readLine();
System.out.printf("Server:%s%n",message);
}
}
}
通过上面代码大家能够对Java下TCP网络编程的开发、Socket的操作、BIO(阻塞IO)模型有了基本的了解,当然一个完整的TCP服务或客户端开发需要考虑的问题还有很多,如IO与线程模型、协议的制定、链接的管理、应用层报文粘包、半包等等,后续我们会在此基础上进行进一步的扩展与完善。
关注微信公众号,查看更多技术文章。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net