Socket编程——TCP&UDP
一、Socket套接字概述
- 网络上具有唯一标识符的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
- 通信的两端都有Socket。
- 网络通信其实就是Socket通信。
- 数据在两个Socket之间通过IO流传输。
- Socket在应用程序中创建,通过一种绑定机制与驱动程序建立关系,告诉自己IP和端口号
可以把主机当做港口,Socket就是码头,端口号就是码头号,不同的应用程序使用不同的端口,码头之间通过IO流传输Data,如图:
二、Java进行TCP通信
1.流程:
- 连接阶段
服务端——创建ServerSocket对象(指定port),监听并等待客户端连接。
客户端——创建Socket对象(指定服务端ip,port),与服务端取得连接。
服务端——连接成功时serverSocket的accept()方法返回一个Socket对象。 - 传输阶段
服务端:通过PrintWriter获取socket套接字的输出流,BufferedReader获取socket套接字的输入流。
客户端:通过PrintWriter获取socket套接字的输出流,BufferedReader获取socket套接字的输入流。
PrintWriter使用方法:
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader使用方法:
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
2.代码
(1)TcpServer.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务端代码
*/
public class TcpServer {
public static void main(String[] args) {
try {
//创建ServerSocket监听器
ServerSocket serverSocket = new ServerSocket(1081);
//阻塞,等待客户端连接。accept()方法返回一个Socket类
Socket socket = serverSocket.accept();
//通过PrintWriter获取客socket套接字的输出流
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
//通过BufferedReader获取socket套接字的输入流
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//从客户端读取数据
String name=in.readLine(),id=in.readLine(),userIn;
System.out.println("收到客户端消息:姓名:"+name+",学号:"+id);
//发送数据到客户端
out.println(name+"同学,我已接受到你的申请");
while ((userIn = in.readLine()) != null) {
System.out.println("收到客户端消息:" + userIn);
//发回客户端
switch(userIn)
{
case "你好骚啊" : out.println("你也是");break;
case "我王境泽就是饿死" : out.println("真香");break;
case "窝窝头,一块钱四个" : out.println("嘿嘿!");break;
case "鸡你太美" : out.println("全民制作人们大家好,我是练习时长两年半的蔡徐坤,喜欢唱跳rap篮球,music~");break;
case "music" : out.println("鸡你太美~~");break;
case "听我的" : out.println("我不要你觉得,我要我觉得");break;
case "" : out.println("哈哈哈哈哈。你说啥?");break;
default : out.println("?");break;
}
}
//关闭监听器
serverSocket.close();
//关闭socket套接字
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
(2)UdpClient.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* 客户端代码
*/
public class TcpClient {
public static void main(String[] args) {
String hostname = "192.168.131.131";//127.0.0.1
//socket端口
int port = 1081;
Scanner userIn = new Scanner(System.in);
try {
//创建socket套接字
Socket socket = new Socket(hostname, port);
//使用PrintWriter获取socket输出流将数据发送到服务器
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
//使用BufferedReader获取socket输入流读取服务器返回的数据
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("请输入你的姓名和学号:");
String name=userIn.next(),id=userIn.next(),userInput;
System.out.println("客户端已发送:姓名:"+name+",学号:"+id);
//向服务端发送数据
out.println(name);out.println(id);
//从服务端获取数据
System.out.println("收到服务端回应:" + in.readLine());
//当用户输入exit时退出
System.out.println("请发送消息(输入exit退出):");
while (!"exit".equals(userInput = userIn.nextLine())) {
out.println(userInput);
System.out.println("收到服务端回应:" + in.readLine());
System.out.print("请发送消息(输入exit退出):");
}
//关闭socket
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.实验结果
先运行TcpServer,再运行TcpClient。
(1)实现两台Linux虚拟机的通信。如图:
(2)实现Windows和虚拟机的通信
这里面有一个很烦人的中文乱码问题,捣鼓很久后成功解决:
因为windows系统使用的是GBK编码,Linux系统使用的是UTF-8编码,所以服务端BufferedReader要设成GBK,客户端要设成UTF-8!!!!!
即:服务端
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(),"GBK"));
客户端
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
其他的都不变。结果如图:
三、Java进行UDP通信
1.流程:
(UDP不分服务端和客户端,因此以接收端和发送端称呼之)
- 接收端——创建DatagramSocket对象(指定port)。创建DatagramPacket对象(空箱,指定空buffer和buffer大小)。使用socket的recieve方法等待数据的接收。
- 发送端——创建DatagramSocket对象(不指定port)。创建DatagramPacket对象(数据装箱,指定buffer、buffer大小以及要送去的port的端口号)
我画了张图来形象的表示UDP的发送和接收过程:
2.代码
(1)UdpReceiver.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UdpReceiver
{
public static void main(String[] args)
{
try {
//创建接收端监听socket,需要指定监听端口,请注意,该端口务必与发送端设定 的目标端口一致
DatagramSocket socket = new DatagramSocket(1081);
//指定存放接收数据的缓存
byte[] buffer = new byte[1024];
//生成用来接收的DatagramPacket,这里与发送端不同,无需关系IP和端口
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
//调用receive方法接收数据,需要注意,该方法是一个阻塞型的方法,没有接收到数据包之前会一直等待
socket.receive(packet);
//获取数据内容
System.out.println("数据内容:"+new String(packet.getData(),0,packet.getLength()));
//获取数据长度
System.out.println("数据长度:"+packet.getLength());
//获取端口号
System.out.println("对端端口号是:"+packet.getPort());
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
(2)UdpSender.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UdpSender
{
public static void main(String[] args)
{
try{
//创建发送端Socket,因为发送端不关心发送端口,故无需设定端口
DatagramSocket socket = new DatagramSocket();
//生成待发送数据缓冲区并写入数据
byte[] buffer = "HelloWorld".getBytes();
//利用InetAddress获取目标IP地址,这里发送的时本机
InetAddress address = InetAddress.getByName("localhost");//192.168.131.131
/* 生成待发送的DatagramPacket,端口值必须与接收端的监听端口 地址一致*/
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address,1081);
//调用send方法发送数据
socket.send(packet);
socket.close();
} catch (Exception e){
e.printStackTrace();
}
}
}
3.实验结果
这是一次UDP收发信的结果(拆包信息)。如果想要实现前面TCP一样的来回发信只需重复的实现这样的一对代码即可。