Java网络编程

一、UDP Socket编程

由于UDP是面向数据报的,我们需要用一个类来表示数据包,即DatagarmPacket.

DatagramPacket API

构造方法:

方法名方法说明
DatagramPacket(byte[] buf, int length)构造一个DatagramPacket来接收数据报,数据存储在buf数组中,长度为length.
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)构造一个DatagramPacket来发送数据报,发送的数据是从buf数组的offset位置开始往后length长度的数据,address指定目的ip和port

成员方法:

方法名方法说明
SocketAddress getSocketAddress()返回SocketAddress,一般是ip+port
int getPort()返回port
InetAddress getAddress()返回ip地址
byte[] getData()获取数据包中的数据

DatagramSocket API

我们为了是层间传输的数据更小,就引入了Socket,我们需要使用Socket来表示一些特定的信息.如UDP的Socket本质上是一个整数,用来代表的一端的会话关系。

构造方法:

方法名方法说明
DatagramSocket()一般是在客户端使用,创建一个UDP的Socket,端口号由系统分配
DatagramSocket(int port)一般是在服务器端使用,创建一个UDP的Socket,端口号手动分配

成员方法:

方法名方法说明
void receive(DatagramPacket p)此处的p是返回型参数,将收到的数据报放到p中,如果没有接受到,会阻塞等待
void send(DatagramPacket p)将数据报p发送

大致流程

简单通信程序

在这里我们实现一个简单的UDP客户端/服务器通信程序,这个程序中没啥业务逻辑,是一个回显服务器,服务器收到客户端的字符串后,原封不动的返回即可。

UDPEchoServer

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

public class UdpEchoServer {
    private DatagramSocket socket = null;

    //手动指定服务器的端口号
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    //通过start方法来启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        //服务器一般都是7*24小时运行着
        while(true){
            //1.读取请求并解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);
            //服务器没收到请求的话就会在这阻塞等待
            socket.receive(requestPacket);

            //2.根据请求计算响应
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            String response = process(request);

            //3.将响应发送给客户端
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), 0, response.getBytes().length, requestPacket.getAddress(), requestPacket.getPort());
            socket.send(responsePacket);

            //打印日志
            System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(), requestPacket.getPort(), request, response);
        }
    }

    public String process(String request){
        return request;
    }

    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(8080);
        server.start();
    }
}

UdpEchoClient

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    //客户端不需要手动指定
    private DatagramSocket socket = null;

    private String serverIp;

    private int serverPort;

    public UdpEchoClient(String ip, int port) throws SocketException {
        serverIp = ip;
        serverPort = port;
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        System.out.println("客户端上线!");
        //循环去发请求
        while(true){
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入:");
            String request = sc.next();

            //1.将服务器的地址和端口号放进数据报,然后发送
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), 0, request.getBytes().length, InetAddress.getByName(serverIp), serverPort);
            socket.send(requestPacket);

            //2.等待接受服务器发来的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
            socket.receive(responsePacket);

            String response = new String(responsePacket.getData(), 0, responsePacket.getData().length);
            System.out.println(response);
        }
    }

    public static void main(String[] args) throws IOException {
        new UdpEchoClient("127.0.0.1", 8080).start();
    }
}

二、TCP Socket编程

由于Tcp是面向字节流和有连接的,因此与Udp编程有一些差异,不过依旧是那三部曲:服务器读取请求并解析,根据请求计算响应,将响应返回给客户端。

Tcp的socket是用来标识通信的双方,是一个四元组,包含了:源ip、源port、目的ip、目的port。

ServerSocket API

ServerSocket是在服务器端使用的。

构造方法

方法名方法说明
ServerSocket(int port)创建一个服务器端的socket,并绑定到指定端口

普通方法:

方法名方法说明
Socket accept()守候自己的端口号上等待用户的连接,当有连接后,返回一个Socket对象,表示与客户端建立的连接。
void close()关闭socket

Socket API

这个socket类可以表示客户端Socket,也可以表示为服务器端接受到客户端连接的请求后,返回的服务器端Socket。

