socket实现TCP UDP

1、socket通信建立流程

1.1、创建服务端流程

  • 使用 socket 函数来创建 socket服务。

  • 使用 bind 函数绑定端口。

  • 使用 listen 函数监听端口。

  • 使用 accept 函数接收客户端请求。

1.2、创建客户端流程

  • 使用 socket 函数来创建 socket 服务。

  • 使用 connect 函数连接到 socket 服务端。

以下图表演示了客户端与服务端之间的通信流程:

1.3、创建服务端代码

package com.example.dyc.mysocket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MySocketServer {
    // 预定义字典
    private static final Map<String, String> dictionary;
    //字典初始化
    static {
        dictionary = new HashMap<>();
        dictionary.put("apple", "苹果");
        dictionary.put("pear", "梨");
    }
    //定义服务器端口,范围在[0, 65535]
    public static final int PORT = 8888;

    public static void main(String[] args) throws IOException {
        System.out.println(new Date() + ":" + 1);
        ServerSocket serverSocket = new ServerSocket(PORT);//从CLOSED到LISTEN状态
        System.out.println(new Date() + ":" + 2);
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        while (true) {
            System.out.println(new Date() + ":" + 3);
            Socket socket = serverSocket.accept();  // 阻塞,等待客户端发起连接,建立连接,到ESTABLISHED状态
            System.out.println(new Date() + ":" + 4);

            SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();//得到客户端地址+端口
            System.out.println(new Date() + ":" + remoteSocketAddress);
            InputStream inputStream = socket.getInputStream();//得到输入流
            Scanner scanner = new Scanner(inputStream, "UTF-8");//字符集编码
            OutputStream outputStream = socket.getOutputStream();//得到输出流
            Writer writer = new OutputStreamWriter(outputStream, "UTF-8");//字符集编码
            PrintWriter printWriter = new PrintWriter(writer);

            // TCP 是一种流式数据,没有明显分界的
            // 隐含着我们的请求一定 XXXX\n
            String request = scanner.nextLine();//由scanner得到客户端输入
            String response = dictionary.getOrDefault(request, "没有找到");
            // 响应的协议也是 XXX\n
            printWriter.println(response);//把响应传入输出
            printWriter.flush();//发送给客户端

            socket.close(); // 关闭连接
        }
    }
}

1.4、创建客户端代码

package com.example.dyc.mysocket;

import java.io.*;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Date;
import java.util.Scanner;

public class MySocketClient {
    public static void main(String[] args) throws IOException {
        System.out.println(new Date() + ":" + 1);
        Socket socket = new Socket("127.0.0.1", 8888);//刚建立完连接,传入客户端IP地址和端口

        SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();//得到服务器地址+端口
        System.out.println(new Date() + ":" + remoteSocketAddress);
        InputStream inputStream = socket.getInputStream();//得到输入流
        Scanner scanner = new Scanner(inputStream, "UTF-8");//字符集编码
        OutputStream outputStream = socket.getOutputStream();//得到输出流
        Writer writer = new OutputStreamWriter(outputStream, "UTF-8");//字符集编码
        PrintWriter printWriter = new PrintWriter(writer);

        printWriter.println("apple");//把apple传入输出
        printWriter.flush();//输出发送给服务器
        String response = scanner.nextLine();//由scanner得到输入的服务器响应
        System.out.println(new Date() + ":" + response);

        socket.close();//关闭连接
    }
}

2、socket实现BIO

2.1、BIO

传统的网络通讯模型,就是BIO,同步阻塞IO, 其实就是服务端创建一个ServerSocket, 然后就是客户端用一个Socket去连接服务端的那个ServerSocket, ServerSocket接收到了一个的连接请求就创建一个Socket和一个线程去跟那个Socket进行通讯。接着客户端和服务端就进行阻塞式的通信,客户端发送一个请求,服务端Socket进行处理后返回响应,在响应返回前,客户端那边就阻塞等待,什么事情也做不了。 这种方式的缺点, 每次一个客户端接入,都需要在服务端创建一个线程来服务这个客户端,这样大量客户端来的时候,就会造成服务端的线程数量可能达到了几千甚至几万,这样就可能会造成服务端过载过高,最后崩溃死掉。

2.2、代码实现

Client


