网络组成

  • TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能,接下来针对这四层进行详细地讲解。
  • 链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
  • 网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
  • 传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
  • 应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。

IP地址

在TCP/IP协议中,这个标识号就是IP地址,它可以唯一标识一台计算机,目前,IP地址广泛使用的版本是IPv4,它是由4个字节大小的二进制数来表示,如:00001010000000000000000000000001。由于二进制形式表示的IP地址非常不便记忆和处理,因此通常会将IP地址写成十进制的形式,每个字节用一个十进制数字(0-255)表示,数字间用符号“.”分开,如 “192.168.1.100”

端口号

通过IP地址可以连接到指定计算机,但如果想访问目标计算机中的某个应用程序,还需要指定端口号。在计算机中,不同的应用程序是通过端口号区分的。端口号是用两个字节(16位的二进制数)表示的,它的取值范围是0~65535,其中,0~1023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序需要使用1024以上的端口号,从而避免端口号被另外一个应用或服务所占用

InetAddress类

Modifier and Type Method and Description
boolean equals(Object obj)
将此对象与指定对象进行比较。
byte[] getAddress()
返回此 InetAddress对象的原始IP地址。
static InetAddress[] getAllByName(String host)
给定主机的名称,根据系统上配置的名称服务返回其IP地址数组。
static InetAddress getByAddress(byte[] addr)
给出原始IP地址的 InetAddress对象。
static InetAddress getByAddress(String host, byte[] addr)
根据提供的主机名和IP地址创建InetAddress。
static InetAddress getByName(String host)
确定主机名称的IP地址。
String getCanonicalHostName()
获取此IP地址的完全限定域名。
String getHostAddress()
返回文本显示中的IP地址字符串。
String getHostName()
获取此IP地址的主机名。
static InetAddress getLocalHost()
返回本地主机的地址。
static InetAddress getLoopbackAddress()
返回回送地址。
int hashCode()
返回此IP地址的哈希码。
boolean isAnyLocalAddress()
检查通配符地址中的InetAddress的实用程序。
boolean isLinkLocalAddress()
检查InetAddress是否是链接本地地址的实用程序。
boolean isLoopbackAddress()
检查InetAddress是否是一个环回地址的实用程序。
boolean isMCGlobal()
检查多播地址是否具有全局范围的实用程序。
boolean isMCLinkLocal()
检查组播地址是否具有链路范围的实用程序。
boolean isMCNodeLocal()
检查多播地址是否具有节点范围的实用程序。
boolean isMCOrgLocal()
检查组播地址是否具有组织范围的实用程序。
boolean isMCSiteLocal()
检查多播地址是否具有站点范围的实用程序。
boolean isMulticastAddress()
检查InetAddress是否是IP组播地址的实用程序。
boolean isReachable(int timeout)
测试该地址是否可达。
boolean isReachable(NetworkInterface netif, int ttl, int timeout)
测试该地址是否可达。
boolean isSiteLocalAddress()
检查InetAddress是否是站点本地地址的实用程序。
String toString()
将此IP地址转换为 String

获取本地ip地址和名称方法 InetAddress类

import java.net.InetAddress;
import java.net.UnknownHostException;

public class Test {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress inet = InetAddress.getLocalHost();
        //输出结果就是主机名,和 IP地址
        System.out.println(inet);
        System.out.println(inet.getHostAddress());
        System.out.println(inet.getHostName());
    }
}

获取主机ip地址和名称方法 InetAddress类

import java.net.InetAddress;
import java.net.UnknownHostException;

public class Test {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress inet = InetAddress.getByName("www.1997sty.com");
        System.out.println(inet);
        System.out.println(inet.getHostAddress());
        System.out.println(inet.getHostName());
    }
}

UDP协议和TCP协议

  • UDP协议

UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。

由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。

  • TCP协议

TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。

第一次握手,客户端向服务器端发出连接请求,等待服务器确认

第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求

第三次握手,客户端再次向服务器端发送确认信息,确认连接

UDP收发数据包

DatagramPacket数据包的作用就如同是“集装箱”,可以将发送端或者接收端的数据封装起来。然而运输货物只有“集装箱”是不够的,还需要有码头。在程序中需要实现通信只有DatagramPacket数据包也同样不行,为此JDK中提供的一个DatagramSocket类。DatagramSocket类的作用就类似于码头,使用这个类的实例对象就可以发送和接收DatagramPacket数据包

