TCP和UDP
TCP和UDP他们在OSI分层中都位于传输层,为应用层提供支持。TCP属于面向连接的网络传输协议,客户端和服务器端需要建立稳定的连接,才能相互传输数据。而UDP是面向无连接的网络传输协议,客户端和服务器端不需要建立连接,客户端发送出去的数据包不管对方是否存在,都可以发送。
TCP
TCP(Transmission Control Protocol)传输控制协议属于一种稳定可靠的连接方式,客户端与服务器在建立连接之前需要进行三次握手。连接虽然稳定,但是由于数据传输过程复杂,导致传输效率较UDP低很多。通常运用在数据要求准确的场景下,如文件传输、网页显示、邮件传递等等。在java中TCP通过Socket和ServerSocket
两个类来实现TCP的功能。
1.TCP客户端Socket
//客户端类
public class TestSocketclient {
//构造方法
public TestSocketclient(String ip,int port){
try {
@SuppressWarnings("resource")
Socket socket = new Socket(ip,port);//指明服务器ip和端口号
//向服务器发送消息
String data = "hello server";
OutputStream os = socket.getOutputStream();//输出流就是向服务器发送数据的流
os.write(data.getBytes());
//接收来自服务器的消息
InputStream is = socket.getInputStream();//输入流就是接收来自服务器数据的流
byte[] serverinfo = new byte[1024];
int n = -1;
while((n=is.read(serverinfo))!=-1){
String string = new String(serverinfo,0,n);
System.out.println(string);
}
os.close();
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//启动方法,应该让服务器端先启动
public static void main(String[] args) {
try {
new TestSocketclient(InetAddress.getLocalHost().getHostAddress(),8888);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
2.TCP服务端ServerSocket
//服务器端
public class TestServerSocket {
//构造方法
public TestServerSocket(int port){
try {
ServerSocket ss = new ServerSocket(port);//监听端口
//接收来自客户端的数据,该方法accept()是阻塞方法,直到接到请求才往下执行。
Socket accept = ss.accept();
InputStream is = accept.getInputStream();
byte[] packet = new byte[1024];
int n = is.read(packet);
String string = new String(packet,0,n);
System.out.println(string);
//向客户端发送消息
OutputStream os = accept.getOutputStream();
os.write("hello client".getBytes());
os.close();
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//启动方法
public static void main(String[] args) {
new TestServerSocket(8888);
}
}
UDP
UDP(User Datagram Protocol)用户数据报协议属于一种不太稳定的数据传输协议,往往在传输过程中容易丢失数据报,但不会丢失太多,不影响总体数据的完整性。看上去UDP既然不稳定,还丢失数据,为什么还保留至今,没有被摒弃?那是因为它具有非常不凡的传输效率。应用也非常广泛,如网络游戏,虽不是所有数据都是UDP(如玩家属性)但其分量占比很大、QQ聊天工具、实时视频通话、视频会议等等。在java中使用DatagramSocket
类实现UDP功能。
1.UDP客户端
//客户端
public class TestUDPclient {
//构造方法
public TestUDPclient(String ip,int port){
try {
//客户端不用在socket上指明ip和port
DatagramSocket ds = new DatagramSocket();
byte[] buf = "nihao 服务器".getBytes();
int length = buf.length-4;//必须是绑定数据的大小,不能大于,可以小于,不能为负
InetAddress address = InetAddress.getByName(ip);
//在数据报上指明服务器的ip和port
DatagramPacket dp = new DatagramPacket(buf,length, address, port);
//发送数据
ds.send(dp);
//关闭套接字
ds.close();
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} 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) {
new TestUDPclient("localhost",7777);
}
}
2.UDP服务器端
//服务器端
public class TestUDPServer {
//构造方法
public TestUDPServer(int port){
try {
DatagramSocket ps = new DatagramSocket(port);//监听port端口是否有请求
byte[] b= new byte[1024];
DatagramPacket pd = new DatagramPacket(b, 1024);//准备一个空包接收数据用
//接收数据
ps.receive(pd);
//得到数据并打印
byte[] data = pd.getData();
System.out.println(new String(data,0,data.length));
//关闭套接字
ps.close();
} catch (Exception e) {
}
}
//启动方法
public static void main(String[] args) {
new TestUDPServer(7777);
}
}
3.补充
UDP在用DatagramSocket
类实现时客户端和服务器端都是使用相同的类,他们是通过是否配置了端口号和IP来区分服务器和客户端的。只要配置了端口和IP的DatagramSocket都可以充当服务器端。但是同一IP地址下端口号不能一样,不然会报端口占用的异常。谁先接收数据谁先启动
,以下是举例说明:
- 客户端
public class TestUDPclient {
public TestUDPclient(String ip,int port){
try {
//作为端口号为8888的服务器端
DatagramSocket ds = new DatagramSocket(8888);
byte[] buf = "nihao 服务器".getBytes();
int length = buf.length;
InetAddress address = InetAddress.getByName(ip);
DatagramPacket dp = new DatagramPacket(buf,length, address, port);
ds.send(dp);
//发送后再来接收数据
DatagramPacket dp2 = new DatagramPacket(new byte[1024], 1024);
ds.receive(dp2);
byte[] data = dp2.getData();
System.out.println(new String(data,0,data.length));
ds.close();
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} 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) {
new TestUDPclient("localhost",7777);
}
}
- 服务器端
public class TestUDPServer {
public TestUDPServer(int port){
try {
//作为端口号为7777的服务器端
DatagramSocket ps = new DatagramSocket(port);
byte[] b= new byte[1024];
DatagramPacket pd = new DatagramPacket(b, 1024);
ps.receive(pd);
byte[] data = pd.getData();
System.out.println(new String(data,0,data.length));
//接收完数据再发送,发送给本地端口号为8888的服务器端
DatagramPacket dp2 = new DatagramPacket("nihao".getBytes(),5,InetAddress.getByName("localhost"),8888);
ps.send(dp2);
ps.close();
} catch (Exception e) {
}
}
public static void main(String[] args) {
new TestUDPServer(7777);
}
}
进一步说明,其实每个应用在启动后,如果应用自己没有指定端口号,系统会自动为其分配一个端口号。但是我们在编程的时候出于方便,传输两端约定成俗,各自指定一个端口号。在客户端通过数据报DatagramPacket发送给服务器端,服务器接收到后,可以直接从数据报中获取客户端的端口号。代码如下
//服务器端
public class TestUDPServer {
public TestUDPServer(int port){
try {
DatagramSocket ps = new DatagramSocket(port);
byte[] b= new byte[1024];
DatagramPacket pd = new DatagramPacket(b, 1024);
ps.receive(pd);
byte[] data = pd.getData();
System.out.println(new String(data,0,data.length));
System.out.println("服务器端接收到的客户端的端口号:"+pd.getPort());
DatagramPacket dp2 = new DatagramPacket("nihao".getBytes(),5,InetAddress.getByName("localhost"),pd.getPort());
ps.send(dp2);
ps.close();
} catch (Exception e) {
}
}
public static void main(String[] args) {
new TestUDPServer(7777);
}
}
//客户端
public class TestUDPclient {
public TestUDPclient(String ip,int port){
try {
DatagramSocket ds = new DatagramSocket();
byte[] buf = "nihao 服务器".getBytes();
int length = buf.length;//必须是数据的大小,不能大于,可以小于,不能为负
InetAddress address = InetAddress.getByName(ip);
DatagramPacket dp = new DatagramPacket(buf,length, address, port);
ds.send(dp);
DatagramPacket dp2 = new DatagramPacket(new byte[1024], 1024);
ds.receive(dp2);
byte[] data = dp2.getData();
System.out.println(new String(data,0,data.length));
System.out.println("客户端接收到的服务器端的端口号:"+dp2.getPort());
ds.close();
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} 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) {
new TestUDPclient("localhost",7777);
}
}