构造方法:

方法名方法说明
Socket(String host, int port)创建一个socket,与对应ip上的对应port建立连接

普通方法:

方法名方法说明
InetAddress getInetAddress()返回socket所连接的地址
InputStream getInputStream()返回socket的输入流
OutputStream getOutputStream()返回socket的输出流

大致流程

简单通信程序

TcpEchoServer:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoServer {
    ServerSocket serverSocket = null;

    //指定服务器端的port
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    //启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        //服务器往往是7*24小时一直运转~
        while(true){
            //获取一个和服务器端的连接
            Socket socket = serverSocket.accept();
            //处理连接
            processConnection(socket);
        }
    }
    public void processConnection(Socket socket){
        System.out.printf("[%s:%d] 客户端已经上线~\n", socket.getInetAddress().toString(), socket.getPort());
        //与udp同理三部曲,不过tcp是字节流,使用流对象来读请求和发响应
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){
            //为了方便,使用Scanner直接当做字符来处理
            Scanner scanner = new Scanner(inputStream);
            //客户端可能会多次发请求
            while(true){
                if(!scanner.hasNext()){
                    System.out.printf("[%s:%d] 客户端已经下线~\n", socket.getInetAddress().toString(), socket.getPort());
                    break;
                }
                //1、读取请求并解析
                String request = scanner.next();
                //2.根据请求计算响应
                String response = process(request);
                //3.将响应发送给客户端
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(response);
                //刷新缓冲区
                writer.flush();
                //打印日志
                System.out.printf("[%s:%d] req: %s, resp: %s\n", socket.getInetAddress().toString(), socket.getPort(), request, response);

            }
        } catch (IOException e){
            e.printStackTrace();
        }
    }

    public String process(String request){
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(8080);
        server.start();
    }
}

 TcpEchoClient:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    //客户端自己的socket
    private Socket socket = null;

    public TcpEchoClient(String ip, int port) throws IOException {
        //指定服务器的地址和端口号
        socket = new Socket(ip, port);
    }

    public void start(){
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){
            Scanner scannerConsole = new Scanner(System.in);
            while(true){
                //1.组织请求
                String request = scannerConsole.next();
                //2.将请求发送给服务器
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(request);
                writer.flush();
                //3.接受服务器端的响应
                Scanner scannerResponse = new Scanner(inputStream);
                String response = scannerResponse.next();
                System.out.println(response);

            }
        } catch (IOException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1", 8080);
        client.start();
    }
}

关闭连接

虽然Java有着垃圾回收机制,但是我们的服务器端一般是7*24小时运行着,而我们的代码中有着一个死循环,它一直在创建Socket对象,而每个socket对象与文件有关,也就是说每创建一个socket对象会占用一定的文件资源,因此我们需要去手动释放它。由于Socket类实现了closeable接口,我们可以使用try with resourse来关闭。

那么我们是否还需要去手动关闭Scanner和PrintWriter呢?不用,这是因为这两个里面持有的是inputStream和outputStream对象的引用,我们已经设置了自动关闭这两个对象,因此我不需要再关闭一次。

多个客户端访问

虽然我们代码现在是安全了,但是还存在一个问题,当我们来了多个客户端访问服务器的时候,由于我们在processConnection中写了一个while循环,那么当客户端A进来后,程序就会阻塞在这个循环中,直到客户端A断开连接后,服务器才能去服务器客户端B,因此我们需要使用多线程来处理并发。

很自然的我们可以写出这样的代码,然后就会出现如下错误:

这是因为我们主线程在执行完代码块的时候,我们的另一个线程去执行了processConnction方法,但是还没执行完,我们的主线程就自动调用了socket.close,于是就抛出了上述异常。

正确写法应该这样:

在processConnection中关闭连接,因为一个processConnection处理完了,也就相当于一个连接结束了,因此我们可以在这进行关闭。

不过手动创建线程会涉及到用户态和内核态的转变,我们还可以使用一个线程池来进一步优化效率

最终结果如下:

  • 27
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值