DatagramPacket:封装数据

DatagramSocket:发送DatagramPacket

DatagramPacket类构造方法

Constructor and Description
DatagramPacket(byte[] buf, int length)
构造一个 DatagramPacket用于接收长度的数据包 length
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
DatagramPacket(byte[] buf, int offset, int length)
构造一个 DatagramPacket用于接收长度的分组 length ,指定偏移到缓冲器中。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
构造用于发送长度的分组数据报包 length具有偏移 ioffset指定主机上到指定的端口号。
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)
构造用于发送长度的分组数据报包 length具有偏移 ioffset指定主机上到指定的端口号。
DatagramPacket(byte[] buf, int length, SocketAddress address)
构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。

DatagramPacket类常规方法

Modifier and Type Method and Description
InetAddress getAddress()
返回该数据报发送或接收数据报的计算机的IP地址。
byte[] getData()
返回数据缓冲区。
int getLength()
返回要发送的数据的长度或接收到的数据的长度。
int getOffset()
返回要发送的数据的偏移量或接收到的数据的偏移量。
int getPort()
返回发送数据报的远程主机上的端口号,或从中接收数据报的端口号。
SocketAddress getSocketAddress()
获取该数据包发送到或正在从其发送的远程主机的SocketAddress(通常为IP地址+端口号)。
void setAddress(InetAddress iaddr)
设置该数据报发送到的机器的IP地址。
void setData(byte[] buf)
设置此数据包的数据缓冲区。
void setData(byte[] buf, int offset, int length)
设置此数据包的数据缓冲区。
void setLength(int length)
设置此数据包的长度。
void setPort(int iport)
设置发送此数据报的远程主机上的端口号。
void setSocketAddress(SocketAddress address)
设置该数据报发送到的远程主机的SocketAddress(通常是IP地址+端口号)。

DatagramSocket类构造方法

Modifier Constructor and Description
  DatagramSocket()
构造数据报套接字并将其绑定到本地主机上的任何可用端口。
protected DatagramSocket(DatagramSocketImpl impl)
使用指定的DatagramSocketImpl创建一个未绑定的数据报套接字。
  DatagramSocket(int port)
构造数据报套接字并将其绑定到本地主机上的指定端口。
  DatagramSocket(int port, InetAddress laddr)
创建一个数据报套接字,绑定到指定的本地地址。
  DatagramSocket(SocketAddress bindaddr)
创建一个数据报套接字,绑定到指定的本地套接字地址。

DatagramSocket类常规方法

Modifier and Type Method and Description
void bind(SocketAddress addr)
将此DatagramSocket绑定到特定的地址和端口。
void close()
关闭此数据报套接字。
void connect(InetAddress address, int port)
将套接字连接到此套接字的远程地址。
void connect(SocketAddress addr)
将此套接字连接到远程套接字地址(IP地址+端口号)。
void disconnect()
断开插座。
boolean getBroadcast()
测试是否启用了SO_BROADCAST。
DatagramChannel getChannel()
返回与该数据报套接字相关联的唯一的DatagramChannel对象(如果有)。
InetAddress getInetAddress()
返回此套接字连接到的地址。
InetAddress getLocalAddress()
获取套接字所绑定的本地地址。
int getLocalPort()
返回此套接字绑定到的本地主机上的端口号。
SocketAddress getLocalSocketAddress()
返回此套接字绑定到的端点的地址。
int getPort()
返回此套接字连接到的端口号。
int getReceiveBufferSize()
获取此 DatagramSocket的SO_RCVBUF选项的值,即平台在此 DatagramSocket上输入的缓冲区大小。
SocketAddress getRemoteSocketAddress()
返回此套接字连接,或端点的地址 null如果是未连接。
boolean getReuseAddress()
测试是否启用了SO_REUSEADDR。
int getSendBufferSize()
获取此 DatagramSocket的SO_SNDBUF选项的值,即该平台用于在此 DatagramSocket上输出的缓冲区大小。
int getSoTimeout()
检索SO_TIMEOUT的设置。
int getTrafficClass()
在从该DatagramSocket发送的数据包的IP数据报头中获取流量类或服务类型。
boolean isBound()
返回套接字的绑定状态。
boolean isClosed()
返回套接字是否关闭。
boolean isConnected()
返回套接字的连接状态。
void receive(DatagramPacket p)
从此套接字接收数据报包。
void send(DatagramPacket p)
从此套接字发送数据报包。
void setBroadcast(boolean on)
启用/禁用SO_BROADCAST。
static void setDatagramSocketImplFactory(DatagramSocketImplFactory fac)
设置应用程序的数据报套接字实现工厂。
void setReceiveBufferSize(int size)
设置SO_RCVBUF选项设置为这个指定的值 DatagramSocket
void setReuseAddress(boolean on)
启用/禁用SO_REUSEADDR套接字选项。
void setSendBufferSize(int size)
设置SO_SNDBUF选项设置为这个指定的值 DatagramSocket
void setSoTimeout(int timeout)
以指定的超时(以毫秒为单位)启用/禁用SO_TIMEOUT。
void setTrafficClass(int tc)
在从该DatagramSocket发送的数据报的IP数据报头中设置流量类别或服务类型的八位字节。

