Socket网络编程+山寨WEB服务器

Socket网络编程

一、网络编程概述

javaEE开发、做一些中间件,底层实现原理,怎么通讯,底层都是通过网络编程。

什么是网络编程?
  网络编程的本质是两个设备之间的数据交换,当然,在计算机网络中,设备主要指计算机。数据传递本身没有多大的难度,不就是把一个设备中的数据发送给两外一个设备,然后接受另外一个设备反馈的数据。
  现在的网络编程基本上都是基于请求/响应方式的,也就是一个设备发送请求数据给另外一个,然后接收另一个设备的反馈。
  在网络编程中,发起连接程序,也就是发送第一次请求的程序,被称作客户端(Client),等待其他程序连接的程序被称作服务器(Server)。客户端程序可以在需要的时候启动,而服务器为了能够时刻相应连接,则需要一直启动。例如以打电话为例,首先拨号的人类似于客户端,接听电话的人必须保持电话畅通类似于服务器。
  连接一旦建立以后,就客户端和服务器端就可以进行数据传递了,而且两者的身份是等价的。

  在一些程序中,程序既有客户端功能也有服务器端功能,最常见的软件就是BT、emule这类软件了。


IP地址和域名
  在现实生活中,如果要打电话则需要知道对应人的电话号码,如果要寄信则需要知道收信人的地址。在网络中也是这样,需要知道一个设备的位置,则需要使用该设备的IP地址,具体的连接过程由硬件实现,程序员不需要过多的关心。
  IP地址是一个规定,现在使用的是IPv4,既由4个0-255之间的数字组成,在计算机内部存储时只需要4个字节即可。在计算机中,IP地址是分配给网卡的,每个网卡有一个唯一的IP地址,如果一个计算机有多个网卡,则该台计算机则拥有多个不同的IP地址,在同一个网络内部,IP地址不能相同。IP地址的概念类似于电话号码、身份证这样的概念。
  由于IP地址不方便记忆,所以有专门创造了域名(Domain Name)的概念,其实就是给IP取一个字符的名字,例如163.com、sina.com等。IP和域名之间存在一定的对应关系。如果把IP地址类比成身份证号的话,那么域名就是你的姓名。

  其实在网络中只能使用IP地址进行数据传输,所以在传输以前,需要把域名转换为IP,这个由称作DNS的服务器专门来完成。所以在网络编程中,可以使用IP或域名来标识网络上的一台设备。

端口的概念
  为了在一台设备上可以运行多个程序,人为的设计了端口(Port)的概念,类似的例子是公司内部的分机号码。
  规定一个设备有216个,也就是65536个端口,每个端口对应一个唯一的(不能重复的)程序。每个网络程序,无论是客户端还是服务器端,都对应一个或多个特定的端口号。由于0-1024之间多被操作系统占用,所以实际编程时一般采用1024以后的端口号。使用端口号,可以找到一台设备上唯一的一个程序。

  所以如果需要和某台计算机建立连接的话,只需要知道IP地址或域名即可,但是如果想和该台计算机上的某个程序交换数据的话,还必须知道该程序使用的端口号。

小结
      网络编程就是使用IP地址,或域名,和端口号连接到另一台计算机上对应的程序,按照规定的协议(数据格式)来交换数据,实际编程中建立连接和发送、接收数据在语言级已经实现,做的更多的工作是设计协议,以及编写生成和解析数据的代码罢了,然后把数据转换成逻辑的结构显示或控制逻辑即可。
      对于初学者,或者没有接触过网络编程的程序员,会觉得网络编程涉及的知识很高深,很难,其实这是一种误区,当你的语法熟悉以后,其实基本的网络编程现在已经被实现的异常简单了。

网络模型图



二、Socket概述 



三、UDP客户端代码实现 

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

/**
 * 客户端
 * 思路:
 * 1.建立可以实现UDP传输的Socket服务
 * 2.明确具体发送的数据
 * 3.通过Socket服务将具体的数据发送出去
 * 4.关闭服务
 * Created by yz on 2018/03/31.
 */
