网络编程--基础TCP

简介: udp是一个不可靠(数据包可能会丢失)的协议什么情况下数据包会出现丢失呢? 1.带宽不足 。 2.cpu的处理能力不足。

udp是一个不可靠(数据包可能会丢失)的协议

什么情况下数据包会出现丢失呢?
1.带宽不足 。
2.cpu的处理能力不足。

如果这个时候要传输一部电影怎么办 随便丢失一个数据就GG
这个时候 TCP就派上用场了
之前有介绍过TCP的三次握手 这次赋上图来看看:
这里写图片描述

TCP通讯协议特点:
1. tcp是基于IO流进行数据 的传输 的,面向连接。
2. tcp进行数据传输的时候是没有大小限制的。
3. tcp是面向连接,通过三次握手的机制保证数据的完整性。 可靠协议。
4. tcp是面向连接的,所以速度慢。
5. tcp是区分客户端与服务端 的。

比如: 打电话、 QQ\feiQ的文件传输、 迅雷下载.... 

tcp协议下的Socket:
Socket(客户端) , tcp的客户端一旦启动马上要与服务端进行连接。
ServerSocket(服务端类)

tcp的客户端使用步骤:
1. 建立tcp的客户端服务。
2. 获取到对应的流对象。 指的事字节流
3.写出或读取数据
4. 关闭资源。

//tcp客户端
public class Demo1Clinet {

    public static void main(String[] args) throws IOException{
        //建立tcp的服务
        Socket socket  = new Socket(InetAddress.getLocalHost(),9090);
        //获取到Socket的输出流对象
        OutputStream outputStream = socket.getOutputStream();
        //利用输出流对象把数据写出即可。
        outputStream.write("服务端你好".getBytes());

        //获取到输入流对象,读取服务端回送的数据。
        InputStream inputStream = socket.getInputStream();
        byte[] buf = new byte[1024];
        int length = inputStream.read(buf);
        System.out.println("客户端接收到的数据:"+ new String(buf,0,length));

        //关闭资源
        socket.close();     
    }
}

题外话:
FileOutputStream继承OutputStream (字节流超类)
FileWriter(字符流)继承OutputStreamWriter(转换刘)继承Writer(字符流抽象类)
System.in是inputStream 是字节流
tcp的服务端

注意: java.net.BindException: 端口被占用。(重复Run)

ServerSocket的使用 步骤
1. 建立tcp服务端 的服务。
2. 接受客户端的连接产生一个Socket.
3. 获取对应的流对象读取或者写出数据。
4. 关闭资源。

//服务端
public class Demo1Server {

    public static void main(String[] args) throws Exception {
        //建立Tcp的服务端,并且监听一个端口。
        ServerSocket serverSocket = new ServerSocket(9090);
        //接受客户端的连接
        Socket socket  =  serverSocket.accept(); //accept()  接受客户端的连接 该方法也是一个阻塞型的方法,没有客户端与其连接时,会一直等待下去。
        //获取输入流对象,读取客户端发送的内容。
        InputStream inputStream = socket.getInputStream();
        byte[] buf = new byte[1024];
        int length = 0;
        length = inputStream.read(buf);
        System.out.println("服务端接收:"+ new String(buf,0,length));

        //获取socket输出流对象,想客户端发送数据
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("客户端你好啊!".getBytes());


        //关闭资源
        serverSocket.close();

    }

}

总结:客户端 可以通过socket.getOutputStream();得到流通道
服务器端 只能这样得到流通道

Socket socket=serverSocket.accept();
inputStream=socket.getInputStream();

看到这里是不是有个问题
为什么ServerSocket没有getInputStream和getOutStream ?
这里写图片描述
如图 一目了然 如果直接getinputStream之类的 那谁知道是接收到哪个客户端的socket呢