UDP发送端

/*
 *  实现UDP协议的发送端:
 *    实现封装数据的类 java.net.DatagramPacket  将你的数据包装
 *    实现数据传输的类 java.net.DatagramSocket  将数据包发出去
 *    
 *  实现步骤:
 *    1. 创建DatagramPacket对象,封装数据, 接收的地址和端口
 *    2. 创建DatagramSocket
 *    3. 调用DatagramSocket类方法send,发送数据包
 *    4. 关闭资源
 *    
 *    DatagramPacket构造方法:
 *      DatagramPacket(byte[] buf, int length, InetAddress address, int port) 
 *      
 *    DatagramSocket构造方法:
 *      DatagramSocket()空参数
 *      方法: send(DatagramPacket d)
 */
import java.net.*;

public class Test {
    public static void main(String[] args) throws Exception {
        //创建数据包对象,封装要发送的数据,接收端IP,端口
        byte[] date = "你好UDP".getBytes();
        //创建InetAddress对象,封装自己的IP地址
        InetAddress inet = InetAddress.getByName("127.0.0.1");
        DatagramPacket dp = new DatagramPacket(date, date.length, inet,6000);
        //创建DatagramSocket对象,数据包的发送和接收对象
        DatagramSocket ds = new DatagramSocket();
        //调用ds对象的方法send,发送数据包
        ds.send(dp);
        //关闭资源
        ds.close();
    }
}

UDP接收端

/*
 *  实现UDP接收端
 *    实现封装数据包 java.net.DatagramPacket 将数据接收
 *    实现输出传输   java.net.DatagramSocket 接收数据包
 *    
 *  实现步骤:
 *     1. 创建DatagramSocket对象,绑定端口号
 *         要和发送端端口号一致
 *     2. 创建字节数组,接收发来的数据
 *     3. 创建数据包对象DatagramPacket
 *     4. 调用DatagramSocket对象方法
 *        receive(DatagramPacket dp)接收数据,数据放在数据包中
 *     5. 拆包
 *          发送的IP地址
 *            数据包对象DatagramPacket方法getAddress()获取的是发送端的IP地址对象
 *            返回值是InetAddress对象
 *          接收到的字节个数
 *            数据包对象DatagramPacket方法 getLength()
 *          发送方的端口号
 *            数据包对象DatagramPacket方法 getPort()发送端口
 *     6. 关闭资源
 */
import java.net.*;

public class Get {
    public static void main(String[] args) throws Exception {
        //创建数据包传输对象DatagramSocket 绑定端口号
        DatagramSocket ds = new DatagramSocket(6000);
        //创建字节数组
        byte[] data = new byte[1024];
        //创建数据包对象,传递字节数组
        DatagramPacket dp = new DatagramPacket(data, data.length);
        //调用ds对象的方法receive传递数据包
        ds.receive(dp);
        //获取发送端的IP地址对象
        String ip=dp.getAddress().getHostAddress();
        //获取发送的端口号
        int port = dp.getPort();
        //获取接收到的字节个数
        int length = dp.getLength();
        System.out.println(new String(data,0,length)+"..."+ip+":"+port);
        ds.close();
    }
}

模拟键盘输入的聊天

  • 输入端
import java.net.*;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        //创建数据包对象,封装要发送的数据,接收端IP,端口
        byte[] date;
        //创建InetAddress对象,封装自己的IP地址
        InetAddress inet = InetAddress.getByName("127.0.0.1");
        while(true)
        {
            date = sc.next().getBytes();
            System.out.println("发送");
            //创建DatagramSocket对象,数据包的发送和接收对象
            DatagramPacket dp = new DatagramPacket(date, date.length, inet,6000);
            DatagramSocket ds = new DatagramSocket();
            //调用ds对象的方法send,发送数据包
            ds.send(dp);
        }
    }
}
  • 接收端
import java.net.*;

public class Get {
    public static void main(String[] args) throws Exception {
        //创建数据包传输对象DatagramSocket 绑定端口号
        DatagramSocket ds = new DatagramSocket(6000);
        //创建字节数组
        byte[] data = new byte[1024];
        while(true)
        {
            //创建数据包对象,传递字节数组
            DatagramPacket dp = new DatagramPacket(data, data.length);
            //调用ds对象的方法receive传递数据包
            ds.receive(dp);
            //获取发送端的IP地址对象
            String ip=dp.getAddress().getHostAddress();
            //获取发送的端口号
            int port = dp.getPort();
            //获取接收到的字节个数
            int length = dp.getLength();
            System.out.println(new String(data,0,length)+"..."+ip+":"+port);
        }
    }
}

TCP收发数据包

TCP通信同UDP通信一样,都能实现两台计算机之间的通信,通信的两端都需要创建socket对象。区别在于,UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。而TCP通信是严格区分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的连接。

在JDK中提供了两个类用于实现TCP程序,一个是ServerSocket类,用于表示服务器端,一个是Socket类,用于表示客户端。通信时,首先创建代表服务器端的ServerSocket对象,该对象相当于开启一个服务,并等待客户端的连接,然后创建代表客户端的Socket对象向服务器端发出连接请求,服务器端响应请求,两者建立连接开始通信。

Socket类构造方法

Modifier Constructor and Description
  Socket()
创建一个未连接的套接字,并使用系统默认类型的SocketImpl。
  Socket(InetAddress address, int port)
创建流套接字并将其连接到指定IP地址的指定端口号。
  Socket(InetAddress host, int port, boolean stream)
已弃用
使用DatagramSocket代替UDP传输。
  Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
创建套接字并将其连接到指定的远程端口上指定的远程地址。
  Socket(Proxy proxy)
创建一个未连接的套接字,指定应该使用的代理类型(如果有的话),无论其他任何设置如何。
protected Socket(SocketImpl impl)
使用用户指定的SocketImpl创建一个未连接的Socket。
  Socket(String host, int port)
创建流套接字并将其连接到指定主机上的指定端口号。
  Socket(String host, int port, boolean stream)
已弃用
使用DatagramSocket代替UDP传输。
  Socket(String host, int port, InetAddress localAddr, int localPort)
创建套接字并将其连接到指定远程端口上的指定远程主机。

Socket类常规方法