public class UdpSocketClient {
    // 写代码使用try注意地方,不要全部try 只需要可能会出现异常的代码才try
    // 整个try catch是比没有全部try catch 执行效率是降低的,底层有异常捕获机制
    public static void main(String[] args) throws SocketException {
        System.out.println("UDP发送端启动成功...");
        // 1.建立可以实现UDP传输的Socket服务
        DatagramSocket datagramSocket = new DatagramSocket();
        // 2.明确具体发送的数据
        String str = "注意啦,UDP发送的数据来啦!";
        byte[] bytes = str.getBytes();
        try {
            InetAddress serverIpAddress = InetAddress.getByName("192.168.230.1");
            // 3.通过Socket服务将具体的数据发送出去。 
            // 参数1字节数据 参数2字节长度 参数3发送端IP地址 参数4端口号
            DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, serverIpAddress, 9999);
            datagramSocket.send(datagramPacket);

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            // 4.关闭服务
            datagramSocket.close();
        }
    }
}

四、UDP服务器端代码实现 

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

/**
 * 服务器端
 * 思路:
 * 1.创建Socket服务,明确一个端口
 * 2.接收数据
 * 3.将其中的所需数据取出来 ip data 端口
 * 4.关闭服务
 * Created by yz on 2018/03/31.
 */
public class UdpSocketServer {
    public static void main(String[] args){
        System.out.println("UDP接受数据启动成功...");
        // 1.创建Socket服务,明确一个端口
        DatagramSocket datagramSocket = null;
        try {
            datagramSocket = new DatagramSocket(9999);
            // 2.定义接收数据格式
            byte[] buf = new byte[1024];
            DatagramPacket dp = new DatagramPacket(buf, buf.length);
            // 3.接收数据 并且有阻塞功能
            datagramSocket.receive(dp);
            // 获取IP地址
            String ip = dp.getAddress().getHostAddress();
            // 获取端口号
            int port = dp.getPort();
            // 数据
            String str = new String(dp.getData(), 0, dp.getLength());
            System.out.println(ip+",port:"+port+","+str);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // 关闭服务
            datagramSocket.close();
        }
    }
}

五、TCP协议客户端 

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

/**
 * TCP协议客户端
 * 思路
 * 1.因为是面向连接,必须有连接才可以进行通讯
 * 2.在创建客户端时,就必须明确服务器端IP地址和端口号,不存在会抛异常
 * 3.一旦连接建立,就有了传输数据的通道,就可以在通道进行数据传输
 * 这个传输其实就是通过IO流实现的。
 * Created by yz on 2018/03/31.
 */
