java网络编程二:socket详解

本文介绍了计算机间通信的基础概念,包括IP地址、端口号及协议的作用,详细解析了TCP和UDP协议的特点,并通过Java代码示例展示了基于这两种协议的Socket通信方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

两台计算机进行通信的基本前提:

(1)IP地址: 
每台计算机都有自己独一无二的IP地址,根据IP地址判断与哪台计算机进行通信。 
(2)端口号: 
每个应用程序都有自己专属的端口,根据端口号判断与计算机中的哪个应用程序进行通信。 
说明: 
<1>用于区分不同应用程序 
<2>端口号的范围:0-65535,其中0-1023是为系统保留的端口号 
<3>常用的协议的端口号: 
http:80 
ftp:21 
telnet:23 
<4>IP地址+端口号=Socket,Socket是TCP和UDP通信的基础 
(3)协议: 
好比是两个计算机通信的共同的语言,只用同时用这种协议才能够听懂对方说的话。 
TCP/IP协议:是目前世界上应用最为广泛的协议,TCP/IP协议是传输层的协议,HTTP超文本传输协议,FTP文件传输协议,SMTP简单邮件传送协议,Telnet远程登录服务是应用层协议。 
TCP:传输控制协议 
IP:互联网协议

Java中提供的网络功能有四大类:

(1)InetAddress:用于标识网络上的硬件资源(就是IP地址的相关信息) 
(2)URL:同一资源定位符,通过URL可以直接读取和写入网络上的数据 
(3)Socket:使用TCP协议实现网络通信的Socket相关的类 
(4)Datagram:使用UDP协议,将数据保存到数据报中,通过网络进行通信 
注意: 
TCP协议:是面向连接的,可靠的,有序的,以字节流的方式发送数据 
UDP协议:是无连接的,不可靠的,无序的,速度比较快,以数据报作为数据传输的载体,进行数据传输时,首先要将传输的数据定义成数据报(Datagram),在数据报中指明数据所要到达的Socket(主机地址和端口号),然后再将数据报发送出去。

基于TCP的Socket通信:

(1)服务器端的实现步骤: 
<1>创建ServerSocket,指定端口号 
<2>调用ServerSocket的accept()方法(该方法为阻塞方法,直到有客户端连接进来才会执行该方法之后的代码),等待客户端的连接,当有客户端连接后就会返回一个Socket实例 
<3>调用Scoket实例的getInputStream()方法,读取客户端发送过来的信息 
<4>调用Socket实例的getOutputStream()方法,向客户端写入服务器发送给该客户端的信息 
<5>关闭所有资源

(2)客户端实现步骤: 
<1>创建Socket对象,并指明服务器的IP地址(或主机名)和端口号 
<2>调用Socket实例的getOutputStream()方法,向服务器发送内容 
<3>调用Socket实例的getInputStream()方法,接收服务器发送来的信息 
<4>关闭所有资源

注意: 
在使用socket进行TCP通信时,对于同一个Socket,如果关闭了输出流, 

则与该输出流关联的socket也会被关闭,所以一般不用关闭流,直接关闭socket即可。

Socket通信示例

基于TCP的Socket通信:

(1)服务器端的实现步骤: 
<1>创建ServerSocket,指定端口号 
<2>调用ServerSocket的accept()方法(该方法为阻塞方法,直到有客户端连接进来才会执行该方法之后的代码),等待客户端的连接,当有客户端连接后就会返回一个Socket实例 ,

<2.1>首先在server端,指定端口号创建serverSocket对象,通过serverSocket的accpet方法获取套接字,这个方法的特点是:侦听并接受到此套接字的连接,此方法在连接传入之前一直阻塞。这也就意味着,如果没有客户端连接请求过来,服务端会一致阻塞在这里

<3>调用Scoket实例的getInputStream()方法,读取客户端发送过来的信息 
<4>调用Socket实例的getOutputStream()方法,向客户端写入服务器发送给该客户端的信息 
<5>关闭所有资源

(2)客户端实现步骤: 
<1>创建Socket对象,并指明服务器的IP地址(或主机名)和端口号 
<2>调用Socket实例的getOutputStream()方法,向服务器发送内容 
<3>调用Socket实例的getInputStream()方法,接收服务器发送来的信息 
<4>关闭所有资源

注意: 
在使用socket进行TCP通信时,对于同一个Socket,如果关闭了输出流, 
则与该输出流关联的socket也会被关闭,所以一般不用关闭流,直接关闭socket即可。

下面一个多客户端(多线程)与服务器通信的例子来说明一下基于TCP协议的Socket通信使用方法: 
服务器端代码:

package com.socket.serversocket;

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

public class Serversocket {

    public static void main(String[] args) {

            ServerSocket serversocket;
            int count = 0;
            try {
                serversocket = new ServerSocket(8080);
                System.out.println("服务器已经启动,等待客户端的连接。。。。");
                while(true){
                    Socket socket = serversocket.accept();
                    new ServerThread(socket).start();
                    count++;
                    System.out.println("客户端的IP:"+socket.getInetAddress().getHostAddress());
                    System.out.println("连接的客户端的总数:"+count);
                }


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


    }




}

package com.socket.serversocket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class ServerThread extends Thread{
    Socket socket = null;
    public ServerThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line = null;
            while((line = br.readLine())!=null){
                System.out.println("我是服务器,客户端发来的信息是:"+line);
            }
            socket.shutdownInput();//关闭socket的输入流

            PrintWriter pw = new PrintWriter(socket.getOutputStream());
            pw.write("欢迎访问服务器");
            socket.shutdownOutput();//关闭socket的输出流

            //关闭资源
            br.close();
            pw.close();
            socket.close();//注意:在使用socket进行TCP通信时,对于同一个Socket,如果关闭了输出流,
            //则与该输出流关联的socket也会被关闭,所以一般不用关闭流,直接关闭socket即可

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

    }
}

  • 40

客户端代码:

package com.socket.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class Client {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost",8080);
            OutputStream os = socket.getOutputStream();
            PrintWriter pw = new PrintWriter(os);
            String s = "用户名:admin;密码:123";
            pw.write(s);
            pw.flush();//因为PrintWriter是字符流,所以在写入完成后一定要调用字符流的flush()方法,冲刷缓冲区保证所有内容已经从流里写出去了
            socket.shutdownOutput();

            InputStream is = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String line = null;
            while((line = br.readLine())!=null){
                System.out.println("我是客户端,接收到服务器发来的信息为:"+line);
            }
            socket.shutdownInput();
            //关闭资源
            br.close();
            is.close();
            pw.close();
            os.close();
            socket.close();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

  • 44

程序运行结果: 
这里写图片描述

三、半关闭的socket

  在上面的Demo中,我们是以行作为通信的最小数据单位,服务器端也是逐行进行处理的。但是我们在大多数场景下,通信的数据单位是多行的,这时候Socket的输出流如何表达输出的数据已经结束?

  在IO学习过程中提到过,如何要表示输出已经结束,则通过关闭输出流来实现,但是在socket中是行不通的,因为关闭socket,会导致无法再从该socket中读取数据了。为了解决这种问题,java提供了两个半关闭的方法:

1、shutdownInput():关闭该Socket的输入流,程序还可以通过该Socket的输出流输出数据。

2、shutdownOutput():关闭该Socket的输出流,程序还可以通过该Socket的输入流读取数据。

如果我们对同一个Socket实例先后调用shutdownInput和shutdownOutput方法,该Socket实例依然没有被关闭,只是该Socket既不能输出数据,也不能读取数据。

服务器端:

 1         ServerSocket ss = new ServerSocket(5555);
 2         Socket socket = ss.accept();
 3         PrintStream ps = new PrintStream(socket.getOutputStream());
 4         ps.println("服务器端:开源中国杭州论坛");
 5         ps.println("服务器端:杭州G20峰会");
 6         //关闭输出流,表明输出已经结束
 7         socket.shutdownOutput();
 8         //判断该socket是否关闭
 9         System.out.println(socket.isClosed());
10         Scanner scan = new Scanner((socket.getInputStream()));
11         while(scan.hasNextLine())
12         {
13             System.out.println(scan.nextLine());
14         }
15         scan.close();
16         socket.close();
17         ss.close();
18         
19     

 客户端:

 1         Socket s = new Socket("localhost", 5555);
 2         InputStream is = s.getInputStream();
 3         byte[] buffer = new byte[1024];
 4         int flag = 0;
 5         while(-1 != (flag = is.read(buffer,0,buffer.length)))
 6         {
 7             String str = new String(buffer,0,flag);
 8             System.out.print(str);
 9         }
10         PrintStream ps = new PrintStream(s.getOutputStream());
11         ps.println("客户端:欢迎参加开源中国论坛");
12         ps.println("客户端:欢迎参加G20峰会");
13         is.close();
14         ps.close();
15         s.close();
16     

执行结果:

   

  

  在服务器端程序中可以看到,在输出两段字符串之后,调用了shutdownOutput方法,表示输出已经结束。随即又去判断了socket是否关闭,执行的结果为false,表示socket并未关闭。

  但是在调用了这两个半关闭的方法关闭了输出输入流之后,该socket无法再次打开该输出流或者输入流。因此这种场景不适合保持持久通信状态的交互使用,只适合一站式的通信协议.例如http协议:客户端连接到服务器之后,开始发送数据,发送完成之后无须再次发送数据,只需要读取服务器响应数据即可,读取数据完毕之后,该socket连接也被关闭了。

四、基于UDP协议的网络编程

  前面介绍的socket编程都是基于TCP协议的,现在来看下基于UDP协议的编程,TCP和UDP的区别在上一章已经有过介绍。

  UDP协议的主要作用就是完成网络数据流和数据报之间的转换-----在信息的发送端,UDP协议将网络数据流封装到数据报,然后将数据报发送出去;在信息的接收端,UDP协议将数据报转换成实际数据报内容。

1、首先在UDP网络编程中没有服务器端和客户端这种说法,两个socket之间没有虚拟链路,只是接收和发送数据报文而已。

2、这里面有两个重要的类:DatagramSocket 和DatagramPacket。前者是用来发送和接收数据包的套接字,后者表示数据包,每条报文仅根据该包中的包含的信息从一台机器        路由到另一台机器。

3、DatagramSocket 的两个构造函数:

     DatagramSocket():构造数据报套接字并将其绑定到本地主机上任何可用的端口。

     DatagramSocket(int port):创建数据报套接字并将其绑定到本地主机上的指定端口。

     在我们下面的DEMO中,UDPServerTest类中先发送数据报,使用的是套接字的无参构造器,而UDPClientTest类中先接收数据报,必须监听某一个端口,所以使用的是套接字的有参构造器。

4、DatagramPacket:创建的时候分为接收和发送两种

      DatagramPacket(byte[] buf, int length):用来接收长度为 length 的数据包。

      DatagramPacket(byte[] buf, int length, InetAddress address, int port):用来将长度为 length 的包发送到指定主机上的指定端口号。

 1 public class UDPServerTest
 2 {
 3     public static void main(String[] args) throws IOException
 4     {
 5         DatagramSocket ds = new DatagramSocket();
 6         String str = "hello world";
 7         //构造用于发送的数据包,指定主机和端口号
 8         DatagramPacket packet = new DatagramPacket(str.getBytes(),
 9                 str.length(), InetAddress.getByName("localhost"), 5555);
10         ds.send(packet);
11         
12         //读取从客户端发送过来的响应
13         byte[] buffer = new byte[1024];
14         DatagramPacket packet2 = new DatagramPacket(buffer,buffer.length);
15         ds.receive(packet2);
16         String str2 = new String(buffer,0,packet2.getLength());
17         System.out.println(str2);
18         ds.close();
19     }
20 }
public class UDPClientTest
{
    public static void main(String[] args) throws Exception
    {
        DatagramSocket ds = new DatagramSocket(5555);
        byte[] buffer = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        ds.receive(packet);
        String str = new String(buffer, 0, packet.getLength());
        System.out.println(str);

        // 接收到数据包之后,客户端返回响应回去
        String str2 = "welcome";
        DatagramPacket packet2 = new DatagramPacket(str2.getBytes(), str2
                .length(), packet.getAddress(), packet.getPort());
        ds.send(packet2);
        ds.close();
    }
}

执行过程:

 

1、上面的程序中,第一步是服务器端(暂且以这种叫法来区分这两个类)创建一个UDP套接字,没有指定端口,使用的是系统分配的端口。然后构建了一个数据包,包中指定      了目标机器的ip和端口号。

2、作为客户端,创建了一个UDP套接字,并且绑定了端口,如果想要接收到服务端发送过来的报文,绑定的端口必须和服务器端发送的包中指定的端口一致。

3、客户端打印了包中的内容之后,想要返回一些内容回去。这个时候,服务器端的ip和端口号可以从之前发送过来的数据包中获取。

     DatagramPacket packet2 = new DatagramPacket(str2.getBytes(), str2.length(), packet.getAddress(), packet.getPort());

4、在服务器接收数据包的时候,已经不需要再像客户端创建套接字一样去绑定端口了,因为目前监听的端口和客户端发送的包中指定的端口是一样的。

5、打印看下服务器端的ip和监听的端口号:

serverIp =/127.0.0.1;serverPort=62965

6、其中DatagramSocket的receive(DatagramPacket p)方法在接收到数据包前一直阻塞。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值