import java.net.InetSocketAddress;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MyBIOClient {
    public static void main(String[] args) throws Exception {
        // 创建 Socket 客户端
        Socket socket = new Socket();
        // 与服务端建立连接
        socket.connect(new InetSocketAddress("127.0.0.1", 8081));

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
        int counter = 0;
        while (counter < 5) {
            String now = simpleDateFormat.format(new Date());
            // 发送请求
            socket.getOutputStream().write(now.getBytes("UTF-8"));
            socket.getOutputStream().flush();
            Thread.sleep(1000);
            counter++;
        }
        // 若方法运行结束后,不调用 close 函数,服务端则会报错:java.net.SocketException: Connection reset
        socket.close();
        System.out.println("客户端关闭了 Socket 连接~!");
    }
}

Serve

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyBIOServe {
    public static void main(String[] args) throws Exception {
        // 创建 Socket 服务端,并设置监听的端口
        ServerSocket serverSocket = new ServerSocket(8081);
        // 创建线程池以执行客户端请求(防止因请求过多,而导致的阻塞)
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1, 10, 60,
                TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
        while (true) {
            // 阻塞方法,监听客户端请求
            Socket socket = serverSocket.accept();
            System.out.println("\r\n" + socket);
            // 创建自定义请求处理器
            SocketHandler handler = new SocketHandler(socket);
            // 处理客户端请求
            poolExecutor.execute(handler);
        }
    }
}
SocketHandler
public class SocketHandler implements Runnable {
    private Socket socket;
    private static final byte[] BUFFER = new byte[1024];