public class TcpSocketClient {
    public static void main(String[] args) throws IOException {
        Socket socket = null;
        OutputStream outputStream = null;
        try {
            // 连接服务器端 参数1服务器连接IP地址 参数2服务器端端口号,连接不上会抛异常
            socket = new Socket("192.168.230.1", 9999);
            // 获取Socket流中的输出流,将数据发送给服务器端
            outputStream = socket.getOutputStream();
            outputStream.write("TCP协议客户端来啦!".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // 关闭连接
            outputStream.close();
            socket.close();
        }
    }
}

六、TCP协议服务器端 

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

/**
 * TCP协议服务器端
 * 思路
 * 1.创建socket服务器端服务,服务器端为了让客户端可以连接上,必须提供监听的端口号
 *  (服务器在哪一个机器部署的,IP地址就是哪个)
 * 2.获取客户端对象,通过客户端Socket流和对应的客户端进行通讯
 * 3.获取客户端的Socket流读取流
 * 4.读取数据并显示在服务器端
 * 5.关闭资源
 * Created by yz on 2018/03/31.
 */
public class TcpSocketServer {
    public static void main(String[] args) throws IOException {
        System.out.println("服务器端启动成功...");
        ServerSocket serverSocket = null;
        Socket accept = null;
        InputStream inputStream = null;
        try {
            // 1.创建socket服务器端
            serverSocket = new ServerSocket(9999);
            // 2.获取与客户端进行连接 有阻塞功能,如果没有客户端连接,一直等待。
            accept = serverSocket.accept();
            // 获取客户端IP
            String ip = accept.getInetAddress().getHostAddress();
            System.out.println("ip:"+ip);
            // 3.获取客户端的Socket流读取流
            inputStream = accept.getInputStream();
            byte[] buf = new byte[1024];
            int len = inputStream.read(buf);
            // 转换成字符串 每次读多少,从0开始,整个长度
            String str = new String(buf, 0, len);
            System.out.println(str);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // 关闭连接
            inputStream.close();
            accept.close();
            serverSocket.close();
        }
    }
}

七、SockeTCP实现QQ聊天案例

实现案例:客户端和服务器端实现无线通讯,类似于QQ聊天(通讯连接不能断)

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

/**
 * TCP协议服务器端
 * 思路
 * 1.创建socket服务器端服务,服务器端为了让客户端可以连接上,必须提供监听的端口号
 *  (服务器在哪一个机器部署的,IP地址就是哪个)
 * 2.获取客户端对象,通过客户端Socket流和对应的客户端进行通讯
 * 3.获取客户端的Socket流读取流
 * 4.读取数据并显示在服务器端
 * 5.关闭资源
 * Created by yz on 2018/03/31.
 */
public class QQTcpSocketServer {
    public static void main(String[] args) throws IOException {
        System.out.println("服务器端启动成功...");
        // 1.创建socket服务器端
        ServerSocket serverSocket = new ServerSocket(9999);
        // 2.获取与客户端进行连接 有阻塞功能,如果没有客户端连接,一直等待。
        Socket accept = serverSocket.accept();
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            while (true){
                // 获取客户端IP
                String ip = accept.getInetAddress().getHostAddress();
                //System.out.println("ip:"+ip);
                // 3.获取客户端的Socket流读取流
                inputStream = accept.getInputStream();
                byte[] buf = new byte[1024];
                int len = inputStream.read(buf);
                // 转换成字符串 每次读多少,从0开始,整个长度
                String str = new String(buf, 0, len);
                System.out.println("客户端发送内容:"+str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // 关闭连接
            inputStream.close();
            outputStream.close();
            accept.close();
            serverSocket.close();
        }
    }
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * TCP协议客户端
 * 思路
 * 1.因为是面向连接,必须有连接才可以进行通讯
 * 2.在创建客户端时,就必须明确服务器端IP地址和端口号,不存在会抛异常
 * 3.一旦连接建立,就有了传输数据的通道,就可以在通道进行数据传输
 * 这个传输其实就是通过IO流实现的。
 * Created by yz on 2018/03/31.
 */
public class QQTcpSocketClient {
    public static void main(String[] args) throws IOException {
        System.out.println("客户端开始发送数据啦!");
            // 连接服务器端 参数1服务器连接IP地址 参数2服务器端端口号,连接不上会抛异常
            Socket socket = new Socket("192.168.230.1", 9999);
            InputStream inputStream = null;
            OutputStream outputStream = null;
            try{
                while (true){
                    // 获取Socket流中的输出流,将数据发送给服务器端
                    outputStream = socket.getOutputStream();
                    Scanner scanner = new Scanner(System.in);
                    System.out.println("给服务器端发送内容:");
                    String next = scanner.next();
                    outputStream.write(next.getBytes());
                    // 拿到服务器端回复的内容
                    inputStream = socket.getInputStream();
                    byte[] buf = new byte[1024];
                    int len = inputStream.read(buf);
                    // 转换成字符串 每次读多少,从0开始,整个长度
                    String str = new String(buf, 0, len);
                    System.out.println("收到服务器端回复内容:"+str);
                }
            }catch (Exception e){
            }finally {
                // 关闭连接
                inputStream.close();
                outputStream.close();
                socket.close();
            }
    }
}
山寨WEB服务器
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 目标:开发一个山寨WEB服务器
 * 1:支持多个浏览器访问
 * 2:如何提供服务(Socket)
 * 3: 如何返回响应(IO)
 * Created by yz on 2018/03/08.
 */
public class TomMonkey {

    private static int POST = 8080;
    /**
     * 程序启动入口
     * @param args
     */
    public static void main(String[] args) {
        // 动态设置服务器的端口
        int p = args.length>0 ? Integer.parseInt(args[0]):POST ;
        new TomMonkey().start(p);
    }

    /**
     * 服务启动方法
     */
    public void start(int port){
        try {
            ServerSocket ss = new ServerSocket(port);
            System.out.println("---------监听8080端口的服务器请求-----------");
            while (true){
                Socket socket = ss.accept();
                System.out.println("--------有客户端请求-------");
                // 创建一个线程池 设置100个线程
                ExecutorService pool = Executors.newFixedThreadPool(100);
                // 将任务提交给线程池去处理
                pool.submit(new HandlerRequestThread(socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

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

/**
 * 专门处理不同客户端请求的 多线程类
 * Created by yz on 2018/03/08.
 */
public class HandlerRequestThread implements Runnable{

    private  InputStream in = null;
    // 因为要涉及浏览器内容的输出,所以使用了PringStream 标准输出流
    private PrintStream out = null;

    // D:\MyWeb\ 好比web应用跟路径
    public static final String WEB_ROOT = "D:"+File.separator+"MyWeb"+File.separator;

    /**
     * 通过构造器获取Socket
     * 并通过Socket获取对客户端的输入和输出流
     * @param socket
     */
    public HandlerRequestThread(Socket socket) {
        try {
            in = socket.getInputStream();
            out = new PrintStream(socket.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 解析请求头,获得客户端请求的资源名称
     * @param in 输入流
     * @return 请求的资源名称
     */
    public String parseRequestHead(InputStream in) throws IOException {
        // 客户端发起请求会将一些请求数据包含在请求头中
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        // 请求头的第一行包含:请求的方式,请求的资源名称,请求的协议版本
        String headContent = br.readLine(); // GET /ttt.html HTTP/1.1
        System.out.println(headContent);
        String[] heads = headContent.split(" ");
        // 设置默认页面
        return heads[1].endsWith("/")? "test.jpg":heads[1];
    }

    /**
     * 获取请求信息
     * @param fileName
     * @throws IOException
     */
    public void getFile(String fileName) throws IOException {
        File file = new File(WEB_ROOT+fileName);
        System.out.println(file.toString());
        if(!file.exists()){
            sendError("404","您请求的资源["+fileName+"]不存在!");
            return;
        }else{
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            byte[] content = new byte[(int) file.length()];
            int len = bis.read(content);
            // 告诉浏览器 本次操作成功
            out.println("HTTP/1.1 200 OK");
            out.println();
            // 写出数据
            out.write(content);
            out.flush();
            out.close();
        }
    }

    /**
     * 返回错误提示信息
     * @param errorNum
     * @param errorMsg
     */
    public void sendError(String errorNum,String errorMsg){
        StringBuilder sb = new StringBuilder();
        sb.append("<html><head><title>错误页面</title>");
        sb.append("<meta http-equiv='content-type' content='text/html;charset=utf-8'></head>");
        sb.append("<body><center><h1><font color='red'>"+errorNum+"</font></h1></center>");
        sb.append("<p>"+errorMsg+"</p></body></html>");
        out.println("HTTP/1.1 404 Not Found");
        out.println();
        out.print(sb.toString());
        out.flush();
        out.close();
    }
    /**
     * 线程体方法
     */
    public void run() {
        System.out.println("-------处理用户请求--------");
        try {
            String fileName = parseRequestHead(this.in);
            getFile(fileName);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


发布了43 篇原创文章 · 获赞 32 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/yz2015/article/details/79491030