还有一个问题 BufferedReader的readLine通过下列字符之一即可认为某行已终止:换行 (‘\n’)、回车 (‘\r’) 或回车后直接跟着换行,但是在最后一行的时候并没有换行或者回车的字符啊,此时为什么会读取到最后一行呢?按理说最后一行是不应该被读取到的。
这里写图片描述
所以读到最后一行就直接返回最后一行字符串 不用担心
为什么会提这个呢
因为下面TCP群聊会用到
需求: 客户端与服务端一问一答聊天。

/*
 聊天的服务端
 */
public class ChatServer {

    public static void main(String[] args) throws IOException {
        //建立tcp的服务端
        ServerSocket serverSocket = new ServerSocket(9090);
        //接受客户端的连接,产生一个SOcket
        Socket socket = serverSocket.accept();
        //获取到Socket的输入流对象
        BufferedReader socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        //获取到Socket输出流对象
        OutputStreamWriter socketOut =  new OutputStreamWriter(socket.getOutputStream());

        //获取键盘的输入流对象
        BufferedReader keyReader = new BufferedReader(new InputStreamReader(System.in));


        //读取客户端的数据
        String line = null;
        while((line = socketReader.readLine())!=null){
            System.out.println("服务端接收到的数据:"+ line);

            System.out.println("请输入回送给客户端的数据:");
            line = keyReader.readLine();
            socketOut.write(line+"\r\n");
            socketOut.flush();
        }

        //关闭资源
        serverSocket.close();
    }

}
//聊天的客户端
public class ChatClient {

    public static void main(String[] args) throws IOException {
        //建立tcp的客户端服务
        Socket socket = new Socket(InetAddress.getLocalHost(),9090);
        //获取socket的输出流对象。
        OutputStreamWriter socketOut =  new OutputStreamWriter(socket.getOutputStream());
        //获取socket的输入流对象
        BufferedReader socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        //获取键盘的输入流对象,读取数据
        BufferedReader keyReader = new BufferedReader(new InputStreamReader(System.in));

        String line = null;
        //不断的读取键盘录入的数据,然后把数据写出
        while((line = keyReader.readLine())!=null){
            socketOut.write(line+"\r\n");
            //刷新
            socketOut.flush();

            //读取服务端回送的数据
            line = socketReader.readLine();
            System.out.println("服务端回送的数据是:"+line);
        }
        //关闭资源
        socket.close();
    }


}

注意:

1.如果使用BuffrerdReader的readline方法一定要加上\r\n才把数据写出。
2.使用字符流一定要调用flush方法数据才会写出。

题外话:
80端口是为HTTP(HyperText Transport Protocol)即超文本传输协议开放的,此为上网冲浪使用次数最多的协议,主要用于WWW(World Wide Web)即万维网传输信息的协议。可以通过HTTP地址(即常说的“网址”)加“:80”来访问网站,因为浏览网页服务默认的端口号都是80,因此只需输入网址即可,不用输入“:80”了。

网络间传输数据是用TCP来传输的 而且是多线程(因为没有广播地址 所以要用多线程接受多个socket)
来验证一下 网络是否用TCP来传输 用网页打开 网址是本地地址+:9090就行
也可以 直接localHost也是代表本地地址
这里写图片描述

//模拟Tomcat服务器
public class TomcatDemo extends Thread {

    Socket socket;

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


    public void run() {
        try {
            //获取socket的输出流对象
            OutputStream outputStream = socket.getOutputStream();
            //把数据写到浏览器上
            outputStream.write("<html><head><title>aaa</title></head><body>你好啊浏览器</body></html>".getBytes());
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) throws IOException {
        //建立tcp的服务端
        ServerSocket serverSocket = new ServerSocket(9090);
        //不断的接受客户端的连接
        while(true){
            Socket socket = serverSocket.accept();
            new TomcatDemo(socket).start();
        }
    }

}

//上传图片的客户端
public class ImageClient {

