Fork me on GitHub

TCP

注意:所有文章除特别说明外,转载请注明出处.

概述

我们想要进行网络编程,首先需要服务端通过ServerSocket对某一个端口进行监听。通过accept()来判断是否有客户端与之相连,若成功连上,则通过readline()和println()函数来进行数据的发送和接收,从而实现网络编程。

网络通信协议

HTTP协议:超文本传输协议,互联网应用最广泛的。

FTP协议:文件传输协议。

SMTP协议:简单邮件传输协议。

TCP协议(Transmission Control Protocol 传输控制协议):面向连接的、可靠的、基于字节流的传输通信协议。

UDP协议(User Datagram Protocol用户数据报协议):无连接的协议,在传输数据之前,客户端和服务器并不建立和维护连接,效率很快。

Socket

通信链路的端点就被称为“套接字”(英文名Socket),是提供给应用程序的接口。这里因为Socket的底层机制复杂,Java平台提供了一些简单的API,可以更简单有效的使用Socket开发而无需了解底层机制。

1.Socket分类

1.流式套接字(SOCK_ STREAM)【面向连接、可靠的数据传输服务(对应服务是TCP链接)】

2.数据报式套接字(SOCK_ DGRAM)【无连接服务(数据报似的,基于UDP)更高效】

3.原始式套接字(SOCK_ RAW)

提示:基于TCP协议的Socket网络通信,用来实现双向安全连接网络通信。Socket通信模型,表示在进行网络通信时,Socket需要借助数据流来完成数据的传递工作。

2.Socket网络编程步骤

1.服务端
    1.创建ServerSocket(…)对象,绑定某一端口号。
    2.通过accept()侦听方法,阻塞,等待对方连接。
    3.通过输入、输出流传递信息【客户端是输出输入,服务器是输入输出】。
    4.关闭释放资源。

2.客户端
    1.创建Socket("…",…)要指定服务器的IP地址,也要指定端口号。
    2.发送请求并被accept()侦听到,并建立连接。
    3.通过输入、输出流传递信息【客户端是输出输入,服务器是输入输出】。
    4.关闭释放资源。

Socket编程

1.直接传参的方式

