Java中的网络编程,UDP,TCP,发送和接收数据
学习目标:
- 了解网络的概念
- 端口
- IP划分
- 子网掩码划分
- 套接字(Socket)
- UDP
- TCP
1、什么是网络
网络是由节点和连线构成,表示诸多对象及其相互联系。在计算机世界里,⽹络就是⼀种辅助双⽅或者多⽅能够连接在⼀起的⼯具。
2、为什么使用网络
1、 使⽤⽹络能够把多⽅连接在⼀起,然后可以进⾏数据传递。
2、 所谓的⽹络编程就是,让在不同的电脑上的软件能够进⾏数据传递,即网络进程之间的通信
什么是端口
端口就是计算机和外界进行数据交换和通信的出入口,操作系统有有65535个端口,大致可以分为两类,一类是固定端口,另一类是动态端口,固定端口一般被系统服务使用
IP划分
A类IP段 0.0.0.0 到127.255.255.255
B类IP段 128.0.0.0 到191.255.255.255
C类IP段 192.0.0.0 到223.255.255.255
子网掩码划分
A类的默认子网掩码 255.0.0.0 一个子网最多可以容纳1677万多台电脑
B类的默认子网掩码 255.255.0.0 一个子网最多可以容纳6万台电脑
C类的默认子网掩码 255.255.255.0 一个子网最多可以容纳254台电脑
套接字(Socket)
应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要 通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字 (Socket)的接口,区分不同应用程序进程间的网络通信和连接。
UDP
UDP全称User Datagram Protocol,中文名是用户数据报协议。它的特点是无连接,不安全,不可靠,但是速度快。通俗的说就是,它不管你接收到没接收到,只管发送。
UDP发送数据测试(由于测试工具版本较老,所以代码中使用gbk格式,测试地址为127.0.0.1)
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Scanner;
public class UDPSend {
public static final String CHARSET = "gbk";
public static final String IP = "127.0.0.1";
public static final int DES_PORT = 8888;
public static final int SELFPORT = 9999;
public static void main(String[] args) {
DatagramSocket ds = null;
try {
// ds = new DatagramSocket(new InetSocketAddress(IPAddress,port));
//创建DatagramSocket对象
ds = new DatagramSocket(SELFPORT);
System.out.println("服务已启动");
Scanner scanner = new Scanner(System.in);
//死循环来进行多次数据发送
while (true){
System.out.println("请输入要发送的数据:");
byte[] ms = scanner.nextLine().getBytes(CHARSET);
System.out.println("等待发送数据");
//获取UDP的报文对象
DatagramPacket datagramPacket = new DatagramPacket(ms, 0, ms.length,new InetSocketAddress(IP, DES_PORT));
//发送数据
ds.send(datagramPacket);
System.out.println("发送成功!!!");
}
} catch (SocketException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(ds!=null){
ds.close();
}
}
}
}
运行结果如下:
这里只测试了UDP的数据是如何进行发送的,下面我们来看看如何使用UDP协议来进行数据的接收
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UDPReceive {
public static final String CHARSET = "gbk";
public static final int SELFPORT = 9999;
public static void main(String[] args) {
DatagramSocket ds = null;
try{
//获取UDP协议对象
ds = new DatagramSocket(SELFPORT);
//创建一个字节流数组保存发送过来的数据
byte[] bf = new byte[1024];
//报文头对象
DatagramPacket dp = new DatagramPacket(bf,0,bf.length);
while (true){
//接收数据
ds.receive(dp);
//输入信息 通过new String()来转换为字符串
String s = new String(bf,0,bf.length,CHARSET);
System.out.println("UDP发送了"+s);
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(ds!=null){
ds.close();
}
}
}
}
结果如下:
那么如何使用UDP来同时进行数据的发送和接收呢
我们不妨这样去思考,当通过UDP发送数据的时候,同时要进行数据的接收,等同于接收和发送算同时在运行,前一个专题我们学了多线程和多任务,那么我们是不是可以通过线程来进行数据的接收,设置一个子线程来用作接收数据。主线程用来发送数据。
测试代码UDPSendReceive.java:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Scanner;
public class UDPSendReceive {
public static void main(String[] args) {
new UDPSendReceive().begin();
}
private void begin() {
DatagramSocket ds = null;
DatagramPacket dp = null;
try {
ds = new DatagramSocket(8888);
Scanner sc = new Scanner(System.in);
//启动一个子线程用来接收数据
ReceiveThread rt = new ReceiveThread(ds);
Thread t1 = new Thread(rt);
t1.start();
while (true){
// System.out.println("请输入要发送的内容:");
String msg = sc.nextLine();
byte[] buf = msg.getBytes();
dp = new DatagramPacket(buf,0,buf.length,new InetSocketAddress("127.0.0.1",9999));
if(msg.equals("")){
System.out.println("输入内容不能为空!!!");
} else {
ds.send(dp);
System.out.println("发送成功!!!");
}
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}if(ds!=null){ds.close();}
}
class ReceiveThread implements Runnable{
private DatagramSocket ds;
private DatagramPacket dp;
public ReceiveThread(DatagramSocket ds){
this.ds = ds;
}
@Override
public void run() {
while (true){
byte[] bf = new byte[1024];
this.dp = new DatagramPacket(bf,0,bf.length);
try {
ds.receive(dp);
String str = new String(bf,0,bf.length);
System.out.println("接收到信息:"+str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
测试代码UDPSendReceive2.java:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Scanner;
public class UDPSendReceive2 {
public static void main(String[] args) {
new UDPSendReceive2().begin();
}
public void begin(){
DatagramSocket ds = null;
DatagramPacket dp = null;
try {
ds = new DatagramSocket(9999);
Scanner sc = new Scanner(System.in);
//启动一个子线程用来接收数据
ReceiveThread rt = new ReceiveThread(ds);
Thread t1 = new Thread(rt);
t1.start();
while (true){
// System.out.println("请输入要发送的内容:");
String msg = sc.nextLine();
byte[] buf = msg.getBytes();
dp = new DatagramPacket(buf,0,buf.length,new InetSocketAddress("127.0.0.1",8888));
if(msg.equals("")){
System.out.println("输入内容不能为空!!!");
} else {
ds.send(dp);
System.out.println("发送成功!!!");
}
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}if(ds!=null){ds.close();}
}
class ReceiveThread implements Runnable{
private DatagramSocket ds;
private DatagramPacket dp;
public ReceiveThread(DatagramSocket ds){
this.ds = ds;
}
@Override
public void run() {
while (true){
byte[] bf = new byte[1024];
this.dp = new DatagramPacket(bf,0,bf.length);
try {
ds.receive(dp);
String str = new String(bf,0,bf.length);
System.out.println("接收到信息:"+str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
分别运行它们,用来进行收发测试,事实证明,当我们用线程来控制接收数据和发送数据是完全可行的,为了让他们可以一直进行发送和接收,,测试结果如下:
TCP
TCP:传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
刚才我们对UDP的收发进行了测试和运行,下面我门对TCP协议的收发做一个测试。
代码如下:
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class TestTCP {
/*
使用ServerSocket创建一个TCP服务器
*/
public static void main(String[] args) {
ServerSocket ss = null;
try {
//1、创建ServerSocket对象,注意,一般需要指定端口
// IP如果不指定,则默认获取本机的IP
ss = new ServerSocket(8888);
System.out.println("Server is running wait connection ! ");
//服务器等待客户端链接上
Socket ac = ss.accept();
System.out.println("有客户端连接上了");
//注意,Socket中如果使用了PrintWriter一定要刷新,
//如果后面没有写上true那么不会显示出发送的信息
PrintWriter pw = new PrintWriter(ac.getOutputStream(),true);
pw.println("Welcome");
} catch (IOException e) {
e.printStackTrace();
}finally {
if(ss!=null){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
那么如何通过TCP来进行数据的接收呢
- 通过使用Socket对象进行连接,设置后IP和端口号
- 然后通过IO流来接收发送的数据
- 最终对接收的数据进行输出
测试代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Socket;
public class TestClient {
/*
使用Socket对象完成通信
*/
public static void main(String[] args) {
Socket s = null;
try {
s = new Socket("127.0.0.1",8888);
// s.bind(new InetSocketAddress(9999));
System.out.println("已经成功连接!!");
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str = br.readLine();
System.out.println("服务器端发送数据"+str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
使用TCP来处理多客户端消息的收发
控制台版的多人聊天,互相发送的消息,大家都可以看到,当用户上线下线时会进行提醒。
Server端代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
public class TestTCPServer {
private List<ServerThread> users;
public static void main(String[] args) {
new TestTCPServer().begin();
}
public void begin(){
ServerSocket ss = null;
try {
ss = new ServerSocket(8888);
System.out.println("Server is running , wait connection......");
//创建一个集合用来保存信息
users = new ArrayList<ServerThread>();
while (true){
Socket accept = ss.accept();
//将Socket传进ServerThread中,重写ServerThread的构造
ServerThread serverThread = new ServerThread(accept);
//启动线程
new Thread(serverThread).start();
//将ServerThread中的信息添加到集合中
users.add(serverThread);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(ss!=null){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class ServerThread implements Runnable{
private Socket socket;
private BufferedReader br;
private PrintWriter pw;
private String name;
public ServerThread(Socket socket) throws IOException {
this.socket = socket;
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
pw = new PrintWriter(socket.getOutputStream(),true);
name = socket.getInetAddress().getHostAddress()+":"+socket.getPort();
}
//将消息遍历使用ServerThread中的PrintWriter推送给所有用户
public void send(String msg){
for(ServerThread st:users){
st.pw.println(name);
st.pw.println(msg);
}
}
//run()方法
@Override
public void run() {
send(name+" 上线了 ! ");
this.pw.println("欢迎"+name+" !");
try {
//接收信息
receive();
}catch (SocketException e){
System.out.println("客户端"+name+"异常下线");
//当有用户下线时,从集合中移除它
users.remove(this);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(this.socket!=null){
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void receive() throws IOException {
String str = null;
System.out.println("开始接收数据");
while ((str= br.readLine())!=null){
//当输入exit时进行下线处理。
if(str.trim().equalsIgnoreCase("exit")){
//释放资源(在真正的环境下需要对资源进行释放)
System.out.println("客户端"+name+"正常下线");
pw.println("exit");
users.remove(this);
break;
}else {
System.out.println("接收到来自于"+name+"的数据: "+str);
send(str);
}
pw.println(str);
}
}
}
}
Client端代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
public class TestTCPClient {
public static void main(String[] args) {
new TestTCPClient().begin();
}
public void begin(){
Socket s = null;
BufferedReader brr = null;
try {
s = new Socket("127.0.0.1",8888);
ClientThread ct = new ClientThread(s);
Thread task = new Thread(ct);
task.start();
while (true){
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str = br.readLine();
if(str.trim().equalsIgnoreCase("exit")){
System.out.println("按回车键退出");
//不推荐使用stop方法,建议自行进行书写判断结束进程的条件
task.stop();
break;
}
System.out.println(s.getInetAddress().getHostAddress()+":"+s.getPort()+"发送了:"+str);
System.out.println("输入要发送的数据:");
}
}catch (ConnectException e){
System.out.println("服务器连接失败,请联系网管");
}
catch (SocketException e){
System.out.println("连接中断");
}
catch (UnknownHostException e){
System.out.println("网络环境不稳定,请检查网络环境");
}
catch (IOException e) {
e.printStackTrace();
}finally {
if(s!=null){
try {
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class ClientThread implements Runnable{
private Socket socket;
private PrintWriter pw;
private BufferedReader br;
public ClientThread(Socket socket) throws IOException {
this.socket = socket;
this.pw = new PrintWriter(socket.getOutputStream(),true);
this.br = new BufferedReader(new InputStreamReader(System.in));
}
@Override
public void run() {
try {
sendMsg();
} catch (IOException e) {
e.printStackTrace();
}
}
private void sendMsg() throws IOException {
while (true){
System.out.print(">>>");
String str = br.readLine();
//发送到服务器
this.pw.println(str);
}
}
}
}
运行结果如图: