网络编程套接字(Socket)

网络编程中的概念

网络编程:网络上的主机,在不同进程中以编程的形式实现数据传输,实现网络通信。同一台主机中,不同进程之间也可以实现网络通信。

发送端:一次网络通信中发送数据的进程,发送端主机称为网络通信的源主机;

接收端:接收数据的进程,接收端主机称为目的主机

收发端:接收端和发送端的两端的简称。

请求和响应:获取一次网络资源进行的两次数据传输。

客户端:发送请求,获取服务的进程。

服务端:响应请求,提供服务的进程

socket(套接字)

socket是操作系统提供用于的网络编程API,基于网络通信的基本操作单元。不同操作系统提供的socket  api 不一样,java对这些系统api进行了统一封装。

分类:socket套接字主要针对传输层协议划分为三类,原始套接字不关注,只关注流套接字(使用传输层TCP协议)  和   数据报套接字(使用传输层UDP协议)。

协议特点

UDP协议特点:无连接,不可靠传输,面向数据报,全双工。

TCP协议特点:可靠传输,有连接,面向字节流,全双工。

连接:通信双方需要保留对方的信息(ip地址,端口号等信息),才能进行正常通信

无连接:通信双方不需要提前保存对方信息 

可靠传输:数据包不会丢失,丢失了再重新传输丢失的数据包,确保对方收到

不可靠传输:传输过程数据包可能丢失,发送数据后不关心对方是否接收到数据

面向字节流:文件操作就是字节流,读写字节可以非常灵活,10个字节可以读10次,10次可以读100个字节。TCP套接字跟文件操作有相同特点。

面向数据报:传输数据的基本单位是一个UDP数据报,读写只能操作一个完整的数据报,不能读半个数据报。

全双工:一条链路可以进行双向通信

半双工:一条链路只能进行单向通信

数据报套接字:使用传输层UDP协议

面向数据报:以数据报的形式传输接收数据,一个数据报最大为64KB字节。

DatagramSocket

对操作系统socket   api的封装。系统socket  api可以看做一个文件,是“网卡”这种硬件设备的抽象表现形式。对socket文件的读写操作就是间接对“网卡”进行读写操作。

普通文件是硬盘的抽象表现形式,对文件的读写就是在间接对硬盘的读写。文件就相当于操作硬件的遥控器,具有这种“遥控器”属性的在计算机中称handle “句柄”。

构造方法

DatagramSocket ()  //创建UDP数据报套接字的Socket,一般用于客户端(操作系统分配空闲端口);

DatagramSocket(int   port)  //一般用于服务端(指定端口,客户端才能知道往主机的哪个程序发送请求)

成员方法

void  recive(DatagramPacket    p);//接收数据(如果没有数据报传输过来就阻塞等待);

void  send(DatagramPacket    p) ; //发送数据报p(不会阻塞等待,直接发送)

void   close()  ;  //相当于关闭文件,关闭此数据报套接字

DatagramPacket

DatagramPacket是对UDP数据报的抽象表示,一个DatagramPacket对象就是一个UDP数据报 ,数据传输的基本单位。

构造方法

 DatagramPacket(byte[ ] buf,int  length); //构造数据报来接收数据,接收的数据存在字节数组中,接收长度0到length;

DatagramPacket(byte[ ]   buf ,int  offset,int length,SocketAddres address);  //构造数据报发送的数据报,数据为字节数组中,offset到length的数据,address为目的主机的ip和port

成员方法

InetAddress   getAddress();//从数据报中获取IP地址

int getPort() ; //从数据报中获取端口号

 getSocketAddress();//从socket中获取客户端地址和IP

byte[ ]   getData();// 获取数据报中的数据

代码实现

在回显服务器中用数据报套接字进行网络通信

package socket;

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

public class UdpEchoService {
    //回显服务器:请求是啥就响应啥
    DatagramSocket socket=null;
    public UdpEchoService(int port) throws SocketException {
        //服务器端口号一开始就得指定,否则客户端不知道往哪里发送请求,客户端端口号由系统自动分配空闲端口号
        socket=new DatagramSocket(port);
    }
    public  void  start() throws IOException {
        System.out.println("服务器启动");
        while (true) {//用while一直响应客户端的请求

            //读取请求并解析
            DatagramPacket request = new DatagramPacket(new byte[4000], 4000);//一个对象就接收一个数据报
            socket.receive(request);//输出型函数:接收数据报,然后将数据填到request的数组空间里。如果没有接收到请求,就IO阻塞
            String s=new String(request.getData(),0, request.getLength());//为了方便处理数据报的数据,转成字符串
            //根据请求生成响应
            String  r= response(s);
            //将响应写回客户端
            DatagramPacket   socketResponse=new DatagramPacket(s.getBytes(),s.getBytes().length,request.getSocketAddress());//从数据报中解析客户端ip,port
            socket.send(socketResponse);//发送数据报
        }
    }

    private String  response(String s) {
       return s;
    }


    public static void main(String[] args) throws IOException {
        UdpEchoService service=new UdpEchoService(2323);
        service.start();
    }
}

客户端

