Socket编程(Java)

概念

Socket

在开发过程中,会用到操作系统提供的类库,这种类库一般被称为 API(Application Programming Interface,应用编程接口); Socket 是TCP/IP 提供的用于网络开发的API;Socket原本是由 BSD UNIX 开发的,但是后被移植到了 Windows 以及嵌入式操作系统中。

TCP(Transmission Control Protocol )

TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。


image.png
2.gif

三次握手

TCP建立连接的过程包括三次握手


image.png
  1. 客户端发送 SYN(SEQ=x)报文给服务器端,进入 SYN_SEND 状态。
  2. 服务器端收到 SYN 报文,回应一个 SYN (SEQ=y)ACK(ACK=x+1)报文,进入 SYN_RECV 状态。
  3. 客户端收到服务器端的 SYN 报文,回应一个 ACK(ACK=y+1)报文,进入 Established 状态。

四次挥手

TCP断开连接的过程包括四次挥手


image.png
  1. 某个端首先调用 close,称该端执行“主动关闭”。该端的 TCP 于是发送一个 FIN 分节,表示数据发送完毕。
  2. 接收到这个 FIN 的对端执行 “被动关闭”,这个 FIN 由 TCP 确认。
  3. 一段时间后,接收到这个文件结束符的应用进程将调用 close 关闭它的套接字,这导致它的 TCP 也发送一个 FIN。
  4. 接收这个最终FIN的原发送端 TCP(即执行主动关闭的那一端)确认这个 FIN。

无论是客户还是服务器,任何一端都可以执行主动关闭。通常情况是,客户执行主动关闭,但是某些协议,例如,HTTP/1.0却由服务器执行主动关闭。

UDP(User Datagram Protocol)

UDP是一种无连接的、不可靠的、面向数据报文的运输层协议;由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。


image.png
1.gif

关键类

ServerSocket

供服务端使用,以获取一个端口,并监听客户端请求

序号 方法 描述
1 public ServerSocket() throws IOException 创建非绑定服务器套接字
2 public ServerSocket(int port) throws IOException 创建绑定到特定端口的服务器套接字
3 public ServerSocket(int port, int backlog) throws IOException 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号
4 public ServerSocket(int port, int backlog, InetAddress address) throws IOException 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器
5 public int getLocalPort() 返回此套接字在其上侦听的端口
6 public Socket accept() throws IOException 侦听并接受到此套接字的连接
7 public void setSoTimeout(int timeout) 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位
8 public void bind(SocketAddress host, int backlog) 将 ServerSocket 绑定到特定地址(IP 地址和端口号)

Socket

序号 方法 描述
1 public Socket(String host, int port) throws UnknownHostException, IOException. 创建一个流套接字并将其连接到指定主机上的指定端口号
2 public Socket(InetAddress host, int port) throws IOException 创建一个流套接字并将其连接到指定 IP 地址的指定端口号
3 public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException 创建一个套接字并将其连接到指定远程主机上的指定远程端口
4 public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException 创建一个套接字并将其连接到指定远程地址上的指定远程端口
5 public Socket() 通过系统默认类型的 SocketImpl 创建未连接套接字
6 public void connect(SocketAddress host, int timeout) throws IOException 将此套接字连接到服务器,并指定一个超时值
7 public InetAddress getInetAddress() 返回套接字连接的地址
8 public int getPort() 返回此套接字连接到的远程端口
9 public int getLocalPort() 返回此套接字绑定到的本地端口
10 public SocketAddress getRemoteSocketAddress() 返回此套接字连接的端点的地址,如果未连接则返回 null
11 public InputStream getInputStream() throws IOException 返回此套接字的输入流
12 public OutputStream getOutputStream() throws IOException 返回此套接字的输出流
13 public void close() throws IOException 关闭此套接字

DatagramSocket

序号 方法 描述
1 1 1

DatagramPacket

序号 方法 描述
1 1 1

示例

TCP

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;

public class GreetingTcp {

    private static final String LOCAL_HOST = "localhost";
    private static final int PORT_SERVER = 2048;


