网络通信基础

 如何使服务器端支持多个客户端同时工作?

    一个服务器端一般都需要同时为多个客户端提供通讯,如果需要同时支持多个客户端,则必须使用前面介绍的线程的概念。简单来说,也就是当服务器端接收到一个连接时,启动一个专门的线程处理和该客户端的通讯。


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class MulSocketClient {
    public static void main(String[] args) {
        Socket socket = null;
        InputStream inputStream = null;
        OutputStream outputStream = null;
        String serverIP = "127.0.0.1";
        int port = 10000;
        String[] out_data = {"client_first", "client_second", "client_third"};
        
        try {
            socket = new Socket(serverIP, port);
            outputStream = socket.getOutputStream();
            inputStream = socket.getInputStream();
            byte[] bytes = new byte[1024];
            for (int i = 0; i < out_data.length; i++) {
                // 发送数据
                outputStream.write(out_data[i].getBytes());
                // 接收数据并显示
                int n = inputStream.read(bytes);
                System.out.println(new String(bytes, 0, n));
            }
        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                outputStream.close();
                inputStream.close();
                socket.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

 服务器端改成接收到一个连接,便新启动一个线程响应,具体代码如下:

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

public class MulThreadSocketServer {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ServerSocket serverSocket = null;
        Socket socket = null;

        int port = 10000;
        try {
            serverSocket = new ServerSocket(port);
            System.out.println("The service has started ... ");
            while (true) {
                socket = serverSocket.accept();
                // start the thread
                new Thread(new LogicThread(socket)).start();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                serverSocket.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}

class LogicThread implements Runnable {
    InputStream inputStream = null;
    OutputStream outputStream = null;
    String[] data_out = {"server1", "server2", "server3"};
    String in_message;
    Socket socket;
    public LogicThread(Socket socket) {
        // TODO Auto-generated constructor stub
        this.socket = socket;
    }
    
    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();
            byte[] bytes = new byte[1024];
            for (int i = 0; i < data_out.length; i++) {
                int n = inputStream.read(bytes);
                in_message = new String(bytes, 0, n);
                System.out.println("Thread id "+Thread.currentThread().getId()+" recieve "+in_message);
                outputStream.write(data_out[i].getBytes());
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
                outputStream.close();
                socket.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
}
运行效果(先启动服务器端程序,然后执行两次客户端程序)(服务器端的打印结果):


The service has started ... 
Thread id 8 recieve client_first
Thread id 8 recieve client_second
Thread id 8 recieve client_third
Thread id 9 recieve client_first
Thread id 9 recieve client_second
Thread id 9 recieve client_third
运行效果(先启动服务器端程序,然后执行两次客户端程序)(客户端的打印结果):

server1
server2
server3

UDP方式的网络编程也在Java语言中获得了良好的支持,由于其在传输数据的过程中不需要建立专用的连接等特点,所以在Java API中设计的实现结构和TCP方式不太一样。当然,需要使用的类还是包含在java.net包中。
    在Java API中,实现UDP方式的编程,包含客户端网络编程和服务器端网络编程,主要由两个类实现,分别是:
    DatagramSocket,DatagramSocket类实现“网络连接”,包括客户端网络连接和服务器端网络连接。虽然UDP方式的网络通讯不需要建立专用的网络连接,但是毕竟还是需要发送和接收数据,DatagramSocket实现的就是发送数据时的发射器,以及接收数据时的监听器的角色。类比于TCP中的网络连接,该类既可以用于实现客户端连接,也可以用于实现服务器端连接。
    DatagramPacket,DatagramPacket类实现对于网络中传输的数据封装,也就是说,该类的对象代表网络中交换的数据。在UDP方式的网络编程中,无论是需要发送的数据还是需要接收的数据,都必须被处理成DatagramPacket类型的对象,该对象中包含发送到的地址、发送到的端口号以及发送的内容等。其实DatagramPacket类的作用类似于现实中的信件,在信件中包含信件发送到的地址以及接收人,还有发送的内容等,邮局只需要按照地址传递即可。在接收数据时,接收到的数据也必须被处理成DatagramPacket类型的对象,在该对象中包含发送方的地址、端口号等信息,也包含数据的内容。和TCP方式的网络传输相比,IO编程在UDP方式的网络编程中变得不是必须的内容,结构也要比TCP方式的网络编程简单一些。

    这里讨论一下Java中TCP方式与UDP方式的不同:这部分来自Java编程艺术
      UDP方式中,Datagram中必须指定股数据报的字节长度,这个长度不能超过64KB。而在TCP方式中则没有这个规定,传送的数据以流的形式,不必考虑其长度。因而UDP方式适用于数据长度固定而且字节长度小的情况;TCP方式适用于大批量集中式数据传输。
      UDP方式不保证数据报传输时的到达次序,而TCP方式则不存在这个问题。因而UDP方式适用于与数据接收次序无关的应用,TCP方式适用于有序数据流的传输。
      UDP方式无需进行专门的计算机间连接操作,而TCP方式在数据传输前必须首先建立计算机间的连接,而且这种连接必须在数据传输器件继续保持,否则抛出IOException。因而UDP方式用于无需监控实时连接状态的应用。
  UDP方式不能保证数据传输的可靠性,它适用于可靠度不高的数据传输应用,而且UDP方式速度快,消耗低,经常用于实时应用中。

    一个最简单的UDP传输的例子(客户端):

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

public class SimpleUDPclient {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        // 连接对象,DatagramSocket实现的就是发送数据时的发射器,以及接收数据时的监听器的角色。
        // 类比于TCP中的网络连接,该类既可以用于实现客户端连接,也可以用于实现服务器端连接。
        DatagramSocket socket = null;
        // 发送数据报对象,在UDP方式的网络编程中,无论是需要发送的数据还是需要接收的数据,
        // 都必须被处理成DatagramPacket类型的对象,
        // 该对象中包含发送到的地址、发送到的端口号以及发送的内容等。
        DatagramPacket send_packet = null;
        // 接收数据包对象
        DatagramPacket receive_packet = null;
        // 服务器IP
        String serverIP = "127.0.0.1";
        // 服务器端口号
        int port = 10000;
        // 客户端发送数据内荣
        String send_message = "Hello";

        try {
            // 该客户端连接使用系统随机分配的一个本地计算机的未用端口号。
            // 在该连接中,不指定服务器端的IP和端口,
            // 所以UDP方式的网络连接更像一个发射器,而不是一个具体的连接。
            // 当然,可以通过制定连接使用的端口号来创建客户端连接。
            socket = new DatagramSocket();
            // 将发送的内容转换为byte数组
            byte[] bytes = send_message.getBytes();
            // 将服务器IP转换为InetAddress
            InetAddress address = InetAddress.getByName(serverIP);
            // 构造发送数据报的内容
            send_packet = new DatagramPacket(bytes, bytes.length, address, port);
            // 发送数据报
            socket.send(send_packet);
            
            // 构造一个数据缓冲数组,该数组用于存储接收的服务器端反馈数据,
            // 该数组的长度必须大于或等于服务器端反馈的实际有效数据的长度
            byte[] re_bytes = new byte[1024];
            // 以该缓冲数组为基础构造一个DatagramPacket数据包对象
            receive_packet = new DatagramPacket(re_bytes, re_bytes.length);
            // 接收数据内容
            socket.receive(receive_packet);
            
            // 输出接收到的内容
            byte[] message = receive_packet.getData();
            // 缓冲区数组中只有一部分数据是反馈数据,
            // 所以需要使用DatagramPacket对象中的getLength方法获得有效数据的长度
            int length = receive_packet.getLength();
            System.out.println(new String(message, 0, length));
            
        } 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();
        } finally {
            socket.close();
        }
    }

}




     一个最简单的UDP传输的例子(服务器端):

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

public class SimpleUDPServer {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        // 连接对象,DatagramSocket实现的就是发送数据时的发射器,以及接收数据时的监听器的角色。
        // 类比于TCP中的网络连接,该类既可以用于实现客户端连接,也可以用于实现服务器端连接。
        DatagramSocket socket = null;
        // 发送数据报对象,在UDP方式的网络编程中,无论是需要发送的数据还是需要接收的数据,
        // 都必须被处理成DatagramPacket类型的对象,
        // 该对象中包含发送到的地址、发送到的端口号以及发送的内容等。
        DatagramPacket send_packet = null;
        // 接收数据包对象
        DatagramPacket receive_packet = null;
        // 要监听端口号
        int port = 10000;
        // 服务器发送数据内容
        String response_message = "World";

        try {
            // 建立连接,监听端口
            socket = new DatagramSocket(port);
            
            // 构造一个数据缓冲数组,该数组用于存储接收的服务器端反馈数据,
            // 该数组的长度必须大于或等于服务器端反馈的实际有效数据的长度
            byte[] re_bytes = new byte[1024];
            // 以该缓冲数组为基础构造一个DatagramPacket数据包对象
            receive_packet = new DatagramPacket(re_bytes, re_bytes.length);
            // 接收数据内容, 和TCP方式的网络编程类似,服务器端的receive方法是阻塞方法,
            // 如果客户端不发送数据,则程序会在该方法处阻塞
            socket.receive(receive_packet);
            
            // 输出接收到的内容
            byte[] message = receive_packet.getData();
            // 缓冲区数组中只有一部分数据是反馈数据,
            // 所以需要使用DatagramPacket对象中的getLength方法获得有效数据的长度
            int length = receive_packet.getLength();
            System.out.println(new String(message, 0, length));
            
            // 将发送的内容转换为byte数组
            byte[] bytes = response_message.getBytes();
            // 获得客户端连接的InetAddress
            InetAddress address = receive_packet.getAddress();
            // 获得客户端连接的端口号
            int client_port = receive_packet.getPort();
            // 构造发送数据报的内容
            send_packet = new DatagramPacket(bytes, bytes.length, address, client_port);
            // 发送数据报
            socket.send(send_packet);
        } 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();
        } finally {
            socket.close();
        }
    }
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值