package socket;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket;
    private String IP;
    private int port;

    public UdpEchoClient(String IP,int port) throws SocketException {
       this.IP=IP;
       this.port=port;
       socket=new DatagramSocket();
    }

    public void start() throws IOException {
            //接收控制台请求
        Scanner in=new Scanner(System.in);
        while (true) {
            System.out.println("请输入请求");
            String s = in.next();
            //读取请求发送数据报
            //InetAddress.getByName(IP)将字符串的IP转换成Java能识别的格式转换成32位二进制
            DatagramPacket request = new DatagramPacket(s.getBytes(), 0, s.getBytes().length, InetAddress.getByName(IP), port);
            socket.send(request);//socket负责接收发送,数据端口地址在数据报里
            //接收服务器响应
            DatagramPacket response = new DatagramPacket(new byte[4000], 4000);//创建数据报,用字节数组接收服务器发送过来的数据报里的数据
            socket.receive(response);//服务器没有响应就阻塞等待
            //显示响应到控制台
            String r = new String(response.getData());
            System.out.println(r);
        }

    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1",2323);
        client.start();
    }
}
流式套接字:使用传输层TCP协议

面向字节流:传输数据基于IO流,在IO流没有关闭的情况下,每次传输数据和接收数据没有数量限制,数据可以多次发送,也可以分开多次接收。

ServerSocket

对操作系统socket   api封装的api,服务器使用 的套接字

ServerSocket(int   port)//创建服务端socket并指定端口号

Socket accept();//监听创建serversocket时绑定的端口,有客户端连接后返回一个服务端socket对象, 基于此socket与客户端连接,否则阻塞等待

void  close()//关闭套接字

Socket

可以存在客户端,也可以存在服务端的套接字。

构造方法

Socket(String   ip,int   port);//创建客户端socket,并与指定IP的主机进程建立连接

成员方法

InetAddress  getInetAddress(); //返回此套接字所连接的地址

InputStream   getInputStream();//获取此socket的输入流

OutputStream    getOutputStream();//获取此套接字的输出流

服务器ServerSocket

 1.监听来自客户端的连接请求;

2.绑定到服务器指定端口号并监听该端口的连接请求;

3.当有新的连接请求到达,接受连接并创建一个新的Socket来处理与该客户端的通信,原来的serversocket继续监听新连接;

客户端Socket

1.主动发起连接请求到指定主机进程建立连接;

2.使用socket与服务器进行数据交换;

代码实现

回显服务器用字节流套接字网络通信

package socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpEchoServer {
    ServerSocket serverSocket;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    //接收请求并解析
    //根据请求生成响应
    //发送响应

    public void start() throws IOException {
        System.out.println("服务器启动");
        //创建自动扩容的线程池
        ExecutorService pool = Executors.newCachedThreadPool();
        while (true) {
            //不停接收客户端socket
            //如果和客户端建立TCP连接,没有数据传输时,accept就阻塞
            Socket clientSocket = serverSocket.accept();//接收客户端socket创建新的socket返回,serverSocket继续监听客户端连接
            pool.submit(new Runnable() {//提交任务到线程池
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);//每个线程跟一个客户端建立tcp连接
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    public void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());

        try (InputStream inputStream = clientSocket.getInputStream();//try完了之后自动关闭输入输出流
             OutputStream outputStream = clientSocket.getOutputStream()) {
            Scanner scan=new Scanner(inputStream);
            while (true) {
                //如果客户端tcp连接没有断,当没有数据接收时hasNext阻塞,并返回false
                //接收到数据返回true
                //如果tcp连接断开,haseNext唤醒返回ture
                if(!scan.hasNext()){
                    System.out.printf("[%S:%d]客户端下线",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                //接收请求并解析
             String  request=scan.next();
                //根据请求生成响应
                String response = process(request);
                //将响应写回客户端
                   outputStream.write(response.getBytes());
                //将响应打印到控制台
                System.out.printf("[%s:%d],req:%s,resp:%s",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            clientSocket.close();//关闭客户端套接字,服务器进程要接收很多客户端请求
            // 进行响应,不会随便销毁服务器进程来关闭所有套接字,所以要手动关闭断开连接的客户端socket
            
        }
    }

    private String process(String request) {
        return request + "\n";
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(1000);
        server.start();
    }
}

客户端

package socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    // 客户端 Socket
    private Socket socket = null;

    //指定服务器的ip ,port
    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // 这样的构造过程, 就会和服务器之间, 建立 tcp 连接.
        // 具体建立连接的流程, 都是系统内核完成的.
        socket = new Socket(serverIp, serverPort);
    }

    public void start() {
        System.out.println("客户端启动!");
        Scanner scan = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            Scanner resp = new Scanner(inputStream);
            while (true) {
                // 1. 从控制台读取数据
                System.out.print("请输入要发送的数据: ");
                String request = scan.next();//没有请求时IO阻塞
                // 2. 把请求发送给服务器, 发送的请求要带有 \n, 和服务器的 scanner.next 是对应的.
                //    由于上述通过 next 读到的 request 本身已经没有 \n 结尾了. 需要手动添加上换行
                request += "\n";
                outputStream.write(request.getBytes());
                // 3. 从服务器读取到响应
                if (!resp.hasNext()) {//发送后等待响应,如果tcp连接断开,退出循环
                    break;
                }
                String response = resp.next();

                // 4. 把响应显示到控制台上
                System.out.println(response);

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

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1", 1000);
        client.start();
    }
}
通信流程图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值