    @Override
    public void run() {
        try {
            while (true){
                System.out.println(Thread.currentThread().getName());
                // 读取客户端 Socket 请求数据
                int read = socket.getInputStream().read(BUFFER);
                if (read != -1) {
                    System.out.println(new String(BUFFER, "UTF-8"));
                }else{
                    socket.close();
                    System.out.println("服务端关闭了 Socket 连接~!");
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public SocketHandler(Socket socket) {
        this.socket = socket;
    }
}

3、NIO

3.1、NIO

NIO: NIO是一种同步非阻塞IO, 基于Reactor模型来实现的。其实相当于就是一个线程处理大量的客户端的请求,通过一个线程轮询大量的channel,每次就获取一批有事件的channel,然后对每个请求启动一个线程处理即可。这里的核心就是非阻塞,就那个selector一个线程就可以不停轮询channel,所有客户端请求都不会阻塞,直接就会进来,大不了就是等待一下排着队而已。这里面优化BIO的核心就是,一个客户端并不是时时刻刻都有数据进行交互,没有必要死耗着一个线程不放,所以客户端选择了让线程歇一歇,只有客户端有相应的操作的时候才发起通知,创建一个线程来处理请求。

3.2、代码实现

MyNIOClient2 


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class MyNIOClient2 {
    public static void main(String[] args) {
        //创建远程地址
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
        SocketChannel channel = null;
        //定义缓存
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        try {
            //开启通道
            channel = SocketChannel.open();
            //连接远程远程服务器
            channel.connect(address);
            Scanner sc = new Scanner(System.in);
            while (true) {
                System.out.println("客户端即将给 服务器发送数据..");
                String line = "clinet2:" + sc.nextLine();
                if (line.equals("exit")) {
                    break;
                }
                //控制台输入数据写到缓存
                buffer.put(line.getBytes("UTF-8"));
                //重置buffer 游标
                buffer.flip();
                //数据发送到数据
                channel.write(buffer);
                //清空缓存数据
                buffer.clear();
                //读取服务器返回的数据
                int readLen = channel.read(buffer);
                if (readLen == -1) {
                    break;
                }
                //重置buffer游标
                buffer.flip();
                byte[] bytes = new byte[buffer.remaining()];
                //读取数据到字节数组
                buffer.get(bytes);
                System.out.println("收到了服务器发送的数据 : " + new String(bytes, "UTF-8"));
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != channel) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

MyNIOService


import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Scanner;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyNIOService extends Thread {
    //1.声明多路复用器
    private Selector selector;
    //2.定义读写缓冲区
    private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
    private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);

    //3.定义构造方法初始化端口
    public MyNIOService(int port) {
        init(port);
    }

    //4.main方法启动线程
    public static void main(String[] args) {
        new Thread(new MyNIOService(8888)).start();
    }

    //5.初始化
    private void init(int port) {
        try {
            System.out.println("服务器正在启动......");
            //1)开启多路复用器
            this.selector = Selector.open();
            //2) 开启服务通道
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //3)设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            //4)绑定端口
            serverSocketChannel.bind(new InetSocketAddress(port));
            //5)注册,标记服务通标状态
            serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);
            System.out.println("服务器启动完毕");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void run() {
        while (true) {
            try {
                //1.当有至少一个通道被选中,执行此方法
                this.selector.select();
                //2.获取选中的通道编号集合
                Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
                //3.遍历keys
                while (keys.hasNext()) {
                    SelectionKey key = keys.next();
                    //4.当前key需要从集合中移出,如果不移出,下次循环会执行对应的逻辑,造成业务错乱
                    keys.remove();
                    //5.判断通道是否有效
                    if (key.isValid()) {
                        try {
                            //6.判断是否可读
                            if (key.isAcceptable()) {
                                accept(key);
                            }
                        } catch (CancelledKeyException e) {
                            //出现异常断开连接
                            key.cancel();
                        }
                        try {
                            //7.判断是否可读
                            if (key.isReadable()) {
                                read(key);
                            }
                        } catch (CancelledKeyException e) {
                            //出现异常断开连接
                            key.cancel();
                        }
                        try {
                            //8.判断是否可写
                            if (key.isWritable()) {
                                write(key);
                            }
                        } catch (CancelledKeyException e) {
                            //出现异常断开连接
                            key.cancel();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void accept(SelectionKey key) {
        try {
            //1.当前通道在init方法中注册到了selector中的ServerSocketChannel
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            //2.阻塞方法, 客户端发起后请求返回.
            SocketChannel channel = serverSocketChannel.accept();
            //3.serverSocketChannel设置为非阻塞
            channel.configureBlocking(false);
            //4.设置对应客户端的通道标记,设置次通道为可读时使用
            channel.register(this.selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //使用通道读取数据
    private void read(SelectionKey key) {
        try {
            //清空缓存
            this.readBuffer.clear();
            //获取当前通道对象
            SocketChannel channel = (SocketChannel) key.channel();
            //将通道的数据(客户发送的data)读到缓存中.
            int readLen = channel.read(readBuffer);
            //如果通道中没有数据
            if (readLen == -1) {
                //关闭通道
                key.channel().close();
                //关闭连接
                key.cancel();
                return;
            }
            //Buffer中有游标,游标不会重置,需要我们调用flip重置. 否则读取不一致
            this.readBuffer.flip();
            //创建有效字节长度数组
            byte[] bytes = new byte[readBuffer.remaining()];
            //读取buffer中数据保存在字节数组
            readBuffer.get(bytes);
            System.out.println("收到了从客户端 " + channel.getRemoteAddress() +
                    " : " + new String(bytes, "UTF-8"));
            //注册通道,标记为写操作
            channel.register(this.selector, SelectionKey.OP_WRITE);
        } catch (Exception e) {
        }
    }

    //给通道中写操作
    private void write(SelectionKey key) {
        //清空缓存
        this.readBuffer.clear();
        //获取当前通道对象
        SocketChannel channel = (SocketChannel) key.channel();
        //录入数据
        Scanner scanner = new Scanner(System.in);
        try {
            System.out.println("即将发送数据到客户端..");
            String line = scanner.nextLine();
            //把录入的数据写到Buffer中
            writeBuffer.put(line.getBytes("UTF-8"));
            //重置缓存游标
            writeBuffer.flip();
            channel.write(writeBuffer);
            //清空writeBuffer
            writeBuffer.clear();
            channel.register(this.selector, SelectionKey.OP_READ);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4、Netty

ps:后面再补充

5、UDP

中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服。UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。

当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。
QQ语音,QQ视频,TFTP

代码实现:

UDPServer

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

/*
 * 服务器端,实现基于UDP的用户登陆
 */
public class UDPServer {
    public static void main(String[] args) throws IOException {
        /*
         * 接收客户端发送的数据
         */
        // 1.创建服务器端DatagramSocket,指定端口
        DatagramSocket socket = new DatagramSocket(8800);
        // 2.创建数据报,用于接收客户端发送的数据
        byte[] data = new byte[1024];// 创建字节数组,指定接收的数据包的大小
        DatagramPacket packet = new DatagramPacket(data, data.length);
        // 3.接收客户端发送的数据
        System.out.println("****服务器端已经启动,等待客户端发送数据");
        socket.receive(packet);// 此方法在接收到数据报之前会一直阻塞
        // 4.读取数据
        String info = new String(data, 0, packet.getLength());
        System.out.println("我是服务器,客户端说:" + info);

        /*
         * 向客户端响应数据
         */
        // 1.定义客户端的地址、端口号、数据
        InetAddress address = packet.getAddress();
        int port = packet.getPort();
        byte[] data2 = "欢迎您!".getBytes();
        // 2.创建数据报,包含响应的数据信息
        DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, port);
        // 3.响应客户端
        socket.send(packet2);
        // 4.关闭资源
        socket.close();
    }
}

UDPClient

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 UDPClient {
    public static void main(String[] args) throws IOException {
        /*
         * 向服务器端发送数据
         */
        // 1.定义服务器的地址、端口号、数据
        InetAddress address = InetAddress.getByName("localhost");
        int port = 8800;
        byte[] data = "用户名:admin;密码:123".getBytes();
        // 2.创建数据报,包含发送的数据信息
        DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
        // 3.创建DatagramSocket对象
        DatagramSocket socket = new DatagramSocket();
        // 4.向服务器端发送数据报
        socket.send(packet);

        /*
         * 接收服务器端响应的数据
         */
        // 1.创建数据报,用于接收服务器端响应的数据
        byte[] data2 = new byte[1024];
        DatagramPacket packet2 = new DatagramPacket(data2, data2.length);
        // 2.接收服务器响应的数据
        socket.receive(packet2);
        // 3.读取数据
        String reply = new String(data2, 0, packet2.getLength());
        System.out.println("我是客户端,服务器说:" + reply);
        // 4.关闭资源
        socket.close();
    }
}

参考文献:

Socket 之 BIO、NIO、Netty 简单实现 - 知乎 (zhihu.com)

什么是NIO?NIO和BIO,AIO之间的区别是什么? - 知乎 (zhihu.com)

java.nio.Buffer 中的 flip()方法_wrap.flip()-CSDN博客

JAVA Socket 实现 UDP 编程-CSDN博客

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的示例代码,用C++封装了Socket类,实现TCPUDP协议的通信功能。 ```c++ #include <iostream> #include <string> #include <cstring> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h> using namespace std; class Socket { public: Socket(int type); ~Socket(); int Bind(const string& ip, unsigned short port); int Listen(int backlog = 5); int Accept(Socket& client); int Connect(const string& ip, unsigned short port); void Close(); int Send(const string& data); int Recv(string& data); private: int _sockfd; int _type; }; Socket::Socket(int type): _type(type) { if (type == SOCK_STREAM) { _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } else if (type == SOCK_DGRAM) { _sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); } else { _sockfd = -1; } } Socket::~Socket() { Close(); } int Socket::Bind(const string& ip, unsigned short port) { sockaddr_in addr = {}; addr.sin_family = AF_INET; addr.sin_port = htons(port); if (ip.empty()) { addr.sin_addr.s_addr = INADDR_ANY; } else { addr.sin_addr.s_addr = inet_addr(ip.c_str()); } return bind(_sockfd, (sockaddr*)&addr, sizeof(addr)); } int Socket::Listen(int backlog) { return listen(_sockfd, backlog); } int Socket::Accept(Socket& client) { sockaddr_in addr = {}; socklen_t addrlen = sizeof(addr); int sockfd = accept(_sockfd, (sockaddr*)&addr, &addrlen); if (sockfd < 0) { return sockfd; } client._sockfd = sockfd; client._type = _type; return 0; } int Socket::Connect(const string& ip, unsigned short port) { sockaddr_in addr = {}; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr(ip.c_str()); return connect(_sockfd, (sockaddr*)&addr, sizeof(addr)); } void Socket::Close() { if (_sockfd != -1) { close(_sockfd); _sockfd = -1; } } int Socket::Send(const string& data) { return send(_sockfd, data.c_str(), data.size(), 0); } int Socket::Recv(string& data) { char buffer[1024] = {}; int n = recv(_sockfd, buffer, sizeof(buffer), 0); if (n > 0) { data = string(buffer, n); } return n; } int main() { Socket server(SOCK_STREAM); server.Bind("127.0.0.1", 8080); server.Listen(); Socket client(SOCK_STREAM); client.Connect("127.0.0.1", 8080); Socket conn(SOCK_STREAM); server.Accept(conn); string message = "Hello, world!"; client.Send(message); string buffer; conn.Recv(buffer); cout << buffer << endl; return 0; } ``` 以上是一个简单的Socket类的实现,可以通过调用Socket类的方法,实现TCPUDP协议的通信。其中,Bind方法用于绑定IP地址和端口号,Listen方法用于监听连接请求,Accept方法用于接受客户端连接,Connect方法用于连接到服务端,Send方法用于发送数据,Recv方法用于接收数据。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值