基本套接字TCP和UDP

时间:2021-07-13 11:02:56
TCP的Java支持

     协议相当于相互通信的程序间达成的一种约定,它规定了分组报文的结构、交换方式、包含的意义以及怎样对报文所包含的信息进行解析,TCP/IP协议族有IP协议、TCP协议和UDP协议现在TCP/IP协议族中的主要socket类型为流套接字(使用TCP协议)和数据报套接字(使用UDP协议)。

    TCP协议提供面向连接的服务,通过它建立的是可靠地连接。Java为TCP协议提供了两个类:Socket类和ServerSocket类。一个Socket实例代表了TCP连接的一个客户端,而一个ServerSocket实例代表了TCP连接的一个服务器端,一般在TCP Socket编程中,客户端有多个,而服务器端只有一个,客户端TCP向服务器端TCP发送连接请求,服务器端的ServerSocket实例则监听来自客户端的TCP连接请求,并为每个请求创建新的Socket实例,由于服务端在调用accept()等待客户端的连接请求时会阻塞,直到收到客户端发送的连接请求才会继续往下执行代码,因此要为每个Socket连接开启一个线程。服务器端要同时处理ServerSocket实例和Socket实例,而客户端只需要使用Socket实例。另外,每个Socket实例会关联一个InputStream和OutputStream对象,我们通过将字节写入套接字的OutputStream来发送数据,并通过从InputStream来接收数据。


TCP连接的建立步骤

客户端向服务器端发送连接请求后,就被动地等待服务器的响应。典型的TCP客户端要经过下面三步操作:

   1、创建一个Socket实例:构造函数向指定的远程主机和端口建立一个TCP连接;

   2.通过套接字的I/O流与服务端通信;

   3、使用Socket类的close方法关闭连接。


 服务端的工作是建立一个通信终端,并被动地等待客户端的连接。典型的TCP服务端执行如下两步操作:

     1、创建一个ServerSocket实例并指定本地端口,用来监听客户端在该端口发送的TCP连接请求;

     2、重复执行:

           1)调用ServerSocket的accept()方法以获取客户端连接,并通过其返回值创建一个Socket实例;

           2)为返回的Socket实例开启新的线程,并使用返回的Socket实例的I/O流与客户端通信;

           3)通信完成后,使用Socket类的close()方法关闭该客户端的套接字连接。

DEMO

  客户端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;

public class Exercise {
public void tcpExample(){
try {
Socket socket = new Socket("192.168.138.46",8344);
System.out.println("Connection ....to server sending echo string");
OutputStream out = socket.getOutputStream();
System.out.println("发送数据……");
out.write("测试连接并发送数据到服务器成功……".getBytes());
socket.close();
System.out.println("客户端已经关闭");

} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}


}
public static void main(String[] args) {
Exercise e = new Exercise();
//e.getInterfaceExample();
e.tcpExample();

}
}

服务端
package NET;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;

public class TCPServer {
//设置缓冲区的大小
private static final int BUFSIZE = 50;

public static void tcpsever(){
try {
ServerSocket serSocket = new ServerSocket(8344);
int recvMsgSize = 0;
int total=0;
byte[] receive = new byte[BUFSIZE];
while(true){
//此方法返回一个服务器的关联socket,用此进行数据交换
Socket clntSock = serSocket.accept();
SocketAddress socadd = clntSock.getRemoteSocketAddress();
System.out.println("clint address:"+socadd);
InputStream in = clntSock.getInputStream();
while((total=in.read(receive, recvMsgSize, BUFSIZE-recvMsgSize))!=-1){
recvMsgSize += total;
}
System.out.println("从客户端收到的数据:"+new String(receive));
clntSock.close();
System.out.println("关闭和此客户端的连接……");
}

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
public static void main(String args[]){
tcpsever();
}

}

基本套接字TCP和UDP基本套接字TCP和UDP

UDP的Java支持

    UDP协议提供的服务不同于TCP协议的端到端服务,它是面向非连接的,属不可靠协议,UDP套接字在使用前不需要进行连接。实际上,UDP协议只实现了两个功能:

    1)在IP协议的基础上添加了端口;