Modifier and Type Method and Description
void bind(SocketAddress bindpoint)
将套接字绑定到本地地址。
void close()
关闭此套接字。
void connect(SocketAddress endpoint)
将此套接字连接到服务器。
void connect(SocketAddress endpoint, int timeout)
将此套接字连接到具有指定超时值的服务器。
SocketChannel getChannel()
返回与此套接字相关联的唯一的SocketChannel对象(如果有)。
InetAddress getInetAddress()
返回套接字所连接的地址。
InputStream getInputStream()
返回此套接字的输入流。
boolean getKeepAlive()
测试是否启用了 SO_KEEPALIVE
InetAddress getLocalAddress()
获取套接字所绑定的本地地址。
int getLocalPort()
返回此套接字绑定到的本地端口号。
SocketAddress getLocalSocketAddress()
返回此套接字绑定到的端点的地址。
boolean getOOBInline()
测试是否启用了 SO_OOBINLINE
OutputStream getOutputStream()
返回此套接字的输出流。
int getPort()
返回此套接字连接到的远程端口号。
int getReceiveBufferSize()
获取这个 SocketSO_RCVBUF选项的值,即平台在此 Socket上输入的缓冲区大小。
SocketAddress getRemoteSocketAddress()
返回此套接字连接,或端点的地址 null如果是未连接。
boolean getReuseAddress()
测试是否启用了 SO_REUSEADDR
int getSendBufferSize()
获取此 SocketSO_SNDBUF选项的值,即该平台在此 Socket上输出使用的缓冲区大小。
int getSoLinger()
SO_LINGER的退货设置。
int getSoTimeout()
SO_TIMEOUT的退货设置。
boolean getTcpNoDelay()
测试是否启用了 TCP_NODELAY
int getTrafficClass()
在从此Socket发送的数据包的IP头中获取流量类或服务类型
boolean isBound()
返回套接字的绑定状态。
boolean isClosed()
返回套接字的关闭状态。
boolean isConnected()
返回套接字的连接状态。
boolean isInputShutdown()
返回套接字连接的一半是否关闭。
boolean isOutputShutdown()
返回套接字连接的写半是否关闭。
void sendUrgentData(int data)
在套接字上发送一个字节的紧急数据。
void setKeepAlive(boolean on)
启用/禁用 SO_KEEPALIVE
void setOOBInline(boolean on)
启用/禁用 SO_OOBINLINE (接收TCP紧急数据)默认情况下,此选项被禁用,并且在套接字上接收的TCP紧急数据被静默地丢弃。
void setPerformancePreferences(int connectionTime, int latency, int bandwidth)
设置此套接字的性能首选项。
void setReceiveBufferSize(int size)
设置 SO_RCVBUF选项为这个指定的值 Socket
void setReuseAddress(boolean on)
启用/禁用 SO_REUSEADDR套接字选项。
void setSendBufferSize(int size)
设置 SO_SNDBUF选项为这个指定的值 Socket
static void setSocketImplFactory(SocketImplFactory fac)
设置应用程序的客户端套接字实现工厂。
void setSoLinger(boolean on, int linger)
启用/禁用 SO_LINGER ,具有指定的逗留时间(以秒为单位)。
void setSoTimeout(int timeout)
启用/禁用 指定超时的 SO_TIMEOUT(以毫秒为单位)。
void setTcpNoDelay(boolean on)
启用/禁用 TCP_NODELAY (禁用/启用Nagle的算法)。
void setTrafficClass(int tc)
在从此Socket发送的数据包的IP头中设置流量类或服务类型字节。
void shutdownInput()
将此套接字的输入流放置在“流的末尾”。
void shutdownOutput()
禁用此套接字的输出流。
String toString()
将此套接字转换为 String

ServerSocket类构造方法

Constructor and Description
ServerSocket()
创建未绑定的服务器套接字。
ServerSocket(int port)
创建绑定到指定端口的服务器套接字。
ServerSocket(int port, int backlog)
创建服务器套接字并将其绑定到指定的本地端口号,并指定了积压。
ServerSocket(int port, int backlog, InetAddress bindAddr)
创建一个具有指定端口的服务器,侦听backlog和本地IP地址绑定。

ServerSocket类常规方法

Modifier and Type Method and Description
Socket accept()
侦听要连接到此套接字并接受它。
void bind(SocketAddress endpoint)
ServerSocket绑定到特定地址(IP地址和端口号)。
void bind(SocketAddress endpoint, int backlog)
ServerSocket绑定到特定地址(IP地址和端口号)。
void close()
关闭此套接字。
ServerSocketChannel getChannel()
返回与此套接字相关联的唯一的ServerSocketChannel对象(如果有)。
InetAddress getInetAddress()
返回此服务器套接字的本地地址。
int getLocalPort()
返回此套接字正在侦听的端口号。
SocketAddress getLocalSocketAddress()
返回此套接字绑定到的端点的地址。
int getReceiveBufferSize()
获取此 ServerSocketSO_RCVBUF选项的值,即将用于从该 ServerSocket接受的套接字的建议缓冲区大小。
boolean getReuseAddress()
测试是否启用了 SO_REUSEADDR
int getSoTimeout()
检索 SO_TIMEOUT的设置。
protected void implAccept(Socket s)
ServerSocket的子类使用这个方法来覆盖accept()来返回自己的套接字子类。
boolean isBound()
返回ServerSocket的绑定状态。
boolean isClosed()
返回ServerSocket的关闭状态。
void setPerformancePreferences(int connectionTime, int latency, int bandwidth)
设置此ServerSocket的性能首选项。
void setReceiveBufferSize(int size)
设置从 ServerSocket接受的套接字的 SO_RCVBUF选项的默认建议值。
void setReuseAddress(boolean on)
启用/禁用 SO_REUSEADDR套接字选项。
static void setSocketFactory(SocketImplFactory fac)
设置应用程序的服务器套接字实现工厂。
void setSoTimeout(int timeout)
启用/禁用 SO_TIMEOUT带有指定超时,以毫秒为单位。
String toString()
将该套接字的实现地址和实现端口返回为 String

