欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

7.4 (java学习笔记)网络编程之TCP

程序员文章站 2022-05-25 19:56:40
一、TCP 1.1 TCP(Transmission Control Protocol 传输控制协议),是一种面向连接的,安全的传输协议,但效率相比于UDP而言比较低。 TCP传输时需要确保先建立连接之后,再进行传输这样就保证了传输的可靠性。 java中将TCP封装成了对应的类。 ServerSoc ......

一、tcp

  1.1 tcp(transmission control protocol 传输控制协议),是一种面向连接的,安全的传输协议,但效率相比于udp而言比较低。

  tcp传输时需要确保先建立连接之后,再进行传输这样就保证了传输的可靠性。

  java中将tcp封装成了对应的类。

    serversocket:服务端

    socket:客户端

 

  1.2tcp连接的建立与取消(三次握手与四次挥手)

    连接(三次握手):

      1.初始状态,服务器处于监听状态,主机的传输控制模块(tcb)像服务器发送连接请求,客户端进入同步已发送状态。

      2.服务器受到客服端发送的连接请求,如果同同意连接则向客户端发送确认,服务器进入同步收到状态。

      3.客户端受到确认后,继续给服务器发送确认报文,客户端进入已连接状态。

      后续服务器收到客服端的确认后也进入已建立连接状态。

 

     建立连接后,客户端和服务器就可以愉快的发送信息了,信息发送完毕后,就要断开连接。

     断开(四次挥手):

      1.客户端发送释放报文,同时停止发送数据主动关闭tcp连接,进入终止等待状态1。

      2.服务器收到释放报文后发送确认,此时服务器进入关闭等待状态。此时客户端到服务器方向的连接就释放了。

      此时tcp进入半连接状态,服务器到客户端的连接未释放,此时服务器还可以将未发送完的数据向客户端发送。

      3.服务器没有数据向客户端发送之后,就会发出连接释放报文等待客户端确认,服务器进入最终确认状态。

      4.客户端收到服务器发送的释放报文后,向服务器发送一个确认报文,服务器进入连接关闭状态。客户端同时进入时间等待(time-wait)状态。

        此时连接还没有被释放掉。客户端会等待2msl的时间,然后进入连接关闭状态。至此连接断开完成。

       

      每一条tcp的连接唯一的被两个通信两端的两个端点表示,也就是是四元组(源ip,源端口,目的ip,目的端口),

      而不是单纯的用一个ip地址和端口区别。

      也就意味着一个tcp可以建立多个连接,比如服务器ip是127.0.0.1,端口是8888;

      例如客户端一:127.0.0.1:3389

        客户端二:127.0.0.1:3390

        客户端三:127.0.0.1:3390

      三个连接对应的四元组

      tcp 127.0.0.1:3889 127.0.0.1:8888 established

      tcp 127.0.0.1:3890 127.0.0.1:8888 established

      tcp 127.0.0.1:3891 127.0.0.1:8888 established

      我们可以发现即使目的地ip和端口相同,但本地的端口不同导致整个四元组不同。

      服务器可以建立多个连接,前提是四元组不同。连接中无法出现两个四元组相同的连接。

       tcp可以连接多个客户端,为其每一个客户端创建一个socket,socket不同代表不同连接。

      客户端服务器之间通过socket通信,服务器加上多线程为每一个socket分配一个线程就可实现并发处理。

      

      参考:1、计算机网络(第四版) 谢希仁编著。

         2、https://www.cnblogs.com/andya/p/7272462.html

           3、

 

