【JavaEE】Socket简单体验, TCP和UDP

作者:邹峰立,微博:zrunker,邮箱:zrunker@yahoo.com,微信公众号:书客创作,个人平台:www.ibooker.cc

本文选自书客创作平台第30篇文章。阅读原文

书客创作

对于程序员来说,在实际生产过程中,使用最多的莫过于Http和Socket通信两中。Socket相对于Http来说速度快,效率高,所以广受青睐。

说到Socket很让人联想到"套接字"、"长连接"、"TCP/UDP"、"通信"。没错,Socket是套接字的意思,Socket能够实现长连接,所以常常人们所使用的一些即时通信的软件大多数是通过Socket来实现。TCP和UDP是两种形式,TCP是面向连接的,较安全。UDP是无连接的,速度较快。

基本概念理解

1、InetAddress用于标识网络上硬件资源。

// 获取本机的InetAddress实例
InetAddress inetAddress = InetAddress.getLocalHost();
System.out.println("计算机名:" + inetAddress.getHostName());
System.out.println("IP地址:" + inetAddress.getHostAddress());
// 获取字节数组形式的IP地址
byte[] bytes = inetAddress.getAddress();
System.out.println("字节数组形式的IP地址:" + Arrays.toString(bytes));
// 直接输出InetAddress对象
System.out.println(inetAddress);
        
// 根据主机名获取InetAddress实例
// InetAddress address = InetAddress.getByName("SC-201702092057");
// 根据IP地址获取InetAddress实例
InetAddress address = InetAddress.getByName("192.168.155.1");
System.out.println("计算机名:" + address.getHostName());
System.out.println("IP地址:" + address.getHostAddress());

2、URL统一资源定位符,通过资源可以读取和写入网络上的数据。

// 创建URL实例
URL baidu = new URL("http://www.baidu.com");
// 根据已存在URL可以创建一个新的URL
URL url = new URL(baidu, "/index.html?username=tom#test");// ?后面表示参数,#后面表示锚点
System.out.println("协议:" + url.getProtocol());
System.out.println("主机:" + url.getHost());
// 如果未指定端口号,则使用默认端口号,此时url.getPort()获取的返回值为-1
System.out.println("端口:" + url.getPort());
System.out.println("文件路径:" + url.getPath());
System.out.println("文件名:" + url.getFile());
System.out.println("相对路径:" + url.getRef());
System.out.println("查询字符串:" + url.getQuery());
        
/**
 * 使用URL获取网络内容
 */
// 通过URL的openStream方法获取URL对象所表示的资源的字节输入流
InputStream is = baidu.openStream();
// 将字节输入流转换成字符输入流(字节流与字符流之间的桥梁)
InputStreamReader isr = new InputStreamReader(is, "utf8");
// 为字符输入流添加缓冲(从字符输入流中读取文本)
BufferedReader br = new BufferedReader(isr);
String data = null;
while ((data = br.readLine()) != null) {
    System.out.println("当前行数据:" + data);
}

Socket实现基于TCP通信

Socket通信分为服务端和客户端,在Java语言中,要实现基于TCP的通信需要通过ServerSocket(服务端)和Socket(客户端)。因为TCP在通信过程中采用字节流传输,所以数据的取出和传递都要通过字节流来实现。

1、服务端搭建

服务端的搭建基本上可以分为5步。

(1)、创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口。

ServerSocket serverSocket = new ServerSocket(2500);

对于端口号,系统默认取值范围是065535,一般认为01023为系统端口号,所以这里采用>1023的端口号。

(2)、用accept()方法开始监听,等待客户端的连接。

Socket socket = serverSocket.accept();

注意accept方法会使程序处于阻塞状态,所以一般是放在子线程中完成。而接受到的socket就是用于服务端和客户端进行通信的socket。

(3)、获取输入流,并读取客户端信息

InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);// 转换成字符输入流
BufferedReader br = new BufferedReader(isr);// 字符输入流缓冲
String info=null;
while((info=br.readLine())!=null){// 循环读取客户端的信息
    System.out.println("*******客户端信息:" + info);
}
socket.shutdownInput();// 关闭输入流

获取客户端数据需要通过获取socket的输入流,这里使用到了字节流->字符流的过程,最后打印出字符。

(4)、获取输出流,响应客户端

OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);// 将输出流包装为打印流
pw.write("我是服务端,你好");
pw.flush();// 刷新缓存,将缓存输出
socket.shutdownOutput();// 关闭输出流

响应客户端需要通过获取socket输出流,这里是字节流->打印流。当然也可以直接进行输出。

(5)、关闭资源,最后将以上资源全部关闭即可。

pw.close();
os.close();
br.close();
isr.close();
is.close();
socket.close();
serverSocket.close();

到这里一个简单的服务端就构建完成了,上面的过程仅仅是用来说来,使用Socket如何构建一个服务端,实际上ServerSocket在接收链接的过程需要在子线程中完成。

优化代码:

// 1、创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口
// (0~65535,使用1023之后的端口,0~1023一般为系统默认端口)
ServerSocket serverSocket = new ServerSocket(2500);
Socket socket = null;
// 记录客户端的数量
int count = 0;
System.out.println("*****服务端已开启******");
// 循环监听等待客户端的连接
while (true) {
    // 调用accept()方法开始监听,等待客户端的连接
    socket = serverSocket.accept();
    // 创建一个新的线程
    TcpServerThread tcpServerThread = new TcpServerThread(socket);
    tcpServerThread.setPriority(4);// 设置线程优先级,[0,10],默认5
    // 启动线程
    tcpServerThread.start();
    count++;// 统计客户端的数量
    System.out.println("客户端的数量:" + count);
    InetAddress address = socket.getInetAddress();
    System.out.println("当前客户端的IP:" + address.getHostAddress());
}

而对于socket的处理,也要放在一个子线程中完成。

public class TcpServerThread extends Thread {
    // 和本线程相关的socket
    private Socket socket = null;

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