    public static void main(String[] args) throws Exception{
        //建立tcp的服务
        Socket socket = new Socket(InetAddress.getLocalHost(),9090);
        //获取socket的输入流对象
        InputStream inputStream = socket.getInputStream();
        //获取文件的输出流对象
        FileOutputStream fileOutputStream = new FileOutputStream("F:\\3.jpg");
        //边读边写
        byte[] buf = new byte[1024];
        int length = 0 ; 
        while((length = inputStream.read(buf))!=-1){
            fileOutputStream.write(buf,0,length);
        }
        //关闭资源
        fileOutputStream.close();
        socket.close();

    }

}
/*
1. 编写一个服务端可以给多个客户端发送图片。 (多线程)

*/
public class ImageServer extends Thread {

    Socket socket ;

    //使用该集合是用于存储ip地址的。
    static HashSet<String> ips = new HashSet<String>();

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

    @Override
    public void run() {
        try {
            //获取到socket输出流对象
            OutputStream outputStream = socket.getOutputStream();
            //获取图片的输入流对象
            FileInputStream fileInputStream = new FileInputStream("F:\\美女\\3.jpg");
            //读取图片数据,把数据写出
            byte[] buf = new byte[1024];
            int length = 0 ; 
            while((length = fileInputStream.read(buf))!=-1){

                outputStream.write(buf,0,length);
            }
            String ip = socket.getInetAddress().getHostAddress();   // socket.getInetAddress() 获取对方的IP地址
            if(ips.add(ip)){
                System.out.println("恭喜"+ip+"同学成功下载,当前下载的人数是:"+ ips.size());
            }



            //关闭资源
            fileInputStream.close();
            socket.close();
        }catch (IOException e) {

        }
    }


    public static void main(String[] args) throws IOException {
        //建立tcp的服务 ,并且要监听一个端口
        ServerSocket serverSocket  = new ServerSocket(9090);
        while(true){
            //接受用户的链接。
            Socket socket = serverSocket.accept();
            new ImageServer(socket).start();

        }
    }

}
/*
2.  实现登陆与注册 功能。  
    客户端与服务端连接的时候,就要提示客户端请选择功能。

    客户端注册的时候,用户名与密码都是发送给服务端 的,服务端需要把数据保存到服务端的文件上。

    登陆: 登陆的时候客户端输入用户名与密码发送给服务端,服务端需要校验,返回结果给客户端。
*/

public class LoginClinet {

    public static void main(String[] args) throws IOException {     
        Socket socket = new Socket(InetAddress.getLocalHost(),9090);
        //获取socket的输出流对象
        OutputStreamWriter  socketOut = new OutputStreamWriter(socket.getOutputStream());

        //获取到socket的输入流对象
        BufferedReader socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        //获取到键盘的输入流对象
        BufferedReader keyReader = new BufferedReader(new InputStreamReader(System.in));
        while(true){
            System.out.println("请选择功能: A(登陆)  B(注册)");
            String option = keyReader.readLine();
            if("a".equalsIgnoreCase(option)){
                getInfo(socketOut, keyReader, option);
                //读取服务器反馈的信息
                String line = socketReader.readLine();
                System.out.println(line);
            }else if("b".equalsIgnoreCase(option)){
                getInfo(socketOut, keyReader, option);
                //读取服务器反馈的信息
                String line = socketReader.readLine();
                System.out.println(line);
            }

        }


    }

    public static void getInfo(OutputStreamWriter  socketOut,BufferedReader keyReader, String option)
            throws IOException {
        System.out.println("请输入用户名:");
        String userName = keyReader.readLine();
        System.out.println("请输入密码:");
        String password = keyReader.readLine();
        String info = option +" "+userName+" "+password+"\r\n";
        socketOut.write(info);
        socketOut.flush();
    }

}




public class LoginServer extends Thread {

    Socket socket;

    static File file = new File("F:\\users.properties");

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