    2)对传输过程中可能产生的数据错误进行了检测,并抛弃已经损坏的数据。


    Java通过DatagramPacket类和DatagramSocket类来使用UDP套接字,客户端和服务器端都通过DatagramSocket的send()方法和receive()方法来发送和接收数据,用DatagramPacket来包装需要发送或者接收到的数据。发送信息时,Java创建一个包含待发送信息的DatagramPacket实例,并将其作为参数传递给DatagramSocket实例的send()方法;接收信息时,Java程序首先创建一个DatagramPacket实例,该实例预先分配了一些空间,并将接收到的信息存放在该空间中,然后把该实例作为参数传递给DatagramSocket实例的receive()方法。在创建DatagramPacket实例时,要注意:如果该实例用来包装待接收的数据,则不指定数据来源的远程主机和端口,只需指定一个缓存数据的byte数组即可(在调用receive()方法接收到数据后,源地址和端口等信息会自动包含在DatagramPacket实例中),而如果该实例用来包装待发送的数据,则要指定要发送到的目的主机和端口。


UDP的通信建立的步骤

UDP客户端首先向被动等待联系的服务器发送一个数据报文。一个典型的UDP客户端要经过下面三步操作:

    1、创建一个DatagramSocket实例,可以有选择地对本地地址和端口号进行设置,如果设置了端口号,则客户端会在该端口号上监听从服务器端发送来的数据;

    2、使用DatagramSocket实例的send()和receive()方法来发送和接收DatagramPacket实例,进行通信;

    3、通信完成后,调用DatagramSocket实例的close()方法来关闭该套接字。


由于UDP是无连接的,因此UDP服务端不需要等待客户端的请求以建立连接。另外,UDP服务器为所有通信使用同一套接字,这点与TCP服务器不同,TCP服务器则为每个成功返回的accept()方法创建一个新的套接字。一个典型的UDP服务端要经过下面三步操作:

1、创建一个DatagramSocket实例,指定本地端口号,并可以有选择地指定本地地址,此时,服务器已经准备好从任何客户端接收数据报文;

    2、使用DatagramSocket实例的receive()方法接收一个DatagramPacket实例,当receive()方法返回时,数据报文就包含了客户端的地址,这样就知道了回复信息应该发送到什么地方;

    3、使用DatagramSocket实例的send()方法向服务器端返回DatagramPacket实例。

UDP Socket Demo

这里有一点需要注意:

UDP程序在receive()方法处阻塞,直到收到一个数据报文或等待超时。由于UDP协议是不可靠协议,如果数据报在传输过程中发生丢失,那么程序将会一直阻塞在receive()方法处,这样客户端将永远都接收不到服务器端发送回来的数据,但是又没有任何提示。为了避免这个问题,我们在客户端使用DatagramSocket类的setSoTimeout()方法来制定receive()方法的最长阻塞时间,并指定重发数据报的次数,如果每次阻塞都超时,并且重发次数达到了设置的上限,则关闭客户端。


DEMO‘

package NET;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

import com.sun.jmx.snmp.InetAddressAcl;

