TCP和UDP 在java上通信上的实现

TCP/IP 实现通信图示:

这里写图片描述
服务器端: ServerSocket
第一步: 服务器端启动一个ServerSocket, 绑定到特定的端口号上,并对该端口号进行监听,等待用户的请求

ServerSocket serverSocket = new ServerSocket(5566);

第二步: 调用accept() 方法,监听端口号, 如果没有用户发送请求连接,这个accept方法是会一致阻塞的,用户发送了请求之后就会被这个方法监听到,并返回一个发送方的socket,(类型是Socket)

socket = serverSocket.accept()

第三步: 建立了连接之后,就通过I/O的方式进行通信,做数据的传递

InputStream  In = socket.getInputStream(); //节点流
OutputStream out = socket.getOutputStream(); // 节点流

需要注意的一点是,服务器一直在监听,当它捕获到一个请求时,会为此请求启动一个线程,为此分配一个绑定到不同端口地址的套接字,而之前的那个端口号继续进行监听

客户端: Socket(套接字): 连接运行在网络上的两个程序间的双向通信的端点
第一步: 向服务器端发送连接请求(需要知道服务器的IP和端口号)

        Socket socket = new Socket(InetAddress.getLocalHost(), 5566);

第二步: 上一步建好连接之后,就通过I/O的方式进行通信

InputStream  In = socket.getInputStream(); //节点流
OutputStream out = socket.getOutputStream(); // 节点流

一个程序是只能占用一个端口号的,下面是一个小例子,通过线程的方式实现服务器端和客户端的通信,将读写操作分别建立线程