    static {
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void run() {
    while(true){

            try {
                // 获取socket的输入流对象
                BufferedReader bufferedReader = new BufferedReader(
                        new InputStreamReader(socket.getInputStream()));
                // 获取socket的输出流对象
                OutputStreamWriter socketOut = new OutputStreamWriter(
                        socket.getOutputStream());

                // 读取客户端输入的信息
                String info = bufferedReader.readLine();
                String[] datas = info.split(" ");
                // 获取到用户 的选择功能
                String option = datas[0];
                // 注册
                String userName = datas[1];

                String password = datas[2];

                if ("a".equalsIgnoreCase(option)) {
                    // 登陆
                    Properties properties = new Properties();
                    // 加载配置文件
                    properties.load(new FileReader(file));
                    if (properties.containsKey(userName)) {
                        String tempPass = properties.getProperty(userName);
                        if (password.equals(tempPass)) {
                            socketOut.write("欢迎" + userName + "登陆成功\r\n");

                        } else {
                            socketOut.write("密码错误\r\n");
                        }

                    } else {
                        socketOut.write("用户名不存在,请重新输入...\r\n");
                    }

                    socketOut.flush();

                } else if ("b".equalsIgnoreCase(option)) {

                    // 创建一个配置文件类
                    Properties properties = new Properties();
                    //加载原来的配置文件
                    properties.load(new FileReader(file));
                    if (!properties.containsKey(userName)) {
                        // 不存在该用户名
                        properties.setProperty(userName, password);
                        // 生成一个配置文件
                        properties.store(new FileWriter(file), "users");
                        socketOut.write("注册成功..\r\n");
                    } else {
                        // 存在用户名
                        socketOut.write("用户名已经被注册,请重新输入\r\n");
                    }
                    socketOut.flush();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9090);
        while (true) {
            Socket socket = serverSocket.accept();
            new LoginServer(socket).start();
        }

    }

}

注意:
1. readLine 的时候结尾要加\r\n 不然只能读取一次数据
2. 可以重复获取Socket的输入流对象 但是不能重复创建Socket
3. write结束后一定要close或者flush 除了打印流之外
5. 配置文件只需要创建一次 所以用static和static块来创建
6. 每次创建配置文件后都要 加载原来的配置文件
properties.load(new FileReader(file));
7. 修改或者添加配置文件后 要再生成一次 配置文件覆盖原来的 如果是追加要true

properties.setProperty(userName, password);
// 生成一个配置文件
properties.store(new FileWriter(file), "users");

如果配置文件有中文 要用字符流输出

目录
相关文章
|
29天前
|
移动开发 网络协议 安全
网络面试题:什么是 TCP/IP?
网络面试题:什么是 TCP/IP?
43 0
网络面试题:什么是 TCP/IP?
|
2月前
|
网络协议 网络性能优化 Python
在Python中进行TCP/IP网络编程
在Python中进行TCP/IP网络编程
33 6
|
30天前
|
监控 负载均衡 网络协议
TCP重传与超时机制:解锁网络性能之秘
TCP重传与超时机制:解锁网络性能之秘
58 0
|
11天前
|
网络协议 安全 网络性能优化
|
21天前
|
缓存 网络协议 数据库连接
【底层服务/编程功底系列】「网络通信体系」深入探索和分析TCP协议的运输连接管理的核心原理和技术要点
【底层服务/编程功底系列】「网络通信体系」深入探索和分析TCP协议的运输连接管理的核心原理和技术要点
20 0
|
30天前
|
网络协议 算法 Linux
探索TCP状态机之旅:发现网络连接的生命周期与神秘魅力
探索TCP状态机之旅:发现网络连接的生命周期与神秘魅力
67 0
|
30天前
|
网络协议 网络性能优化
网络面试题:TCP和UDP的区别
网络面试题:TCP和UDP的区别
25 0
|
1月前
|
网络协议 Python
Python网络编程实现TCP和UDP连接
Python网络编程实现TCP和UDP连接
27 0
|
1月前
|
缓存 网络协议 安全
计算机网络:传输层(TCP详解)
计算机网络:传输层(TCP详解)
|
1月前
|
网络协议 Java
Java基于TCP的网络编程
Java基于TCP的网络编程