public class UDP {
private final static int TIMEOUT = 3000;
private final static int BUFSIZE = 50;
private final static int tries = 5;
/**
* udp连接的客户端
* @throws SocketException
* @throws UnknownHostException
*/
public void udpclnt() throws SocketException, UnknownHostException{
int tril = 0;
DatagramSocket socket = new DatagramSocket();
String art = "UDP连接测试成功……";
byte[] by = art.getBytes();
InetAddress address = null;
address = InetAddress.getByName("lenovo-PC");
socket.setSoTimeout(0);
DatagramPacket send = new DatagramPacket(by,by.length,address,30000 );
try {
socket.send(send);

} catch (IOException e) {
tril++;
System.out.println("进行重发:剩余重发次数"+ (tries-tril));
}
socket.close();
}
/**
* udp连接的服务器
*/
public void udpServer(){
try {
DatagramSocket socket = new DatagramSocket(30000);
socket.setSoTimeout(TIMEOUT);
DatagramPacket receive = new DatagramPacket(new byte[50], BUFSIZE);
socket.receive(receive);
System.out.println("连接的客户端IP:"+receive.getAddress().getHostAddress()+"端口:"+receive.getPort());
byte[]re= receive.getData();
System.out.println("收到的数据:"+new String(re));
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String []args){
final UDP udp = new UDP();
Thread thread = new Thread(){
public void run(){
try {
udp.udpclnt();
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
thread.start();
udp.udpServer();

}
}


基本套接字TCP和UDP

  总结:

1、编写TCP客户端程序,在实例化Socket类时,要注意,底层的TCP协议只能处理IP协议,如果传递的第一个参数是主机名字而不是你IP地址,Socket类具体实现的时候会将其解析成相应的地址,若因为某些原因连接失败,构造函数会抛出一个IOException异常。

2、TCP协议读写数据时,read()方法在没有可读数据时会阻塞等待,直到有新的数据可读。另外,TCP协议并不能确定在read()和write()方法中所发送信息的界限,接收或发送的数据可能被TCP协议分割成了多个部分。说到这里,其实是与内部结构相关的

TCP在发送之后会把所有的数据复制到缓冲区中,而UDP则send之后所有的消息都在发送途中。

3、编写TCP服务器端的程序将在accept()方法处阻塞,以等待客户端的连接请求,一旦取得连接,便要为每个客户端的连接建立一个Socket实例来进行数据通信。

4、在UDP程序中,创建DatagramPacket实例时,如果没有指定远程主机地址和端口,则该实例用来接收数据(尽管可以调用setXXX()等方法指定),如果指定了远程主机地址和端口,则该实例用来发送数据。

5、UDP程序在receive()方法处阻塞,直到收到一个数据报文或等待超时。由于UDP协议是不可靠协议,如果数据报在传输过程中发生丢失,那么程序将会一直阻塞在receive()方法处,这对客户端来说是肯定不行的,为了避免这个问题,我们在客户端使用DatagramSocket类的setSoTimeout()方法来制定receive()方法的最长阻塞时间,并指定重发数据报的次数,如果每次阻塞都超时,并且重发次数达到了设置的上限,则关闭客户端。

6、UDP服务器为所有通信使用同一套接字,这点与TCP服务器不同,TCP服务器则为每个成功返回的accept()方法创建一个新的套接字。

7、在UDP程序中,DatagramSocket的每一次receive()调用最多只能接收调用一次send()方法所发送的数据,而且,不同的receive()方法调用绝对不会返回同一个send()方法所发送的额数据。

8、在UDP套接字编程中,如果receive()方法在一个缓冲区大小为n的DatagramPscket实例中调用,而接受队列中的第一个消息长度大于n,则receive()方法只返回这条消息的前n个字节,超出的其他字节部分将自动被丢弃,而且也没有任何消息丢失的提示。因此,接受者应该提供一个足够大的缓存空间的DatagramPacket实例,以完整地存放调用receive()方法时应用程序协议所允许的最大长度的消息。一个DatagramPacket实例中所运行传输的最大数据量为65507个字节,即UDP数据报文所能负载的最多数据,因此,使用一个有65600字节左右缓存数组的数据总是安全的。

9、在UDP套接字编程中,每一个DatagramPacket实例都包含一个内部消息长度值,而该实例一接收到新消息,这个长度值便可能改变(以反映实际接收的消息的字节数)。如果一个应用程序使用同一个DatagramPacket实例多次调用receive()方法,每次调用前就必须显式地将消息的内部长度重置为缓冲区的实际长度。

10、另一个潜在问题的根源是DatagramPacket类的getData()方法,该方法总是返回缓冲区的原始大小,忽略了实际数据的内部偏移量和长度信息。

11.TCP是需要连接的,UDP不用连接,是不可靠的传输,所以使用UDP要重排乱序消息或者处理丢失消息,因此这就加大了服务器的负担。但是之所以使用UDP是因为在传输信息很少时,TCP握手花费的时间基本是传输的2倍,还有在没有可靠性要求时,UDP很灵活。