    // 线程执行的操作,响应客户端的请求
    @Override
    public void run() {
        super.run();
        if (socket != null) {
            InputStream is = null;
            InputStreamReader isr = null;
            BufferedReader br = null;
            OutputStream os = null;
            PrintWriter pw = null;
            try {
                // 获取输入流,并读取客户端信息
                is = socket.getInputStream();
                isr = new InputStreamReader(is);// 转换成字符输入流
                br = new BufferedReader(isr);// 字符输入流缓冲
                String info = null;
                while ((info = br.readLine()) != null) {// 循环读取客户端的信息
                    System.out.println("*******客户端信息:" + info);
                }
                socket.shutdownInput();// 关闭输入流

                // 获取输出流,相应客户端
                os = socket.getOutputStream();
                pw = new PrintWriter(os);// 将输出流包装为打印流
                pw.write("我是服务端,你好");
                pw.flush();// 刷新缓存,将缓存输出
                socket.shutdownOutput();// 关闭输出流
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 关闭资源
                try {
                    if (pw != null)
                        pw.close();
                    if (os != null)
                        os.close();
                    if (br != null)
                        br.close();
                    if (isr != null)
                        isr.close();
                    if (is != null)
                        is.close();
                    if (socket != null)
                        socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2、客户端搭建

客户端的搭建只需要4步

(1)、创建客户端Socket,指定服务端地址和端口

Socket socket = new Socket("localhost", 2500);

这里的服务端地址指的是服务端ip地址,端口是服务端设置的端口号。

(2)、创建输出流,向服务端发送消息

OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);// 将输出流包装为打印流
pw.write("我是客户端,你好啊");
pw.flush();// 刷新缓存
//pw.close();// 不能关闭输出流,会导致socket也关闭
socket.shutdownOutput();// 关闭输出流

注意一点,输出数据之后不要将流关闭,否则会导致socket也关闭。

(3)、获取输入流,并读取服务端信息

InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);// 转换成字符输入流
BufferedReader br = new BufferedReader(isr);// 字符输入流缓冲
String info=null;
while((info=br.readLine()) != null){// 循环读取客户端的信息
    System.out.println("*******服务端信息:" + info);
}
socket.shutdownInput();// 关闭输入流

获取服务端数据需要通过获取socket的输入流,这里使用到了字节流->字符流的过程,最后打印出字符。

(4)、关闭资源,最后将以上资源全部关闭即可。

br.close();
isr.close();
is.close();
pw.close();
os.close();
socket.close();

Socket实现基于UDP通信

UDP通信时无连接的,传输过程中是通过数据包来完成,所以要想实现UDP通信,需要使用到Java中的DatagramSocket和DatagramPacket。DatagramSocket是用来建立连接,而DatagramPacket是用来传递数据。

1、服务端搭建

服务端搭建分为接收和响应客户端两个过程。

首先实现接受客户端发送的数据。

(1)、创建服务端DatagramSocket,指定端口

DatagramSocket datagramSocket = new DatagramSocket(2501);

同样要设置端口号,端口号取>1023并且<65535的整数。

(2)、创建数据报用来接收客户端传递过来的数据

byte[] data = new byte[1024];// 创建字节数组,指定接受数据包大小
DatagramPacket datagramPacket = new DatagramPacket(data, data.length);

这里接受数据为数据报,所以要采用DatagramPacket类来实现数据的接收。

(3)、接受客户端发送的数据,receive方法用于接收链接,最后的方式也是放于子线程完成这一过程

datagramSocket.receive(datagramPacket);// 此方法在接收到数据报之前会处于阻塞

(4)、读取数据

String info = new String(data, 0, datagramPacket.getLength());
System.out.println("****客户端数据*****" + info);

其次是响应客户端

(1)、定义客户端地址,端口号,数据

InetAddress inetAddress = datagramPacket.getAddress();
int port = datagramPacket.getPort();
byte[] data2 = "我是服务端,你好".getBytes();

这里就采用了InetAddress来获取客户端的相关信息,例如客户端ip和相关端口号。

(2)、创建数据报,包含发送信息

DatagramPacket datagramPacket2 = new DatagramPacket(data2, data2.length, inetAddress, port);

此处的DatagramPacket构造方式与上面的构造方法不同,此处还要知道客户端的ip才能准确的发送数据。

(3)、向客户端发送数据报,通过send方法发送数据。

datagramSocket.send(datagramPacket2);

最后关闭资源

datagramSocket.close();

2、客户端搭建

其实客户端的构建与服务端的构建是一个相似的过程,一个逆向的过程。

向服务端发送数据。

// 1、定义服务端地址,端口号,数据
InetAddress inetAddress = InetAddress.getByName("localhost");
int port = 2501;
byte[] data = "我是客户端,你好".getBytes();
// 2、创建数据报,包含发送信息
DatagramPacket datagramPacket = new DatagramPacket(data, data.length, inetAddress, port);
// 3、创建DataGramSocket对象
DatagramSocket datagramSocket = new DatagramSocket();
// 4、向服务端发送数据报
datagramSocket.send(datagramPacket);

接受服务端响应的数据

// 1、创建数据报用来接收服务端传递过来的数据
byte[] data2 = new byte[1024];// 创建字节数组,指定接受数据包大小
DatagramPacket datagramPacket2 = new DatagramPacket(data2, data2.length);
// 2、接受服务端发送的数据
datagramSocket.receive(datagramPacket2);// 此方法在接收到数据报之前会处于阻塞
// 2、读取数据
String info = new String(data2, 0, datagramPacket2.getLength());
System.out.println("****服务端数据*****" + info);

关闭资源

datagramSocket.close();

Github地址
阅读原文


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

推荐阅读更多精彩内容