服务器端代码

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
// 
public class ServerSocketTest {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(5566);
        Socket socket;
        while (true){
            socket = serverSocket.accept(); // 如果没有客户端的请求,这一句是会一直阻塞的
            // 启动读线程
            serverReadThread readThread = new serverReadThread(socket);
            readThread.start();
            // 启动写线程
            User user = new User("liuxw",18,"server");
            serverWriteThread writeThread = new serverWriteThread(socket,user);
            writeThread.start();
        }
    }
}
class serverReadThread extends Thread {
    private Socket socket;
    private User user;
    public serverReadThread(Socket socket) {
        this.socket = socket;
    }
    public void run() {
        try {
            // try 语句不要写在循环里面,要尽量写在循环外面,因为异常对象的创建与处理消耗时间
            while (true) {
                ObjectInputStream in = new ObjectInputStream( socket
                        .getInputStream());
                ArrayList<User> list = (ArrayList<User>) in.readObject();
                System.out.println(list.get(0).getName());
                System.out.println(list.get(1).getName());
                // 如何关闭呢, 如果直接加上in.close() 这样的话就会频繁的进行创建销毁,代价较大
                // 解决方法,要么连接一直开着,要么定义一个特定字符来关闭流,要么使用连接池来管理连接
            }
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

class serverWriteThread extends Thread {
    private Socket socket;
    private User user;
    public serverWriteThread(Socket socket, User user) {
        this.socket = socket;
        this.user = user;
    }
    public void run() {
        try {
            // try 语句不要写在循环里面,要尽量写在循环外面,因为异常对象的创建与处理消耗时间
            while (true) {
                ObjectOutputStream objOut = new ObjectOutputStream(socket.getOutputStream());
                //  ObjectOutputStream 也是可以把一个集合写进去的,读的时候按照集合的方式来读就行了
                ArrayList<User> list = new ArrayList<User>();
                User user2 = new User("cao",22,"sss");
                list.add(user);
                list.add(user2);
                objOut.writeObject(list);
            }
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } 
    }
}

客户端程序:

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

public class ClientSocketTest {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket(InetAddress.getLocalHost(), 5566);
        ObjectOutputStream objOut = new ObjectOutputStream(socket.getOutputStream());
        User user = new User("wangsw", 20, "client");
        // 启动读线程, 其实这里的操作和服务器端的操作是一致的,因此我们就不再重复的写另外一个类
        serverReadThread readThread = new serverReadThread(socket);
        readThread.start();
        // 启动写线程
        serverWriteThread writeThread = new serverWriteThread(socket,user);
        writeThread.start();
    }
}

UDP 实现网络通信:
UDP 是面向的无连接的通信方式,因此UDP基本上是不区分服务器端和客户端的,但是我们在这里可以进行区分一下,我们把首先启动进行监听UDP报文消息的叫做服务器端,把先进行发送数据的叫做客户端。

为什么我会想着在这里做一下区分呢?这是因为我在写下面的测试程序的时候,发现两个用户如果启动的顺序不对,那么是收不到报文的,当时我就想为什么顺序不对会接收不到呢?
这是因为,监听的一方先启动,那么就会一直对网络进行监听,当有人向他发送消息时他能捕获到,但是当发送的一方先启动,那么报文传输到网络中,一定时间内找不到要发送的对象,那么就会发生报文丢失的现象

当时为了弄懂这个原因,我还看了帮助文档,却意外的发现了有一个connect方法,不是面向无连接的吗?为什么有一个connect连接方法,这个我会在下一篇中详细进行讲解。

现在先让我们来看看如何在java中实现UDP通信吧
java中使用DatagramSocket 和DatagramPacket 来实现UDP通信,Socket类是用来创建一个socket的,Packet是用来创建一个要发送的报文的。
socket.send(), socket.recieve()用来发送和接收数据包
这里写图片描述

需要注意的是,我们在发送数据包的时候,数据包里面要设置被发送方的IP和Port, 但是隐藏的我们自身的IP 和Port也会添加到这个数据包里面, 这样对方接收到我们发送的数据包,就能够从数据包里面获取到我们的IP和Port 然后就可以向我们发送数据包。

流程如下:
服务器端(先进行监听的一方)
第一步: 首选创建一个DatagramSocket,为其分配一个端口号,然后就会一直监听从这个端口号传进来的UDP数据包

DatagramSocket socket = new DatagramSocket(7000);

第二步: 创建一个DatagramPacket 包用来存储从该端口接收到的数据包,然后调用socket.receive() 方法,将收到的数据包放在创建的对象里。

        byte[] buff = new byte[100];
        DatagramPacket packetRecieve = new DatagramPacket(buff, 0, buff.length);
        socket.receive(packetRecieve);

第三步: 获取数据包中的信息,并且根据数据包中发送方的IP和Port向客户端发送数据包。

        String str = "world";
        DatagramPacket packetSend = new DatagramPacket(str.getBytes(),
                str.length(), packetRecieve.getAddress(),
                packetRecieve.getPort());
        socket.send(packetSend);

第四步:关闭socket

        socket.close();

客户端的代码和这个差不多,只不过顺序颠倒一下,先发送后接收。

下面给出了一个实现了UDP通信的小例子
服务器端(先监听的)

// UDP传输是没有IO的,每次发送的信息的包里都包含了要发送对象的IP 和端口号 ,而TCP、IP 一旦建立连接就通过流来读取接收数据
public class UDPServerTest {
    // 服务器端先接收消息,然后在发送消息
    public static void main(String[] args) throws Exception {
        // 自己开一个7000端口号,监听网络中的UDP数据包
        DatagramSocket socket = new DatagramSocket(7000);
        // 接收一个用户的packet
        // 首先定义一个packet,这个packet 用来存储接收到的packet
        byte[] buff = new byte[100];
        DatagramPacket packetRecieve = new DatagramPacket(buff, 0, buff.length);
        socket.receive(packetRecieve);
        System.out.println(new String(buff, 0, packetRecieve.getLength())); // 输出接收到的消息

        // 然后再向用户发送消息
        // 首先将要发送的数据包封装到一个packet里面,  通过接收到的包获取对方的Ip 和端口号
        String str = "world";
        DatagramPacket packetSend = new DatagramPacket(str.getBytes(),
                str.length(), packetRecieve.getAddress(),
                packetRecieve.getPort());
        socket.send(packetSend);
        socket.close();
    }
}

客户端(先发送的)

public class UDPClientTest {
    // 客户端先发送消息
    public static void main(String[] args) throws Exception {
        // 先new 一个 socket ,其中设置你自己的端口号
        DatagramSocket socket = new DatagramSocket(7001);
    //  socket.connect(InetAddress.getLocalHost(), 7000);
    //   socket.disconnect();
        // 将你要发送的消息,封装到一个data
        String str = "hello";
        // 注意的是packet包中都是字节数组,所以数据要转化为字节数组
        // 首先将消息封装到一个packet中,packet中包含了指定的对方的ip,port,还有发送给对方的消息(隐藏的:自己的ip,port也会在这个packet中)
        DatagramPacket packetSend = new DatagramPacket(str.getBytes(), str.length(), InetAddress.getLocalHost(), 7000);
        // 发送
        socket.send(packetSend);

        byte[] buff = new byte[100];
        DatagramPacket packetRecieve = new DatagramPacket(buff, 0, buff.length);
        socket.receive(packetRecieve);
        System.out.println(new String(buff, 0, packetRecieve.getLength())); // 输出接收到的消息
        socket.close();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章