1.客户端程序

    package cn.edu.xidian.see.*.Inet;
    import java.io.*;
    import java.net.Socket;
    import java.net.UnknownHostException;

    public class LogicClient {
        public static void main(String[] args) {
            //创建一个客户端的Socket
            try {
                Socket socket = new Socket("localhost",5000);
                //通过输出流发送请求
                //直接输出数据流
                String info = "用户名:樱桃小丸子;密码:123456";
                OutputStream os = socket.getOutputStream();
                //打散成数据数组
                byte[] infos = info.getBytes();
                os.write(infos);
                //关闭输出流,这是一个半关闭
                socket.shutdownOutput();
                //通过输入流接收服务器给我的响应
                InputStream is = socket.getInputStream();
                BufferedReader br = new BufferedReader(new InputStreamReader(is));
                String reply;
                while((reply = br.readLine())!=null){
                    System.out.println("服务器:"+reply);
                }
                //释放资源
                os.close();
                socket.close();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

2.服务端程序

    package cn.edu.xidian.see.*.Inet;
    import java.io.*;
    import java.net.ServerSocket;
    import java.net.Socket;

    public class LogicServicer {
        public static void main(String[] args) {
            try {
                //接收客户端请求
                //创建一个Socket
                ServerSocket serverSocket = new ServerSocket(5000);
                //使用accept()侦听并接收到此ServerSocket的连接
                Socket socket = serverSocket.accept();//侦听到之前都是阻塞的
                //获得输入流,获得用户的请求
                //数据流输入
                InputStream is = socket.getInputStream();
                // BufferedReader读取字符流
                BufferedReader br = new BufferedReader(new InputStreamReader(is));
                String info;
                // BufferedReader的方法可以一行一行的读
                while((info = br.readLine())!=null){
                    System.out.println("客户端:"+info);
                }
                //给客户端一个响应
                String reply = "欢迎登陆";
                //通过输出流将响应发送回给客户端
                OutputStream os = socket.getOutputStream();
                os.write(reply.getBytes());
                //释放相应资源
                os.close();
                br.close();
                is.close();
                socket.close();
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

2.通过用户对象传参的方式

1.用户类程序

    package cn.edu.xidian.see.*.Inet.user;
    import java.io.Serializable;
    public class User implements Serializable {
        private String username;
        private String password;

        public User() {
        }

        public User(String username, String password) {
            this.username = username;
            this.password = password;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }
    }

2.客户端程序

    package cn.edu.xidian.see.*.Inet.user;
    import java.io.*;
    import java.net.Socket;
    import java.net.UnknownHostException;

    public class LogicClient {
        public static void main(String[] args) {
            //创建一个客户端的Socket
            try {
                Socket socket = new Socket("localhost",5000);
                //通过输出流发送请求
                //将对象序列化变成输出流
                User user = new User("樱桃小丸子","123456");
                OutputStream os = socket.getOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(os);
                oos.writeObject(user);
                //关闭输出流,这是一个半关闭
                socket.shutdownOutput();
                //通过输入流接收服务器给我的响应
                InputStream is = socket.getInputStream();
                BufferedReader br = new BufferedReader(new InputStreamReader(is));
                String reply;
                while((reply = br.readLine())!=null){
                    System.out.println("服务器:"+reply);
                }
                //释放资源
                os.close();
                oos.close();
                socket.close();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

3.服务端程序

    package cn.edu.xidian.see.*.Inet.user;

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

    public class LogicServicer {
        public static void main(String[] args) {
            try {
                //接收客户端请求
                //创建一个Socket
                ServerSocket serverSocket = new ServerSocket(5000);
                //使用accept()侦听并接收到此ServerSocket的连接
                Socket socket = serverSocket.accept();//侦听到之前都是阻塞的
                //获得输入流,获得用户的请求
                //将对象反序列化变成输入流
                InputStream is = socket.getInputStream();
                ObjectInputStream ois = new ObjectInputStream(is);
                User user = (User)ois.readObject();
                System.out.println("客户端:"+user.getUsername()+"-"+user.getPassword());
                //给客户端一个响应
                String reply = "欢迎登陆";
                //通过输出流将响应发送回给客户端
                OutputStream os = socket.getOutputStream();
                os.write(reply.getBytes());
                //释放相应资源
                ois.close();
                os.close();
                is.close();
                socket.close();
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

提示:关于如何实现多客户请求,采用多线程的方式,然后写一个专门负责监听的应用主服务程序,以及一个专门负责处理请求的线程程序。

服务器与客户端的多线程连接(服务器与客户端可以无限对话)

1.服务器

    1.一直监听客户请求。
    2.一旦监听到有客户请求,立即创建一个线程,开启线程。

1.1 程序实现

    package cn.edu.xidian.see.*.Inet.ThreadUser;

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

    public class LogicServicer {
        //一直监听客户请求
        //一旦监听到有客户请求,立即创建一个线程,开启线程
        public static void main(String[] args) {
            try {
                //接收客户端请求
                //创建一个Socket
                ServerSocket serverSocket = new ServerSocket(8848);
                System.out.println("启动服务器");
                //使用accept()侦听并接收到此ServerSocket的连接
                //一直监听客户请求
                while(true){
                    Socket socket = serverSocket.accept();//侦听到之前都是阻塞的
                    System.out.println("已连接");
                    //创建一个和该客户端响应的线程
                    LogicThread logicThread = new LogicThread(socket);
                    logicThread.start();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

2.线程

    1.接收客户请求,基于客户的一个响应(这过程在之前是服务器要做的事情)。
    2.线程构造方法中绑定客户端的Socket。

2.1 程序实现

    package cn.edu.xidian.see.*.Inet.ThreadUser;

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

    public class LogicThread extends Thread{
        //接收客户请求,基于客户一个响应(这在之前是服务器所需要做的事情)
        //线程构造方法中去绑定客户端的Socket

        private Socket socket;

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

        //接收客户请求,基于客户的一个响应
        public void run(){
            InputStream is = null;
            BufferedReader br = null;
            PrintWriter pw = null;
            OutputStream os = null;
            try {
                Scanner sc = new Scanner(System.in);
                //获得输入流,获得用户的请求
                while (true){
                    is = socket.getInputStream();
                    br = new BufferedReader(new InputStreamReader(is));
                    System.out.println(socket.getInetAddress() + "说" + br.readLine());

                    //给客户端一个响应,通过输出流将响应发送回客户端
                    String reply = sc.next();
                    os = socket.getOutputStream();
                    pw = new PrintWriter(os);
                    pw.println(reply);
                    pw.flush();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    pw.close();
                    os.close();
                    br.close();
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.客户端

    1.发送请求到服务器。
    2.接收服务器的响应。

3.1 程序实现

    package cn.edu.xidian.see.*.Inet.ThreadUser;

    import java.io.*;
    import java.net.Socket;
    import java.net.UnknownHostException;
    import java.util.Scanner;

    public class LogicClient {
        //发送请求到服务器
        //接收服务器的响应
        public static void main(String[] args) {
            Scanner input = new Scanner(System.in);
            OutputStream os=null;
            PrintWriter pw=null;
            InputStream is=null;
            BufferedReader br=null;
            //创建一个客户端的Socket
            try {
                Socket socket = new Socket("localhost",8848);
                System.out.println("客户端已启动");
                //通过输出流发送请求
                while(true){
                    String info =input.next();
                    os = socket.getOutputStream();
                    pw = new PrintWriter(os);
                    pw.println(info);
                    pw.flush();
                    //通过输入流接收服务器给我的响应
                    is = socket.getInputStream();
                    br = new BufferedReader(new InputStreamReader(is));
                    System.out.println("服务器:"+br.readLine());
                }
                //释放资源
            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                try {
                    os.close();
                    pw.close();
                    br.close();
                    is.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

使用代理服务器

1.代理的作用

1.出于安全原因防止远程主机了解到本地网络配置的秘密细节。

2.为了通过滤出站请求,限制可以浏览的网络。

3.出于性能考虑,运行多个用户从本地缓存获取同样的一些经常访问的文档,而不是重复从远程主机下载。

本文标题:TCP

文章作者:Bangjin-Hu

发布时间:2019年10月15日 - 09:22:26

最后更新:2020年03月30日 - 07:58:50

原始链接:http://bangjinhu.github.io/undefined/TCP/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Bangjin-Hu wechat
欢迎扫码关注微信公众号,订阅我的微信公众号.
坚持原创技术分享,您的支持是我创作的动力.