TCP客户端

/*
 *  实现TCP客户端,连接到服务器
 *  和服务器实现数据交换
 *  实现TCP客户端程序的类 java.net.Socket
 *  
 *  构造方法:
 *      Socket(String host, int port)  传递服务器IP和端口号
 *      注意:构造方法只要运行,就会和服务器进行连接,连接失败,抛出异常
 *      
 *    OutputStream  getOutputStream() 返回套接字的输出流
 *      作用: 将数据输出,输出到服务器
 *      
 *    InputStream getInputStream() 返回套接字的输入流
 *      作用: 从服务器端读取数据
 *      
 *    客户端服务器数据交换,必须使用套接字对象Socket中的获取的IO流,自己new流,不行
 */
import java.io.*;
import java.net.*;

public class Test {
    public static void main(String[] args) throws Exception {
        //创建Socket对象,连接服务器
        Socket socket = new Socket("127.0.0.1", 8888);
        //通过客户端的套接字对象Socket方法,获取字节输出流,将数据写向服务器
        OutputStream out = socket.getOutputStream();
        out.write("服务器OK".getBytes());

        //读取服务器发回的数据,使用socket套接字对象中的字节输入流
        InputStream in = socket.getInputStream();
        byte[] data = new byte[1024];
        int len = in.read(data);
        System.out.println(new String(data,0,len));

        socket.close();
    }
}

TCP服务端

/*
 *  实现TCP服务器程序
 *  表示服务器程序的类 java.net.ServerSocket
 *  构造方法:
 *    ServerSocket(int port) 传递端口号
 *  
 *  很重要的事情: 必须要获得客户端的套接字对象Socket
 *    Socket  accept()
 */
import java.io.*;
import java.net.*;

public class Get {
    public static void main(String[] args) throws Exception {
        ServerSocket server = new ServerSocket(8888);
        //调用服务器套接字对象中的方法accept() 获取客户端套接字对象
        Socket socket = server.accept();
        //通过客户端套接字对象,socket获取字节输入流,读取的是客户端发送来的数据
        InputStream in = socket.getInputStream();
        byte[] data = new byte[1024];
        int len = in.read(data);
        System.out.println(new String(data,0,len));

        //服务器向客户端回数据,字节输出流,通过客户端套接字对象获取字节输出流
        OutputStream out = socket.getOutputStream();
        out.write("收到,谢谢".getBytes());

        socket.close();
        server.close();
    }
}

TCP上传图片

  • 客户端
/*
 *  实现TCP图片上传客户端
 *  实现步骤:
 *    1. Socket套接字连接服务器
 *    2. 通过Socket获取字节输出流,写图片
 *    3. 使用自己的流对象,读取图片数据源
 *         FileInputStream
 *    4. 读取图片,使用字节输出流,将图片写到服务器
 *       采用字节数组进行缓冲
 *       完成循环后给服务器写终止序列
 *    5. 通过Socket套接字获取字节输入流
 *       读取服务器发回来的上传成功
 *    6. 关闭资源
 */
import java.io.*;
import java.net.*;

public class Test {
    public static void main(String[] args) throws Exception {
          Socket socket = new Socket("127.0.0.1", 8000);
          //获取字节输出流,图片写到服务器
          OutputStream out = socket.getOutputStream();
          //创建字节输入流,读取本机上的数据源图片
          FileInputStream fis = new FileInputStream("c:\\t.jpg");
          //开始读写字节数组
          int len = 0 ;
          byte[] bytes = new byte[1024];
          while((len = fis.read(bytes))!=-1){
            out.write(bytes, 0, len);
          }
          //给服务器写终止序列
          //不写这行服务端会无限等待
          socket.shutdownOutput();

          //获取字节输入流,读取服务器的上传成功
          InputStream in = socket.getInputStream();

          len = in.read(bytes);
          System.out.println(new String(bytes,0,len));

          fis.close();
          socket.close();
    }
}
  • 服务端