    public static void main(String[] args) {
        try {
            // 开启服务端
            Server server = new Server();
            server.start();
            // 开启客户端1
            Client client1 = new Client(1);
            client1.start();
            // 开启客户端2
            Client client2 = new Client(2);
            client2.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    private static class Client extends Thread {

        private int num;

        Client(int num) {
            this.num = num;
        }

        @Override
        public void run() {
            String serverName = LOCAL_HOST;
            int port = PORT_SERVER;
            try {
                // 连接(客户端端口由系统随机分配)
                Socket client = new Socket(serverName, port);
                System.out.println("客户端-" + num + "-log--连接成功,"
                        + "LocalSocketAddress:" + client.getLocalSocketAddress()
                        + ",RemoteSocketAddress:" + client.getRemoteSocketAddress()
                        + ",LocalAddress:" + client.getLocalAddress()
                        + ",InetAddress:" + client.getInetAddress());

                // 发送
                OutputStream outToServer = client.getOutputStream();
                DataOutputStream out = new DataOutputStream(outToServer);
                out.writeUTF("=====Hello from " + client.getLocalSocketAddress() + "====");

                // 接收
                InputStream inFromServer = client.getInputStream();
                DataInputStream in = new DataInputStream(inFromServer);
                System.out.println(in.readUTF());

                // 关闭
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static class Server extends Thread {

        ServerSocket serverSocket;

        Server() throws IOException {
            serverSocket = new ServerSocket(PORT_SERVER);
            serverSocket.setSoTimeout(10000);
            System.out.println("服务端-0-log--创建欢迎socket,"
                    + "LocalSocketAddress:" + serverSocket.getLocalSocketAddress()
                    + ",InetAddress:" + serverSocket.getInetAddress());
        }

        @Override
        public void run() {
            while (!interrupted()) {
                try {
                    // 连接
                    Socket server = serverSocket.accept();
                    System.out.println("服务端-0-log--创建连接socket,"
                            + "LocalSocketAddress:" + server.getLocalSocketAddress()
                            + ",RemoteSocketAddress:" + server.getRemoteSocketAddress()
                            + ",LocalAddress:" + server.getLocalAddress()
                            + ",InetAddress:" + server.getInetAddress());

                    // 接收
                    DataInputStream in = new DataInputStream(server.getInputStream());
                    System.out.println(in.readUTF());

                    // 回复
                    DataOutputStream out = new DataOutputStream(server.getOutputStream());
                    out.writeUTF("=====Hello too from " + server.getLocalSocketAddress() + "====");

                    // 关闭
                    server.close();
                } catch (SocketTimeoutException s) {
                    System.out.println("服务端-0-log--ServerSocket timed out!");
                    break;
                } catch (IOException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    }


}

运行结果:

服务端-0-log--创建欢迎socket,LocalSocketAddress:0.0.0.0/0.0.0.0:2048,InetAddress:0.0.0.0/0.0.0.0
客户端-2-log--连接成功,LocalSocketAddress:/127.0.0.1:59159,RemoteSocketAddress:localhost/127.0.0.1:2048,LocalAddress:/127.0.0.1,InetAddress:localhost/127.0.0.1
客户端-1-log--连接成功,LocalSocketAddress:/127.0.0.1:59160,RemoteSocketAddress:localhost/127.0.0.1:2048,LocalAddress:/127.0.0.1,InetAddress:localhost/127.0.0.1
服务端-0-log--创建连接socket,LocalSocketAddress:/127.0.0.1:2048,RemoteSocketAddress:/127.0.0.1:59159,LocalAddress:/127.0.0.1,InetAddress:/127.0.0.1
=====Hello from /127.0.0.1:59159====
=====Hello too from /127.0.0.1:2048====
服务端-0-log--创建连接socket,LocalSocketAddress:/127.0.0.1:2048,RemoteSocketAddress:/127.0.0.1:59160,LocalAddress:/127.0.0.1,InetAddress:/127.0.0.1
=====Hello from /127.0.0.1:59160====
=====Hello too from /127.0.0.1:2048====

UDP

import java.io.*;
import java.net.*;

public class GreetingUDP {

    private static final String LOCAL_HOST = "localhost";
    private static final int PORT_SERVER = 2049;


    public static void main(String[] args) {
        try {
            // 开启服务端
            Server server = new Server();
            server.start();

            // 开启客户端1
            Client client1 = new Client(1);
            client1.start();

            // 开启客户端2
            Client client2 = new Client(2);
            client2.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    private static class Client extends Thread {

        private int num;

        Client(int num) {
            this.num = num;
        }

        @Override
        public void run() {
            String serverName = LOCAL_HOST;
            int port = PORT_SERVER;
            try {
                // 连接
                DatagramSocket client = new DatagramSocket();
                client.connect(InetAddress.getByName(serverName), port);
                System.out.println("客户端-" + num + "-log--连接成功,"
                        + "LocalSocketAddress:" + client.getLocalSocketAddress()
                        + ",RemoteSocketAddress:" + client.getRemoteSocketAddress()
                        + ",LocalAddress:" + client.getLocalAddress()
                        + ",InetAddress:" + client.getInetAddress());

                // 发送
                byte[] data = ("=====Hello from " + client.getLocalSocketAddress() + "====").getBytes();
                DatagramPacket packet = new DatagramPacket(data, data.length);
                client.send(packet);

                // 接收
                byte[] buffer = new byte[1024];
                packet = new DatagramPacket(buffer, buffer.length);
                client.receive(packet);
                String resp = new String(packet.getData(), packet.getOffset(), packet.getLength());
                System.out.println(resp);


                // 关闭
                client.disconnect();
                client.close();

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

    private static class Server extends Thread {

        private DatagramSocket datagramSocket;

        Server() throws SocketException {
            datagramSocket = new DatagramSocket(PORT_SERVER);
            datagramSocket.setSoTimeout(10000);
            System.out.println("服务端-0-log--创建socket :" + datagramSocket.getLocalSocketAddress());
        }

        @Override
        public void run() {
            while (!interrupted()) {
                try {
                    // 连接
                    byte[] buffer = new byte[1024];
                    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                    datagramSocket.receive(packet);

                    System.out.println("服务端-0-log--连接成功,"
                            + "LocalSocketAddress:" + datagramSocket.getLocalSocketAddress()
                            + ",RemoteSocketAddress:" + datagramSocket.getRemoteSocketAddress()
                            + ",LocalAddress:" + datagramSocket.getLocalAddress()
                            + ",InetAddress:" + datagramSocket.getInetAddress());

                    // 接收
                    String data = new String(packet.getData(), packet.getOffset(), packet.getLength());
                    System.out.println("服务端-0-log--读取到数据:" + data);

                    // 回复
                    String str_send = "=====Hello too from " + datagramSocket.getLocalSocketAddress() + "====";
                    packet = new DatagramPacket(str_send.getBytes(), str_send.length(), packet.getAddress(), packet.getPort());
                    datagramSocket.send(packet);

                } catch (SocketTimeoutException s) {
                    System.out.println("服务端-0-log--DatagramSocket timed out!");
                    break;
                } catch (IOException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    }

}

运行结果:

服务端-0-log--创建socket :0.0.0.0/0.0.0.0:2049
客户端-2-log--连接成功,LocalSocketAddress:0.0.0.0/0.0.0.0:52745,RemoteSocketAddress:localhost/127.0.0.1:2049,LocalAddress:0.0.0.0/0.0.0.0,InetAddress:localhost/127.0.0.1
客户端-1-log--连接成功,LocalSocketAddress:0.0.0.0/0.0.0.0:52744,RemoteSocketAddress:localhost/127.0.0.1:2049,LocalAddress:0.0.0.0/0.0.0.0,InetAddress:localhost/127.0.0.1
服务端-0-log--连接成功,LocalSocketAddress:0.0.0.0/0.0.0.0:2049,RemoteSocketAddress:null,LocalAddress:0.0.0.0/0.0.0.0,InetAddress:null
服务端-0-log--读取到数据:=====Hello from 0.0.0.0/0.0.0.0:52745====
服务端-0-log--连接成功,LocalSocketAddress:0.0.0.0/0.0.0.0:2049,RemoteSocketAddress:null,LocalAddress:0.0.0.0/0.0.0.0,InetAddress:null
服务端-0-log--读取到数据:=====Hello from 0.0.0.0/0.0.0.0:52744====
=====Hello too from 0.0.0.0/0.0.0.0:2049====
=====Hello too from 0.0.0.0/0.0.0.0:2049====

参考资料

http://tutorials.jenkov.com/java-networking/index.html
https://blog.csdn.net/freekiteyu/article/details/72236734
https://blog.fundebug.com/2019/03/22/differences-of-tcp-and-udp/
https://zhuanlan.zhihu.com/p/33797520

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343