二、serversocket

    serversocket(int port)//创建绑定到指定端口的服务器套字节。

    默认绑定的ip地址是本地的ip地址。

    例如我这里是在个人电脑上面运行,绑定的地址就是当前主机的ip地址。

    当前ip地址可按win键+r输入cmd,然后输入ipconfig -all查看以太网适配器的ipv4地址,后面带有首选的。

    7.4 (java学习笔记)网络编程之TCP

    也可以认为是绑定到127.0.0.1上,因为当前c/s都是在一台电脑上运行都属于本机访问,

    所以本地测试使用的回环地址(127.0.0.1和本机ip(192.168.190.1)都可以。

    7.4 (java学习笔记)网络编程之TCP

     2.主要方法

    socket accept()//监听要对当前对象ip上指定端口的连接,如果发现有连接请求则连接它。

    例如客户端发送一个连接请求到当前服务端的对应端口,则建立客户端与服务端的连接。

    这个监听是一个阻塞式的监听,意思就是说如果没有建立连接的话当前进程就不会继续向下运行。

    成功建立连接后,会返回一个socket对象,而socket对象中有获取输入输出流的方法,这时就在

    客户端,服务端之间建立输入输出流管道,两者就可以通过这个管道通信。

 

三、socket

     1.构造方法:

    socket(inetaddress address, int port)

    socket(string host, int port)
    //创建套字节,并将其绑定到指定的(ip|域名)上的指定端口。

 

    2.主要方法:

    inputstream getinputstream()//返回当前socket对象的输入流

    outputstream getoutputstream()//返回当前socket对象的输出流

 

四、例子

  server:

import java.io.bufferedwriter;
import java.io.ioexception;
import java.io.outputstreamwriter;
import java.net.serversocket;
import java.net.socket;
import java.net.unknownhostexception;

public class server {
    public static void main(string[] args) throws unknownhostexception, ioexception {
        string msg = "欢迎连接到server!";
        serversocket server = new serversocket(8880);//绑定到本地ip的8880端口
        socket socket = server.accept();//阻塞式接收,接收成功建立连接管道
        //连接管道的输出流,即对连接对象(客户端)进行输出。
        bufferedwriter bos = new bufferedwriter(new outputstreamwriter(socket.getoutputstream(),"utf-8"));
        bos.write(msg);//服务器将指定内容发给客户端
        bos.newline();
        bos.flush();
    }
}

 

client:

import java.io.bufferedreader;
import java.io.filedescriptor;
import java.io.fileoutputstream;
import java.io.ioexception;
import java.io.inputstreamreader;
import java.io.printstream;
import java.net.socket;
import java.net.unknownhostexception;

public class client {
    public static void main(string[] args) throws unknownhostexception, ioexception {
        socket client = new socket("192.168.190.1",8880);//向指定ip地址的指定端口进行连接
    //    socket client = new socket("127.0.0.1",8880);//使用127.0.0.1和使用192.168.190.1都可以完成通信
        //连接成功后,获取连接管道的输入流,即对服务器写入内容进行读取
        bufferedreader isr = new bufferedreader(new inputstreamreader(client.getinputstream(),"utf-8"));
        string re = isr.readline();//读取内容
        system.out.println(re);
    }
}
运行结果:
欢迎连接到server!

先运行server会进行阻塞式接收,没有建立连接前后面的语句都不会执行。

然后运行client建立连接后,server向连接管道中写入数据,client向连接管道中读取数据。

最后将内容显示到控制台。 

 

 

五、简易聊天室

下面结合多线程,和网络编程实现一个简易聊天室。

客户端先将消息发送到服务器,服务接收消息后转发给其他客户端。

每个客户端是一个线程。

 

基本流程:

1.a客户端读取键盘输入数据,并将其发送到服务器。

2.服务器与a客户端建立连接后,将a客户端放入一个容器,同时将a客户端发送的消息,转发给容器中除a客户端之外的所有客户端。

 服务器中为每一个socket分配一个线程,就可以实现并发转发所有聊天消息。

3.发送给其他客户端后,其他客户端会读取服务器发送的内容并显示到自己控制台。

 

send:读取键盘输入内容并将其发送给服务器

import java.io.bufferedreader;
import java.io.datainputstream;
import java.io.dataoutputstream;
import java.io.ioexception;
import java.io.inputstreamreader;
import java.io.reader;
import java.net.socket;

public class send implements runnable {
    private boolean running = true;
    private datainputstream dis;//用于读取service返回的消息
    private dataoutputstream dos;//用于向server发送消息
    private bufferedreader br;
    public send(){
        
    }
    
    public send(socket client){
        try {
            dis = new datainputstream(client.getinputstream());
            dos = new dataoutputstream(client.getoutputstream());
        } catch (ioexception e) {
            // todo auto-generated catch block
            system.err.println("初始化连接失败!");
            running = false;
            try {
                dis.close();
                dos.close();
            } catch (ioexception e1) {
                // todo auto-generated catch block
                system.err.println("关闭异常!");
            }
            
        }
        
    }
    //读取键盘输入信息并返回
    private string reciver(){
        string msg=null;
        br = new bufferedreader(new inputstreamreader(system.in));
        try {
            msg = br.readline();
        } catch (ioexception e) {
            // todo auto-generated catch block
            system.out.println("读取用户输入异常!");
            running = false;
            try {
                br.close();
            } catch (ioexception e1) {
                // todo auto-generated catch block
                e1.printstacktrace();
            }
        }
        return msg;
    }
    //将键盘输入信息发送至服务器
    private void send(){
        string msg = reciver();
        try {
            if(msg != null && !msg.equals("")){
                dos.writeutf(msg);
                dos.flush();
            }
        } catch (ioexception e) {
            // todo auto-generated catch block
            e.printstacktrace();
            system.err.println("用户发送信息异常!");
            running = false;
            try {
                dos.close();
            } catch (ioexception e1) {
                // todo auto-generated catch block
                e1.printstacktrace();
            }
        }
    }
    
    public void run(){
        while(running){
            send();
        }
    }
}

 

reciver:读取服务器发送的数据

package chatroom;

import java.io.datainputstream;
import java.io.ioexception;
import java.net.socket;

public class reciver implements runnable{
    private boolean running = true;
    private datainputstream dis;
    
    public reciver(){
        
    }
    //初始化,获取连接
    public reciver(socket client){
        try {
            dis = new datainputstream(client.getinputstream());
        } catch (ioexception e) {
            // todo auto-generated catch block
            system.err.println("client-->server连接失败!");
            running = false;
            try {
                dis.close();
            } catch (ioexception e1) {
                // todo auto-generated catch block
                system.err.println("关闭异常!");
            }
        }
    }
    //读取客户端发送的数据
    private string reciver(){
        string msg=null;
        try {
            msg = dis.readutf();
        } catch (ioexception e) {
            // todo auto-generated catch block
            system.err.println("接受客户端消息异常!");
            running = false;
            try {
                dis.close();
            } catch (ioexception e1) {
                // todo auto-generated catch block
                system.err.println("关闭异常!");
            }
        }
        return msg;
    }
    
    public void run(){
        while(running){
            system.out.println(reciver());
        }
    }
}

 

client:(192.168.1.1~253) 255.255.255.0

import java.io.ioexception;
import java.net.socket;
import java.net.unknownhostexception;

public class client {

    public static void main(string[] args) throws unknownhostexception, ioexception {
        // todo auto-generated method stub
        socket client = new socket("192.168.1.254",8888);//连接服务器
            new thread(new send(client)).start();//读取键盘数据并发送给服务器
            new thread(new reciver(client)).start();//读取服务器发送回来的消息
    }

}

 

server: 192.168.1.254   255.255.255.0

import java.io.datainputstream;
import java.io.dataoutputstream;
import java.io.ioexception;
import java.net.serversocket;
import java.net.socket;
import java.util.linkedlist;
import java.util.list;

public class server {
    static list<server.connect> alluser;
    public static void main(string[] args) throws ioexception{
        alluser = new linkedlist<server.connect>();//存储客户端的容器
        serversocket serversocket = new serversocket(8888);//设置监听端口
        while(true){//不断接受客户端的连接请求
            socket con = serversocket.accept();//获取服务器与客户端的socket
       //   system.out.println(con.getport());
            server server = new server();//实例化一个服务器
            server.connect connect = server.new connect(con);//创建一个客户端到服务器的连接(socket)
            alluser.add(connect);//将已经连接的客户端放入容器,也可以看做将socket放入服务器
            new thread(connect).start();//每连接一个客户端(socket)就为其开辟一条线程,一个服务器对应多个客户端。
        }
    }
    
    class connect implements runnable{//
        private boolean running = true;//运行标志位
        datainputstream dis;
        dataoutputstream dos;
        
        public connect(){
            
        }
        
        public connect(socket client){//客户端连接上服务器后的socket
            try {//初始化获取对客户的读写流
                dis = new datainputstream(client.getinputstream());
                dos = new dataoutputstream(client.getoutputstream());
            } catch (ioexception e) {
                // todo auto-generated catch block
                system.err.println("server-->client连接失败!");
                try {
                    dos.close();
                    dis.close();
                } catch (ioexception e1) {
                    // todo auto-generated catch block
                    system.err.println("关闭异常");
                }
            }
        }
        
        public string reciver(){//读取客户端发送的消息
            string msg = null; 
            try {
                msg = dis.readutf();
            } catch (ioexception e) {
                // todo auto-generated catch block
                e.printstacktrace();
                system.err.println("获取客户端信息异常!");
                running = false;
                try {
                    dis.close();
                } catch (ioexception e1) {
                    // todo auto-generated catch block
                    system.err.println("关闭异常");
                }
            }
            return msg;//返回读取的消息
        }
        
        public void send(string msg){//将消息发送到输出流dos对应的客户端
            try {
                dos.writeutf(msg);
                dos.flush();
            } catch (ioexception e) {
                // todo auto-generated catch block
                system.err.println("发送客户端信息异常!");
                running = false;
                try {
                    dos.close();
                } catch (ioexception e1) {
                    // todo auto-generated catch block
                    system.err.println("关闭异常");
                }
            }
            
        }
        
        public void sendother(){//将消息发送到其它客户端,例如a客户端发送过来的消息,就发送给除a之外的客户端
            string msg = this.reciver();
            system.out.println(msg);
            for(connect temp : alluser){//遍历存放客户端的容器
                if(temp == this)//如果容器中当前对象时是a,就跳过这次循环,不是则将消息发送到对应的客户端。
                    continue;
                temp.send(msg);//哪一个客户端调用就将消息发给谁,假如这里的temp是b就将调用b中的send,此时发送的输出流是向客户端b写入的。
            }
        }

        @override
        public void run() {//开启多线程后服务器不断接收客户端消息,然后转发
            // todo auto-generated method stub
            while(running){//运行标志位,如果中途出现读写异常则终止。
                sendother();
            }
        }    
    }
}

如果是一台电脑上测试,则将客户端中连接服务器的地址修改为127.0.0.1或localhost端口任选(大于1024即可)。

如果是多台电脑测试,例如两台电脑(将两台电脑的网线接口用一根网线连接)。

将其中一台电脑的ip地址修改为192.169.1.245:255.255.255.0,

另外一台ip地址只需和其保持同一网段即可例如(192.168.1.1:255.255.255.0)。(最好禁用其余网卡)

在192.168.1.254上先运行服务器然后运行客户端,在另外一个电脑上运行客户端。

通过控制台输入可以实现聊天。

如果想实现同一网段多个电脑间通信需要用到交换机连接多个电脑。