/*
 *  TCP图片上传服务器
 *   1. ServerSocket套接字对象,监听端口8000
 *   2. 方法accept()获取客户端的连接对象
 *   3. 客户端连接对象获取字节输入流,读取客户端发送图片
 *   4. 创建File对象,绑定上传文件夹
 *       判断文件夹存在, 不存,在创建文件夹
 *   5. 创建字节输出流,数据目的File对象所在文件夹
 *   6. 字节流读取图片,字节流将图片写入到目的文件夹中
 *   7. 将上传成功会写客户端
 *   8. 关闭资源
 *       
 */
import java.io.*;
import java.net.*;

public class Get {
    public static void main(String[] args) throws Exception {
          ServerSocket server = new ServerSocket(8000);
          Socket socket = server.accept();
          //通过客户端连接对象,获取字节输入流,读取客户端图片
          InputStream in = socket.getInputStream();
          //将目的文件夹封装到File对象
          File upload = new File("d:\\upload");
          if(!upload.exists())
            upload.mkdirs();

          //创建字节输出流,将图片写入到目的文件夹中                         
          FileOutputStream fos = new FileOutputStream(upload+"t.jpg");
          //读写字节数组
          byte[] bytes = new byte[1024];
          int len = 0 ;
          while((len = in.read(bytes))!=-1){
            fos.write(bytes, 0, len);
          }
          //通过客户端连接对象获取字节输出流
          //上传成功写回客户端
          socket.getOutputStream().write("上传成功".getBytes());

          fos.close();
          socket.close();
          server.close();
    }
}

多线程上传

  • 客户端
import java.io.*;
import java.net.*;

public class Test {
    public static void main(String[] args) throws Exception{
          Socket socket = new Socket("127.0.0.1", 8000);
          //获取字节输出流,图片写到服务器
          OutputStream out = socket.getOutputStream();
          //创建字节输入流,读取本机上的数据源图片
          FileInputStream fis = new FileInputStream("c:\\a.txt");
          //开始读写字节数组
          int len = 0 ;
          byte[] bytes = new byte[1024];
          while((len = fis.read(bytes))!=-1){
            out.write(bytes, 0, len);
          }
          //给服务器写终止序列
          socket.shutdownOutput();

          //获取字节输入流,读取服务器的上传成功
          InputStream in = socket.getInputStream();

          len = in.read(bytes);
          System.out.println(new String(bytes,0,len));

          fis.close();
          socket.close();
    }
}
  • 服务端
import java.io.*;
import java.net.*;
import java.util.Random;


public class Server implements Runnable {
    public Socket socket;
    public Server(){}
    public Server(Socket socket){
        this.socket = socket;
    }

    public void run(){
        try{
            //通过客户端连接对象,获取字节输入流,读取客户端图片
            InputStream in = socket.getInputStream();
            //将目的文件夹封装到File对象
            File upload = new File("c:\\upload");
            if(!upload.exists())
              upload.mkdirs();
            // 防止文件同名被覆盖,从新定义文件名字
            // 规则: 毫秒值+6位随机数
            String filename = System.currentTimeMillis()+new Random().nextInt(999999)+".txt";
            //创建字节输出流,将图片写入到目的文件夹中                         
            FileOutputStream fos = new FileOutputStream(upload+File.separator+filename);
            //读写字节数组
            byte[] bytes = new byte[1024];
            int len = 0 ;
            while((len = in.read(bytes))!=-1){
              fos.write(bytes, 0, len);
            }
            //通过客户端连接对象获取字节输出流
            //上传成功写回客户端
            socket.getOutputStream().write("上传成功".getBytes());

            fos.close();
            socket.close();
        }catch(Exception e)
        {
            System.out.println("失败");
            System.out.println(e);
        }

      }
}
  • 开启多线程
import java.net.*;

public class TCPthread {
    public static void main(String[] args) throws Exception{
        ServerSocket server = new ServerSocket(8000);
        while(true)
        {
            Socket socket = server.accept();
            new Thread(new Server(socket)).start();
        }
    }
}